Skip to content

Commit 0147a13

Browse files
committed
feat(#194): enhance text file formatting rules and add guardrails
- update documentation to specify newline and blank line requirements - introduce Test-TextFileFormatting script to enforce formatting rules - add tests to validate text file formatting compliance
1 parent c830a6c commit 0147a13

13 files changed

Lines changed: 348 additions & 47 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
1717
- The generated guidance now expresses PowerShell source/helper-script maintainability rules through `code-quality-matrix.instructions.md` and keeps test-specific design guidance in the testing instruction/skill files.
1818
- The generated guidance now requires `docs/<ProjectName>/en-US/*.md` to stay valid PlatyPS help with YAML metadata and build-compatible structure, tells agents to use the documented `New-MarkdownCommandHelp` / `Update-MarkdownCommandHelp` / `Test-MarkdownCommandHelp` workflow instead of replacing command help with plain Markdown that breaks `nova build`, and requires a matching help file for every new public entry point in the same change.
1919
- The generated PowerShell guidance now requires agents to review every changed or generated text file before handoff and normalize the file ending to exactly one trailing newline with no extra blank lines at the bottom.
20+
- The starter now also ships a generated `scripts/build/Test-TextFileFormatting.ps1` helper and, when project tests are enabled, a `tests/TextFileFormatting.Tests.ps1` guardrail so trailing blank-line violations fail `Test-NovaBuild` instead of relying on instructions alone.
2021
- When Git is enabled during either scaffold flow, Nova now creates or updates a default `.gitignore` in the generated project root and appends only the missing Nova-managed artifact entries instead of overwriting existing ignore rules.
2122
- `Initialize-NovaModule` and `% nova init` now check for newer NovaModuleTools releases before the first interactive scaffold question and reuse the same non-blocking update warning behavior already used by build.
2223

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,8 @@ Packaged resources that ship with the module, including:
506506
- the packaged example project under `src/resources/example/`
507507
- the Agentic Copilot starter package under `src/resources/agentic-copilot/`
508508

509-
The example project is both a shipped resource and a maintained working reference. The Agentic Copilot starter package is generated from Nova's repository-local agentic guidance, including Nova build/test/package expectations, `project.json` `Manifest.PowerShellHostVersion` compatibility guidance, generated `dist` module files, command-help ownership, source-mirrored test guidance, Test-NovaBuild-only project test guidance that forbids direct `Invoke-Pester` so agent validation matches Nova's build/import/StrictMode flow, guidance that ScriptAnalyzer findings reported by `run.ps1` must be fixed before handoff, explicit PSScriptAnalyzer workflow guidance for `./scripts/build/Invoke-ScriptAnalyzerCI.ps1`, `./run.ps1`, and focused `Invoke-ScriptAnalyzer` usage, explicit public/private PowerShell file ownership rules, best-effort source/helper-script maintainability guidance plus separate test-design guidance that live in Agentic Copilot files, explicit valid-PlatyPS help guidance for
509+
The example project is both a shipped resource and a maintained working reference. The Agentic Copilot starter package is generated from Nova's repository-local agentic guidance, including Nova build/test/package expectations, `project.json` `Manifest.PowerShellHostVersion` compatibility guidance, generated `dist` module files, command-help ownership, source-mirrored test guidance, Test-NovaBuild-only project test guidance that forbids direct `Invoke-Pester` so agent validation matches Nova's build/import/StrictMode flow, guidance that ScriptAnalyzer findings reported by `run.ps1` must be fixed before handoff, explicit PSScriptAnalyzer workflow guidance for `./scripts/build/Invoke-ScriptAnalyzerCI.ps1`, `./run.ps1`, and focused `Invoke-ScriptAnalyzer` usage, explicit public/private PowerShell file ownership rules, best-effort source/helper-script maintainability guidance plus separate test-design guidance that live in Agentic Copilot files, a generated
510+
`scripts/build/Test-TextFileFormatting.ps1` helper and matching `tests/TextFileFormatting.Tests.ps1` guardrail when Pester is enabled so extra blank lines at EOF fail the project test flow, and explicit valid-PlatyPS help guidance for
510511
`docs/<ProjectName>/en-US/*.md` that uses the documented `New-MarkdownCommandHelp` / `Update-MarkdownCommandHelp` /
511512
`Test-MarkdownCommandHelp` workflow and requires a matching help file for every new public entry point in the same change, and a strict file-ending rule for changed or generated text files. When the init flow adds that starter package, it also asks for a short project name so scaffolded guidance can replace placeholders such as `Invoke-<ShortName>*`. Run
512513
`./scripts/build/Sync-AgenticCopilotScaffold.ps1` after changing `.github/agents/`, `.github/instructions/`,

RELEASE_NOTE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ This file summarizes public cmdlet, CLI, configuration, and migration changes fo
77
### Added
88
- `Initialize-NovaModule` and `% nova init` now offer an optional Agentic Copilot starter package in both the minimal and example interactive scaffold flows.
99
- The starter package follows a filtered mirror of Nova's maintained agentic guidance so newly scaffolded projects receive a broader Nova-style agentic baseline without Nova-specific surfaces.
10+
- The starter now includes a generated text-file-formatting guardrail helper and, when project tests are enabled, a Pester guardrail test so trailing blank-line violations fail the normal project test flow.
1011
- Before the first interactive scaffold question, Nova now also warns when a newer NovaModuleTools release is available by reusing the same non-blocking update warning behavior already used by `build`.
1112

1213
### Changed

docs/NovaModuleTools/en-US/Initialize-NovaModule.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ PS> Initialize-NovaModule [-Path <string>] [-Example] [-WhatIf] [-Confirm] [<Com
3030
The command collects project details interactively, including the module name, description, version, author, minimum PowerShell version, Git initialization, and an optional Agentic Copilot starter package. Before the first interactive question, Nova checks whether a newer `NovaModuleTools` release is available and warns without blocking the scaffold flow. When Git initialization is enabled, Nova also creates or updates a default `.gitignore` in the generated project root so common local and CI artifact paths such as `artifacts/`, `dist/`, `run.ps1`, and `reload.ps1` are ignored from the start. Existing `.gitignore` files are preserved and only receive the missing Nova-managed entries. When you enable the Agentic package, Nova also asks for a project short name used in generated guidance placeholders such as `Invoke-<ShortName>*`; for NovaModuleTools the short name is `Nova`, but it could also have been
3131
`NMT`. The Agentic package follows Nova's maintained agentic guidance through a filtered starter mirror, including Nova build/test/package expectations, `project.json`
3232
`Manifest.PowerShellHostVersion` compatibility guidance, strict ScriptAnalyzer guidance without excluded rules, generated `dist` module files, command-help ownership, source-mirrored test guidance, guidance that ScriptAnalyzer findings reported by
33-
`run.ps1` must be fixed before handoff, explicit PSScriptAnalyzer workflow guidance for `./scripts/build/Invoke-ScriptAnalyzerCI.ps1`, `./run.ps1`, and focused `Invoke-ScriptAnalyzer` usage, explicit public/private PowerShell file ownership rules, best-effort source/helper-script maintainability guidance plus separate test-design guidance delivered through Agentic Copilot files, explicit valid-PlatyPS help guidance for `docs/<ProjectName>/en-US/*.md` that uses `New-MarkdownCommandHelp`, `Update-MarkdownCommandHelp`, and `Test-MarkdownCommandHelp`, and a strict file-ending rule for changed or generated text files. For the standard scaffold, the interactive flow also includes optional basic Pester support.
33+
`run.ps1` must be fixed before handoff, explicit PSScriptAnalyzer workflow guidance for `./scripts/build/Invoke-ScriptAnalyzerCI.ps1`, `./run.ps1`, and focused `Invoke-ScriptAnalyzer` usage, explicit public/private PowerShell file ownership rules, best-effort source/helper-script maintainability guidance plus separate test-design guidance delivered through Agentic Copilot files, a generated `scripts/build/Test-TextFileFormatting.ps1` helper plus `tests/TextFileFormatting.Tests.ps1` when project tests are enabled so trailing blank-line violations fail the project test flow, explicit valid-PlatyPS help guidance for `docs/<ProjectName>/en-US/*.md` that uses `New-MarkdownCommandHelp`, `Update-MarkdownCommandHelp`, and `Test-MarkdownCommandHelp`, and a strict file-ending rule for changed or generated text files. For the standard scaffold, the interactive flow also includes optional basic Pester support.
3434

3535
If you enter an invalid answer during the interactive flow, `Initialize-NovaModule` reports the validation problem immediately and retries that prompt before it continues to the next question.
3636

@@ -140,7 +140,7 @@ This cmdlet does not emit an output object.
140140
## NOTES
141141
142142
Generated projects start with NovaModuleTools defaults for recursive discovery, source markers, duplicate-function validation, and—when Git is enabled—a default `.gitignore` for common local and CI artifact paths. The packaged example scaffold keeps its bundled example source and tests, while still updating prompt-driven metadata such as project name, description, version, author, and PowerShell host version. The optional Agentic Copilot starter package adds repository-local Copilot workflow files under the project root and `.github/` that tell agents to keep build/test/package workflows on Nova, treat `project.json` `Manifest.PowerShellHostVersion` as the PowerShell compatibility target, run ScriptAnalyzer before build/test through the repo-standard `Invoke-ScriptAnalyzerCI.ps1` / `run.ps1` workflow, fix any ScriptAnalyzer findings before handoff, run project tests through `Test-NovaBuild` instead of direct `Invoke-Pester`, leave `.psm1` / `.psd1` files to generated
143-
`dist` output, generate and validate command help with the Microsoft.PowerShell.PlatyPS cmdlets instead of hand-written Markdown structure, require a matching help file for every new public entry point in the same change, and mirror new tests to source files.
143+
`dist` output, generate and validate command help with the Microsoft.PowerShell.PlatyPS cmdlets instead of hand-written Markdown structure, require a matching help file for every new public entry point in the same change, mirror new tests to source files, and add a text-file-formatting guardrail helper plus a Pester guardrail test when project tests are enabled.
144144

145145
`Initialize-NovaModule` uses `SupportsShouldProcess`, so `Get-Help Initialize-NovaModule -Full` surfaces native
146146
`-WhatIf` and

scripts/build/Sync-AgenticCopilotScaffold.psd1

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
'CHANGELOG.md'
1717
'RELEASE_NOTE.md'
1818
'.github/pull_request_template.md'
19+
'scripts/build/Test-TextFileFormatting.ps1'
20+
'tests/TextFileFormatting.Tests.ps1'
1921
)
2022

2123
ExcludedPaths = @(

src/private/scaffold/InitializeNovaModuleAgenticCopilotScaffold.ps1

Lines changed: 84 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,80 @@ function Expand-NovaModuleAgenticCopilotTemplateContent {
9090
return $expandedContent
9191
}
9292

93+
function Test-NovaModuleAgenticCopilotTemplateFileEnabled {
94+
[CmdletBinding()]
95+
param(
96+
[Parameter(Mandatory)][hashtable]$Answer,
97+
[Parameter(Mandatory)][string]$RelativePath,
98+
[switch]$Example
99+
)
100+
101+
if (-not $RelativePath.StartsWith('tests/', [System.StringComparison]::OrdinalIgnoreCase)) {
102+
return $true
103+
}
104+
105+
if ($Example) {
106+
return $true
107+
}
108+
109+
return $Answer.ContainsKey('EnablePester') -and $Answer.EnablePester -eq 'Yes'
110+
}
111+
112+
function ConvertTo-NovaModuleAgenticCopilotNormalizedFileContent {
113+
[CmdletBinding()]
114+
param(
115+
[AllowEmptyString()][string]$Content
116+
)
117+
118+
if ($null -eq $Content -or $Content.Length -eq 0) {
119+
return $Content
120+
}
121+
122+
$trimmedContent = $Content.TrimEnd([char[]]@([char]13, [char]10))
123+
if ($trimmedContent.Length -eq 0) {
124+
return $Content
125+
}
126+
127+
$lineEnding = if ( $Content.Contains("`r`n")) {
128+
"`r`n"
129+
} else {
130+
"`n"
131+
}
132+
133+
return $trimmedContent + $lineEnding
134+
}
135+
136+
function Get-NovaModuleAgenticCopilotDestinationContent {
137+
[CmdletBinding()]
138+
param(
139+
[Parameter(Mandatory)][string]$RelativePath,
140+
[Parameter(Mandatory)][string]$TemplateContent,
141+
[Parameter(Mandatory)][System.Collections.IDictionary]$ScaffoldContext
142+
)
143+
144+
if ($RelativePath -eq 'README.md') {
145+
return Get-NovaModuleAgenticCopilotReadmeContent -TemplateContent $TemplateContent -TokenMap $ScaffoldContext['TokenMap'] -ProjectRoot $ScaffoldContext['ProjectRoot'] -Example:([bool]$ScaffoldContext['Example'])
146+
}
147+
148+
return Expand-NovaModuleAgenticCopilotTemplateContent -Content $TemplateContent -TokenMap $ScaffoldContext['TokenMap']
149+
}
150+
151+
function Write-NovaModuleAgenticCopilotDestinationFile {
152+
[CmdletBinding()]
153+
param(
154+
[Parameter(Mandatory)][string]$DestinationPath,
155+
[Parameter(Mandatory)][string]$Content
156+
)
157+
158+
$destinationDirectory = Split-Path -Parent $DestinationPath
159+
if (-not (Test-Path -LiteralPath $destinationDirectory)) {
160+
New-Item -ItemType Directory -Path $destinationDirectory -Force | Out-Null
161+
}
162+
163+
$normalizedContent = ConvertTo-NovaModuleAgenticCopilotNormalizedFileContent -Content $Content
164+
Set-Content -LiteralPath $DestinationPath -Value $normalizedContent -Encoding utf8 -NoNewline
165+
}
166+
93167
function Initialize-NovaModuleAgenticCopilotScaffold {
94168
[CmdletBinding()]
95169
param(
@@ -100,23 +174,22 @@ function Initialize-NovaModuleAgenticCopilotScaffold {
100174

101175
$templateRoot = Get-NovaModuleAgenticCopilotTemplateRoot
102176
$tokenMap = Get-NovaModuleAgenticCopilotTemplateTokenMap -Answer $Answer
177+
$scaffoldContext = [ordered]@{
178+
TokenMap = $tokenMap
179+
ProjectRoot = $ProjectRoot
180+
Example = [bool]$Example
181+
}
103182
$templateFileList = @(Get-ChildItem -LiteralPath $templateRoot -File -Recurse -Force | Sort-Object FullName)
104183

105184
foreach ($templateFile in $templateFileList) {
106185
$relativePath = Get-NormalizedRelativePath -Root $templateRoot -FullName $templateFile.FullName
107-
$destinationPath = Join-Path $ProjectRoot ($relativePath -replace '/', [System.IO.Path]::DirectorySeparatorChar)
108-
$destinationDirectory = Split-Path -Parent $destinationPath
109-
if (-not (Test-Path -LiteralPath $destinationDirectory)) {
110-
New-Item -ItemType Directory -Path $destinationDirectory -Force | Out-Null
186+
if (-not (Test-NovaModuleAgenticCopilotTemplateFileEnabled -Answer $Answer -RelativePath $relativePath -Example:$Example)) {
187+
continue
111188
}
112189

190+
$destinationPath = Join-Path $ProjectRoot ($relativePath -replace '/', [System.IO.Path]::DirectorySeparatorChar)
113191
$templateContent = Get-Content -LiteralPath $templateFile.FullName -Raw
114-
$destinationContent = if ($relativePath -eq 'README.md') {
115-
Get-NovaModuleAgenticCopilotReadmeContent -TemplateContent $templateContent -TokenMap $tokenMap -ProjectRoot $ProjectRoot -Example:$Example
116-
} else {
117-
Expand-NovaModuleAgenticCopilotTemplateContent -Content $templateContent -TokenMap $tokenMap
118-
}
119-
120-
Set-Content -LiteralPath $destinationPath -Value $destinationContent -Encoding utf8
192+
$destinationContent = Get-NovaModuleAgenticCopilotDestinationContent -RelativePath $relativePath -TemplateContent $templateContent -ScaffoldContext $scaffoldContext
193+
Write-NovaModuleAgenticCopilotDestinationFile -DestinationPath $destinationPath -Content $destinationContent
121194
}
122195
}

src/resources/agentic-copilot/AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ This repository uses an Agentic Copilot workflow.
77
This is a Nova-managed PowerShell module project. Use Nova commands and `project.json` for build, test, package, and release behavior. Match PowerShell code, tests, and examples to `project.json` `Manifest.PowerShellHostVersion`; if it is `5.1`, avoid PowerShell 7.x-only features. Fix any ScriptAnalyzer findings reported by `run.ps1` before handoff. Follow `.github/instructions/psscriptanalyzer.instructions.md` as the ScriptAnalyzer workflow source of truth, with `./scripts/build/Invoke-ScriptAnalyzerCI.ps1` and `./run.ps1` as the normal entrypoints. Use `Test-NovaBuild` as the project test entrypoint and do not validate with direct `Invoke-Pester`. Keep one externally called function per file and match the file name to that function, with private-file extras limited to related same-file top-level support helpers and no nested function declarations inside PowerShell functions. Follow `.github/instructions/code-quality-matrix.instructions.md` for source/helper-script maintainability and
88
`.github/instructions/testing-policy.instructions.md` for test design. Keep
99
`docs/{{ProjectName}}/en-US/*.md` as valid PlatyPS command help by using `New-MarkdownCommandHelp`,
10-
`Update-MarkdownCommandHelp`, and `Test-MarkdownCommandHelp`, and create the matching help file immediately for every new public entry point. End every changed or generated text file with exactly one trailing newline and no extra blank lines at the bottom. Do not hand-create module
10+
`Update-MarkdownCommandHelp`, and `Test-MarkdownCommandHelp`, and create the matching help file immediately for every new public entry point. End every changed or generated text file immediately after exactly one newline terminator with no blank spacer line at the bottom. Use `pwsh -NoLogo -NoProfile -File ./scripts/build/Test-TextFileFormatting.ps1` for a focused check, and keep `tests/TextFileFormatting.Tests.ps1` passing when Pester is enabled. Do not hand-create module
1111
`.psm1` or module `.psd1` files in source. Do not exclude or suppress PSScriptAnalyzer rules.
1212

1313
## Workflow

src/resources/agentic-copilot/CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Before opening a pull request:
1818
- keep one externally called function per file and match the file name to that function; private files may keep extra related functions only as same-file top-level support helpers, and PowerShell functions must not declare nested functions inside their bodies
1919
- follow `.github/instructions/code-quality-matrix.instructions.md` for source/helper-script maintainability and `.github/instructions/testing-policy.instructions.md` for test design
2020
- keep `docs/{{ProjectName}}/en-US/*.md` as valid PlatyPS command help by using `New-MarkdownCommandHelp`, `Update-MarkdownCommandHelp`, and `Test-MarkdownCommandHelp`
21-
- make every changed or generated text file end with exactly one trailing newline and no extra blank lines at the bottom
21+
- make every changed or generated text file end immediately after exactly one newline terminator with no blank spacer line at the bottom; use `pwsh -NoLogo -NoProfile -File ./scripts/build/Test-TextFileFormatting.ps1` for a focused check, and keep `tests/TextFileFormatting.Tests.ps1` green when Pester is enabled
2222
- do not exclude or suppress PSScriptAnalyzer rules
2323
- do not hand-create module `.psm1` or module `.psd1` files in source
2424
- review `README.md`, `CHANGELOG.md`, and `RELEASE_NOTE.md` when the workflow or public behavior changed

0 commit comments

Comments
 (0)