Add ks-zl skill

This commit is contained in:
2026-05-13 18:14:30 +08:00
commit b81d919176
11 changed files with 1492 additions and 0 deletions

View File

@@ -0,0 +1,204 @@
param(
[string]$Root = (Get-Location).Path,
[string]$IndexPath,
[Parameter(Mandatory = $true)]
[string]$Query,
[int]$Top = 10,
[switch]$Json,
[switch]$Rebuild
)
$ErrorActionPreference = "Stop"
if ([string]::IsNullOrWhiteSpace($Query)) {
throw "Query must not be empty."
}
if (-not (Test-Path -LiteralPath $Root)) {
throw "Root path does not exist: $Root"
}
$rootPath = (Resolve-Path -LiteralPath $Root).Path.TrimEnd([char[]]"\/")
if ([string]::IsNullOrWhiteSpace($IndexPath)) {
$IndexPath = Join-Path (Join-Path $rootPath "_indexes") "requirements-index.json"
}
if ($Rebuild -or -not (Test-Path -LiteralPath $IndexPath)) {
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$buildScript = Join-Path $scriptDir "build-requirement-index.ps1"
& $buildScript -Root $rootPath -OutputPath $IndexPath -Quiet
}
if (-not (Test-Path -LiteralPath $IndexPath)) {
throw "Index file does not exist: $IndexPath"
}
function Get-QueryTerms {
param([string]$Value)
$normalized = $Value.ToLowerInvariant()
$terms = New-Object System.Collections.Generic.List[string]
foreach ($part in [regex]::Split($normalized, "[\s,;,;、]+")) {
$trimmed = $part.Trim()
if (-not [string]::IsNullOrWhiteSpace($trimmed)) {
[void]$terms.Add($trimmed)
}
}
return @($terms)
}
function Test-ContainsText {
param(
[string]$Haystack,
[string]$Needle
)
if ([string]::IsNullOrWhiteSpace($Haystack) -or [string]::IsNullOrWhiteSpace($Needle)) {
return $false
}
return $Haystack.ToLowerInvariant().Contains($Needle)
}
function Get-TextValue {
param($Value)
if ($null -eq $Value) {
return ""
}
if ($Value -is [array]) {
return (@($Value) -join " ")
}
return [string]$Value
}
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-QueryTerms -Value $Query)
if ($terms.Count -eq 0) {
throw "Query must contain at least one searchable term."
}
$rawItems = Get-Content -Raw -Encoding UTF8 -LiteralPath $IndexPath | ConvertFrom-Json
$items = @()
if ($null -ne $rawItems) {
if ($rawItems -is [array]) {
$items = @($rawItems)
}
else {
$items = @($rawItems)
}
}
$results = New-Object System.Collections.Generic.List[object]
foreach ($item in $items) {
$score = 0
$matchedTerms = New-Object System.Collections.Generic.HashSet[string]
$matchedFields = New-Object System.Collections.Generic.List[string]
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.slug) -Term $term -Weight 60 -FieldName "slug" -MatchedFields $matchedFields
$termScore = Add-Score -Current $termScore -FieldValue (Get-TextValue $item.tags) -Term $term -Weight 55 -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.feature) -Term $term -Weight 45 -FieldName "feature" -MatchedFields $matchedFields
$termScore = Add-Score -Current $termScore -FieldValue (Get-TextValue $item.businessRules) -Term $term -Weight 40 -FieldName "businessRules" -MatchedFields $matchedFields
$termScore = Add-Score -Current $termScore -FieldValue (Get-TextValue $item.acceptance) -Term $term -Weight 25 -FieldName "acceptance" -MatchedFields $matchedFields
$termScore = Add-Score -Current $termScore -FieldValue (Get-TextValue $item.flow) -Term $term -Weight 18 -FieldName "flow" -MatchedFields $matchedFields
$termScore = Add-Score -Current $termScore -FieldValue (Get-TextValue $item.dataTables) -Term $term -Weight 12 -FieldName "dataTables" -MatchedFields $matchedFields
$termScore = Add-Score -Current $termScore -FieldValue (Get-TextValue $item.dictionaries) -Term $term -Weight 12 -FieldName "dictionaries" -MatchedFields $matchedFields
$termScore = Add-Score -Current $termScore -FieldValue (Get-TextValue $item.sourceEvidence) -Term $term -Weight 8 -FieldName "sourceEvidence" -MatchedFields $matchedFields
if ($termScore -gt 0) {
[void]$matchedTerms.Add($term)
$score += $termScore
}
}
if ($matchedTerms.Count -eq $terms.Count) {
$score += 30
}
if ($score -gt 0) {
[void]$results.Add([pscustomobject][ordered]@{
score = $score
title = $item.title
domain = $item.domain
packageType = $item.packageType
slug = $item.slug
relativePath = $item.relativePath
fullPath = $item.fullPath
tags = @($item.tags)
updated = $item.updated
matchedTerms = @($matchedTerms)
matchedFields = @($matchedFields | Select-Object -Unique)
feature = $item.feature
businessRules = $item.businessRules
})
}
}
$resultArray = @($results.ToArray())
$ranked = @($resultArray |
Sort-Object `
@{ Expression = "score"; Descending = $true },
@{ Expression = "updated"; Descending = $true },
@{ Expression = "relativePath"; Descending = $false } |
Select-Object -First $Top)
if ($Json) {
if ($ranked.Count -eq 0) {
Write-Output "[]"
}
elseif ($ranked.Count -eq 1) {
Write-Output "[$($ranked[0] | ConvertTo-Json -Depth 8)]"
}
else {
ConvertTo-Json -InputObject $ranked -Depth 8
}
return
}
if ($ranked.Count -eq 0) {
Write-Host "No matching requirement packages found."
return
}
foreach ($result in $ranked) {
Write-Host "[$($result.score)] $($result.title) ($($result.domain)/$($result.slug))"
Write-Host " path: $($result.relativePath)"
if ($result.tags.Count -gt 0) {
Write-Host " tags: $(@($result.tags) -join ', ')"
}
Write-Host " matched: $(@($result.matchedTerms) -join ', ') in $(@($result.matchedFields) -join ', ')"
if (-not [string]::IsNullOrWhiteSpace($result.feature)) {
Write-Host " feature: $($result.feature)"
}
Write-Host ""
}