程序员阿江(Relakkes) 5f59c693c4 Let contributors choose quality-gate providers
The live baseline previously accepted provider UUIDs, which made the gate hard to run on another contributor's machine. Add a local provider listing command and resolve quality-gate targets from stable provider-name selectors while keeping UUIDs and current runtime support.

Constraint: Provider configuration is local machine state under CLAUDE_CONFIG_DIR and must not expose API keys.

Rejected: Require contributors to inspect providers.json manually | too error-prone and leaks implementation detail into the workflow

Confidence: high

Scope-risk: narrow

Directive: Keep live-provider baseline selection copyable from quality:providers before adding more live test lanes.

Tested: bun test scripts/quality-gate/providerTargets.test.ts

Tested: bun test scripts/quality-gate/*.test.ts scripts/quality-gate/baseline/*.test.ts

Tested: bun run quality:providers

Tested: bun run quality:gate --mode baseline --dry-run --provider-model codingplan:main --provider-model minimax:main

Tested: bun run quality:gate --mode baseline --dry-run --provider-model custom:haiku

Tested: bun run quality:gate --mode pr --dry-run

Tested: bun run check:server
2026-05-02 15:35:59 +08:00

80 lines
2.3 KiB
TypeScript

#!/usr/bin/env bun
import { runQualityGate } from './runner'
import { formatProviderTargets, loadProviderIndex, parseBaselineTargetValues } from './providerTargets'
import type { BaselineTarget, QualityGateMode } from './types'
function parseArgs(argv: string[]) {
const args = new Map<string, Array<string | boolean>>()
for (let index = 0; index < argv.length; index += 1) {
const arg = argv[index]
if (!arg.startsWith('--')) {
continue
}
const next = argv[index + 1]
if (next && !next.startsWith('--')) {
args.set(arg, [...(args.get(arg) ?? []), next])
index += 1
} else {
args.set(arg, [...(args.get(arg) ?? []), true])
}
}
return args
}
function firstArg(args: Map<string, Array<string | boolean>>, name: string) {
return args.get(name)?.[0]
}
function hasFlag(args: Map<string, Array<string | boolean>>, name: string) {
return args.has(name)
}
function readMode(value: string | boolean | undefined): QualityGateMode {
if (value === 'pr' || value === 'baseline' || value === 'release') {
return value
}
throw new Error('Usage: bun run quality:gate --mode <pr|baseline|release> [--dry-run] [--allow-live] [--provider-model provider:model[:label]]')
}
function readBaselineTargets(args: Map<string, Array<string | boolean>>): BaselineTarget[] {
const values = (args.get('--provider-model') ?? [])
.filter((value): value is string => typeof value === 'string')
return parseBaselineTargetValues(values)
}
const args = parseArgs(process.argv.slice(2))
if (hasFlag(args, '--list-providers')) {
console.log(formatProviderTargets(loadProviderIndex()))
process.exit(0)
}
const mode = readMode(firstArg(args, '--mode'))
const dryRun = hasFlag(args, '--dry-run')
const allowLive = hasFlag(args, '--allow-live')
const artifactsDir = typeof firstArg(args, '--artifacts-dir') === 'string'
? String(firstArg(args, '--artifacts-dir'))
: undefined
const baselineTargets = readBaselineTargets(args)
const { report, outputDir } = await runQualityGate({
mode,
dryRun,
allowLive,
baselineTargets,
rootDir: process.cwd(),
artifactsDir,
})
console.log(`Quality gate report: ${outputDir}/report.md`)
console.log(`Summary: passed=${report.summary.passed} failed=${report.summary.failed} skipped=${report.summary.skipped}`)
if (report.summary.failed > 0) {
process.exit(1)
}