Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .codespellrc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

# rouge - Rouge is a syntax highlighter (not "rogue")

# autor - Spanish word for "Author", used as intentional bilingual regex pattern in anonymize_sql.py

# categor - TypeScript template literal in website/src/scripts/pages/skills.ts:70 (categor${...length > 1 ? "ies" : "y"})

# aline - proper name (Aline Ávila, contributor)
Expand Down Expand Up @@ -54,7 +56,7 @@

# CAF - Microsoft Cloud Adoption Framework acronym

ignore-words-list = numer,wit,aks,edn,ser,ois,gir,rouge,categor,aline,ative,afterall,deques,dateA,dateB,TE,FillIn,alle,vai,LOD,InOut,pixelX,aNULL,Wee,Sherif,queston,Vertexes,nin,FO,CAF,Parth
ignore-words-list = numer,wit,aks,edn,ser,ois,gir,rouge,categor,aline,ative,afterall,deques,dateA,dateB,TE,FillIn,alle,vai,LOD,InOut,pixelX,aNULL,Wee,Sherif,queston,Vertexes,nin,FO,CAF,Parth,autor

# Skip certain files and directories

Expand Down
6 changes: 6 additions & 0 deletions .github/plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@
"description": "Comprehensive Azure cloud development tools including Infrastructure as Code, serverless functions, architecture patterns, and cost optimization for building scalable cloud applications.",
"version": "1.0.1"
},
{
"name": "boostdba",
"source": "boostdba",
"description": "AI-augmented SQL Server DBA toolkit with orchestration, dependency analysis, performance diagnostics, security governance, and modernization workflows.",
"version": "1.0.0"
},
{
"name": "cast-imaging",
"source": "cast-imaging",
Expand Down
218 changes: 218 additions & 0 deletions .github/scripts/analyze-sp-migration.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
param(
[Parameter(Mandatory = $true)]
[string]$SchemaFile,
[string]$OutDir = "workspaces/ProjectName/plans"
)

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"

if (-not (Test-Path $SchemaFile)) {
throw "No se encontro el archivo de schema: $SchemaFile"
}

if (-not (Test-Path $OutDir)) {
New-Item -ItemType Directory -Path $OutDir -Force | Out-Null
}

$content = Get-Content -Path $SchemaFile -Raw

# Capturar cada bloque de procedimiento desde CREATE PROCEDURE ... hasta el siguiente GO
$procRegex = '(?is)CREATE\s+PROCEDURE\s+\[(?<schema>[^\]]+)\]\.\[(?<name>[^\]]+)\](?<body>.*?)(?:\r?\nGO\b|\z)'
$matches = [regex]::Matches($content, $procRegex)

$results = @()

foreach ($m in $matches) {
$schema = $m.Groups['schema'].Value
$name = $m.Groups['name'].Value
$body = $m.Groups['body'].Value
$fullName = "$schema.$name"

$isSelectHeavy = [regex]::IsMatch($body, '(?is)^\s*(?:--.*\r?\n|/\*.*?\*/\s*)*\bAS\b.*\bSELECT\b')
$hasInsert = [regex]::IsMatch($body, '(?i)\bINSERT\b')
$hasUpdate = [regex]::IsMatch($body, '(?i)\bUPDATE\b')
$hasDelete = [regex]::IsMatch($body, '(?i)\bDELETE\b')
$hasMerge = [regex]::IsMatch($body, '(?i)\bMERGE\b')
$hasTran = [regex]::IsMatch($body, '(?i)\bBEGIN\s+(TRAN|TRANSACTION)\b|\bCOMMIT\b|\bROLLBACK\b')
$hasCursor = [regex]::IsMatch($body, '(?i)\bCURSOR\b')
$hasDynamicSql = [regex]::IsMatch($body, '(?i)\bsp_executesql\b|\bEXEC\s*\(\s*@')
$hasCrypto = [regex]::IsMatch($body, '(?i)\bOPEN\s+SYMMETRIC\s+KEY\b|\bDECRYPT\w*\b|\bENCRYPT\w*\b')

$writes = @($hasInsert, $hasUpdate, $hasDelete, $hasMerge) | Where-Object { $_ } | Measure-Object | Select-Object -ExpandProperty Count

$category = "Simple"
$wave = "Wave-2"
$strategy = "CSharp-Service"

if ($hasCrypto -or $hasTran -or $hasCursor -or $hasDynamicSql) {
$category = "Critical"
$wave = "Wave-4"
$strategy = "Domain-Service+High-Coverage"
} elseif ($writes -ge 2 -or (($writes -ge 1) -and -not $isSelectHeavy)) {
$category = "Complex"
$wave = "Wave-3"
$strategy = "Domain-Service"
} elseif ($writes -eq 1) {
$category = "Simple"
$wave = "Wave-2"
$strategy = "Command-Handler"
} else {
$category = "CRUD"
$wave = "Wave-1"
$strategy = "Dapper-Query"
}

# Reglas por esquema segun la estrategia de migracion
if ($schema -eq "bi") {
$category = "CRUD"
$wave = "Wave-1"
$strategy = "Dapper-Query"
}

$results += [PSCustomObject]@{
Schema = $schema
Procedure = $name
FullName = $fullName
Category = $category
Wave = $wave
Strategy = $strategy
HasWriteOps = ($writes -gt 0)
HasTransaction = $hasTran
HasCursor = $hasCursor
HasDynamicSql = $hasDynamicSql
HasCrypto = $hasCrypto
}
}

$jsonPath = Join-Path $OutDir "full-db-sp-classification.json"
$mdPath = Join-Path $OutDir "full-db-sp-classification.md"
$schemaSummaryPath = Join-Path $OutDir "full-db-schema-wave-summary.json"

$results = $results | Sort-Object Schema, Procedure
$classificationPayload = [ordered]@{
metadata = [ordered]@{
schemaVersion = "1.0"
versionEsquema = "1.0"
generatedAt = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
generadoEn = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
sourceSchemaFile = $SchemaFile
archivoSchemaOrigen = $SchemaFile
total = $results.Count
totalElementos = $results.Count
}
data = $results
}
$classificationPayload | ConvertTo-Json -Depth 8 | Set-Content -Path $jsonPath -Encoding UTF8

$byCategory = $results | Group-Object Category | Sort-Object Name
$byWave = $results | Group-Object Wave | Sort-Object Name
$bySchema = $results | Group-Object Schema | Sort-Object Name

$schemaWaveRows = @()
foreach ($g in $bySchema) {
$items = $g.Group
$schemaWaveRows += [PSCustomObject]@{
Schema = $g.Name
Total = $items.Count
Wave1 = @($items | Where-Object Wave -eq "Wave-1").Count
Wave2 = @($items | Where-Object Wave -eq "Wave-2").Count
Wave3 = @($items | Where-Object Wave -eq "Wave-3").Count
Wave4 = @($items | Where-Object Wave -eq "Wave-4").Count
}
}
$schemaSummaryPayload = [ordered]@{
metadata = [ordered]@{
schemaVersion = "1.0"
versionEsquema = "1.0"
generatedAt = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
generadoEn = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
sourceClassificationFile = $jsonPath
archivoClasificacionOrigen = $jsonPath
totalSchemas = $schemaWaveRows.Count
totalEsquemas = $schemaWaveRows.Count
}
data = $schemaWaveRows
}
$schemaSummaryPayload | ConvertTo-Json -Depth 8 | Set-Content -Path $schemaSummaryPath -Encoding UTF8

$topCritical = $results |
Where-Object { $_.Wave -eq "Wave-4" } |
Sort-Object Schema, Procedure |
Select-Object -First 40

$topWave1 = $results |
Where-Object { $_.Wave -eq "Wave-1" } |
Sort-Object Schema, Procedure |
Select-Object -First 40

$nl = [Environment]::NewLine
$md = "# Clasificacion completa de SPs (Migracion C#)`n`n"
$md += "- Generado: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')`n"
$md += "- Fuente: $SchemaFile`n"
$md += "- Total de procedimientos: $($results.Count)`n`n"

$md += "## Resumen por categoria`n`n"
$md += "| Categoria | Cantidad |`n|---|---:|`n"
foreach ($c in $byCategory) {
$md += "| $($c.Name) | $($c.Count) |`n"
}

$md += "`n## Resumen por ola`n`n"
$md += "| Ola | Cantidad | Estrategia |`n|---|---:|---|`n"
foreach ($w in $byWave) {
$strategy = switch ($w.Name) {
"Wave-1" { "Consultas Dapper (lectura primero)" }
"Wave-2" { "Commands/simple handlers" }
"Wave-3" { "Extraccion a servicio de dominio" }
"Wave-4" { "Transaccional/criptografia critica" }
default { "Por definir" }
}
$md += "| $($w.Name) | $($w.Count) | $strategy |`n"
}

$md += "`n## Esquema x ola`n`n"
$md += "| Esquema | Total | Wave-1 | Wave-2 | Wave-3 | Wave-4 |`n|---|---:|---:|---:|---:|---:|`n"
foreach ($s in ($schemaWaveRows | Sort-Object Total -Descending)) {
$md += "| $($s.Schema) | $($s.Total) | $($s.Wave1) | $($s.Wave2) | $($s.Wave3) | $($s.Wave4) |`n"
}

$md += "`n## Primeros 40 candidatos Wave-1`n`n"
$md += "| NombreCompleto | Estrategia |`n|---|---|`n"
foreach ($p in $topWave1) {
$md += "| $($p.FullName) | $($p.Strategy) |`n"
}

$md += "`n## Primeros 40 candidatos criticos Wave-4`n`n"
$md += "| NombreCompleto | Tx | Cursor | SQLDinamico | Cripto |`n|---|---|---|---|---|`n"
foreach ($p in $topCritical) {
$md += "| $($p.FullName) | $($p.HasTransaction) | $($p.HasCursor) | $($p.HasDynamicSql) | $($p.HasCrypto) |`n"
}

$md += "`n## Salidas`n`n"
$md += "- $jsonPath`n"
$md += "- $schemaSummaryPath`n"
$md += "- $mdPath`n"

Set-Content -Path $mdPath -Value $md -Encoding UTF8

Write-Host "Clasificacion completada"
Write-Host "Total de SPs: $($results.Count)"
Write-Host "JSON: $jsonPath"
Write-Host "Resumen por esquema (JSON): $schemaSummaryPath"
Write-Host "MD: $mdPath"

$resolvedOutDir = (Resolve-Path $OutDir).Path
$projectName = $null
if ($resolvedOutDir -match '[\\/]workspaces[\\/](?<project>[^\\/]+)[\\/]') {
$projectName = $matches['project']
}

if ($projectName) {
$anonymizeScript = Join-Path $PSScriptRoot 'apply-artifact-anonymization.ps1'
if (Test-Path $anonymizeScript) {
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..' '..')).Path
& $anonymizeScript -ProjectName $projectName -Scope all -Root $repoRoot
}
}

92 changes: 92 additions & 0 deletions .github/scripts/apply-artifact-anonymization.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
param(
[Parameter(Mandatory = $true)]
[string]$ProjectName,
[ValidateSet('reports', 'plans', 'entrega', 'all')]
[string]$Scope = 'all',
[string]$Root = (Get-Location).Path
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

$repoRoot = (Resolve-Path $Root).Path
$workspace = Join-Path $repoRoot "workspaces" $ProjectName
$manifestPath = Join-Path $workspace "fuente-de-verdad" "manifest.json"

if (-not (Test-Path $manifestPath)) {
throw "Manifest no encontrado: $manifestPath"
}

$manifest = Get-Content $manifestPath -Raw | ConvertFrom-Json
$manifestAnonymized = $false
if ($manifest.PSObject.Properties['anonymizationEnabled']) {
$manifestAnonymized = [bool]$manifest.anonymizationEnabled
}

if (-not $manifestAnonymized) {
Write-Host "Anonimización desactivada en manifest. No se aplican cambios." -ForegroundColor Yellow
return
}

# Replace both plain and backticked identifiers seen in reports/plans.
$regexReplacements = @(
@{ Pattern = '(?<!\w)dbo(?!\w)'; Value = 'SCH_CORE' },
@{ Pattern = '(?<!\w)bi(?!\w)'; Value = 'SCH_BI' },
@{ Pattern = '(?<!\w)bya(?!\w)'; Value = 'SCH_GRANTS' },
@{ Pattern = '(?<!\w)plc(?!\w)'; Value = 'SCH_LIQUID' },
@{ Pattern = '(?<!\w)vt(?!\w)'; Value = 'SCH_VALUATION' },
@{ Pattern = '(?<!\w)gba(?!\w)'; Value = 'SCH_GRANTS_ADV' },
@{ Pattern = '(?<!\w)gcc(?!\w)'; Value = 'SCH_COSTS' },
@{ Pattern = '(?<!\w)req(?!\w)'; Value = 'SCH_REQS' },
@{ Pattern = '(?<!\w)rei(?!\w)'; Value = 'SCH_REFUNDS' },
@{ Pattern = '(?<!\w)ale(?!\w)'; Value = 'SCH_APPEALS' },
@{ Pattern = '\bSubvencionesSymmetricKey\b'; Value = 'SYM_KEY_001' },
@{ Pattern = '\bT_GRUPO_PARTICIPANTE\b'; Value = 'T_ENTITY_PARTICIPANTS' },
@{ Pattern = '\bT_SOLICITUD\b'; Value = 'T_REQUEST' },
@{ Pattern = '\bT_PARTICIPANTE_DATOSBANCARIOS\b'; Value = 'T_PARTICIPANT_BANK' },
@{ Pattern = '\bT_PLANFORMACION\b'; Value = 'T_TRAINING_PLAN' },
@{ Pattern = '\bT_PLANFORM_AF\b'; Value = 'T_TRAINING_ACTION' },
@{ Pattern = '\bUP_V_ABRIR_LLAVE\b'; Value = 'SP_OPEN_KEY' },
@{ Pattern = '\bUP_U_CANCELAR_DEVOLUCION\b'; Value = 'SP_CANCEL_REFUND' },
@{ Pattern = '\bDecryptByKey\b'; Value = 'DecryptByKeyAlias' }
)

$targets = @()
switch ($Scope) {
'reports' { $targets += [PSCustomObject]@{ Path = (Join-Path $workspace 'reports'); Recurse = $true } }
'plans' { $targets += [PSCustomObject]@{ Path = (Join-Path $workspace 'plans'); Recurse = $true } }
'entrega' { $targets += [PSCustomObject]@{ Path = (Join-Path $workspace 'entrega'); Recurse = $true } }
default {
# Include root masters (e.g. <Project>-INFORME-*.md) without walking source-of-truth recursively.
$targets += [PSCustomObject]@{ Path = $workspace; Recurse = $false }
$targets += [PSCustomObject]@{ Path = (Join-Path $workspace 'reports'); Recurse = $true }
$targets += [PSCustomObject]@{ Path = (Join-Path $workspace 'plans'); Recurse = $true }
$targets += [PSCustomObject]@{ Path = (Join-Path $workspace 'entrega'); Recurse = $true }
}
}

$files = foreach ($t in $targets) {
if (Test-Path $t.Path) {
if ($t.Recurse) {
Get-ChildItem -Path $t.Path -Recurse -File -Include *.md, *.txt, *.json
} else {
Get-ChildItem -Path $t.Path -File -Include *.md, *.txt, *.json
}
}
}

$updated = 0
foreach ($file in $files) {
$original = Get-Content $file.FullName -Raw
$content = $original
foreach ($r in $regexReplacements) {
$content = [regex]::Replace($content, $r.Pattern, $r.Value)
}

if ($content -ne $original) {
Set-Content -Path $file.FullName -Value $content -Encoding UTF8
$updated++
}
}

Write-Host "Anonimización de artefactos aplicada. Ficheros actualizados: $updated" -ForegroundColor Green
Loading
Loading