chore(release): prepare public source release

This commit is contained in:
MercuryToolbox Release
2026-07-18 15:33:01 +08:00
commit 34d6a57f38
510 changed files with 163501 additions and 0 deletions
+341
View File
@@ -0,0 +1,341 @@
[CmdletBinding()]
param(
[ValidateSet('Debug', 'Release', 'ReleaseFast', 'ReleaseSize')]
[string]$Configuration = 'ReleaseFast',
[string]$OutputPath = (Join-Path (Join-Path (Split-Path -Parent $PSScriptRoot) 'docs') 'ai\mercury-toolbox-ai-prompt.md'),
[string]$NotesPath = (Join-Path (Join-Path (Split-Path -Parent $PSScriptRoot) 'docs') 'ai\toolbox-ai-prompt-notes.json'),
[switch]$Check,
[switch]$SkipBuild
)
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest
. (Join-Path $PSScriptRoot 'toolbox-commands.ps1')
function Invoke-StrictNative {
param(
[Parameter(Mandatory = $true)]
[string]$FilePath,
[Parameter(Mandatory = $true)]
[string[]]$ArgumentList
)
& $FilePath @ArgumentList
if ($LASTEXITCODE -ne 0) {
throw "Command failed with exit code ${LASTEXITCODE}: $FilePath $($ArgumentList -join ' ')"
}
}
function Get-ToolHelpSections {
param(
[Parameter(Mandatory = $true)]
[string]$HelpText
)
$lines = $HelpText -split "`r?`n"
$summary = ($lines | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -First 1).Trim()
$usage = [System.Collections.Generic.List[string]]::new()
$examples = [System.Collections.Generic.List[string]]::new()
$section = ''
foreach ($line in $lines) {
$trimmed = $line.Trim()
if ($trimmed -ceq 'Usage:') {
$section = 'usage'
continue
}
if ($trimmed -ceq 'Examples:') {
$section = 'examples'
continue
}
if ([string]::IsNullOrWhiteSpace($trimmed)) {
continue
}
if (Test-HelpSectionHeader -Line $trimmed) {
$section = ''
continue
}
switch ($section) {
'usage' { $usage.Add($trimmed) }
'examples' { $examples.Add($trimmed) }
}
}
return @{
Summary = $summary
Usage = @($usage)
Examples = @($examples)
}
}
function Test-HelpSectionHeader {
param(
[Parameter(Mandatory = $true)]
[string]$Line
)
return $Line -cmatch '^[A-Z][A-Za-z0-9 /_-]*:$'
}
function Append-Section {
param(
[Parameter(Mandatory = $true)]
[System.Text.StringBuilder]$Builder,
[Parameter(Mandatory = $true)]
[string]$Heading,
[Parameter(Mandatory = $true)]
[string[]]$Items
)
[void]$Builder.AppendLine("## $Heading")
[void]$Builder.AppendLine()
foreach ($item in $Items) {
[void]$Builder.AppendLine("- $item")
}
[void]$Builder.AppendLine()
}
function Select-PromptExamples {
param(
[Parameter(Mandatory = $true)]
[string[]]$Examples,
[int]$Limit = 1
)
$selected = [System.Collections.Generic.List[string]]::new()
if ($Examples.Count -eq 0) {
return @()
}
foreach ($example in $Examples) {
if (-not $example.Contains('Get-Content') -and -not $example.Contains('fixtures')) {
$selected.Add($example)
break
}
}
if ($selected.Count -eq 0) {
foreach ($example in $Examples) {
if (-not $example.Contains('Get-Content')) {
$selected.Add($example)
break
}
}
}
if ($selected.Count -eq 0) {
$selected.Add($Examples[0])
}
foreach ($example in $Examples | Select-Object -Skip 1) {
if (
$selected.Count -lt $Limit -and
$example -cne $selected[0] -and
-not $example.Contains('Get-Content') -and
-not $example.Contains('fixtures') -and
($example.Contains('|') -or $example.Contains('ConvertFrom-Json'))
) {
$selected.Add($example)
}
}
foreach ($example in $Examples | Select-Object -Skip 1) {
if ($selected.Count -ge $Limit) {
break
}
if (
-not $selected.Contains($example) -and
-not $example.Contains('Get-Content') -and
-not $example.Contains('fixtures')
) {
$selected.Add($example)
}
}
foreach ($example in $Examples | Select-Object -Skip 1) {
if ($selected.Count -ge $Limit) {
break
}
if (-not $selected.Contains($example)) {
$selected.Add($example)
}
}
return @($selected)
}
function Normalize-GeneratedExample {
param(
[Parameter(Mandatory = $true)]
[string]$Example
)
return ($Example -replace '(?i)(?:[A-Z]:)?[^''"\s|]*fixtures(?:[\\/][^''"\s|]+)+', '<PATH>')
}
function Test-ByteSequenceEqual {
param(
[Parameter(Mandatory = $true)]
[byte[]]$Left,
[Parameter(Mandatory = $true)]
[byte[]]$Right
)
if ($Left.Length -ne $Right.Length) {
return $false
}
for ($index = 0; $index -lt $Left.Length; $index += 1) {
if ($Left[$index] -ne $Right[$index]) {
return $false
}
}
return $true
}
function ConvertTo-LfText {
param(
[Parameter(Mandatory = $true)]
[string]$Content
)
return $Content.Replace("`r`n", "`n").Replace("`r", "`n")
}
function Write-Utf8FileWithRetry {
param(
[Parameter(Mandatory = $true)]
[string]$Path,
[Parameter(Mandatory = $true)]
[string]$Content,
[int]$Attempts = 20,
[int]$DelayMilliseconds = 100
)
for ($attempt = 1; $attempt -le $Attempts; $attempt++) {
try {
$encoding = [System.Text.UTF8Encoding]::new($false)
[System.IO.File]::WriteAllText($Path, (ConvertTo-LfText -Content $Content), $encoding)
return
}
catch {
if ($attempt -eq $Attempts) {
throw
}
Start-Sleep -Milliseconds $DelayMilliseconds
}
}
}
function Select-PromptUsage {
param(
[Parameter(Mandatory = $true)]
[hashtable]$ToolNotes,
[Parameter(Mandatory = $true)]
$Help,
[Parameter(Mandatory = $true)]
[string]$CommandName
)
if ($ToolNotes.ContainsKey('prompt_usage') -and -not [string]::IsNullOrWhiteSpace([string]$ToolNotes.prompt_usage)) {
return [string]$ToolNotes.prompt_usage
}
if ($Help.Usage.Count -gt 0) {
return (([string[]]$Help.Usage) -join ' | ')
}
return "$CommandName --help"
}
$workspaceRoot = Split-Path -Parent $PSScriptRoot
$profileName = Resolve-ToolboxProfileName -Configuration $Configuration
$binaryRoot = Join-Path $workspaceRoot (Join-Path 'target' $profileName)
$dependencyRoot = Join-Path $binaryRoot 'deps'
if (Test-Path -LiteralPath $dependencyRoot -PathType Container) {
$env:PATH = "$dependencyRoot$([System.IO.Path]::PathSeparator)$env:PATH"
}
$cargoPath = 'cargo'
$notes = Get-Content -Raw -LiteralPath $NotesPath | ConvertFrom-Json -AsHashtable
$missingBinary = $false
foreach ($commandName in Get-ToolboxCommandNames) {
if (-not (Test-Path -LiteralPath (Join-Path $binaryRoot "$commandName.exe"))) {
$missingBinary = $true
break
}
}
if ($missingBinary) {
if ($SkipBuild) {
throw "Required toolbox binaries are missing under $binaryRoot. Build profile $Configuration before running generate-ai-prompt.ps1 -SkipBuild."
}
Invoke-ToolboxBuild -CargoPath $cargoPath -Configuration $Configuration
}
$builder = [System.Text.StringBuilder]::new()
[void]$builder.AppendLine("# $($notes.title)")
[void]$builder.AppendLine(('Available tools: {0}' -f ((Get-ToolboxCommandNames) -join ', ')))
[void]$builder.AppendLine(('Rules: {0}' -f (([string[]]$notes.selection_rules) -join ' ')))
foreach ($commandName in Get-ToolboxCommandNames) {
$binaryPath = Join-Path $binaryRoot "$commandName.exe"
$toolNotes = $notes.tools[$commandName]
if ($null -eq $toolNotes) {
throw "Missing AI prompt notes for tool '$commandName'"
}
$helpText = (& $binaryPath '--help' | Out-String).TrimEnd()
$help = Get-ToolHelpSections -HelpText $helpText
$usageLine = Select-PromptUsage -ToolNotes $toolNotes -Help $help -CommandName $commandName
$examples = @(Select-PromptExamples -Examples ([string[]]$help.Examples))
$exampleLine = if ($toolNotes.ContainsKey('prompt_example') -and -not [string]::IsNullOrWhiteSpace([string]$toolNotes.prompt_example)) {
[string]$toolNotes.prompt_example
} elseif ($examples.Count -gt 0) {
$examples[0]
} else {
"$commandName --help"
}
$exampleLine = Normalize-GeneratedExample -Example $exampleLine
$guided = $toolNotes.guided_triage
if ($null -eq $guided) {
throw "Missing guided_triage notes for tool '$commandName'"
}
$nextActions = ([string[]]$guided.next_actions) -join ' | '
[void]$builder.AppendLine(('{0}: {1} Better than: {2} Usage: `{3}` Example: `{4}` Guided: answer={5} trust={6} next={7}' -f $commandName, $toolNotes.use_when, $toolNotes.why, $usageLine, $exampleLine, $guided.answer, $guided.trust, $nextActions))
}
$content = $builder.ToString()
if ($Check) {
if (-not (Test-Path -LiteralPath $OutputPath)) {
throw "Generated AI prompt is missing: $OutputPath"
}
$temporaryOutputPath = Join-Path ([System.IO.Path]::GetTempPath()) ([System.IO.Path]::GetRandomFileName())
try {
$encoding = [System.Text.UTF8Encoding]::new($false)
[System.IO.File]::WriteAllText($temporaryOutputPath, (ConvertTo-LfText -Content $content), $encoding)
$existingBytes = [System.IO.File]::ReadAllBytes((Resolve-Path -LiteralPath $OutputPath))
$generatedBytes = [System.IO.File]::ReadAllBytes($temporaryOutputPath)
if (-not (Test-ByteSequenceEqual -Left $existingBytes -Right $generatedBytes)) {
throw "Generated AI prompt is out of date. Run pwsh -NoProfile -File .\scripts\generate-ai-prompt.ps1"
}
}
finally {
Remove-Item -LiteralPath $temporaryOutputPath -Force -ErrorAction SilentlyContinue
}
Write-Host "AI prompt is up to date: $OutputPath"
return
}
$outputDirectory = Split-Path -Parent $OutputPath
New-Item -ItemType Directory -Force -Path $outputDirectory | Out-Null
Write-Utf8FileWithRetry -Path $OutputPath -Content $content
Write-Host "Generated AI prompt: $OutputPath"