258 lines
8.1 KiB
PowerShell
258 lines
8.1 KiB
PowerShell
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 ', ')"
|
|
}
|
|
}
|