Snapshot of development activity and estimated effort for the entire directory tree.
+ $fsSummaryHtml
- $chartSection
+
$chartSection
+
$fsChartSection
- $commitTable
+
$commitTable
+
$fsCard
-
+
Alternative Effort Comparison
Educational estimates based on lines of code and benchmarked productivity factors for different developer types and contexts. Formula: Labor Hours = LinesOfText × Productivity Factor. Basis here is Lines Added + 0.5 × Lines Modified − 0.5 × Lines Removed for the selected period.
Sources and references are provided for transparency. Adjust factors to match your org’s historical data if available.
+
+
+
Workspace Setup Guidance
+
Workspace: $(_escHtml $root)
+
This guide was generated from your workspace validation. It doesn’t change your files; it explains what was checked, why it matters for clinical work, and how to fix items with safe, beginner-friendly steps.
+
+
Before you start
+
Think of this as a checklist you can open any time. Each topic below is collapsible—expand only what you need. Fixes are designed for Windows and VS Code, and they avoid risky changes. If a step feels unfamiliar, open the link beside it to learn more, or ask a teammate to pair for a few minutes.
+
Why this matters in a clinical environment:
+
+
Consistency reduces rework: shared settings and tasks mean fewer surprises when exchanging files.
+
Safety and traceability: clear folder structure and version control help you audit changes and roll back safely.
+
Speed: once your environment is healthy, Copilot and VS Code can assist more effectively.
+
+
If your device is on a secure network drive or has limited permissions, prefer working in a local folder under your user profile (e.g., C:\Users\\YourName\\Documents\\Projects) and copy outputs back to shared locations as needed.
When you’ve made changes, re-run copilot-instructions/Validate-WorkspaceSetup.ps1. If everything’s green, consider committing your changes with Git so your team benefits too.
+
@@ -1495,11 +2138,13 @@ function Export-Report {
"@
Set-Content -Path $OutputPath -Value $FullHtml -Encoding UTF8
}
- "JSON" {
- $Data | ConvertTo-Json -Depth 10 | Set-Content -Path $OutputPath -Encoding UTF8
- }
- "CSV" {
- $CsvData = @"
+ default {
+ switch ($Format) {
+ "JSON" {
+ $Data | ConvertTo-Json -Depth 10 | Set-Content -Path $OutputPath -Encoding UTF8
+ }
+ "CSV" {
+ $CsvData = @"
Metric,Value
Report Generated,$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
Git Commits,$($Data.Git.CommitCount)
@@ -1508,8 +2153,24 @@ Lines Removed,$($Data.Git.LinesRemoved)
Lines Modified,$($Data.Git.LinesModified)
Files Changed,$($Data.Git.FilesChanged)
Estimated Work Minutes,$($Data.Git.EstimatedWorkMinutes)
+Filesystem Root,$($Data.Filesystem.Root)
+Filesystem Total Items,$($Data.Filesystem.TotalItems)
+Filesystem Files,$($Data.Filesystem.TotalFiles)
+Filesystem Folders,$($Data.Filesystem.TotalFolders)
+Filesystem Shortcuts,$($Data.Filesystem.TotalShortcuts)
+Filesystem Reparse Points,$($Data.Filesystem.TotalReparse)
+Filesystem Text Lines (sum),$($Data.Filesystem.SumLines)
+Filesystem Characters (sum),$($Data.Filesystem.SumChars)
+Filesystem Size Bytes,$($Data.Filesystem.SumSizeBytes)
+Filesystem Last Modified,$((if ($Data.Filesystem.LastModified) { $Data.Filesystem.LastModified.ToString('yyyy-MM-dd HH:mm:ss') } else { '' }))
"@
- Set-Content -Path $OutputPath -Value $CsvData -Encoding UTF8
+ Set-Content -Path $OutputPath -Value $CsvData -Encoding UTF8
+ }
+ default {
+ # Fallback: write the original content
+ Set-Content -Path $OutputPath -Value $Content -Encoding UTF8
+ }
+ }
}
}
}
@@ -1524,6 +2185,10 @@ try {
New-Divider
}
+ # Ensure progress is visible in this session (will be restored later)
+ $origProgressPreference = $ProgressPreference
+ if (-not $Quiet) { $ProgressPreference = 'Continue' }
+
$progressId = 1
# Determine date range (interactive if no parameters provided)
@@ -1547,6 +2212,26 @@ try {
}
if (-not $Quiet -and $OutputFormat) { Write-Host "Output Format: $OutputFormat" -ForegroundColor Yellow }
+ # Normalize -ExportFormats: allow a single comma-separated string (e.g., "HTML,JSON") or spaces/semicolons
+ if ($ExportFormats) {
+ if ($ExportFormats.Count -eq 1 -and ($ExportFormats[0] -match ',' -or $ExportFormats[0] -match ';' -or $ExportFormats[0] -match '\s')) {
+ $ExportFormats = @($ExportFormats[0] -split '[,;\s]+' | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
+ }
+ # Canonicalize names (case-insensitive) to one of: Markdown, HTML, JSON, CSV
+ $canon = New-Object System.Collections.Generic.List[string]
+ foreach ($f in $ExportFormats) {
+ $v = ($f.ToString()).Trim()
+ switch -Regex ($v) {
+ '^(?i)md|markdown$' { [void]$canon.Add('Markdown'); continue }
+ '^(?i)htm|html$' { [void]$canon.Add('HTML'); continue }
+ '^(?i)json$' { [void]$canon.Add('JSON'); continue }
+ '^(?i)csv$' { [void]$canon.Add('CSV'); continue }
+ default { if ($v) { [void]$canon.Add($v) } }
+ }
+ }
+ $ExportFormats = @($canon.ToArray())
+ }
+
# Determine default repo path and prompt user for an override with 15s timeout
$DefaultRepo = Get-DefaultRepoPath -StartPath $PSScriptRoot
if (-not $NonInteractive -and (-not $PSBoundParameters.ContainsKey('GitRepoPath') -or [string]::IsNullOrWhiteSpace($GitRepoPath))) {
@@ -1626,12 +2311,13 @@ try {
if ($ExportFormats -and $ExportFormats.Count -gt 0) {
$ReportsPath = Split-Path -Parent $OutputPath
foreach ($fmt in $ExportFormats) {
- $ext = switch ($fmt) {
- "Markdown" { "md" }
- "HTML" { "html" }
- "JSON" { "json" }
- "CSV" { "csv" }
- default { $fmt }
+ $fmtName = ($fmt.ToString()).Trim()
+ $ext = switch -Regex ($fmtName) {
+ '^(?i)Markdown$' { 'md' }
+ '^(?i)HTML$' { 'html' }
+ '^(?i)JSON$' { 'json' }
+ '^(?i)CSV$' { 'csv' }
+ default { ($fmtName -replace '^\.+','') } # fall back to raw, strip leading dots
}
$outFile = Join-Path $ReportsPath ("productivity-report-" + (Get-Date -Format 'yyyyMMdd-HHmmss') + "." + $ext)
Export-Report -Content $ReportContent -Format $fmt -OutputPath $outFile -Data $ProductivityData
@@ -1685,13 +2371,25 @@ try {
Write-Log "Productivity report generated successfully: $OutputPath" "SUCCESS"
- # Keep window open so user can read output
- if (-not $NonInteractive -and -not $Quiet) { try { [void](Read-Host -Prompt "Press Enter to exit") } catch {} }
+ # Exit behavior: if the HTML report was opened successfully, don't prompt (allow window/process to close).
+ # If it was not opened (error or non-HTML), keep the prompt so the user can read output/errors.
+ $isHtml = $false
+ try { $isHtml = ([System.IO.Path]::GetExtension($OutputPath)).Equals('.html', 'InvariantCultureIgnoreCase') } catch {}
+ $shouldPrompt = $true
+ if ($opened -and $isHtml) { $shouldPrompt = $false }
+ if ($shouldPrompt -and -not $NonInteractive -and -not $Quiet) {
+ try { [void](Read-Host -Prompt "Press Enter to exit") } catch {}
+ }
+
+ # Restore original progress preference
+ if ($PSBoundParameters.ContainsKey('origProgressPreference') -or $null -ne $origProgressPreference) { $ProgressPreference = $origProgressPreference }
} catch {
Write-Log "Error generating productivity report: $($_.Exception.Message)" "ERROR"
Write-Host "Error generating report: $($_.Exception.Message)" -ForegroundColor Red
# Pause on error as well so the user can read the message (if interactive)
if (-not $NonInteractive -and -not $Quiet) { try { [void](Read-Host -Prompt "Press Enter to exit") } catch {} }
+ # Restore original progress preference on error
+ if ($PSBoundParameters.ContainsKey('origProgressPreference') -or $null -ne $origProgressPreference) { $ProgressPreference = $origProgressPreference }
exit 1
}
diff --git a/copilot-instructions/Generate-WorkspaceGuidance.ps1 b/copilot-instructions/Generate-WorkspaceGuidance.ps1
new file mode 100644
index 0000000..7d5ccc9
--- /dev/null
+++ b/copilot-instructions/Generate-WorkspaceGuidance.ps1
@@ -0,0 +1,353 @@
+# Copyright 2025 Kyle J. Coder
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+<#
+.SYNOPSIS
+ Generate a detailed HTML guidance file from a workspace validation JSON report.
+
+.DESCRIPTION
+ Reads a machine-readable validation report (JSON) emitted by Validate-WorkspaceSetup.ps1
+ and creates a highly detailed, step-by-step HTML document that educates the user on how
+ to resolve each error or warning. Optionally opens the HTML on completion and deletes
+ the JSON report when finished.
+
+.PARAMETER JsonReport
+ Path to the JSON report created by Validate-WorkspaceSetup.ps1.
+
+.PARAMETER OutputHtml
+ Optional path for the generated HTML file. If omitted, will create a temp file.
+
+.PARAMETER Open
+ Opens the generated HTML in the default browser.
+
+.PARAMETER DeleteJson
+ Deletes the JsonReport file after the HTML is created.
+
+.EXAMPLE
+ pwsh -File Generate-WorkspaceGuidance.ps1 -JsonReport "$env:TEMP\workspace-validation-20250101-010101.json" -Open -DeleteJson
+#>
+
+[CmdletBinding()]
+param(
+ [Parameter(Mandatory = $true)] [string] $JsonReport,
+ [string] $OutputHtml,
+ [switch] $Open,
+ [switch] $DeleteJson
+)
+
+Write-Host "Generate Workspace Guidance" -ForegroundColor Green
+Write-Host "=============================" -ForegroundColor Green
+
+if (-not (Test-Path -LiteralPath $JsonReport)) {
+ throw "JsonReport not found: $JsonReport"
+}
+
+try {
+ $data = Get-Content -LiteralPath $JsonReport -Raw | ConvertFrom-Json -Depth 10
+} catch {
+ throw "Failed to parse JSON report: $($_.Exception.Message)"
+}
+
+$root = $data.Root
+if (-not $OutputHtml) {
+ $ts = (Get-Date).ToString('yyyyMMdd-HHmmss')
+ if ($root -and (Test-Path -LiteralPath $root)) {
+ $OutputHtml = Join-Path $root "Workspace-Guidance-$ts.html"
+ } else {
+ $OutputHtml = Join-Path $env:TEMP "Workspace-Guidance-$ts.html"
+ }
+}
+
+function _escHtml([string] $s) {
+ if ($null -eq $s) { return '' }
+ return ($s -replace '&','&' -replace '<','<' -replace '>','>')
+}
+
+# Normalize findings
+$missingDirs = @($data.Findings.MissingDirs) | Where-Object { $_ }
+$missingFiles = @($data.Findings.MissingFiles) | Where-Object { $_ }
+$invalidJson = @($data.Findings.InvalidJsonFiles) | Where-Object { $_ }
+$deps = $data.Findings.Dependencies
+$scriptErrors = @($data.Findings.ScriptSyntaxErrors) | Where-Object { $_ }
+$ws = $data.Findings.Workspace
+
+# Dependency booleans (safe if null)
+$depsGit = $false
+$depsPac = $false
+if ($deps -and $deps.PSObject.Properties['Git']) { $depsGit = [bool]$deps.Git }
+if ($deps -and $deps.PSObject.Properties['PAC']) { $depsPac = [bool]$deps.PAC }
+
+# Derived flags
+$hasDepIssues = (-not $depsGit) -or (-not $depsPac)
+$hasInvalidJson = $invalidJson.Count -gt 0
+$hasMissingDirs = $missingDirs.Count -gt 0
+$hasMissingFiles = $missingFiles.Count -gt 0
+$hasMissingTasks = ($missingFiles -contains ".vscode\tasks.json") -or ($invalidJson -contains ".vscode\tasks.json")
+$hasMissingSettings = ($missingFiles -contains ".vscode\settings.json") -or ($invalidJson -contains ".vscode\settings.json")
+$hasMissingOpsScripts = ($missingFiles -contains "copilot-instructions\Generate-ProductivityReport.ps1") -or ($missingFiles -contains "copilot-instructions\Clean-Workspace.ps1")
+
+# Guidance blocks
+$howtoMissingDir = @'
+
Why did this fail? Your project is missing one or more standard folders (for example .vscode, docs, or scripts). The validator checks for these so everyone on your team has a consistent layout.
+This consistency helps Copilot and coworkers find things quickly.
+
Benefits if you fix it: Faster onboarding, easier automation (tasks know where files live), and fewer errors when sharing work. VS Code also recognizes .vscode settings per project, which improves your editing experience.
+
Step-by-step: Create a folder
+
+
Open File Explorer and go to your workspace folder. If you're unsure, it’s the folder you opened in VS Code (listed at the top of the Explorer).
+
Right-click inside the folder background and choose New > Folder.
+
Type the folder name exactly as shown (for example docs or .vscode) and press Enter.
+
If you’re on a VA network, prefer a local path like C:\Users\<YourName>\Desktop\YourProject to avoid slow network performance.
+
Return to VS Code. The folder should appear in the Explorer. If not, click the Refresh icon or press F5.
+
Re-run the validator to confirm the error is gone.
+
+
Tip: Workspace settings that live inside .vscode apply only to this project. See VS Code settings docs for more.
+'@
+
+$howtoMissingFile = @'
+
Why did this fail? A key file wasn’t found—common examples are README.md, .vscode\settings.json, or .github\copilot-instructions.md.
+These files provide instructions for humans (README), project-specific editor behavior (settings), and personalized Copilot guidance (instructions).
+
Benefits if you fix it: Colleagues understand your project, VS Code behaves consistently, and Copilot tailors help to your role and tasks.
+
Step-by-step: Create a README.md
+
+
In VS Code, right-click your workspace root folder in the Explorer > New File.
+
Name it README.md (all caps is traditional but not required).
+
Paste a simple outline: What this project is, how to open it, how to run scripts (if any), and who to contact for help.
Why did this fail? One or more JSON files (often under .vscode) could not be parsed. Common causes are trailing commas, missing quotes, or comments inside JSON.
+
Step-by-step: Fix JSON errors
+
+
Open the file listed (for example .vscode\tasks.json).
+
Look at the Problems panel in VS Code (bottom pane). It will show the exact line and character.
+
Remove trailing commas, ensure all keys/strings use double quotes, and remove comments (// like this) which are not allowed in JSON.
+
Save the file and re-run the validator.
+
+'@
+
+$howtoDeps = @'
+
About dependencies: Git is required for source control and updates; PAC (Power Platform CLI) is needed if your team uses Dataverse or Power Platform automation.
If winget is available, you can run winget install Microsoft.PowerApps.CLI in an elevated PowerShell.
+
Reopen VS Code and run pac --version to verify.
+
+'@
+
+$howtoScriptSyntax = @'
+
Why did this fail? A PowerShell script in your scripts folder has a syntax error. This is like a grammar mistake that stops the script from running.
+
Benefits if you fix it: Your automations and reports run reliably. You can schedule or re-run them without manual edits.
+
Step-by-step: Fix script syntax
+
+
Open the file shown in the error list (for example scripts\MyReport.ps1).
+
Read the message in the Problems panel. If it mentions a missing } or quote, check the lines just above for balance.
+
Use indentation to align blocks clearly, then save and re-run the validator.
+
If stuck, ask Copilot Chat: paste the error and the function around the line number and request a fix.
+
+'@
+
+$howtoWorkspaceNone = @'
+
Why did this fail? No .code-workspace file was found. While optional, a workspace file lets you save window layout, tasks, and multi-folder setups under one file.
+
Benefits if you fix it: One-click open for your entire project, shared tasks, and consistent settings for your team.
+
Step-by-step: Create a workspace file
+
+
In VS Code, go to File > Save Workspace As....
+
Save it in your project root (for example YourProject.code-workspace).
+
Next time, double-click this file to reopen the same project with your layout and tasks.
+
+'@
+
+$howtoWorkspaceMultiple = @'
+
Why is this a warning? More than one .code-workspace file was found. This can confuse teammates about which one to use.
+
What to do: Pick a single “official” workspace file and remove or archive the others to an archive folder.
+'@
+
+$howtoVscodeTasks = @'
+
About tasks: VS Code Tasks let you run repeatable actions (like “Validate Workspace”) with a single command or hotkey. They’re stored in .vscode\tasks.json.
+
Step-by-step: Add tasks.json
+
+
In VS Code, create the .vscode folder if it doesn’t exist.
About project settings:.vscode\settings.json stores project-specific editor preferences (wrapping, tabs, linters) so everyone gets the same experience.
+
Step-by-step: Add settings.json
+
+
Create .vscode if it doesn’t exist, then create settings.json inside it.
About VA automation scripts: The copilot-instructions folder often includes helpful scripts like Generate-ProductivityReport.ps1 and Clean-Workspace.ps1 used by teams for reporting and cleanup.
+
Step-by-step: Acquire automation scripts
+
+
Create a copilot-instructions folder in your workspace if it doesn’t exist.
+
Copy the missing scripts from your team’s Copilot kit (the same place you got this validator), or request them from your team lead.
+
After copying, right-click each .ps1 in VS Code and select Open to review inline help comments.
+
Re-run the validator to confirm the files are detected.
+
+'@
+
+# HTML output (double-quoted here-string for interpolation)
+$html = @"
+
+
+