Initial commit
This commit is contained in:
257
scripts/select-library-assets.ps1
Normal file
257
scripts/select-library-assets.ps1
Normal file
@@ -0,0 +1,257 @@
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$LibraryRoot,
|
||||
[string]$TargetRoot = (Get-Location).Path,
|
||||
[string]$Query = "",
|
||||
[string]$Type = "",
|
||||
[string]$Domain = "",
|
||||
[int]$Top = 10,
|
||||
[switch]$Json,
|
||||
[switch]$Rebuild
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
if (-not (Test-Path -LiteralPath $LibraryRoot)) {
|
||||
throw "Library root does not exist: $LibraryRoot"
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $TargetRoot)) {
|
||||
throw "Target root does not exist: $TargetRoot"
|
||||
}
|
||||
|
||||
$libraryPath = (Resolve-Path -LiteralPath $LibraryRoot).Path
|
||||
$targetPath = (Resolve-Path -LiteralPath $TargetRoot).Path
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$detectScript = Join-Path $scriptDir "detect-project-signals.ps1"
|
||||
$indexPath = Join-Path $libraryPath "_indexes\artifacts-index.json"
|
||||
$buildScript = Join-Path $libraryPath "scripts\build-artifact-index.ps1"
|
||||
|
||||
if (-not (Test-Path -LiteralPath $detectScript)) {
|
||||
throw "Missing detect-project-signals script: $detectScript"
|
||||
}
|
||||
|
||||
if ($Rebuild -or -not (Test-Path -LiteralPath $indexPath)) {
|
||||
if (-not (Test-Path -LiteralPath $buildScript)) {
|
||||
throw "Library artifact index is missing and no build script was found: $indexPath"
|
||||
}
|
||||
|
||||
& $buildScript -Root $libraryPath -OutputPath $indexPath -Quiet
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $indexPath)) {
|
||||
throw "Library artifact index does not exist: $indexPath"
|
||||
}
|
||||
|
||||
$signalInfo = & $detectScript -Root $targetPath -Json | ConvertFrom-Json
|
||||
$signalNames = @()
|
||||
if ($null -ne $signalInfo -and $null -ne $signalInfo.signals) {
|
||||
$signalNames = @($signalInfo.signals | ForEach-Object { $_.name } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
|
||||
}
|
||||
|
||||
$signalsValue = $signalNames -join ","
|
||||
|
||||
if ($signalNames.Count -eq 0 -and
|
||||
[string]::IsNullOrWhiteSpace($Query) -and
|
||||
[string]::IsNullOrWhiteSpace($Type) -and
|
||||
[string]::IsNullOrWhiteSpace($Domain)) {
|
||||
throw "No target project signals or search criteria were found. Provide -Query, -Type, or -Domain."
|
||||
}
|
||||
|
||||
function Get-Terms {
|
||||
param([string]$Value)
|
||||
|
||||
$terms = New-Object System.Collections.Generic.List[string]
|
||||
foreach ($part in [regex]::Split($Value.ToLowerInvariant(), "[\s,;\uFF0C\uFF1B\u3001]+")) {
|
||||
$trimmed = $part.Trim()
|
||||
if (-not [string]::IsNullOrWhiteSpace($trimmed)) {
|
||||
[void]$terms.Add($trimmed)
|
||||
}
|
||||
}
|
||||
|
||||
return @($terms)
|
||||
}
|
||||
|
||||
function Get-TextValue {
|
||||
param($Value)
|
||||
|
||||
if ($null -eq $Value) {
|
||||
return ""
|
||||
}
|
||||
|
||||
if ($Value -is [array]) {
|
||||
return (@($Value) -join " ")
|
||||
}
|
||||
|
||||
return [string]$Value
|
||||
}
|
||||
|
||||
function Test-ContainsText {
|
||||
param(
|
||||
[string]$Haystack,
|
||||
[string]$Needle
|
||||
)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($Haystack) -or [string]::IsNullOrWhiteSpace($Needle)) {
|
||||
return $false
|
||||
}
|
||||
|
||||
return $Haystack.ToLowerInvariant().Contains($Needle.ToLowerInvariant())
|
||||
}
|
||||
|
||||
function Test-ExactText {
|
||||
param(
|
||||
[string[]]$Values,
|
||||
[string]$Needle
|
||||
)
|
||||
|
||||
foreach ($value in @($Values)) {
|
||||
if (-not [string]::IsNullOrWhiteSpace($value) -and $value.Trim().ToLowerInvariant() -eq $Needle.Trim().ToLowerInvariant()) {
|
||||
return $true
|
||||
}
|
||||
}
|
||||
|
||||
return $false
|
||||
}
|
||||
|
||||
function Add-Score {
|
||||
param(
|
||||
[int]$Current,
|
||||
[string]$FieldValue,
|
||||
[string]$Term,
|
||||
[int]$Weight,
|
||||
[string]$FieldName,
|
||||
[System.Collections.Generic.List[string]]$MatchedFields
|
||||
)
|
||||
|
||||
if (Test-ContainsText -Haystack $FieldValue -Needle $Term) {
|
||||
[void]$MatchedFields.Add($FieldName)
|
||||
return ($Current + $Weight)
|
||||
}
|
||||
|
||||
return $Current
|
||||
}
|
||||
|
||||
$terms = @(Get-Terms -Value $Query)
|
||||
$rawItems = Get-Content -Raw -Encoding UTF8 -LiteralPath $indexPath | ConvertFrom-Json
|
||||
$items = @()
|
||||
if ($null -ne $rawItems) {
|
||||
if ($rawItems -is [array]) {
|
||||
$items = @($rawItems)
|
||||
}
|
||||
else {
|
||||
$items = @($rawItems)
|
||||
}
|
||||
}
|
||||
|
||||
$rankableResults = New-Object System.Collections.Generic.List[object]
|
||||
|
||||
foreach ($item in $items) {
|
||||
if (-not [string]::IsNullOrWhiteSpace($Type) -and ((Get-TextValue $item.type).ToLowerInvariant() -ne $Type.ToLowerInvariant())) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($Domain) -and ((Get-TextValue $item.domain).ToLowerInvariant() -ne $Domain.ToLowerInvariant())) {
|
||||
continue
|
||||
}
|
||||
|
||||
$score = 0
|
||||
$matchedTerms = New-Object System.Collections.Generic.HashSet[string]
|
||||
$matchedSignals = New-Object System.Collections.Generic.List[string]
|
||||
$matchedFields = New-Object System.Collections.Generic.List[string]
|
||||
|
||||
foreach ($signal in $signalNames) {
|
||||
if (Test-ExactText -Values @($item.appliesWhen) -Needle $signal) {
|
||||
[void]$matchedSignals.Add($signal)
|
||||
$score += 70
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($term in $terms) {
|
||||
$termScore = 0
|
||||
$termScore = Add-Score -Current $termScore -FieldValue (Get-TextValue $item.title) -Term $term -Weight 80 -FieldName "title" -MatchedFields $matchedFields
|
||||
$termScore = Add-Score -Current $termScore -FieldValue (Get-TextValue $item.tags) -Term $term -Weight 60 -FieldName "tags" -MatchedFields $matchedFields
|
||||
$termScore = Add-Score -Current $termScore -FieldValue (Get-TextValue $item.domain) -Term $term -Weight 35 -FieldName "domain" -MatchedFields $matchedFields
|
||||
$termScore = Add-Score -Current $termScore -FieldValue (Get-TextValue $item.category) -Term $term -Weight 25 -FieldName "category" -MatchedFields $matchedFields
|
||||
$termScore = Add-Score -Current $termScore -FieldValue (Get-TextValue $item.appliesWhen) -Term $term -Weight 35 -FieldName "appliesWhen" -MatchedFields $matchedFields
|
||||
$termScore = Add-Score -Current $termScore -FieldValue (Get-TextValue $item.targetOutputs) -Term $term -Weight 25 -FieldName "targetOutputs" -MatchedFields $matchedFields
|
||||
$termScore = Add-Score -Current $termScore -FieldValue (Get-TextValue $item.body) -Term $term -Weight 15 -FieldName "body" -MatchedFields $matchedFields
|
||||
|
||||
if ($termScore -gt 0) {
|
||||
[void]$matchedTerms.Add($term)
|
||||
$score += $termScore
|
||||
}
|
||||
}
|
||||
|
||||
if ($terms.Count -gt 0 -and $matchedTerms.Count -eq $terms.Count) {
|
||||
$score += 30
|
||||
}
|
||||
|
||||
if ($signalNames.Count -gt 0 -and $matchedSignals.Count -eq $signalNames.Count) {
|
||||
$score += 25
|
||||
}
|
||||
|
||||
if ($score -gt 0 -or ($terms.Count -eq 0 -and $signalNames.Count -eq 0)) {
|
||||
[void]$rankableResults.Add([pscustomobject]@{
|
||||
score = $score
|
||||
type = $item.type
|
||||
title = $item.title
|
||||
domain = $item.domain
|
||||
category = $item.category
|
||||
relativePath = $item.relativePath
|
||||
fullPath = $item.fullPath
|
||||
tags = @($item.tags)
|
||||
appliesWhen = @($item.appliesWhen)
|
||||
targetOutputs = @($item.targetOutputs)
|
||||
updated = $item.updated
|
||||
matchedTerms = @($matchedTerms)
|
||||
matchedSignals = @($matchedSignals | Select-Object -Unique)
|
||||
matchedFields = @($matchedFields | Select-Object -Unique)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
$results = @($rankableResults.ToArray() |
|
||||
Sort-Object `
|
||||
@{ Expression = "score"; Descending = $true },
|
||||
@{ Expression = "updated"; Descending = $true },
|
||||
@{ Expression = "relativePath"; Descending = $false } |
|
||||
Select-Object -First $Top)
|
||||
|
||||
$selection = [pscustomobject]@{
|
||||
libraryRoot = $libraryPath
|
||||
targetRoot = $targetPath
|
||||
signals = @($signalInfo.signals)
|
||||
indexPath = $indexPath
|
||||
query = $Query
|
||||
type = $Type
|
||||
domain = $Domain
|
||||
results = @($results)
|
||||
}
|
||||
|
||||
if ($Json) {
|
||||
ConvertTo-Json -InputObject $selection -Depth 10
|
||||
return
|
||||
}
|
||||
|
||||
Write-Host "Library: $libraryPath"
|
||||
Write-Host "Target: $targetPath"
|
||||
if ($signalNames.Count -gt 0) {
|
||||
Write-Host "Signals: $($signalNames -join ', ')"
|
||||
}
|
||||
else {
|
||||
Write-Host "Signals: none"
|
||||
}
|
||||
|
||||
if ($results.Count -eq 0) {
|
||||
Write-Host "No matching library assets selected."
|
||||
return
|
||||
}
|
||||
|
||||
foreach ($result in $results) {
|
||||
Write-Host "[$($result.score)] $($result.type): $($result.title)"
|
||||
Write-Host " path: $($result.relativePath)"
|
||||
if (@($result.targetOutputs).Count -gt 0) {
|
||||
Write-Host " targetOutputs: $(@($result.targetOutputs) -join ', ')"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user