All checks were successful
build / build-test (push) Successful in 16s
Windows/EA session per BUILD-CHECKLIST 0-6 (the planned human-gate path, executed on the EA machine). Baseline built from EABase.eapx via build-model.ps1 and verified per checklist 4: 3 actors, 12 UCs with scenarios, 13 associations, 0 include connectors, 7 SS requirements, 12 realization packages / 36 elements; UC-08 MSS (9 steps) and UC-11 learn spell level (3 steps) match use-case-model.md verbatim. Script fixes (defects 1-3 of the session log): PS 5.1 hashtable parse error; realization UC lookup now accepts short and full use-case names. Enrichment after a manual layout pass - model changes are surgical from now on, re-running build-model.ps1 is forbidden (checklist 6): - add-analysis-associations.ps1: 24 analysis associations per mapping 2a (boundary-controller-entity; UC-04 boundary-entity) rendered via DiagramLinks without touching manual layout; 4 uses-dependencies to DomainError (UC-05/06/08/12 error paths, model-level) - add-design-package.ps1: Analysis-Design/Design - joint component model (skara-brae-client, sb-api, sb-auth, sb-db, sb-core, sb-shared, sb-common; 10 dependencies, rule inward). Joint-model decision recorded in mapping 1: one model for server+client+common; client- side UC realizations are a future work item. Markdown sources updated first (drift rule): mapping 1 tree + 2a interactions + 3 design component model; checklist 6. Exports regenerated from the model: use-case-model.json new, uc-realizations.json re-exported (useCase full names, notes merged, crossCutting added; element content verified equal to the markdown-to-json generator output). Open: XMI backup - ExportPackageXMI fails via COM (DISP_E_TYPEMISMATCH, PS 5.1 + cscript); manual EA UI export per model/README.md option 2.
173 lines
8.2 KiB
PowerShell
173 lines
8.2 KiB
PowerShell
# Skara Brae — EA analysis-association adder (post-WI-2 enrichment).
|
|
# SURGICAL: adds connectors + diagram links to EXISTING realization packages.
|
|
# Never deletes or rebuilds anything - manual layout in the .eapx is preserved.
|
|
# Re-running build-model.ps1 after a manual layout session is FORBIDDEN (it rebuilds).
|
|
#
|
|
# Source of truth: docs/openup/analysis-design/uc-realization-mapping.md §2a
|
|
# (24 associations + 4 cross-cutting «uses» dependencies to DomainError).
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
|
|
$modelPath = Join-Path $repoRoot 'docs\openup\model\skara-brae.eapx'
|
|
if (-not (Test-Path $modelPath)) {
|
|
Write-Output "MISSING $modelPath"
|
|
exit 1
|
|
}
|
|
|
|
# (packageId, fromElement, toElement) per mapping §2a - 24 rows.
|
|
$associationRows = @(
|
|
@{ Pkg = 'UC-01 Realization'; From = 'AuthBoundary'; To = 'AccountRegistry' },
|
|
@{ Pkg = 'UC-01 Realization'; From = 'AccountRegistry'; To = 'UserAccount' },
|
|
@{ Pkg = 'UC-02 Realization'; From = 'TokenService'; To = 'Authenticator' },
|
|
@{ Pkg = 'UC-03 Realization'; From = 'CharacterCatalog'; To = 'CharacterFactory' },
|
|
@{ Pkg = 'UC-03 Realization'; From = 'CharacterFactory'; To = 'Character' },
|
|
@{ Pkg = 'UC-03 Realization'; From = 'CharacterFactory'; To = 'ResourceRegenerator' },
|
|
@{ Pkg = 'UC-04 Realization'; From = 'CatalogueBrowser'; To = 'GameData' },
|
|
@{ Pkg = 'UC-05 Realization'; From = 'GymBoundary'; To = 'TrainingService' },
|
|
@{ Pkg = 'UC-05 Realization'; From = 'TrainingService'; To = 'TrainingGrounds' },
|
|
@{ Pkg = 'UC-06 Realization'; From = 'EquipmentBoundary'; To = 'EquipmentService' },
|
|
@{ Pkg = 'UC-06 Realization'; From = 'EquipmentService'; To = 'Equipment/Inventory' },
|
|
@{ Pkg = 'UC-07 Realization'; From = 'ShopBoundary'; To = 'ShopService' },
|
|
@{ Pkg = 'UC-07 Realization'; From = 'ShopService'; To = 'ShopStock' },
|
|
@{ Pkg = 'UC-08 Realization'; From = 'AdventureBoundary'; To = 'AdventureService' },
|
|
@{ Pkg = 'UC-08 Realization'; From = 'AdventureService'; To = 'CombatEngine' },
|
|
@{ Pkg = 'UC-08 Realization'; From = 'AdventureService'; To = 'Combatant' },
|
|
@{ Pkg = 'UC-08 Realization'; From = 'AdventureService'; To = 'SpellEffect / BardSong' },
|
|
@{ Pkg = 'UC-09 Realization'; From = 'TempleBoundary'; To = 'HealingService' },
|
|
@{ Pkg = 'UC-10 Realization'; From = 'SongBoundary'; To = 'SongService' },
|
|
@{ Pkg = 'UC-10 Realization'; From = 'SongService'; To = 'BardSong' },
|
|
@{ Pkg = 'UC-11 Realization'; From = 'MagicBoundary'; To = 'MagicService' },
|
|
@{ Pkg = 'UC-11 Realization'; From = 'MagicService'; To = 'Spell' },
|
|
@{ Pkg = 'UC-12 Realization'; From = 'ReviewBoundary'; To = 'ReviewBoard' },
|
|
@{ Pkg = 'UC-12 Realization'; From = 'ReviewBoard'; To = 'Progression' }
|
|
)
|
|
|
|
# (packageId, controllerName) - 4 «uses» dependencies to DomainError.
|
|
$domainErrorRows = @(
|
|
@{ Pkg = 'UC-05 Realization'; From = 'TrainingService' },
|
|
@{ Pkg = 'UC-06 Realization'; From = 'EquipmentService' },
|
|
@{ Pkg = 'UC-08 Realization'; From = 'AdventureService' },
|
|
@{ Pkg = 'UC-12 Realization'; From = 'ReviewBoard' }
|
|
)
|
|
|
|
$step = 'start'
|
|
$app = $null
|
|
$eaBefore = @(Get-Process -Name EA -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Id)
|
|
|
|
function Test-ConnectorExists([object]$clientElement, [int]$supplierId, [string]$type) {
|
|
$clientElement.Connectors.Refresh()
|
|
for ($c = 0; $c -lt $clientElement.Connectors.Count; $c++) {
|
|
$existing = $clientElement.Connectors.GetAt([int]$c)
|
|
if ($existing.Type -eq $type -and $existing.SupplierID -eq $supplierId) { return $true }
|
|
}
|
|
return $false
|
|
}
|
|
|
|
try {
|
|
$app = New-Object -ComObject EA.App
|
|
$app.Visible = $false
|
|
$repo = $app.Repository
|
|
$null = $repo.OpenFile($modelPath)
|
|
$root = $repo.Models.GetAt([int]0)
|
|
|
|
$step = 'resolve Analysis-Design + DomainError'
|
|
$analysisDesign = $root.Packages.GetByName('Analysis-Design')
|
|
$architecture = $analysisDesign.Packages.GetByName('Architecture Overview')
|
|
$architecture.Elements.Refresh()
|
|
$domainError = $null
|
|
for ($i = 0; $i -lt $architecture.Elements.Count; $i++) {
|
|
$candidate = $architecture.Elements.GetAt([int]$i)
|
|
if ($candidate.Name -eq 'DomainError') { $domainError = $candidate; break }
|
|
}
|
|
if (-not $domainError) { throw 'DomainError element not found in Architecture Overview' }
|
|
|
|
$step = 'collect realization package elements'
|
|
$elementsByPkg = @{}
|
|
for ($p = 0; $p -lt $analysisDesign.Packages.Count; $p++) {
|
|
$pkg = $analysisDesign.Packages.GetAt([int]$p)
|
|
if ($pkg.Name -notlike 'UC-?? Realization') { continue }
|
|
$pkg.Elements.Refresh()
|
|
$map = @{}
|
|
for ($e = 0; $e -lt $pkg.Elements.Count; $e++) {
|
|
$element = $pkg.Elements.GetAt([int]$e)
|
|
if ($element.Type -eq 'Class') { $map[$element.Name] = $element }
|
|
}
|
|
$elementsByPkg[$pkg.Name] = @{ Map = $map; Package = $pkg }
|
|
}
|
|
|
|
$step = 'add associations + diagram links'
|
|
$createdAssoc = 0; $skippedAssoc = 0
|
|
foreach ($row in $associationRows) {
|
|
$entry = $elementsByPkg[$row.Pkg]
|
|
if (-not $entry) { throw "package not found: $($row.Pkg)" }
|
|
$client = $entry.Map[$row.From]
|
|
$supplier = $entry.Map[$row.To]
|
|
if (-not $client) { throw "element not found in $($row.Pkg): $($row.From)" }
|
|
if (-not $supplier) { throw "element not found in $($row.Pkg): $($row.To)" }
|
|
if (Test-ConnectorExists $client $supplier.ElementID 'Association') { $skippedAssoc++; continue }
|
|
|
|
$connector = $client.Connectors.AddNew('', 'Association')
|
|
$connector.ClientID = $client.ElementID
|
|
$connector.SupplierID = $supplier.ElementID
|
|
$null = $connector.Update()
|
|
$client.Connectors.Refresh()
|
|
|
|
$diagram = $entry.Package.Diagrams.GetAt([int]0)
|
|
$diagram.DiagramLinks.Refresh()
|
|
$link = $diagram.DiagramLinks.AddNew('', '')
|
|
$link.ConnectorID = $connector.ConnectorID
|
|
$null = $link.Update()
|
|
$null = $diagram.Update()
|
|
$createdAssoc++
|
|
}
|
|
|
|
$step = 'add DomainError dependencies (model-level, no diagram links - DomainError is not placed on realization diagrams; manual layout stays untouched)'
|
|
$createdDeps = 0; $skippedDeps = 0
|
|
foreach ($row in $domainErrorRows) {
|
|
$entry = $elementsByPkg[$row.Pkg]
|
|
$client = $entry.Map[$row.From]
|
|
if (-not $client) { throw "element not found in $($row.Pkg): $($row.From)" }
|
|
if (Test-ConnectorExists $client $domainError.ElementID 'Dependency') { $skippedDeps++; continue }
|
|
|
|
$connector = $client.Connectors.AddNew('', 'Dependency')
|
|
$connector.ClientID = $client.ElementID
|
|
$connector.SupplierID = $domainError.ElementID
|
|
$connector.Stereotype = 'uses'
|
|
$null = $connector.Update()
|
|
$client.Connectors.Refresh()
|
|
$createdDeps++
|
|
}
|
|
|
|
$step = 'verify'
|
|
$assocTotal = 0; $depTotal = 0
|
|
foreach ($pkgName in $elementsByPkg.Keys) {
|
|
$entry = $elementsByPkg[$pkgName]
|
|
foreach ($className in $entry.Map.Keys) {
|
|
$element = $entry.Map[$className]
|
|
$element.Connectors.Refresh()
|
|
for ($c = 0; $c -lt $element.Connectors.Count; $c++) {
|
|
$connector = $element.Connectors.GetAt([int]$c)
|
|
if ($connector.Type -eq 'Association') { $assocTotal++ }
|
|
if ($connector.Type -eq 'Dependency' -and $connector.SupplierID -eq $domainError.ElementID) { $depTotal++ }
|
|
}
|
|
}
|
|
}
|
|
$assocTotal = $assocTotal / 2
|
|
|
|
Write-Output "ASSOCIATIONS created=$createdAssoc skipped(existing)=$skippedAssoc -> total=$assocTotal (expect 24)"
|
|
Write-Output "DOMAINERROR DEPS created=$createdDeps skipped(existing)=$skippedDeps -> total=$depTotal (expect 4)"
|
|
if ($assocTotal -ne 24 -or $depTotal -ne 4) { Write-Output 'VERIFY FAILED'; exit 1 }
|
|
|
|
$null = $repo.CloseFile()
|
|
Write-Output 'ALL STEPS OK - associations added surgically; manual layout untouched'
|
|
}
|
|
catch {
|
|
Write-Output "FAILED at [$step]: $($_.Exception.Message)"
|
|
exit 1
|
|
}
|
|
finally {
|
|
Get-Process -Name EA -ErrorAction SilentlyContinue |
|
|
Where-Object { $eaBefore -notcontains $_.Id } |
|
|
ForEach-Object { Stop-Process -Id $_.Id -Force }
|
|
}
|