Skip to content

Commit 9496bc6

Browse files
committed
feat(#194): add Agentic Copilot scaffold files for NovaModule initialization
- Implement Get-NovaModuleAgenticCopilotTemplateRoot function - Create AGENTS.md for Agentic Copilot workflow documentation - Add CHANGELOG.md and RELEASE_NOTE.md for version tracking - Introduce initial test coverage for Agentic Copilot scaffold sync
1 parent 7f5bb92 commit 9496bc6

30 files changed

Lines changed: 1858 additions & 0 deletions
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
[CmdletBinding()]
2+
param(
3+
[string]$RepositoryRoot = (Split-Path -Parent (Split-Path -Parent $PSScriptRoot)),
4+
[string]$ManifestPath = (Join-Path $PSScriptRoot 'Sync-AgenticCopilotScaffold.psd1'),
5+
[string]$OutputRoot
6+
)
7+
8+
Set-StrictMode -Version Latest
9+
$ErrorActionPreference = 'Stop'
10+
$ProgressPreference = 'SilentlyContinue'
11+
12+
function ConvertTo-AgenticNormalizedRelativePath {
13+
param([Parameter(Mandatory)][string]$Path)
14+
15+
$normalizedPath = $Path.Replace('\', '/')
16+
if ( $normalizedPath.StartsWith('./', [System.StringComparison]::Ordinal)) {
17+
return $normalizedPath.Substring(2)
18+
}
19+
20+
return $normalizedPath.TrimStart('/')
21+
}
22+
23+
function Get-AgenticRelativePath {
24+
param(
25+
[Parameter(Mandatory)][string]$RootPath,
26+
[Parameter(Mandatory)][string]$Path
27+
)
28+
29+
$relativePath = [System.IO.Path]::GetRelativePath(
30+
[System.IO.Path]::GetFullPath($RootPath),
31+
[System.IO.Path]::GetFullPath($Path)
32+
)
33+
34+
return ConvertTo-AgenticNormalizedRelativePath -Path $relativePath
35+
}
36+
37+
function Resolve-AgenticRepositoryPath {
38+
param(
39+
[Parameter(Mandatory)][string]$RootPath,
40+
[Parameter(Mandatory)][string]$RelativePath
41+
)
42+
43+
return [System.IO.Path]::GetFullPath((Join-Path $RootPath $RelativePath))
44+
}
45+
46+
function Test-AgenticPathExcluded {
47+
param(
48+
[Parameter(Mandatory)][string]$RelativePath,
49+
[Parameter(Mandatory)][string[]]$ExcludedPathList
50+
)
51+
52+
$normalizedRelativePath = ConvertTo-AgenticNormalizedRelativePath -Path $RelativePath
53+
foreach ($excludedPath in $ExcludedPathList) {
54+
$normalizedExcludedPath = ConvertTo-AgenticNormalizedRelativePath -Path $excludedPath
55+
if ( $normalizedExcludedPath.EndsWith('/', [System.StringComparison]::Ordinal)) {
56+
if ( $normalizedRelativePath.StartsWith($normalizedExcludedPath, [System.StringComparison]::Ordinal)) {
57+
return $true
58+
}
59+
}
60+
elseif ($normalizedRelativePath -eq $normalizedExcludedPath) {
61+
return $true
62+
}
63+
}
64+
65+
return $false
66+
}
67+
68+
function Get-AgenticMirrorSourceFile {
69+
param(
70+
[Parameter(Mandatory)][string]$RootPath,
71+
[Parameter(Mandatory)][string[]]$SourcePathList,
72+
[Parameter(Mandatory)][string[]]$ExcludedPathList
73+
)
74+
75+
$sourceFileList = foreach ($sourcePath in $SourcePathList) {
76+
$resolvedSourcePath = Resolve-AgenticRepositoryPath -RootPath $RootPath -RelativePath $sourcePath
77+
if (-not (Test-Path -LiteralPath $resolvedSourcePath)) {
78+
throw "Agentic scaffold source path does not exist: $sourcePath"
79+
}
80+
81+
if (Test-Path -LiteralPath $resolvedSourcePath -PathType Leaf) {
82+
Get-Item -LiteralPath $resolvedSourcePath
83+
}
84+
else {
85+
Get-ChildItem -LiteralPath $resolvedSourcePath -File -Recurse -Force
86+
}
87+
}
88+
89+
foreach ($sourceFile in $sourceFileList) {
90+
$relativePath = Get-AgenticRelativePath -RootPath $RootPath -Path $sourceFile.FullName
91+
if (-not (Test-AgenticPathExcluded -RelativePath $relativePath -ExcludedPathList $ExcludedPathList)) {
92+
[pscustomobject]@{
93+
SourcePath = $sourceFile.FullName
94+
RelativePath = $relativePath
95+
}
96+
}
97+
}
98+
}
99+
100+
function ConvertTo-AgenticScaffoldContent {
101+
param(
102+
[Parameter(Mandatory)][string]$Content,
103+
[Parameter(Mandatory)][object[]]$ReplacementList
104+
)
105+
106+
$updatedContent = $Content
107+
foreach ($replacement in $ReplacementList) {
108+
$updatedContent = $updatedContent.Replace([string]$replacement.Old, [string]$replacement.New)
109+
}
110+
111+
while ( $updatedContent.Contains("`n`n`n")) {
112+
$updatedContent = $updatedContent.Replace("`n`n`n", "`n`n")
113+
}
114+
115+
return $updatedContent
116+
}
117+
118+
function Copy-AgenticScaffoldOwnedPath {
119+
param(
120+
[Parameter(Mandatory)][string]$SourceRoot,
121+
[Parameter(Mandatory)][string]$TargetRoot,
122+
[Parameter(Mandatory)][string]$RelativePath
123+
)
124+
125+
$sourcePath = Resolve-AgenticRepositoryPath -RootPath $SourceRoot -RelativePath $RelativePath
126+
if (-not (Test-Path -LiteralPath $sourcePath)) {
127+
throw "Agentic scaffold-owned path does not exist: $RelativePath"
128+
}
129+
130+
$targetPath = Join-Path $TargetRoot ($RelativePath -replace '/', [System.IO.Path]::DirectorySeparatorChar)
131+
$targetDirectory = Split-Path -Parent $targetPath
132+
if (-not (Test-Path -LiteralPath $targetDirectory)) {
133+
New-Item -ItemType Directory -Path $targetDirectory -Force | Out-Null
134+
}
135+
136+
Copy-Item -LiteralPath $sourcePath -Destination $targetPath -Recurse -Force
137+
}
138+
139+
function Write-AgenticMirroredFile {
140+
param(
141+
[Parameter(Mandatory)][object]$SourceFile,
142+
[Parameter(Mandatory)][string]$TargetRoot,
143+
[Parameter(Mandatory)][object[]]$ReplacementList
144+
)
145+
146+
$targetPath = Join-Path $TargetRoot ($SourceFile.RelativePath -replace '/', [System.IO.Path]::DirectorySeparatorChar)
147+
$targetDirectory = Split-Path -Parent $targetPath
148+
if (-not (Test-Path -LiteralPath $targetDirectory)) {
149+
New-Item -ItemType Directory -Path $targetDirectory -Force | Out-Null
150+
}
151+
152+
$sourceContent = Get-Content -LiteralPath $SourceFile.SourcePath -Raw
153+
$targetContent = ConvertTo-AgenticScaffoldContent -Content $sourceContent -ReplacementList $ReplacementList
154+
Set-Content -LiteralPath $targetPath -Value $targetContent -Encoding utf8 -NoNewline
155+
}
156+
157+
function Assert-AgenticOutputRootSafe {
158+
param(
159+
[Parameter(Mandatory)][string]$RootPath,
160+
[Parameter(Mandatory)][string]$TargetPath
161+
)
162+
163+
$resolvedRootPath = [System.IO.Path]::GetFullPath($RootPath).TrimEnd([System.IO.Path]::DirectorySeparatorChar)
164+
$resolvedTargetPath = [System.IO.Path]::GetFullPath($TargetPath).TrimEnd([System.IO.Path]::DirectorySeparatorChar)
165+
if ($resolvedTargetPath -eq $resolvedRootPath) {
166+
throw 'Agentic scaffold output root cannot be the repository root.'
167+
}
168+
}
169+
170+
function Invoke-AgenticCopilotScaffoldSync {
171+
param(
172+
[Parameter(Mandatory)][string]$RootPath,
173+
[Parameter(Mandatory)][string]$ConfigPath,
174+
[Parameter(Mandatory)][string]$TargetRoot
175+
)
176+
177+
$manifest = Import-PowerShellDataFile -LiteralPath $ConfigPath
178+
$sourceRoot = Resolve-AgenticRepositoryPath -RootPath $RootPath -RelativePath $manifest.OutputPath
179+
$sourceFileList = @(Get-AgenticMirrorSourceFile -RootPath $RootPath -SourcePathList $manifest.SourcePaths -ExcludedPathList $manifest.ExcludedPaths | Sort-Object RelativePath)
180+
$stagingParent = Join-Path ([System.IO.Path]::GetTempPath()) "NovaAgenticCopilotScaffold-$([guid]::NewGuid() )"
181+
$stagingRoot = Join-Path $stagingParent 'agentic-copilot'
182+
183+
try {
184+
New-Item -ItemType Directory -Path $stagingRoot -Force | Out-Null
185+
foreach ($ownedPath in $manifest.ScaffoldOwnedPaths) {
186+
Copy-AgenticScaffoldOwnedPath -SourceRoot $sourceRoot -TargetRoot $stagingRoot -RelativePath $ownedPath
187+
}
188+
189+
foreach ($sourceFile in $sourceFileList) {
190+
Write-AgenticMirroredFile -SourceFile $sourceFile -TargetRoot $stagingRoot -ReplacementList $manifest.TextReplacements
191+
}
192+
193+
if (Test-Path -LiteralPath $TargetRoot) {
194+
Remove-Item -LiteralPath $TargetRoot -Recurse -Force
195+
}
196+
197+
$targetParent = Split-Path -Parent $TargetRoot
198+
if (-not (Test-Path -LiteralPath $targetParent)) {
199+
New-Item -ItemType Directory -Path $targetParent -Force | Out-Null
200+
}
201+
202+
Move-Item -LiteralPath $stagingRoot -Destination $TargetRoot
203+
204+
return [pscustomobject]@{
205+
OutputRoot = $TargetRoot
206+
MirroredFileCount = $sourceFileList.Count
207+
ScaffoldOwnedPathCount = @($manifest.ScaffoldOwnedPaths).Count
208+
}
209+
}
210+
finally {
211+
if (Test-Path -LiteralPath $stagingParent) {
212+
Remove-Item -LiteralPath $stagingParent -Recurse -Force
213+
}
214+
}
215+
}
216+
217+
$resolvedRepositoryRoot = [System.IO.Path]::GetFullPath($RepositoryRoot)
218+
$resolvedManifestPath = [System.IO.Path]::GetFullPath($ManifestPath)
219+
$manifestForOutput = Import-PowerShellDataFile -LiteralPath $resolvedManifestPath
220+
if ( [string]::IsNullOrWhiteSpace($OutputRoot)) {
221+
$OutputRoot = Resolve-AgenticRepositoryPath -RootPath $resolvedRepositoryRoot -RelativePath $manifestForOutput.OutputPath
222+
}
223+
224+
$resolvedOutputRoot = [System.IO.Path]::GetFullPath($OutputRoot)
225+
Assert-AgenticOutputRootSafe -RootPath $resolvedRepositoryRoot -TargetPath $resolvedOutputRoot
226+
Invoke-AgenticCopilotScaffoldSync -RootPath $resolvedRepositoryRoot -ConfigPath $resolvedManifestPath -TargetRoot $resolvedOutputRoot

0 commit comments

Comments
 (0)