AI skills in use in my daily
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

164 lines
5.7 KiB

[CmdletBinding()]
param(
[string] $SourceRoot = 'C:\Users\dkucinski\.scout\m-skills',
[string] $RepositoryRoot = $PSScriptRoot
)
$ErrorActionPreference = 'Stop'
$repositoryRoot = (Resolve-Path -LiteralPath $RepositoryRoot).Path
$sourceRoot = (Resolve-Path -LiteralPath $SourceRoot).Path
$logDirectory = Join-Path $repositoryRoot 'logs'
$logPath = Join-Path $logDirectory 'sync.log'
$allowedExtensions = @('.gitignore', '.js', '.json', '.mjs', '.ps1', '.psm1', '.yaml', '.yml')
$excludedDirectories = @('.git', 'cache', 'caches', 'logs', 'node_modules')
$excludedFiles = @('.token-cache.json', 'skills-metadata.json')
New-Item -ItemType Directory -Path $logDirectory -Force | Out-Null
function Write-SyncLog {
param(
[ValidateSet('INFO', 'WARN', 'ERROR')]
[string] $Level,
[string] $Message
)
$entry = '{0:o} [{1}] {2}' -f (Get-Date), $Level, $Message
Add-Content -LiteralPath $logPath -Value $entry
Write-Output $entry
}
function Invoke-Git {
param([string[]] $Arguments)
& git -C $repositoryRoot @Arguments
if ($LASTEXITCODE -ne 0) {
throw "Git command failed: git $($Arguments -join ' ')"
}
}
function Test-AllowedSourceFile {
param(
[System.IO.FileInfo] $File,
[string] $SkillRoot
)
$relativePath = $File.FullName.Substring($SkillRoot.Length).TrimStart('\')
$segments = $relativePath -split '\\'
if ($segments | Where-Object {
$_ -like '.pw-profile*' -or $excludedDirectories -contains $_
}) {
return $false
}
if ($excludedFiles -contains $File.Name) {
return $false
}
return $File.Name -eq 'SKILL.md' -or $allowedExtensions -contains $File.Extension.ToLowerInvariant()
}
function Get-SkillDescription {
param([string] $SkillFile)
$content = Get-Content -LiteralPath $SkillFile
$descriptionLine = $content | Select-String -Pattern '^\s*description:\s*["'']?(.*?)["'']?\s*$' | Select-Object -First 1
if ($null -eq $descriptionLine) {
return 'No description provided.'
}
return $descriptionLine.Matches[0].Groups[1].Value.Trim()
}
$stagingRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("PromptWorks-{0}" -f [guid]::NewGuid())
try {
if (-not (Test-Path -LiteralPath (Join-Path $repositoryRoot '.git'))) {
throw "'$repositoryRoot' is not a Git repository."
}
Write-SyncLog -Level INFO -Message "Starting sync from '$sourceRoot'."
New-Item -ItemType Directory -Path $stagingRoot | Out-Null
$sourceSkills = Get-ChildItem -LiteralPath $sourceRoot -Directory | Sort-Object Name
foreach ($skill in $sourceSkills) {
$stagingSkillRoot = Join-Path $stagingRoot $skill.Name
$copiedFiles = 0
Get-ChildItem -LiteralPath $skill.FullName -Recurse -File | Where-Object {
Test-AllowedSourceFile -File $_ -SkillRoot $skill.FullName
} | ForEach-Object {
$relativePath = $_.FullName.Substring($skill.FullName.Length).TrimStart('\')
$destination = Join-Path $stagingSkillRoot $relativePath
New-Item -ItemType Directory -Path (Split-Path -Parent $destination) -Force | Out-Null
Copy-Item -LiteralPath $_.FullName -Destination $destination
$copiedFiles++
}
if (-not (Test-Path -LiteralPath (Join-Path $stagingSkillRoot 'SKILL.md'))) {
throw "Skill '$($skill.Name)' has no allowed SKILL.md file."
}
Write-SyncLog -Level INFO -Message "Staged skill '$($skill.Name)' ($copiedFiles files)."
}
$existingSkills = Get-ChildItem -LiteralPath $repositoryRoot -Directory | Where-Object {
Test-Path -LiteralPath (Join-Path $_.FullName 'SKILL.md')
}
foreach ($skill in $existingSkills) {
if ($skill.Name -notin $sourceSkills.Name) {
Remove-Item -LiteralPath $skill.FullName -Recurse -Force
Write-SyncLog -Level INFO -Message "Removed stale skill '$($skill.Name)'."
}
}
foreach ($skill in $sourceSkills) {
$destination = Join-Path $repositoryRoot $skill.Name
if (Test-Path -LiteralPath $destination) {
Remove-Item -LiteralPath $destination -Recurse -Force
}
Move-Item -LiteralPath (Join-Path $stagingRoot $skill.Name) -Destination $destination
}
$readmeLines = @(
'# PromptWorks',
'',
'Private version-controlled mirror of custom Microsoft Scout skills.',
'',
'Generated by `sync-skills.ps1`; do not edit manually.',
'',
'## Skills',
'',
'| Skill | Description |',
'| --- | --- |'
)
foreach ($skill in $sourceSkills) {
$description = (Get-SkillDescription -SkillFile (Join-Path (Join-Path $repositoryRoot $skill.Name) 'SKILL.md')).Replace('|', '\|')
$readmeLines += "| [$($skill.Name)]($($skill.Name)/SKILL.md) | $description |"
}
Set-Content -LiteralPath (Join-Path $repositoryRoot 'README.md') -Value $readmeLines
Invoke-Git -Arguments @('add', '--all')
& git -C $repositoryRoot diff --cached --quiet
if ($LASTEXITCODE -eq 0) {
Write-SyncLog -Level INFO -Message 'Sync completed with no changes.'
exit 0
}
if ($LASTEXITCODE -ne 1) {
throw 'Unable to determine whether staged changes exist.'
}
$commitMessage = 'chore: sync Scout skills {0:yyyy-MM-dd}' -f (Get-Date)
Invoke-Git -Arguments @('commit', '-m', $commitMessage)
Invoke-Git -Arguments @('push', '-u', 'origin', 'main')
Write-SyncLog -Level INFO -Message "Sync completed and pushed commit '$commitMessage'."
}
catch {
Write-SyncLog -Level ERROR -Message $_.Exception.Message
throw
}
finally {
if (Test-Path -LiteralPath $stagingRoot) {
Remove-Item -LiteralPath $stagingRoot -Recurse -Force
}
}