diff --git a/.gitignore b/.gitignore index a4af64a..8e62a9f 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,8 @@ yarn-error.log* .DS_Store Thumbs.db .idea/ -.vscode/ +.vscode/* +.vscode/tasks.json *.code-workspace # Archives / temp @@ -33,6 +34,8 @@ docs/reports/ .env.* secrets* .url +copilot-instructions/COPILOT_BRIEFING.md + # PowerShell transient *.ps1.xml @@ -42,4 +45,4 @@ coverage/ # Macros / binary office caches ~$*.docx -~$*.xlsx +~$*.xlsx \ No newline at end of file diff --git a/README.md b/README.md index 5966bf9..a8bc177 100644 --- a/README.md +++ b/README.md @@ -36,21 +36,56 @@ Interactive Reveal.js slide deck and printable guide to help VA employees (clini 3. Visit: `https://.github.io/GitHub-Copilot-Setup-Guide/`. 4. Open `index.html` for the interactive version. For printing/distribution, use the PDF at `docs/GitHub Copilot Setup Guide (for VA Employees).pdf`. +## Local Workspace Setup (High Priority) + +Run these steps in the workspace where you actually do your work so Copilot follows your local rules. + +1) Download or clone this repository. +2) Open your own active VS Code workspace (the project you want Copilot to obey). +3) Run `copilot-instructions/Install-Copilot-Instructions.bat` from this repo in that workspace. + - Installs/updates `.github/copilot-instructions.md` in your workspace + - Opens VS Code (deep link) and Copilot Chat + - Copies a ready-to-paste chat prompt to your clipboard +4) Run `copilot-instructions/Validate-WorkspaceSetup.ps1` to check for standard folders/files and common tooling. + - Use `-Detailed` for extra checks; `-FixIssues` can create missing directories safely + - The validator emits a JSON report and generates a collapsible `Workspace-Guidance-.html` in your workspace root with step-by-step fixes + - Add `-Open` to launch the guidance in your browser and `-DeleteJson` to remove the JSON after generating the HTML +5) (Recommended) Bring over the rest of the automation: + - Copy the entire `copilot-instructions/` folder into your workspace root, or copy specific scripts you need + - Copy the `prompts/` folder for role/domain starter prompts + - Copy any templates from `docs/` you want to standardize locally +6) (Optional) Import tasks for one-click usage: copy `copilot-instructions/tasks.json` into `.vscode/tasks.json` in your workspace. + +Why this matters: +- Ensures deterministic Copilot behavior by giving it a single, discoverable rules file in `.github` +- Reduces setup time for teammates; gives everyone the same guardrails and shortcuts +- Validates a healthy workspace structure quickly and non-destructively + ## Printable Version Use the "Printable Version" button in the top-right of `index.html` to open the PDF (`docs/GitHub Copilot Setup Guide (for VA Employees).pdf`). -## PowerShell Automation Scripts -Located in `copilot-instructions/`: -- `Add-LicenseHeaders.ps1` – Injects standardized Apache 2.0 headers +## Automation & Scripts +All automation lives under `copilot-instructions/`. + +Key scripts: +- `Install-Copilot-Instructions.bat` – Creates/updates `.github/copilot-instructions.md`, opens VS Code/Copilot Chat, and copies a ready-to-paste prompt +- `Generate-ProductivityReport.ps1` – Summarizes effort & repository metrics (HTML/MD/CSV/JSON) +- `Recursive-Directory-Analysis.ps1` – Inventories a directory tree and exports a CSV summary - `Clean-Workspace.ps1` – Multi-level safe cleanup -- `Generate-ProductivityReport.ps1` – Summarizes effort & repository metrics - `Validate-Syntax.ps1` – Basic syntax validation for config/script files -- `Validate-WorkspaceSetup.ps1` – Environment readiness & auto-fixes -- `tasks.json` – VS Code tasks to wire common actions +- `Validate-WorkspaceSetup.ps1` – Environment readiness & optional auto-fixes +- `Generate-WorkspaceGuidance.ps1` – Reads the validator's JSON report and produces a collapsible, beginner‑friendly HTML with expanded intro/footer, resource links, and only the relevant fix blocks; can auto‑open and optionally delete the JSON +- `tasks.json` – VS Code tasks for common actions ## Prompts & Instruction Files -Use the role-based prompt templates in `prompts/` to seed Copilot context. Place / adapt `copilot-instructions.md` and `COPILOT_BRIEFING.md` in active workspaces for persistent personalization. +Use the role-based prompt templates in `prompts/` to seed Copilot context. The workspace-level instructions file should live at `.github/copilot-instructions.md`. + +Install/update steps: +- Double‑click `copilot-instructions/Install-Copilot-Instructions.bat` (or run the VS Code task) +- Then run `copilot-instructions/Validate-WorkspaceSetup.ps1` to verify structure and dependencies + +Next, copy the `prompts/` folder and any templates you want into your workspace so your team has them locally. ## Development / Contribution Pull requests welcome for: diff --git a/copilot-instructions/Generate-ProductivityReport.ps1 b/copilot-instructions/Generate-ProductivityReport.ps1 index 1007979..6805210 100644 --- a/copilot-instructions/Generate-ProductivityReport.ps1 +++ b/copilot-instructions/Generate-ProductivityReport.ps1 @@ -184,9 +184,16 @@ param( [Parameter(Mandatory=$false)] [switch]$Quiet, [Parameter(Mandatory=$false)] - [int]$MaxCommits = 10, + [int]$MaxCommits = 99, [Parameter(Mandatory=$false)] - [string]$BaselineJson = "" + [string]$BaselineJson = "", + # Filesystem snapshot filter controls (opt-in to include normally-excluded areas) + [Parameter(Mandatory=$false)] + [switch]$FSIncludeGit, + [Parameter(Mandatory=$false)] + [switch]$FSIncludeCompressed, + [Parameter(Mandatory=$false)] + [switch]$FSIncludeArchiveTemp ) # Error handling and logging @@ -647,6 +654,238 @@ function Get-WorkEstimate { return [Math]::Round($EstimatedMinutes, 0) } +<# + Filesystem snapshot helpers (lightweight port of Recursive-Directory-Analysis.ps1) + - Non-interactive, no prompts, no CSV; returns aggregate counts for the repo root. + - Excludes .git, common compressed archives, and archive/temp folders by default. +#> +function Test-FSReparsePoint { + param([System.IO.FileSystemInfo]$Item) + try { return (($Item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) } catch { return $false } +} + +function Test-FSIsBinaryFile { + param([string]$FullPath) + try { + $fs = [System.IO.File]::Open($FullPath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite) + try { + $buf = New-Object byte[] 8192 + $read = $fs.Read($buf, 0, $buf.Length) + for ($i=0; $i -lt $read; $i++) { if ($buf[$i] -eq 0) { return $true } } + return $false + } finally { $fs.Dispose() } + } catch { return $true } +} + +function Measure-FSTextFile { + param([string]$FullPath) + $totalChars = 0; $totalLines = 0; $anyChars = $false; $lastWasCR = $false + try { + $fs = [System.IO.File]::Open($FullPath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite) + try { + $sr = New-Object System.IO.StreamReader($fs, $true) + try { + $buffer = New-Object char[] 4096 + while (($count = $sr.Read($buffer, 0, $buffer.Length)) -gt 0) { + for ($i = 0; $i -lt $count; $i++) { + $c = $buffer[$i]; $anyChars = $true; $totalChars++ + if ($c -eq "`n") { $totalLines++; $lastWasCR = $false } + elseif ($c -eq "`r") { $lastWasCR = $true } + else { if ($lastWasCR) { $totalLines++; $lastWasCR = $false } } + } + } + if ($lastWasCR) { $totalLines++ } + if (-not $anyChars) { $totalLines = 0 } + elseif ($totalLines -eq 0) { $totalLines = 1 } + } finally { $sr.Dispose() } + } finally { $fs.Dispose() } + } catch { return @{ Lines = $null; Chars = $null } } + return @{ Lines = $totalLines; Chars = $totalChars } +} + +function Get-FilesystemSnapshot { + param( + [Parameter(Mandatory=$true)][string]$RootPath, + [bool]$ExcludeGit = $true, + [bool]$ExcludeCompressed = $true, + [bool]$ExcludeArchiveOrTemp = $true + ) + $snapshot = [ordered]@{ + Root = $RootPath + TotalItems = 0 + TotalFiles = 0 + TotalFolders = 0 + TotalShortcuts = 0 + TotalReparse = 0 + SumLines = 0 + SumChars = 0 + SumSizeBytes = 0 + LastModified = $null + TopExtensions = @() + Files = @() + Filters = [ordered]@{ ExcludeGit=$ExcludeGit; ExcludeCompressed=$ExcludeCompressed; ExcludeArchiveOrTemp=$ExcludeArchiveOrTemp } + } + + try { + if (-not (Test-Path -LiteralPath $RootPath -PathType Container)) { return $snapshot } + $root = Get-Item -LiteralPath $RootPath -Force + $stack = New-Object System.Collections.Stack + $items = New-Object System.Collections.Generic.List[System.IO.FileSystemInfo] + $items.Add($root) | Out-Null + $stack.Push($root) + + $scanCount = 0 + $scanProgressId = 42 + # spinner + throttle for smoother, visible updates + $scanSpinChars = @('|','/','-','\') + $scanSpinIdx = 0 + $scanThrottleMs = 150 + $lastScanTick = [Environment]::TickCount + if (-not $Quiet) { try { Write-Progress -Id $scanProgressId -Activity "Scanning directories |" -Status "Starting..." -PercentComplete 0 } catch {} } + while ($stack.Count -gt 0) { + $dir = $stack.Pop() + try { $children = Get-ChildItem -LiteralPath $dir.FullName -Force -ErrorAction Stop } catch { continue } + foreach ($child in $children) { + # Apply path-based excludes + $path = $null; try { $path = $child.FullName } catch {} + $name = $null; try { $name = $child.Name } catch {} + if ($ExcludeGit) { + if ($path -and ($path -match "(?i)(\\|/)\.git(\\/|$|\\)")) { continue } + if ($name -and ($name -match '^(?i)\.git$')) { continue } + } + if ($ExcludeArchiveOrTemp) { + if ($path -and ($path -match '(?i)(\\|/)(archive|archives|archived|temp|tmp)(\\|/)')) { continue } + if ($name -and ($name -match '(?i)^(archive|archives|archived|temp|tmp)$')) { continue } + } + $items.Add($child) | Out-Null + $scanCount++ + if (-not $Quiet) { + $nowTick = [Environment]::TickCount + if ((($nowTick - $lastScanTick) -ge $scanThrottleMs) -or ($scanCount % 100 -eq 0)) { + $spin = $scanSpinChars[$scanSpinIdx % $scanSpinChars.Length] + $scanSpinIdx++ + try { Write-Progress -Id $scanProgressId -Activity ("Scanning directories {0}" -f $spin) -Status ("Found {0} items..." -f $scanCount) -PercentComplete 10 } catch {} + $lastScanTick = $nowTick + } + } + if ($child.PSIsContainer) { + if (Test-FSReparsePoint -Item $child) { continue } + $stack.Push($child) + } + } + } + + # Final filter set for reporting + $reportItems = @() + foreach ($it in $items) { + $skip = $false + try { + if ($ExcludeGit) { + if ($it.FullName -match "(?i)(\\|/)\.git(\\/|$|\\)") { $skip = $true } + if ($it.Name -match '^(?i)\.git$') { $skip = $true } + } + if ($ExcludeArchiveOrTemp) { + if ($it.FullName -match '(?i)(\\|/)(archive|archives|archived|temp|tmp)(\\|/)') { $skip = $true } + if ($it.Name -match '(?i)^(archive|archives|archived|temp|tmp)$') { $skip = $true } + } + if (-not $skip -and $ExcludeCompressed -and -not $it.PSIsContainer) { + $ext = ''; try { $ext = $it.Extension.ToLowerInvariant() } catch {} + $compressedExts = @('.zip','.7z','.rar','.gz','.tar','.bz2','.xz','.zipx') + if ($compressedExts -contains $ext) { $skip = $true } + } + } catch {} + if (-not $skip) { $reportItems += ,$it } + } + + $snapshot.TotalItems = $reportItems.Count + if (-not $Quiet) { try { Write-Progress -Id $scanProgressId -Activity "Scanning directories" -Status ("Enumerated {0} items" -f $snapshot.TotalItems) -PercentComplete 20 } catch {} } + $files = @($reportItems | Where-Object { -not $_.PSIsContainer -and -not (Test-FSReparsePoint -Item $_) -and ($_.Extension.ToLowerInvariant() -ne '.lnk') }) + $folders = @($reportItems | Where-Object { $_.PSIsContainer -and -not (Test-FSReparsePoint -Item $_) }) + $shorts = @($reportItems | Where-Object { -not $_.PSIsContainer -and ($_.Extension.ToLowerInvariant() -eq '.lnk') }) + $reparse = @($reportItems | Where-Object { Test-FSReparsePoint -Item $_ }) + $snapshot.TotalFiles = $files.Count + $snapshot.TotalFolders = $folders.Count + $snapshot.TotalShortcuts= $shorts.Count + $snapshot.TotalReparse = $reparse.Count + + # Top extensions (files only) + $extGroups = $files | ForEach-Object { $_.Extension.ToLowerInvariant() } | Group-Object | Sort-Object Count -Descending | Select-Object -First 8 + $topExt = @() + foreach ($g in $extGroups) { + $ename = if ($g.Name) { $g.Name } else { '(none)' } + $topExt += [pscustomobject]@{ Extension=$ename; Count=$g.Count } + } + $snapshot.TopExtensions = $topExt + + # Size, lines, chars, last-modified + [int64]$sumSize = 0; [int64]$sumChars = 0; [int64]$sumLines = 0 + $fileRows = New-Object System.Collections.Generic.List[object] + $rootResolved = $RootPath + try { $rootResolved = (Resolve-Path -LiteralPath $RootPath -ErrorAction Stop).Path } catch {} + $lastMod = $null + $processed = 0 + $totalFiles = [math]::Max(1, $files.Count) + $procProgressId = 43 + # spinner + throttle for file processing progress + $procSpinChars = @('|','/','-','\') + $procSpinIdx = 0 + $procThrottleMs = 150 + $lastProcTick = [Environment]::TickCount + if (-not $Quiet) { try { Write-Progress -Id $procProgressId -Activity "Analyzing files |" -Status "Measuring text files..." -PercentComplete 0 } catch {} } + foreach ($f in $files) { + try { $sumSize += [int64]$f.Length } catch {} + try { if ($null -eq $lastMod -or $f.LastWriteTime -gt $lastMod) { $lastMod = $f.LastWriteTime } } catch {} + # Text metrics for non-binary only + $isBin = $false + try { $isBin = Test-FSIsBinaryFile -FullPath $f.FullName } catch { $isBin = $true } + if (-not $isBin) { + $m = Measure-FSTextFile -FullPath $f.FullName + if ($m -and $null -ne $m.Lines -and $null -ne $m.Chars) { + $sumLines += [int64]$m.Lines + $sumChars += [int64]$m.Chars + # Add per-file row for reporting + $rel = $f.FullName + try { + if ($rootResolved -and $f.FullName.StartsWith($rootResolved, [System.StringComparison]::OrdinalIgnoreCase)) { + $rel = $f.FullName.Substring($rootResolved.Length).TrimStart('\\','/') + } + } catch {} + $fileRows.Add([pscustomobject]@{ + Path = $f.FullName + RelativePath = $rel + Name = $f.Name + SizeBytes = [int64]$f.Length + Lines = [int64]$m.Lines + Chars = [int64]$m.Chars + LastWriteTime = $f.LastWriteTime + }) | Out-Null + } + } + $processed++ + if (-not $Quiet) { + $pct = [int][math]::Min(100, [math]::Round(($processed / $totalFiles) * 100)) + $nowTick = [Environment]::TickCount + if ((($nowTick - $lastProcTick) -ge $procThrottleMs) -or ($processed -eq $totalFiles) -or ($processed % 20 -eq 0)) { + $spin = $procSpinChars[$procSpinIdx % $procSpinChars.Length] + $procSpinIdx++ + try { Write-Progress -Id $procProgressId -Activity ("Analyzing files {0}" -f $spin) -Status ("Processed {0}/{1} files" -f $processed, $totalFiles) -PercentComplete $pct } catch {} + $lastProcTick = $nowTick + } + } + } + if (-not $Quiet) { try { Write-Progress -Id $procProgressId -Activity "Analyzing files" -Completed } catch {} } + if (-not $Quiet) { try { Write-Progress -Id $scanProgressId -Activity "Scanning directories" -Completed } catch {} } + $snapshot.SumSizeBytes = $sumSize + $snapshot.SumChars = $sumChars + $snapshot.SumLines = $sumLines + $snapshot.LastModified = $lastMod + $snapshot.Files = $fileRows + } catch { + Write-Log "Filesystem snapshot failed: $($_.Exception.Message)" "WARN" + } + + return [pscustomobject]$snapshot +} function Get-ProductivityData { param($DateRange, [string]$RepoPath, [string]$GitRef = 'HEAD', [switch]$AllBranches) @@ -796,6 +1035,17 @@ function Get-ProductivityData { Write-Log "Error analyzing Git statistics: $($_.Exception.Message)" "WARN" } + # Filesystem snapshot (repo root) + try { + if ($RepoPath -and (Test-Path -LiteralPath $RepoPath -PathType Container)) { + Write-Log "Analyzing filesystem snapshot for: $RepoPath" "INFO" + $fs = Get-FilesystemSnapshot -RootPath $RepoPath -ExcludeGit:(!$FSIncludeGit) -ExcludeCompressed:(!$FSIncludeCompressed) -ExcludeArchiveOrTemp:(!$FSIncludeArchiveTemp) + $Data.Filesystem = $fs + } + } catch { + Write-Log "Filesystem snapshot failed: $($_.Exception.Message)" "WARN" + } + # Parse productivity logs (existing functionality) try { $ProductivityFiles = Get-ChildItem -Path $LogsPath -Filter "productivity-log-*.md" -ErrorAction SilentlyContinue @@ -885,6 +1135,26 @@ function Format-MarkdownReport { $Report += "- **Code Quality:** Structured commit messages and version tagging`n" $Report += "- **Workspace Organization:** Proper Git workflow and documentation`n`n" + # Filesystem Snapshot + if ($null -ne $Data.Filesystem) { + $fs = $Data.Filesystem + $sizeMB = [Math]::Round(($fs.SumSizeBytes / 1MB), 2) + $lm = if ($fs.LastModified) { $fs.LastModified.ToString('yyyy-MM-dd HH:mm:ss') } else { 'n/a' } + $Report += "## Filesystem Snapshot`n`n" + $Report += "- **Root:** $($fs.Root)`n" + $Report += "- **Items:** $($fs.TotalItems) (Files=$($fs.TotalFiles), Folders=$($fs.TotalFolders), Shortcuts=$($fs.TotalShortcuts), ReparsePoints=$($fs.TotalReparse))`n" + $Report += "- **Text Lines (sum):** $($fs.SumLines)`n" + $Report += "- **Characters (sum):** $($fs.SumChars)`n" + $Report += "- **Size:** $sizeMB MB`n" + $Report += "- **Last Modified (latest file):** $lm`n" + $Report += "- **Filters:** ExcludeGit=$($fs.Filters.ExcludeGit), ExcludeCompressed=$($fs.Filters.ExcludeCompressed), ExcludeArchiveOrTemp=$($fs.Filters.ExcludeArchiveOrTemp)`n" + if ($fs.TopExtensions -and $fs.TopExtensions.Count -gt 0) { + $Report += "- **Top Extensions:** `n" + foreach ($e in $fs.TopExtensions) { $Report += " - $($e.Extension): $($e.Count)`n" } + } + $Report += "`n" + } + $Report += "---`n`n" $Report += " *Generated by VA Power Platform Workspace Template*`n" $Report += " *Report ID: $(Get-Date -Format 'yyyyMMdd-HHmmss')*`n" @@ -964,21 +1234,45 @@ function Export-Report { $chipFiles = if ($dc) { New-DeltaChip $dc.FilesChanged } else { '' } $chipEffort = if ($dc) { New-DeltaChip ([int]$dc.EstimatedWorkMinutes) } else { '' } - $kpiHtml = @" -
-
Git Commits
$($Data.Git.CommitCount) $chipCommits
-
Lines Added
$($Data.Git.LinesAddedRaw) $chipAdded
-
Lines Removed
$($Data.Git.LinesRemovedRaw) $chipRemoved
-
Lines Modified (est.)
$($Data.Git.LinesModified) $chipModified
-
Files Changed
$($Data.Git.FilesChanged) $chipFiles
-
Est. Work
$($Data.Git.EstimatedWorkMinutes) min $chipEffort
($workHours h)
-
+ $kpiHtml = @" +
+
Git Commits
$($Data.Git.CommitCount) $chipCommits
+
Lines Added (raw)
$($Data.Git.LinesAddedRaw) $chipAdded
+
Lines Removed (raw)
$($Data.Git.LinesRemovedRaw) $chipRemoved
+
Lines Modified (est.)
$($Data.Git.LinesModified) $chipModified
+
Files Changed
$($Data.Git.FilesChanged) $chipFiles
+
Est. Work
$($Data.Git.EstimatedWorkMinutes) min $chipEffort
($workHours h)
+
+ +"@ + $fsSummaryHtml = '' + if ($null -ne $Data.Filesystem) { + $fs = $Data.Filesystem + function _fmtBytes2([Nullable[int64]]$b) { + if ($null -eq $b) { return '0 B' } + $sizes = 'B','KB','MB','GB','TB' + $i=0; $val=[double]$b + while ($val -ge 1024 -and $i -lt $sizes.Length-1) { $val/=1024; $i++ } + return ('{0:N2} {1}' -f $val, $sizes[$i]) + } + $sizeTxt2 = _fmtBytes2 $fs.SumSizeBytes + $lmTxt2 = if ($fs.LastModified) { $fs.LastModified.ToString('yyyy-MM-dd HH:mm') } else { 'n/a' } + $fsSummaryHtml = @" +
+
Root
$repoAnchor
+
Items
$($fs.TotalItems)
+
Text Lines (sum)
$($fs.SumLines)
+
Characters (sum)
$($fs.SumChars)
+
Total Size
$sizeTxt2
+
Last Modified
$lmTxt2
+
"@ - # Build activity trend data from per-day stats (primary bars: FilesChanged; trend line: Lines basis) + } # end if ($null -ne $Data.Filesystem) + $labels = New-Object System.Collections.Generic.List[string] - $bars = New-Object System.Collections.Generic.List[int] - $trend = New-Object System.Collections.Generic.List[int] + $bars = New-Object System.Collections.Generic.List[int] + $trend = New-Object System.Collections.Generic.List[int] $barLegend = 'Files changed' $tooltipBarLabel = 'Files changed' # Precompute a map of daily lines-basis for later alignment (used for commits-per-day fallback too) @@ -1048,7 +1342,38 @@ function Export-Report { if ([string]::IsNullOrWhiteSpace($barsJson)) { $barsJson = '[]' } if ([string]::IsNullOrWhiteSpace($trendJson)) { $trendJson = '[]' } + # Build filesystem activity arrays (daily): bars = files modified per day; trend = sum of lines for those files + $fsLabelsJson = '[]'; $fsBarsJson = '[]'; $fsTrendJson = '[]' + try { + if ($Data.Filesystem -and $Data.Filesystem.Files -and $Data.Filesystem.Files.Count -gt 0) { + $fsGroups = $Data.Filesystem.Files | + Where-Object { $_.LastWriteTime } | + Group-Object { $_.LastWriteTime.Date } | + Sort-Object { try { [datetime]$_.Name } catch { Get-Date 0 } } + $fsLabels = New-Object System.Collections.Generic.List[string] + $fsBars = New-Object System.Collections.Generic.List[int] + $fsTrend = New-Object System.Collections.Generic.List[int] + foreach ($g in $fsGroups) { + try { $d = [datetime]$g.Name } catch { $d = $null } + $labelStr = if ($d) { $d.ToString('MM/dd') } else { '' } + if (-not [string]::IsNullOrWhiteSpace($labelStr)) { [void]$fsLabels.Add($labelStr) } else { [void]$fsLabels.Add('') } + [void]$fsBars.Add([int]$g.Count) + $sumLinesDay = 0 + try { $sumLinesDay = [int]([long]((($g.Group | Measure-Object -Property Lines -Sum).Sum) -as [long])) } catch {} + if ($sumLinesDay -lt 0) { $sumLinesDay = 0 } + [void]$fsTrend.Add([int]$sumLinesDay) + } + $fsLabelsJson = ($fsLabels.ToArray() | ConvertTo-Json -Compress) + $fsBarsJson = ($fsBars.ToArray() | ConvertTo-Json -Compress) + $fsTrendJson = ($fsTrend.ToArray() | ConvertTo-Json -Compress) + if ([string]::IsNullOrWhiteSpace($fsLabelsJson)) { $fsLabelsJson = '[]' } + if ([string]::IsNullOrWhiteSpace($fsBarsJson)) { $fsBarsJson = '[]' } + if ([string]::IsNullOrWhiteSpace($fsTrendJson)) { $fsTrendJson = '[]' } + } + } catch {} + $chartSection = @" +

Activity Trend

@@ -1285,6 +1610,189 @@ function Export-Report { }} })(); +
+"@ + + # Filesystem Activity Trend chart + $fsChartSection = @" +
+

Filesystem Activity Trend

+
+ +
+
+ Files modified + Lines (sum) +
+
+ + + +
+
+
Hover bars to see period details
+
+ +
"@ $commitTable = if ($commitRows) { @@ -1302,52 +1810,91 @@ function Export-Report { '

No commits found in the selected period.

' } - # Build HTML rows for Alternative Effort Comparison (with per-row accordion) - $altRowsHtml = "" + # Filesystem card (if present) + $fsCard = '' try { - $hadAny = $false - if ($Data.Git.AlternativeEffort -and $Data.Git.AlternativeEffort.Items) { - foreach ($it in @($Data.Git.AlternativeEffort.Items)) { - if (-not $it) { continue } - $hadAny = $true - $label = & $encode $it.Label - $factorTxt = ('{0:N2}' -f [double]$it.Factor) - $hoursTxt = ('{0:N2}' -f [double]$it.Hours) - - # Build accordion content (description + sources) - $descBlock = "" - if ($it.PSObject.Properties.Match('Description').Count -gt 0 -and $it.Description) { - $descBlock = [string]$it.Description - } - - $linksList = "" - $linkCount = 0 - if ($it.Links) { - $linkItems = @() - foreach ($lnk in @($it.Links)) { - if ($lnk) { - $linkCount++ - $safe = & $encode $lnk - $linkItems += "
  • ${safe}
  • " - } - } - if ($linkItems.Count -gt 0) { - $linksList = '
    Sources:
      ' + ($linkItems -join '') + '
    ' - } - } - - $summaryText = if ($linkCount -gt 0) { "Details (${linkCount} links)" } else { "Details" } - $accordion = "
    ${summaryText}
    ${descBlock}${linksList}
    " - - $altRowsHtml += "${label}${factorTxt}${hoursTxt}${accordion}" + if ($null -ne $Data.Filesystem) { + $fs = $Data.Filesystem + function _fmtBytes([Nullable[int64]]$b) { + if ($null -eq $b) { return '0 B' } + $sizes = 'B','KB','MB','GB','TB' + $i=0; $val=[double]$b + while ($val -ge 1024 -and $i -lt $sizes.Length-1) { $val/=1024; $i++ } + return ('{0:N2} {1}' -f $val, $sizes[$i]) } + $sizeTxt = _fmtBytes $fs.SumSizeBytes + $lmTxt = if ($fs.LastModified) { $fs.LastModified.ToString('yyyy-MM-dd HH:mm') } else { 'n/a' } + $extList = '' + if ($fs.TopExtensions -and $fs.TopExtensions.Count -gt 0) { + $li = @() + foreach ($e in $fs.TopExtensions) { $li += ('
  • ' + (& $encode $e.Extension) + ': ' + $e.Count + '
  • ') } + $extList = '' + } + # Build file table rows (limited to first 500 for HTML size) + $fileRowsHtml = '' + try { + $maxRows = 500 + $rows = @() + # Sort by LastWriteTime (modified date) descending + foreach ($row in @($fs.Files | Sort-Object -Property @{Expression='LastWriteTime'; Descending=$true}, @{Expression='RelativePath'; Descending=$false} | Select-Object -First $maxRows)) { + $rp = & $encode $row.RelativePath + $sz = _fmtBytes $row.SizeBytes + $ln = $row.Lines + $ch = $row.Chars + $dt = if ($row.LastWriteTime) { $row.LastWriteTime.ToString('yyyy-MM-dd HH:mm') } else { '' } + $rows += "$rp$sz$ln$ch$dt" + } + if ($rows.Count -gt 0) { $fileRowsHtml = ($rows -join "") } else { $fileRowsHtml = "No non-binary text files found." } + } catch { $fileRowsHtml = "Failed to render file list." } + $fsCard = @" +
    +

    Recursive Directory Analysis

    +
    +
    Root
    $repoAnchor
    +
    Items
    $($fs.TotalItems)
    +
    Text Lines (sum)
    $($fs.SumLines)
    +
    Characters (sum)
    $($fs.SumChars)
    +
    Total Size
    $sizeTxt
    +
    Last Modified
    $lmTxt
    +
    +
    +

    Top Extensions:

    + $extList +
    +

    Non-binary Text Files (first 500)

    + + + + $fileRowsHtml + +
    PathSizeLinesCharsLast Modified
    +
    +"@ } - if (-not $hadAny -or [string]::IsNullOrWhiteSpace($altRowsHtml)) { - $altRowsHtml = "No data available for this period." - } + } catch { $fsCard = '' } + + # Prepare Alternative Effort dynamic rendering data + try { + $gitAltBasisVal = [int]$Data.Git.AlternativeEffort.LinesBasis + $gitAltBasisLabel = [string]$Data.Git.AlternativeEffort.BasisLabel } catch { - $altRowsHtml = "Failed to render comparison rows." + $gitAltBasisVal = 0 + $gitAltBasisLabel = 'Git lines basis' } + try { + if ($Data.Filesystem) { $fsAltBasisVal = [int]$Data.Filesystem.SumLines } else { $fsAltBasisVal = 0 } + $fsAltBasisLabel = 'Text Lines (sum) across directory' + } catch { $fsAltBasisVal = 0; $fsAltBasisLabel = 'Text Lines (sum) across directory' } + try { + $altItemsJson = ($Data.Git.AlternativeEffort.Items | Select-Object Label,Factor,Links,Description | ConvertTo-Json -Depth 6 -Compress) + } catch { $altItemsJson = '[]' } + # JSON literals for safe JS embedding + $gitAltBasisValJson = ($gitAltBasisVal | ConvertTo-Json -Compress) + $gitAltBasisLabelJson = ($gitAltBasisLabel | ConvertTo-Json -Compress) + $fsAltBasisValJson = ($fsAltBasisVal | ConvertTo-Json -Compress) + $fsAltBasisLabelJson = ($fsAltBasisLabel | ConvertTo-Json -Compress) + + # Alternative Effort rows are now client-rendered dynamically based on selected view # Precompute header metadata strings to avoid complex subexpressions inside the here-string $metaPeriodText = if ($period) { "Period: $period" } else { '' } @@ -1381,11 +1928,12 @@ function Export-Report { @media (prefers-color-scheme: dark){ :root{ --bg:#0b0f14; --fg:#e5e7eb; --muted:#94a3b8; --card:#0f172a; --border:#233044; } } - body{ margin:32px; font-family:Segoe UI, Roboto, Arial, sans-serif; background:var(--bg); color:var(--fg); } - .container{ max-width:1100px; margin:0 auto; } + body{ margin:0; padding:32px; font-family:Segoe UI, Roboto, Arial, sans-serif; background:var(--bg); color:var(--fg); display:flex; min-height:100vh; flex-direction:column; } + .container{ max-width:1100px; margin:0 auto; width:100%; flex:1; display:block; } header h1{ margin:0; font-size:26px; color:var(--brand); } header .meta{ color:var(--muted); margin-top:4px; } .cards{ display:grid; grid-template-columns:1fr 1fr; gap:16px; margin:24px 0; } + .cards-single{ grid-template-columns:1fr; } .card{ background:var(--card); border:1px solid var(--border); border-radius:10px; padding:16px 18px; } .kpis{ display:grid; grid-template-columns:repeat(3, minmax(0,1fr)); gap:12px; margin-top:8px; } .kpi{ background:#fff0; border:1px dashed var(--border); border-radius:8px; padding:12px; text-align:center; } @@ -1404,15 +1952,21 @@ function Export-Report { .table thead{ background:var(--card); } .table th, .table td{ padding:10px 12px; border-bottom:1px solid var(--border); vertical-align:top; } .muted{ color:var(--muted); } - footer{ margin-top:24px; color:var(--muted); font-size:12px; } + footer{ margin-top:24px; color:var(--muted); font-size:12px; padding-top:8px; border-top:1px solid var(--border); } + footer .meta-row{ margin-top:6px; } code{ background:var(--card); padding:2px 6px; border-radius:6px; } #trend{ width:100%; display:block; } .chart-card{ background:var(--card); border:1px solid var(--border); border-radius:10px; padding:16px 18px; margin-top:16px; } + .chart-legend{ font-size:12px; color:var(--muted); } .meta-row{ display:flex; flex-wrap:wrap; gap:8px; margin-top:8px; } .meta-chip{ background:var(--card); border:1px solid var(--border); color:var(--muted); border-radius:999px; padding:4px 10px; font-size:12px; } .intro{ background:var(--card); border:1px solid var(--border); border-radius:10px; padding:14px 16px; margin-top:12px; } .intro p{ margin:6px 0; color:var(--muted); } .intro ul{ margin:6px 0 0 18px; color:var(--muted); } + .toggle .toggle-btn{ transition: background-color .15s ease, color .15s ease; } + .toggle-btn.active{ background:var(--brand); color:#fff !important; } + .toggle.toggle--intro .toggle-btn{ font-size:18px; padding:14px 22px !important; font-weight:600; } + .placeholder{ text-align:center; padding:48px 12px 24px 12px; } /* Accordions */ details { border:1px solid var(--border); border-radius:8px; padding:8px 10px; background:#fff0; } details + details { margin-top:8px; } @@ -1440,54 +1994,143 @@ function Export-Report { -
    +
    +
    + + +
    +
    + +
    +

    Select a view to continue

    +

    Use the toggle above to switch between repository commits and the recursive directory analysis.

    +
    + + + + - $chartSection +
    $chartSection
    +
    $fsChartSection
    - $commitTable +
    $commitTable
    +
    $fsCard
    -
    + + +
    Generated by VA Power Platform Workspace Template · Report ID: $(Get-Date -Format 'yyyyMMdd-HHmmss')
    $metaChipsHtml
    -
    @@ -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

    +
      +
    1. 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).
    2. +
    3. Right-click inside the folder background and choose New > Folder.
    4. +
    5. Type the folder name exactly as shown (for example docs or .vscode) and press Enter.
    6. +
    7. If you’re on a VA network, prefer a local path like C:\Users\<YourName>\Desktop\YourProject to avoid slow network performance.
    8. +
    9. Return to VS Code. The folder should appear in the Explorer. If not, click the Refresh icon or press F5.
    10. +
    11. Re-run the validator to confirm the error is gone.
    12. +
    +

    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

    +
      +
    1. In VS Code, right-click your workspace root folder in the Explorer > New File.
    2. +
    3. Name it README.md (all caps is traditional but not required).
    4. +
    5. Paste a simple outline: What this project is, how to open it, how to run scripts (if any), and who to contact for help.
    6. +
    7. Save. For ideas, see Make a README.
    8. +
    +

    Step-by-step: Create .vscode\settings.json

    +
      +
    1. In VS Code Explorer, right-click the root folder > New Folder and name it .vscode (if it doesn’t exist).
    2. +
    3. Right-click the new .vscode folder > New File and name it settings.json.
    4. +
    5. Add a starter: +
      {
      +  "files.eol": "\r\n",
      +  "editor.tabSize": 2,
      +  "editor.wordWrap": "on"
      +}
      +  
      +
    6. +
    7. Save the file and re-run the validator.
    8. +
    +'@ + +$howtoInvalidJson = @' +

    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

    +
      +
    1. Open the file listed (for example .vscode\tasks.json).
    2. +
    3. Look at the Problems panel in VS Code (bottom pane). It will show the exact line and character.
    4. +
    5. Remove trailing commas, ensure all keys/strings use double quotes, and remove comments (// like this) which are not allowed in JSON.
    6. +
    7. Save the file and re-run the validator.
    8. +
    +'@ + +$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.

    +

    Install Git (Windows)

    +
      +
    1. Visit git-scm.com/downloads/win and run the installer.
    2. +
    3. Accept defaults unless your team advises otherwise.
    4. +
    5. Close and reopen VS Code, then run git --version in a terminal to confirm.
    6. +
    +

    Install Power Platform CLI (PAC)

    +
      +
    1. See Microsoft’s guide: Install PAC CLI.
    2. +
    3. If winget is available, you can run winget install Microsoft.PowerApps.CLI in an elevated PowerShell.
    4. +
    5. Reopen VS Code and run pac --version to verify.
    6. +
    +'@ + +$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

    +
      +
    1. Open the file shown in the error list (for example scripts\MyReport.ps1).
    2. +
    3. Read the message in the Problems panel. If it mentions a missing } or quote, check the lines just above for balance.
    4. +
    5. Use indentation to align blocks clearly, then save and re-run the validator.
    6. +
    7. If stuck, ask Copilot Chat: paste the error and the function around the line number and request a fix.
    8. +
    +'@ + +$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

    +
      +
    1. In VS Code, go to File > Save Workspace As....
    2. +
    3. Save it in your project root (for example YourProject.code-workspace).
    4. +
    5. Next time, double-click this file to reopen the same project with your layout and tasks.
    6. +
    +'@ + +$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

    +
      +
    1. In VS Code, create the .vscode folder if it doesn’t exist.
    2. +
    3. Inside .vscode, create a file named tasks.json.
    4. +
    5. Paste a starter config: +
      {
      +  "version": "2.0.0",
      +  "tasks": [
      +    {
      +      "label": "Validate Workspace",
      +      "type": "shell",
      +      "command": "pwsh",
      +      "args": ["-NoProfile","-ExecutionPolicy","Bypass","-File","copilot-instructions/Validate-WorkspaceSetup.ps1","-Detailed"],
      +      "problemMatcher": []
      +    }
      +  ]
      +}
      +  
      +
    6. +
    7. Run it via Terminal > Run Task... and pick “Validate Workspace”.
    8. +
    9. Learn more in VS Code Tasks.
    10. +
    +'@ + +$howtoVscodeSettings = @' +

    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

    +
      +
    1. Create .vscode if it doesn’t exist, then create settings.json inside it.
    2. +
    3. Paste: +
      {
      +  "files.eol": "\r\n",
      +  "editor.tabSize": 2,
      +  "editor.wordWrap": "on"
      +}
      +  
      +
    4. +
    5. Open File > Preferences > Settings to discover more helpful options.
    6. +
    7. See User & Workspace Settings.
    8. +
    +'@ + +$howtoOpsScripts = @' +

    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

    +
      +
    1. Create a copilot-instructions folder in your workspace if it doesn’t exist.
    2. +
    3. Copy the missing scripts from your team’s Copilot kit (the same place you got this validator), or request them from your team lead.
    4. +
    5. After copying, right-click each .ps1 in VS Code and select Open to review inline help comments.
    6. +
    7. Re-run the validator to confirm the files are detected.
    8. +
    +'@ + +# HTML output (double-quoted here-string for interpolation) +$html = @" + + + + + + Workspace Setup Guidance + + + +

    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.

    + +

    Summary

    +
      +
    • Passed: $($data.Summary.Passed)
    • +
    • Warnings: $($data.Summary.Warnings)
    • +
    • Errors: $($data.Summary.Errors)
    • +
    • Total Checks: $($data.Summary.TotalChecks)
    • +
    + +

    Fixes

    + $(if($hasMissingDirs){"
    Missing directories" + $missingDirs.Count + "

    Detected: $(_escHtml (($missingDirs -join ', ')))

    $howtoMissingDir
    "}) + + $(if($hasMissingFiles){"
    Missing files" + $missingFiles.Count + "

    Detected: $(_escHtml (($missingFiles -join ', ')))

    $howtoMissingFile
    "}) + + $(if($hasMissingTasks){"
    VS Code tasks configuration
    $howtoVscodeTasks
    "}) + $(if($hasMissingSettings){"
    VS Code project settings
    $howtoVscodeSettings
    "}) + $(if($hasMissingOpsScripts){"
    Automation scripts
    $howtoOpsScripts
    "}) + + $(if($hasInvalidJson){"
    Invalid JSON in VS Code files" + $invalidJson.Count + "

    Detected: $(_escHtml (($invalidJson -join ', ')))

    $howtoInvalidJson
    "}) + + $(if($hasDepIssues){"
    Dependency checks

    Git: " + $(if($depsGit){'Installed'}else{'Not found'}) + "   |   PAC: " + $(if($depsPac){'Installed'}else{'Not found'}) + "

    $howtoDeps
    "}) + + $(if($scriptErrors -and $scriptErrors.Count -gt 0){"
    PowerShell script syntax" + $scriptErrors.Count + "

    Detected: $(_escHtml (($scriptErrors | ForEach-Object { \"$($_.File): $($_.Message)\" }) -join '; '))

    $howtoScriptSyntax
    "}) + + $(if($ws -and -not $ws.HasWorkspaceFile){"
    Workspace file missing
    $howtoWorkspaceNone
    "}) + $(if($ws -and $ws.MultipleWorkspaceFiles){"
    Multiple workspace files detected
    $howtoWorkspaceMultiple
    "}) + +
    +

    More learning and help

    +

    Keep this handy library of references. Each link opens official documentation or a trusted guide:

    + +

    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.

    + + +"@ + +try { + $dir = Split-Path -Parent $OutputHtml + if ($dir -and -not (Test-Path -LiteralPath $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null } + $html | Set-Content -Path $OutputHtml -Encoding UTF8 + Write-Host "Guidance written to: $OutputHtml" -ForegroundColor Cyan +} catch { + throw "Failed to write HTML: $($_.Exception.Message)" +} + +if ($Open) { + try { Start-Process $OutputHtml | Out-Null } catch { Write-Host "WARN: Failed to open guidance: $($_.Exception.Message)" -ForegroundColor Yellow } +} + +if ($DeleteJson) { + try { Remove-Item -Path $JsonReport -Force -ErrorAction Stop } catch { Write-Host "WARN: Failed to delete JSON: $($_.Exception.Message)" -ForegroundColor Yellow } +} diff --git a/copilot-instructions/Install-Copilot-Instructions.bat b/copilot-instructions/Install-Copilot-Instructions.bat new file mode 100644 index 0000000..9ab2c06 --- /dev/null +++ b/copilot-instructions/Install-Copilot-Instructions.bat @@ -0,0 +1,243 @@ +@echo off +:: ---------------------------------------------------------------------------- +:: Install-Copilot-Instructions.bat +:: ---------------------------------------------------------------------------- +:: Purpose: +:: Ensure a workspace-level Copilot instructions file exists at +:: \.github\copilot-instructions.md by downloading your +:: public template or using a local fallback, then creating or updating +:: the target file. +:: +:: What this script does (high level): +:: 1) Finds its own folder and the one-level-up repo root ("grandDir"). +:: 2) Ensures a .github folder exists at the repo root. +:: 3) Downloads the latest template from GitHub to a temp file. +:: 4) If a target exists, prepends the template and pushes existing content +:: down by 385 lines; otherwise it creates the file. +:: 5) Prints a success message and pauses so users can read output. +:: +:: Requirements: +:: - Windows (batch + PowerShell available by default on Win 10/11) +:: - Write access to the repo root to create/modify .github +:: - Network access to raw.githubusercontent.com +:: +:: Exit codes: +:: 0 = Success; 1 = Error encountered +:: ---------------------------------------------------------------------------- +setlocal EnableExtensions EnableDelayedExpansion +:: Track a single exit code for all branches; we jump to :done to pause. +set "EXITCODE=0" + +rem --------------------------------------------------------------------- +rem Install/Update .github\copilot-instructions.md from repo template +rem --------------------------------------------------------------------- +echo. +echo [INFO] Starting Copilot instructions installer... +echo Date/Time: %DATE% %TIME% +echo. +echo [INFO] Initializing environment... please wait if this is the first run. +echo (PowerShell can take a few seconds to start on a cold machine.) +rem Warm up PowerShell so users see progress early and reduce cold-start lag later +powershell -NoProfile -ExecutionPolicy Bypass -Command "Write-Host ' [OK] PowerShell initialized'" 2>nul +echo. + +rem 1) Identify this .bat file's directory (parent of the .bat) +rem %~dp0 yields the drive+path of this script, with a trailing backslash. +set "batDir=%~dp0" +for %%I in ("%batDir%..") do set "grandDir=%%~fI" +set "parentDir=%batDir%" +echo [INFO] Script folder: %parentDir% +echo [INFO] Candidate repo root (grandparent): %grandDir% + +rem Normalize paths (remove trailing backslashes where needed) +if "%parentDir:~-1%"=="\" set "parentDir=%parentDir:~0,-1%" +if "%grandDir:~-1%"=="\" set "grandDir=%grandDir:~0,-1%" + +rem 2) Discovery only: we now have parentDir and grandDir for later checks. +rem No directory listings are printed; this keeps the script quiet. + +rem 3) Check if the same folder as this .bat contains copilot-instructions.md +set "parentMd=%parentDir%\copilot-instructions.md" +if exist "%parentMd%" ( + set "HAS_PARENT_MD=1" + echo [INFO] Found local template beside script: "%parentMd%" +) else ( + set "HAS_PARENT_MD=0" + echo [INFO] No local template beside script. +) + +rem 4) Check if the repo root (grandDir) contains a .github folder +set "ghDir=%grandDir%\.github" +if exist "%ghDir%" ( + set "HAS_GH_DIR=1" + echo [INFO] .github folder exists at repo root: "%ghDir%" +) else ( + set "HAS_GH_DIR=0" + echo [INFO] .github folder not found at repo root. Will create if required. +) + +rem 5/6) Ensure .github folder exists when required +rem Per requirements: if parent has MD and .github is missing, create it. +if "%HAS_GH_DIR%"=="0" ( + rem If parent has MD and .github is missing, create it (per requirement #6) + if "%HAS_PARENT_MD%"=="1" ( + echo [ACTION] Creating missing .github folder at "%ghDir%"... + mkdir "%ghDir%" 2>nul + if errorlevel 1 ( + echo ERROR: Failed to create "%ghDir%". + set "EXITCODE=1" & goto :done + ) + set "HAS_GH_DIR=1" + echo [OK] Created .github folder. + ) +) + +rem 7) Fetch template from GitHub RAW (always fetch to ensure latest) +rem We save to a precomputed temp path to avoid PowerShell $env syntax. +set "TEMPLATE_URL=https://raw.githubusercontent.com/KCoderVA/GitHub-Copilot-Setup-Guide/main/copilot-instructions/copilot-instructions.md" +set "TMP_TPL=%TEMP%\copilot_instructions_template.md" + +rem PowerShell download with a quiet progress display and explicit out file +echo [ACTION] Downloading template from: +echo %TEMPLATE_URL% +echo to temp file: "%TMP_TPL%" +powershell -NoProfile -ExecutionPolicy Bypass -Command "try { $ProgressPreference='SilentlyContinue'; Invoke-WebRequest -UseBasicParsing -Uri '%TEMPLATE_URL%' -OutFile '%TMP_TPL%'; exit 0 } catch { Write-Host ('Download failed: ' + $_.Exception.Message); exit 1 }" +if errorlevel 1 ( + if "%HAS_PARENT_MD%"=="1" ( + echo [WARN] Download failed. Using local template beside script: "%parentMd%" + copy /Y "%parentMd%" "%TMP_TPL%" >nul + if errorlevel 1 ( + echo ERROR: Local fallback copy failed. + set "EXITCODE=1" & goto :done + ) + ) else ( + echo ERROR: Unable to download template from %TEMPLATE_URL% and no local fallback found. + set "EXITCODE=1" & goto :done + ) +) +for %%A in ("%TMP_TPL%") do set "TPL_SIZE=%%~zA" +echo [OK] Template ready. Size: !TPL_SIZE! bytes + +rem Ensure .github exists now (for creation path too) +if "%HAS_GH_DIR%"=="0" ( + mkdir "%ghDir%" 2>nul + if errorlevel 1 ( + echo ERROR: Failed to create "%ghDir%". + set "EXITCODE=1" & goto :done + ) +) + +set "ghFile=%ghDir%\copilot-instructions.md" +echo [INFO] Target file path: "%ghFile%" + +rem 5,8,9) Create or inject content at top (with 385-line spacer when merging) +rem - If the target exists: prepend the template, add 385 blank lines, +rem then append the previous content. This preserves the existing file +rem while surfacing the template at the top. +rem - If the target doesn't exist: create it from the template. +if exist "%ghFile%" ( + rem Inject: [template] + 5 blank lines + existing content + echo [ACTION] Existing target detected. Prepending template and pushing prior content down by 385 lines... + powershell -NoProfile -ExecutionPolicy Bypass -Command ^ + "$ErrorActionPreference='Stop'; " ^ + "$tpl = Get-Content -Raw '%TMP_TPL%'; " ^ + "$existing = Get-Content -Raw '%ghFile%' -ErrorAction SilentlyContinue; " ^ + "$nl = [Environment]::NewLine; " ^ + "$blanks = [string]::Concat((1..5 | ForEach-Object { $nl })); " ^ + "$out = $tpl + $blanks + $existing; " ^ + "$tmp = [System.IO.Path]::GetTempFileName(); " ^ + "$utf8NoBom = New-Object System.Text.UTF8Encoding($false); " ^ + "[System.IO.File]::WriteAllText($tmp, $out, $utf8NoBom); " ^ + "Move-Item -Force -LiteralPath $tmp -Destination '%ghFile%'" + if errorlevel 1 ( + echo ERROR: Failed to inject into "%ghFile%". + set "EXITCODE=1" & goto :done + ) + echo [OK] Injection completed. +) else ( + rem Create new file from template + echo [ACTION] Target doesn't exist. Creating new file from template... + powershell -NoProfile -ExecutionPolicy Bypass -Command ^ + "$ErrorActionPreference='Stop'; " ^ + "$src = '%TMP_TPL%'; " ^ + "$tmp = [System.IO.Path]::GetTempFileName(); " ^ + "Copy-Item -LiteralPath $src -Destination $tmp -Force; " ^ + "Move-Item -Force -LiteralPath $tmp -Destination '%ghFile%'" + if errorlevel 1 ( + echo ERROR: Failed to create "%ghFile%". + set "EXITCODE=1" & goto :done + ) + echo [OK] Created new target file. +) + +for %%A in ("%ghFile%") do set "GH_SIZE=%%~zA" +echo [INFO] Final target size: !GH_SIZE! bytes +echo. + +rem 10) Success notification +rem Friendly summary and next steps. We pause below so users can read it. +echo. +echo ===================================================================== +echo SUCCESS: Copilot instructions have been installed/updated at: +echo "%ghFile%" +echo. +echo Next steps: +echo 1) Restart VS Code to pick up the updated .github\copilot-instructions.md +echo 2) Open Copilot Chat and prompt your agent to identify and comply with +echo the workspace instructions (pin the file for deterministic behavior) +echo 3) Optionally open the file now to review or customize local guidance +echo (Path shown above). Consider committing it to source control. +echo 4) Share this installer with teammates who need the same setup. +echo ===================================================================== +echo. + +rem Attempt to open the target file directly in VS Code using the vscode:// protocol +echo [ACTION] Opening the instructions file in VS Code (if installed)... +set "VSC_URI=" +for /f "usebackq delims=" %%U in (`powershell -NoProfile -ExecutionPolicy Bypass -Command "$p=[IO.Path]::GetFullPath('%ghFile%'); $u='vscode://file/'+($p -replace '\\','/'); Write-Output $u"`) do set "VSC_URI=%%U" +if not "%VSC_URI%"=="" ( + echo [INFO] VS Code URI: %VSC_URI% + start "" "%VSC_URI%" >nul 2>nul +) else ( + echo [WARN] Could not construct VS Code URI. +) + +rem If the 'code' CLI is available, also open the file via CLI as a fallback +where code >nul 2>nul && ( + echo [ACTION] Opening via 'code' CLI fallback... + code "%ghFile%" >nul 2>nul +) + +rem Try to open Copilot Chat view (may no-op if extension/commands not present) +echo [ACTION] Attempting to open Copilot Chat in VS Code... +start "" "vscode://command/github.copilot.openChat" >nul 2>nul +start "" "vscode://command/workbench.action.chat.open" >nul 2>nul +echo [INFO] If Copilot Chat did not open automatically, open it from the sidebar. + +rem Provide a ready-to-use Copilot Chat prompt and copy it to clipboard +set "COPILOT_PROMPT=Use the workspace instructions at \"%ghFile%\" (.github\\copilot-instructions.md). Read it now, pin it in this chat, and comply with its rules for all tasks." +echo [ACTION] Copying a suggested Copilot Chat prompt to your clipboard... +echo %COPILOT_PROMPT% | clip +if errorlevel 1 ( + powershell -NoProfile -ExecutionPolicy Bypass -Command "$p=\"%COPILOT_PROMPT%\"; Set-Clipboard -Value $p" >nul 2>nul +) +echo [OK] Prompt copied. In Copilot Chat, press Ctrl+V to paste it. + +rem Check if VS Code appears to be installed (CLI and/or URI handler). If not, guide the user. +set "HAS_CODE=0" +where code >nul 2>nul && set "HAS_CODE=1" +set "HAS_VSCODE_URI=0" +reg query "HKCR\vscode" >nul 2>nul && set "HAS_VSCODE_URI=1" +if "%HAS_CODE%"=="0" if "%HAS_VSCODE_URI%"=="0" ( + echo [WARN] Visual Studio Code does not appear to be installed on this machine. + echo You can download and install the latest version from: + echo https://code.visualstudio.com/download + start "" "https://code.visualstudio.com/download" >nul 2>nul +) + +:: Final pause and exit summary +:done +echo Press any key to close this window... +pause >nul + +endlocal & exit /b %EXITCODE% diff --git a/copilot-instructions/Validate-Syntax.ps1 b/copilot-instructions/Validate-Syntax.ps1 index 70b1263..9d5a722 100644 --- a/copilot-instructions/Validate-Syntax.ps1 +++ b/copilot-instructions/Validate-Syntax.ps1 @@ -12,12 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Generate-ProductivityReport.ps1 -# VA Power Platform Productivity Reporting Script -# Author: Kyle J. Coder - Edward Hines Jr. VA Hospital -# Purpose: Generates comprehensive productivity reports from workspace activity with enhanced Git statistics - - <# .SYNOPSIS Validates syntax for configuration files (YAML, JSON, XML, MSAPP) diff --git a/copilot-instructions/Validate-WorkspaceSetup.ps1 b/copilot-instructions/Validate-WorkspaceSetup.ps1 index 00caf95..57af4e1 100644 --- a/copilot-instructions/Validate-WorkspaceSetup.ps1 +++ b/copilot-instructions/Validate-WorkspaceSetup.ps1 @@ -12,12 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Generate-ProductivityReport.ps1 -# VA Power Platform Productivity Reporting Script -# Author: Kyle J. Coder - Edward Hines Jr. VA Hospital -# Purpose: Generates comprehensive productivity reports from workspace activity with enhanced Git statistics - - # Validate-WorkspaceSetup.ps1 # VA Power Platform Workspace Validation Script # Author: Kyle J. Coder - Edward Hines Jr. VA Hospital @@ -36,6 +30,14 @@ $ErrorCount = 0 $WarningCount = 0 $PassCount = 0 +# Collections for JSON report +$MissingDirs = @() +$MissingFiles = @() +$InvalidJsonFiles = @() +$WorkspaceFilesFound = @() +$ScriptSyntaxErrors = @() +$Dependencies = [ordered]@{ Git = $false; PAC = $false } + Write-Host "VA Power Platform Workspace Validation" -ForegroundColor Green Write-Host "=======================================" -ForegroundColor Green @@ -55,6 +57,7 @@ foreach ($Dir in $RequiredDirs) { } else { Write-Host " FAIL: $Dir (Missing)" -ForegroundColor Red $ErrorCount++ + $MissingDirs += $Dir if ($FixIssues) { try { @@ -74,10 +77,8 @@ Write-Host "`nChecking required files..." -ForegroundColor Yellow $RequiredFiles = @( 'README.md', - 'scripts\Generate-ProductivityReport.ps1', - 'scripts\Clean-Workspace.ps1', - 'scripts\Unpack-PowerApp.ps1', - 'scripts\Pack-PowerApp.ps1' + 'copilot-instructions\Generate-ProductivityReport.ps1', + 'copilot-instructions\Clean-Workspace.ps1' ) foreach ($File in $RequiredFiles) { @@ -88,6 +89,7 @@ foreach ($File in $RequiredFiles) { } else { Write-Host " FAIL: $File (Missing)" -ForegroundColor Red $ErrorCount++ + $MissingFiles += $File } } @@ -110,14 +112,49 @@ foreach ($File in $VSCodeFiles) { } catch { Write-Host " WARN: $File (Invalid JSON)" -ForegroundColor Yellow $WarningCount++ + $InvalidJsonFiles += $File } } else { Write-Host " FAIL: $File (Missing)" -ForegroundColor Red $ErrorCount++ + $MissingFiles += $File } } -# 4. Check Workspace File +# 4. Check .github instructions file +Write-Host "`nChecking Copilot instructions (.github) ..." -ForegroundColor Yellow +$GithubDir = Join-Path $WorkspaceRoot '.github' +$InstructionsPath = Join-Path $GithubDir 'copilot-instructions.md' +if (Test-Path $InstructionsPath) { + Write-Host " PASS: .github\\copilot-instructions.md present" -ForegroundColor Green + $PassCount++ +} else { + Write-Host " FAIL: .github\\copilot-instructions.md missing" -ForegroundColor Red + $ErrorCount++ + $MissingFiles += ".github\\copilot-instructions.md" + $InstallerBat = Join-Path $WorkspaceRoot 'copilot-instructions\Install-Copilot-Instructions.bat' + if ($FixIssues -and (Test-Path $InstallerBat)) { + Write-Host " FIX: Running installer to create/update instructions..." -ForegroundColor Cyan + try { + $p = Start-Process -FilePath 'cmd.exe' -ArgumentList '/c', '"' + $InstallerBat + '"' -Wait -PassThru -WindowStyle Hidden + if ($p.ExitCode -eq 0 -and (Test-Path $InstructionsPath)) { + Write-Host " FIXED: Instructions installed" -ForegroundColor Green + $ErrorCount-- + $PassCount++ + } else { + Write-Host " WARN: Installer exited with code $($p.ExitCode)." -ForegroundColor Yellow + } + } catch { + Write-Host " WARN: Failed to run installer: $($_.Exception.Message)" -ForegroundColor Yellow + } + } else { + if (-not (Test-Path $InstallerBat)) { + Write-Host " HINT: Run copilot-instructions\\Install-Copilot-Instructions.bat to set this up." -ForegroundColor Gray + } + } +} + +# 5. Check Workspace File Write-Host "`nChecking workspace file..." -ForegroundColor Yellow $WorkspaceFiles = Get-ChildItem -Path $WorkspaceRoot -Filter "*.code-workspace" -ErrorAction SilentlyContinue @@ -129,12 +166,13 @@ if ($WorkspaceFiles.Count -gt 0) { Write-Host " WARN: Multiple workspace files found" -ForegroundColor Yellow $WarningCount++ } + $WorkspaceFilesFound = $WorkspaceFiles | ForEach-Object { $_.Name } } else { Write-Host " FAIL: No workspace file found" -ForegroundColor Red $ErrorCount++ } -# 5. Check External Dependencies +# 6. Check External Dependencies Write-Host "`nChecking external dependencies..." -ForegroundColor Yellow # Check Git @@ -143,13 +181,16 @@ try { if ($LASTEXITCODE -eq 0) { Write-Host " PASS: Git installed" -ForegroundColor Green $PassCount++ + $Dependencies.Git = $true } else { Write-Host " WARN: Git not found" -ForegroundColor Yellow $WarningCount++ + $Dependencies.Git = $false } } catch { Write-Host " WARN: Git not found" -ForegroundColor Yellow $WarningCount++ + $Dependencies.Git = $false } # Check Power Platform CLI @@ -158,16 +199,19 @@ try { if ($LASTEXITCODE -eq 0) { Write-Host " PASS: Power Platform CLI installed" -ForegroundColor Green $PassCount++ + $Dependencies.PAC = $true } else { Write-Host " WARN: Power Platform CLI not found" -ForegroundColor Yellow $WarningCount++ + $Dependencies.PAC = $false } } catch { Write-Host " WARN: Power Platform CLI not found" -ForegroundColor Yellow $WarningCount++ + $Dependencies.PAC = $false } -# 6. Check PowerShell Scripts Syntax +# 7. Check PowerShell Scripts Syntax Write-Host "`nValidating script syntax..." -ForegroundColor Yellow $ScriptFiles = Get-ChildItem -Path (Join-Path $WorkspaceRoot "scripts") -Filter "*.ps1" -ErrorAction SilentlyContinue @@ -183,6 +227,7 @@ foreach ($Script in $ScriptFiles) { if ($Detailed) { Write-Host " Error: $($_.Exception.Message)" -ForegroundColor Gray } + $ScriptSyntaxErrors += [ordered]@{ File = $Script.Name; Message = $_.Exception.Message } } } @@ -209,7 +254,7 @@ Write-Host " Warnings: $WarningCount" -ForegroundColor Yellow Write-Host " Errors: $ErrorCount" -ForegroundColor Red Write-Host " Total Checks: $TotalChecks" -ForegroundColor Cyan -# Save report if requested +# Save report if requested (Markdown) if ($OutputPath) { $ReportContent = @" # VA Power Platform Workspace Validation Report @@ -230,7 +275,7 @@ elseif ($ErrorCount -eq 0) { "GOOD - Minor warnings found" } else { "ISSUES FOUND - Errors require attention" }) --- -Generated by VA Power Platform Workspace Template +Generated by VA Power Platform Workspace Template (updated Aug 2025) "@ try { @@ -241,6 +286,49 @@ Generated by VA Power Platform Workspace Template } } +# Always produce a JSON report to a temp file if errors or warnings were found, then trigger guidance generator +if ($ErrorCount -gt 0 -or $WarningCount -gt 0) { + try { + $ts = (Get-Date).ToString('yyyyMMdd-HHmmss') + $tempJson = Join-Path $env:TEMP "workspace-validation-$ts.json" + $jsonReport = [ordered]@{ + GeneratedUtc = (Get-Date).ToUniversalTime().ToString('o') + Root = $WorkspaceRoot + Summary = [ordered]@{ Passed = $PassCount; Warnings = $WarningCount; Errors = $ErrorCount; TotalChecks = $TotalChecks } + Findings = [ordered]@{ + MissingDirs = $MissingDirs + MissingFiles = $MissingFiles + InvalidJsonFiles = $InvalidJsonFiles + WorkspaceFiles = $WorkspaceFilesFound + Dependencies = $Dependencies + ScriptSyntaxErrors= $ScriptSyntaxErrors + Workspace = [ordered]@{ + HasWorkspaceFile = ($WorkspaceFilesFound.Count -gt 0) + MultipleWorkspaceFiles= ($WorkspaceFilesFound.Count -gt 1) + } + } + } + $jsonReport | ConvertTo-Json -Depth 8 | Set-Content -Path $tempJson -Encoding UTF8 + Write-Host "`nJSON report created: $tempJson" -ForegroundColor Cyan + + # Create the educational HTML in the workspace root (per requirements) + $outHtml = Join-Path $WorkspaceRoot "Workspace-Guidance-$ts.html" + + $guidanceScript = Join-Path $PSScriptRoot 'Generate-WorkspaceGuidance.ps1' + if (Test-Path $guidanceScript) { + try { + & $guidanceScript -JsonReport $tempJson -OutputHtml $outHtml -Open -DeleteJson + } catch { + Write-Host "WARN: Failed to generate guidance: $($_.Exception.Message)" -ForegroundColor Yellow + } + } else { + Write-Host "HINT: Guidance script not found: $guidanceScript" -ForegroundColor Gray + } + } catch { + Write-Host "WARN: Failed to create JSON report: $($_.Exception.Message)" -ForegroundColor Yellow + } +} + Write-Host "`nValidation complete!" -ForegroundColor Green # Return appropriate exit code diff --git a/copilot-instructions/copilot-instructions.md b/copilot-instructions/copilot-instructions.md index 6b1e333..9ee4c14 100644 --- a/copilot-instructions/copilot-instructions.md +++ b/copilot-instructions/copilot-instructions.md @@ -1,24 +1,61 @@ # Copilot Instructions - {{PROJECT_NAME}} ## 🎯 Project Overview -You are working with **{{PROJECT_NAME}}**, a {{PROJECT_TYPE}} project for {{VA_FACILITY}} built using the VA Power Platform workspace template. This workspace provides a complete development environment with pre-built automation scripts, organized folder structure, and VA-compliant configurations. +You are working on **{{PROJECT_NAME}}**, a {{PROJECT_TYPE}} project for {{VA_FACILITY}} built using the VA workspace template. This workspace provides a complete development environment with pre-built automation scripts, organized folder structure, and VA-compliant configurations. ## 🏥 VA Environment Understanding @@ -35,6 +72,205 @@ You are working with **{{PROJECT_NAME}}**, a {{PROJECT_TYPE}} project for {{VA_F - **Project Type:** {{PROJECT_TYPE}} - **Created:** {{CURRENT_DATE}} +## 👤 User Demographics + + +- **Your Name:** {{USER_NAME}} + + +- **Primary Role:** {{USER_ROLE}} + + +- **Team Name:** {{TEAM_NAME}} + + +- **Facility Name:** {{FACILITY_NAME}} + + +- **Facility Number:** {{FACILITY_NUMBER}} + + +- **VA Network Number:** {{VA_NETWORK_NUMBER}} + + +- **Location/Timezone:** {{USER_LOCATION}} + + +- **Working Hours:** {{WORK_HOURS}} + + +- **Accessibility Needs:** {{ACCESSIBILITY_NEEDS}} + +## 👤 User Work Preferences + + +- **GitHub Username:** {{GITHUB_USERNAME}} + + +- **File Naming Convention:** {{FILE_NAMING_PREF}} + + +- **Preferred Collaboration Tool:** {{COLLAB_TOOLS}} + + +- **Code Review Preferences:** {{CODE_REVIEW_PREFS}} + + +- **Preferred Output Format:** {{OUTPUT_FORMAT}} + + +- **VS Code Extensions:** {{PREFERRED_VSCODE_EXTENSIONS}} + + +- **Software Tools Used:** {{SOFTWARE_TOOLS}} + + +- **Font:** {{FONT_PREF}} + + +- **Font Size:** {{FONT_SIZE_PREF}} + + +- **Color Scheme / Theme:** {{COLOR_SCHEME_PREF}} + + +- **UI Layout Preferences:** {{UI_PREFS}} + + +- **Copilot Response Style:** {{COPILOT_RESPONSE_STYLE}} + + +- **Documentation Detail Level:** {{DOC_DETAIL_LEVEL}} + + +- **Automation Notification Preference:** {{AUTOMATION_NOTIFY_PREF}} + + +- **Default Project Language:** {{DEFAULT_PROJECT_LANGUAGE}} + + +- **Collaboration Style:** {{COLLAB_STYLE}} + + +- **Task Tracking Tool:** {{TASK_TRACKING_TOOL}} + + +- **Preferred Testing Frameworks:** {{TEST_FRAMEWORKS}} + + +- **Accessibility Settings:** {{ACCESSIBILITY_PREFS}} + + +- **Keyboard Shortcuts:** {{KEYBOARD_SHORTCUTS_PREF}} + + +- **Workspace Start-up Tasks:** {{STARTUP_TASKS}} + + +- **Preferred Commit Message Style:** {{COMMIT_MESSAGE_STYLE}} + + +- **Notification Preferences:** {{NOTIFICATION_PREFS}} + + +- **Branch Naming Convention:** {{BRANCH_NAMING_PREF}} + + +- **Project Documentation Language:** {{DOC_LANGUAGE}} + + +- **Preferred Issue Template:** {{ISSUE_TEMPLATE_PREF}} + + +- **Learning Mode:** {{LEARNING_MODE}} + + +- **Favorite Productivity Tools:** {{PRODUCTIVITY_TOOLS}} + + +- **Programming Paradigm:** {{PROGRAMMING_PARADIGM}} + + +- **Linter/Formatter:** {{LINTER_FORMATTER}} + ## 📁 Workspace Structure This project follows a professional organization pattern: @@ -126,4 +362,21 @@ This project follows a professional organization pattern: **Last Updated:** {{CURRENT_DATE}} **Project:** {{PROJECT_NAME}} -**Template Version:** 1.0.0 + + + +**Template Version:** 1.0.0 \ No newline at end of file diff --git a/copilot-instructions/tasks.json b/copilot-instructions/tasks.json index cf94d4b..a37ccb3 100644 --- a/copilot-instructions/tasks.json +++ b/copilot-instructions/tasks.json @@ -24,285 +24,84 @@ limitations under the License. "version": "2.0.0", "tasks": [ { - "label": "Generate Productivity Report", + "label": "Install/Update Copilot Instructions (.github)", "type": "shell", - "command": "powershell.exe", - "args": [ - "-ExecutionPolicy", - "Bypass", - "-File", - "${workspaceFolder}/scripts/Generate-ProductivityReport.ps1" - ], + "command": "cmd.exe", + "args": ["/c", "\"${workspaceFolder}\\copilot-instructions\\Install-Copilot-Instructions.bat\""] "group": { "kind": "build", "isDefault": true }, - "presentation": { - "echo": true, - "reveal": "always", - "focus": false, - "panel": "shared" - }, - "problemMatcher": [], - "detail": "Creates supervisor-ready productivity and progress reports" - }, - { - "label": "Update Version & Commit", - "type": "shell", - "command": "powershell.exe", - "args": [ - "-ExecutionPolicy", - "Bypass", - "-File", - "${workspaceFolder}/scripts/Update-VersionAndCommit.ps1", - "-CommitMessage", - "${input:commitMessage}" - ], - "group": "build", - "presentation": { - "echo": true, - "reveal": "always", - "focus": false, - "panel": "shared" - }, - "problemMatcher": [], - "detail": "Automatically updates version number and commits changes with proper VA formatting" - }, - { - "label": "Clean Workspace (Archive TEMP_ files)", - "type": "shell", - "command": "powershell.exe", - "args": [ - "-ExecutionPolicy", - "Bypass", - "-File", - "${workspaceFolder}/scripts/Clean-Workspace.ps1" - ], - "group": "build", - "presentation": { - "echo": true, - "reveal": "always", - "focus": false, - "panel": "shared" - }, - "problemMatcher": [], - "detail": "Intelligently moves temporary and duplicate files to archive folder" - }, - { - "label": "Unpack PowerApp (.msapp)", - "type": "shell", - "command": "powershell.exe", - "args": [ - "-ExecutionPolicy", - "Bypass", - "-File", - "${workspaceFolder}/scripts/Unpack-PowerApp.ps1", - "-AppPath", - "${input:msappFile}" - ], - "group": "build", - "presentation": { - "echo": true, - "reveal": "always", - "focus": false, - "panel": "shared" - }, - "problemMatcher": [], - "detail": "Unpacks a .msapp file into individual component files for Git version control" - }, - { - "label": "Pack PowerApp (Create .msapp)", - "type": "shell", - "command": "powershell.exe", - "args": [ - "-ExecutionPolicy", - "Bypass", - "-File", - "${workspaceFolder}/scripts/Pack-PowerApp.ps1", - "-AppName", - "${input:appName}" - ], - "group": "build", - "presentation": { - "echo": true, - "reveal": "always", - "focus": false, - "panel": "shared" - }, + "presentation": { "echo": true, "reveal": "always", "focus": false, "panel": "shared" }, "problemMatcher": [], - "detail": "Packs unpacked PowerApp components back into .msapp file for deployment" + "detail": "Creates or updates .github\\copilot-instructions.md, opens VS Code/Copilot Chat, and copies a suggested prompt" }, { - "label": "Export SQL Results to CSV", + "label": "Generate Productivity Report", "type": "shell", "command": "powershell.exe", - "args": [ - "-ExecutionPolicy", - "Bypass", - "-File", - "${workspaceFolder}/scripts/Export-SQLQuery.ps1", - "-QueryFile", - "${input:sqlFile}" - ], + "args": ["-NoProfile","-ExecutionPolicy","Bypass","-File","${workspaceFolder}/copilot-instructions/Generate-ProductivityReport.ps1","-NonInteractive"], "group": "build", - "presentation": { - "echo": true, - "reveal": "always", - "focus": false, - "panel": "shared" - }, + "presentation": { "echo": true, "reveal": "always", "focus": false, "panel": "shared" }, "problemMatcher": [], - "detail": "Executes SQL query and exports results to CSV for testing" + "detail": "Creates supervisor-ready productivity and progress reports" }, { - "label": "Add License Headers", + "label": "Recursive Directory Analysis", "type": "shell", "command": "powershell.exe", - "args": [ - "-ExecutionPolicy", - "Bypass", - "-File", - "${workspaceFolder}/scripts/Add-LicenseHeaders.ps1" - ], + "args": ["-NoProfile","-ExecutionPolicy","Bypass","-File","${workspaceFolder}/copilot-instructions/Recursive-Directory-Analysis.ps1"], "group": "build", - "presentation": { - "echo": true, - "reveal": "always", - "focus": false, - "panel": "shared" - }, + "presentation": { "echo": true, "reveal": "always", "focus": false, "panel": "shared" }, "problemMatcher": [], - "detail": "Automatically adds VA-compliant license headers to source code files" + "detail": "Recursively inventories a directory and writes a CSV summary" }, { - "label": "Organize Files", + "label": "Clean Workspace", "type": "shell", "command": "powershell.exe", - "args": [ - "-ExecutionPolicy", - "Bypass", - "-File", - "${workspaceFolder}/scripts/Organize-Files.ps1" - ], + "args": ["-NoProfile","-ExecutionPolicy","Bypass","-File","${workspaceFolder}/copilot-instructions/Clean-Workspace.ps1"], "group": "build", - "presentation": { - "echo": true, - "reveal": "always", - "focus": false, - "panel": "shared" - }, + "presentation": { "echo": true, "reveal": "always", "focus": false, "panel": "shared" }, "problemMatcher": [], - "detail": "Organizes workspace files into appropriate subfolders based on type and content" + "detail": "Safely cleans temporary files, logs, and outdated items" }, { "label": "Validate Syntax", "type": "shell", "command": "powershell.exe", - "args": [ - "-ExecutionPolicy", - "Bypass", - "-File", - "${workspaceFolder}/scripts/Validate-Syntax.ps1" - ], + "args": ["-NoProfile","-ExecutionPolicy","Bypass","-File","${workspaceFolder}/copilot-instructions/Validate-Syntax.ps1"], "group": "test", - "presentation": { - "echo": true, - "reveal": "always", - "focus": false, - "panel": "shared" - }, + "presentation": { "echo": true, "reveal": "always", "focus": false, "panel": "shared" }, "problemMatcher": [], - "detail": "Validates syntax for YAML, JSON, XML, and MSAPP configuration files" + "detail": "Validates syntax for YAML, JSON, XML, and script files" }, { "label": "Validate Workspace Setup", "type": "shell", "command": "powershell.exe", - "args": [ - "-ExecutionPolicy", - "Bypass", - "-File", - "${workspaceFolder}/scripts/Validate-WorkspaceSetup.ps1" - ], + "args": ["-NoProfile","-ExecutionPolicy","Bypass","-File","${workspaceFolder}/copilot-instructions/Validate-WorkspaceSetup.ps1","-Detailed"], "group": "test", - "presentation": { - "echo": true, - "reveal": "always", - "focus": false, - "panel": "shared" - }, - "problemMatcher": [], - "detail": "Validates workspace structure, dependencies, and configuration for VA compliance" - }, - { - "label": "Track File Versions - List", - "type": "shell", - "command": "powershell.exe", - "args": [ - "-ExecutionPolicy", - "Bypass", - "-File", - "${workspaceFolder}/scripts/Track-FileVersions.ps1", - "-Action", - "List" - ], - "group": "build", - "presentation": { - "echo": true, - "reveal": "always", - "focus": false, - "panel": "shared" - }, + "presentation": { "echo": true, "reveal": "always", "focus": false, "panel": "shared" }, "problemMatcher": [], - "detail": "Shows version tracking status for all tracked files" + "detail": "Validates workspace structure and key automation availability" }, { - "label": "Track File Versions - Backup Current File", + "label": "Add License Headers", "type": "shell", "command": "powershell.exe", - "args": [ - "-ExecutionPolicy", - "Bypass", - "-File", - "${workspaceFolder}/scripts/Track-FileVersions.ps1", - "-Action", - "Track", - "-FilePath", - "${file}" - ], + "args": ["-NoProfile","-ExecutionPolicy","Bypass","-File","${workspaceFolder}/copilot-instructions/Add-LicenseHeaders.ps1"], "group": "build", - "presentation": { - "echo": true, - "reveal": "always", - "focus": false, - "panel": "shared" - }, + "presentation": { "echo": true, "reveal": "always", "focus": false, "panel": "shared" }, "problemMatcher": [], - "detail": "Creates a backup version of the currently open file" + "detail": "Adds Apache 2.0 headers to source files" } ], "inputs": [ - { - "id": "msappFile", - "description": "Select .msapp file to unpack", - "type": "pickString", - "options": [] - }, - { - "id": "appName", - "description": "Enter PowerApp name", - "type": "promptString" - }, { "id": "commitMessage", "description": "Enter commit message", "type": "promptString" - }, - { - "id": "sqlFile", - "description": "Select SQL file to execute", - "type": "pickString", - "options": [] } ] } diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index c05d5dc..78ea720 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -56,6 +56,13 @@ The format loosely follows [Keep a Changelog](https://keepachangelog.com/en/1.0. ## [Unreleased] ### Planned +### Added +- Batch installer `copilot-instructions/Install-Copilot-Instructions.bat` that installs/updates `.github/copilot-instructions.md`, deep-links VS Code/Copilot Chat, and places a suggested prompt on the clipboard. +- VS Code tasks (`copilot-instructions/tasks.json`) updated to run the installer, productivity report, recursive directory analysis, cleanup, and validations directly. + +### Changed +- `Validate-WorkspaceSetup.ps1` now checks for `.github/copilot-instructions.md` and can auto-run the installer with `-FixIssues`. +- Documentation (`README.md`, `docs/README.md`, `index.html`) updated to reference the new installer workflow and script locations. - Add screenshot thumbnail for sharing (update OpenGraph image). - Introduce CHANGELOG automation script. - Add Lighthouse/axe accessibility summary. diff --git a/docs/README.md b/docs/README.md index 94d8f77..fa4ea2b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -23,3 +23,8 @@ This folder contains the documentation and release artifacts for the GitHub Copi - Legacy materials: `archive/` For the interactive guide, open the repository root `index.html` or visit the GitHub Pages site. + +## Workspace Automation (Summary) + +- Use `copilot-instructions/Install-Copilot-Instructions.bat` to create or update `.github/copilot-instructions.md` in any active workspace. The installer opens VS Code/Copilot Chat and copies a ready prompt. +- Run automation via VS Code Tasks (Task: “Install/Update Copilot Instructions (.github)”, “Generate Productivity Report”, “Recursive Directory Analysis”, “Clean Workspace”). diff --git a/index.html b/index.html index 194762f..89303d4 100644 --- a/index.html +++ b/index.html @@ -870,12 +870,15 @@

    Step 12: Generate Custom Copilot Instructions

    alt="VS Code Gear Icon" style="height:1em;vertical-align:middle;">
  • Select GENERATE INSTRUCTIONS
  • -
  • This creates a copilot-instructions.md file in your workspace
  • +
  • This installs/updates .github/copilot-instructions.md for your workspace
  • Copy and paste the advanced prompt from the next slide for deeper personalization
  • What does this do? Customizes Copilot’s behavior to your role, preferred style, and VA context.
    +

    + Run locally: Execute Install-Copilot-Instructions.bat from the root of your active project on a local drive (not a network share). This ensures the file is created at .github/copilot-instructions.md, VS Code opens to your project, and Copilot Chat can use the context correctly. After installing, run Validate-WorkspaceSetup.ps1 to verify everything. +

    @@ -1109,9 +1112,13 @@

    Prompt Templates – Creative & Educational

    Importable Copilot Template Files (Ready to Use)

      +
    • + Install-Copilot-Instructions.bat
      + Creates/updates .github/copilot-instructions.md, opens VS Code/Copilot Chat, and copies a ready-to-paste prompt. +
    • copilot-instructions.md
      - Template for workspace/project-specific Copilot instructions. Sets standards, folder structure, automation, and best practices for VA Power Platform projects. Use to bootstrap or customize Copilot for a new VA project workspace. + Template for workspace/project-specific Copilot instructions. The installer will place this at .github/copilot-instructions.md and prepend updates while preserving prior content.
    • COPILOT_BRIEFING.md
      @@ -1133,20 +1140,31 @@

      Importable Copilot Template Files (Ready to Use)

      Generate-ProductivityReport.ps1
      Generates detailed productivity reports from workspace and Git activity, including VA-specific work effort estimates. Supports custom date ranges and multiple output formats. Ready for supervisor or compliance reporting.
    • +
    • + Recursive-Directory-Analysis.ps1
      + Recursively inventories a directory and writes a CSV summary with counts, sizes, authors, and text metrics. +
    • Validate-Syntax.ps1
      Checks configuration files (YAML, JSON, XML, MSAPP) for syntax errors. Can auto-fix simple mistakes and logs all results. Use before deploying configs or committing changes.
    • Validate-WorkspaceSetup.ps1
      - Validates full workspace setup (folders, files, VS Code config, Git, Power Platform CLI). Optionally auto-fixes missing directories and checks PowerShell script syntax. Outputs a report for onboarding and compliance. + Validates full workspace setup (folders, files, VS Code config, Git, Power Platform CLI). Optionally auto-fixes missing directories and checks PowerShell script syntax. Emits a JSON report and generates a collapsible guidance HTML in the workspace root for onboarding and fixes. +
    • +
    • + Generate-WorkspaceGuidance.ps1
      + Converts a validator JSON report into a detailed, beginner-friendly HTML guide with collapsible sections, expanded intro/footer, and curated resource links. Supports -Open to launch and -DeleteJson to clean up.
    +

    + Recommended order: 1) Save these files into your local workspace folder. 2) Run Install-Copilot-Instructions.bat from that folder. 3) Run Validate-WorkspaceSetup.ps1 in PowerShell (pwsh). It will emit a JSON report and generate a Workspace-Guidance-*.html with collapsible fix sections. This avoids network path issues and makes the setup idempotent and reliable. +

    How to use: Open the ./copilot-instructions/prompts/ or ./copilot-instructions/ folder in VS Code, then copy or run the desired template in your own workspace. Each file contains usage notes or documentation.

    - Tip: These templates are designed for a Power Platform developer in Clinical Informatics at Hines VA, but are very easilyadaptable for any VA project. You can even tell your new Copilot Agent to update these templates to for you, to customize your needs. + Tip: These templates are designed for a Power Platform developer in Clinical Informatics at Hines VA, but are very easily adaptable for any VA project. You can even tell your new Copilot Agent to update these templates for you to customize your needs.

    diff --git a/prompts/va-copilot-agent-templates.md b/prompts/va-copilot-agent-templates.md index 37bbbd8..667fb22 100644 --- a/prompts/va-copilot-agent-templates.md +++ b/prompts/va-copilot-agent-templates.md @@ -18,6 +18,8 @@ limitations under the License. **Use these templates in your Copilot Chat to automate, analyze, and create in your VA workspace. Copy, adapt, and import as needed.** +Note: The workspace instructions live at `.github/copilot-instructions.md`. Run `copilot-instructions/Install-Copilot-Instructions.bat` to install/update them and open Copilot Chat with a ready-to-paste prompt. + --- ## Clinical Documentation & Patient Care