Add AGENTS and Definition of Done documentation; implement project standards reconciliation scripts
continuous-integration/drone/push Build is failing
continuous-integration/drone/push Build is failing
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$RootPath = "C:\Users\stefan\git",
|
||||
[string]$StandardsRepoPath = "C:\Users\stefan\git\project-standards",
|
||||
[string[]]$Exclude = @('project-standards'),
|
||||
[switch]$CheckOnly,
|
||||
[switch]$Watch,
|
||||
[int]$IntervalSeconds = 60
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$agentsTemplate = Join-Path $StandardsRepoPath 'templates/AGENTS.base.md'
|
||||
$dodTemplate = Join-Path $StandardsRepoPath 'templates/DEFINITION_OF_DONE.base.md'
|
||||
|
||||
if (-not (Test-Path -Path $agentsTemplate -PathType Leaf)) {
|
||||
throw "AGENTS template not found: $agentsTemplate"
|
||||
}
|
||||
|
||||
if (-not (Test-Path -Path $dodTemplate -PathType Leaf)) {
|
||||
throw "DoD template not found: $dodTemplate"
|
||||
}
|
||||
|
||||
if ($IntervalSeconds -lt 5) {
|
||||
throw 'IntervalSeconds must be >= 5.'
|
||||
}
|
||||
|
||||
function Get-ContentHashOrMissing {
|
||||
param([string]$Path)
|
||||
|
||||
if (-not (Test-Path -Path $Path -PathType Leaf)) {
|
||||
return '__MISSING__'
|
||||
}
|
||||
|
||||
return (Get-FileHash -Path $Path -Algorithm SHA256).Hash
|
||||
}
|
||||
|
||||
function Ensure-FileFromTemplate {
|
||||
param(
|
||||
[string]$Template,
|
||||
[string]$Target,
|
||||
[switch]$OnlyCheck
|
||||
)
|
||||
|
||||
$templateHash = Get-ContentHashOrMissing -Path $Template
|
||||
$targetHash = Get-ContentHashOrMissing -Path $Target
|
||||
|
||||
if ($templateHash -eq $targetHash) {
|
||||
return 'ok'
|
||||
}
|
||||
|
||||
if ($OnlyCheck) {
|
||||
return 'drift'
|
||||
}
|
||||
|
||||
$parent = Split-Path -Parent $Target
|
||||
if (-not (Test-Path -Path $parent -PathType Container)) {
|
||||
New-Item -ItemType Directory -Path $parent | Out-Null
|
||||
}
|
||||
|
||||
Copy-Item -Path $Template -Destination $Target -Force
|
||||
return 'updated'
|
||||
}
|
||||
|
||||
function Invoke-ReconcileOnce {
|
||||
param([switch]$OnlyCheck)
|
||||
|
||||
$resolvedRoot = (Resolve-Path -Path $RootPath -ErrorAction Stop).Path
|
||||
$repos = Get-ChildItem -Path $resolvedRoot -Directory | Where-Object {
|
||||
$Exclude -notcontains $_.Name
|
||||
}
|
||||
|
||||
$summary = [ordered]@{
|
||||
scanned = 0
|
||||
updated = 0
|
||||
drift = 0
|
||||
}
|
||||
|
||||
foreach ($repo in $repos) {
|
||||
$repoPath = $repo.FullName
|
||||
$agentsTarget = Join-Path $repoPath 'AGENTS.md'
|
||||
$dodTarget = Join-Path (Join-Path $repoPath 'docs') 'DEFINITION_OF_DONE.md'
|
||||
|
||||
$summary.scanned++
|
||||
|
||||
$agentsState = Ensure-FileFromTemplate -Template $agentsTemplate -Target $agentsTarget -OnlyCheck:$OnlyCheck
|
||||
$dodState = Ensure-FileFromTemplate -Template $dodTemplate -Target $dodTarget -OnlyCheck:$OnlyCheck
|
||||
|
||||
if ($agentsState -eq 'updated' -or $dodState -eq 'updated') {
|
||||
$summary.updated++
|
||||
Write-Host "UPDATED: $repoPath"
|
||||
continue
|
||||
}
|
||||
|
||||
if ($agentsState -eq 'drift' -or $dodState -eq 'drift') {
|
||||
$summary.drift++
|
||||
Write-Host "DRIFT: $repoPath"
|
||||
continue
|
||||
}
|
||||
|
||||
Write-Host "OK: $repoPath"
|
||||
}
|
||||
|
||||
Write-Host "Summary -> scanned=$($summary.scanned), updated=$($summary.updated), drift=$($summary.drift)"
|
||||
return $summary
|
||||
}
|
||||
|
||||
if ($Watch) {
|
||||
while ($true) {
|
||||
$now = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
|
||||
Write-Host "[$now] Reconciling project standards..."
|
||||
[void](Invoke-ReconcileOnce -OnlyCheck:$CheckOnly)
|
||||
Start-Sleep -Seconds $IntervalSeconds
|
||||
}
|
||||
}
|
||||
|
||||
$result = Invoke-ReconcileOnce -OnlyCheck:$CheckOnly
|
||||
if ($CheckOnly -and $result.drift -gt 0) {
|
||||
exit 1
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
|
||||
ROOT_PATH="/c/Users/stefan/git"
|
||||
STANDARDS_REPO="/c/Users/stefan/git/project-standards"
|
||||
EXCLUDE_NAME="project-standards"
|
||||
CHECK_ONLY=0
|
||||
WATCH=0
|
||||
INTERVAL=60
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
reconcile-project-standards.sh [options]
|
||||
|
||||
Options:
|
||||
--root <path> Root folder containing repositories
|
||||
--standards-repo <path> Path to project-standards repository
|
||||
--exclude <name> First-level directory name to skip (default: project-standards)
|
||||
--check-only Check drift only, do not update files
|
||||
--watch Continuously scan and reconcile
|
||||
--interval <seconds> Watch interval in seconds (default: 60)
|
||||
-h, --help Show this help
|
||||
EOF
|
||||
}
|
||||
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--root)
|
||||
ROOT_PATH=$2
|
||||
shift 2
|
||||
;;
|
||||
--standards-repo)
|
||||
STANDARDS_REPO=$2
|
||||
shift 2
|
||||
;;
|
||||
--exclude)
|
||||
EXCLUDE_NAME=$2
|
||||
shift 2
|
||||
;;
|
||||
--check-only)
|
||||
CHECK_ONLY=1
|
||||
shift
|
||||
;;
|
||||
--watch)
|
||||
WATCH=1
|
||||
shift
|
||||
;;
|
||||
--interval)
|
||||
INTERVAL=$2
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1" >&2
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ "$INTERVAL" -lt 5 ]; then
|
||||
echo "interval must be >= 5" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
AGENTS_TEMPLATE="$STANDARDS_REPO/templates/AGENTS.base.md"
|
||||
DOD_TEMPLATE="$STANDARDS_REPO/templates/DEFINITION_OF_DONE.base.md"
|
||||
|
||||
if [ ! -f "$AGENTS_TEMPLATE" ]; then
|
||||
echo "AGENTS template not found: $AGENTS_TEMPLATE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$DOD_TEMPLATE" ]; then
|
||||
echo "DoD template not found: $DOD_TEMPLATE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
hash_or_missing() {
|
||||
path=$1
|
||||
if [ ! -f "$path" ]; then
|
||||
printf "%s" "__MISSING__"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if command -v sha256sum >/dev/null 2>&1; then
|
||||
sha256sum "$path" | awk '{print $1}'
|
||||
else
|
||||
shasum -a 256 "$path" | awk '{print $1}'
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_file() {
|
||||
template=$1
|
||||
target=$2
|
||||
|
||||
template_hash=$(hash_or_missing "$template")
|
||||
target_hash=$(hash_or_missing "$target")
|
||||
|
||||
if [ "$template_hash" = "$target_hash" ]; then
|
||||
printf "%s" "ok"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ "$CHECK_ONLY" -eq 1 ]; then
|
||||
printf "%s" "drift"
|
||||
return 0
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname "$target")"
|
||||
cp "$template" "$target"
|
||||
printf "%s" "updated"
|
||||
}
|
||||
|
||||
run_once() {
|
||||
scanned=0
|
||||
updated=0
|
||||
drift=0
|
||||
|
||||
for repo in "$ROOT_PATH"/*; do
|
||||
[ -d "$repo" ] || continue
|
||||
|
||||
name=$(basename "$repo")
|
||||
[ "$name" = "$EXCLUDE_NAME" ] && continue
|
||||
|
||||
scanned=$((scanned + 1))
|
||||
|
||||
agents_target="$repo/AGENTS.md"
|
||||
dod_target="$repo/docs/DEFINITION_OF_DONE.md"
|
||||
|
||||
agents_state=$(ensure_file "$AGENTS_TEMPLATE" "$agents_target")
|
||||
dod_state=$(ensure_file "$DOD_TEMPLATE" "$dod_target")
|
||||
|
||||
if [ "$agents_state" = "updated" ] || [ "$dod_state" = "updated" ]; then
|
||||
updated=$((updated + 1))
|
||||
echo "UPDATED: $repo"
|
||||
continue
|
||||
fi
|
||||
|
||||
if [ "$agents_state" = "drift" ] || [ "$dod_state" = "drift" ]; then
|
||||
drift=$((drift + 1))
|
||||
echo "DRIFT: $repo"
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "OK: $repo"
|
||||
done
|
||||
|
||||
echo "Summary -> scanned=$scanned, updated=$updated, drift=$drift"
|
||||
|
||||
if [ "$CHECK_ONLY" -eq 1 ] && [ "$drift" -gt 0 ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
if [ "$WATCH" -eq 1 ]; then
|
||||
while :; do
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Reconciling project standards..."
|
||||
run_once || true
|
||||
sleep "$INTERVAL"
|
||||
done
|
||||
else
|
||||
run_once
|
||||
fi
|
||||
Reference in New Issue
Block a user