mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-16 13:03:31 +08:00
The desktop product needs a repeatable local gate that can prove the core Coding Agent loop still works after changes, not only that unit tests pass. This adds a quality-gate runner with PR, baseline, and release modes, structured reports, explicit quarantine metadata, and fixture-based live baseline cases that can run across provider/model targets. Constraint: Existing check:pr and CI policy behavior must remain usable while the stronger baseline grows around it Constraint: Default PR gates must not require real model credentials or provider quota Rejected: Build a standalone QA platform first | too heavy before the baseline task shape is proven Rejected: Keep unstable server exclusions hardcoded in run-server-tests | hides quarantine policy from reports and future review Confidence: medium Scope-risk: moderate Directive: Expand baseline cases by adding focused fixtures and verifiers; do not make normal PR checks depend on live providers Tested: bun test scripts/quality-gate/*.test.ts scripts/quality-gate/baseline/*.test.ts Tested: bun run check:server Tested: bun run quality:gate --mode baseline --allow-live --provider-model 2944f963-ce75-45b7-bac1-6e4f57df0970:kimi-k2.6:volc-kimi-k2.6 --provider-model 9c78d3df-7fb5-44c7-8436-3a41c3a59231:MiniMax-M2.7-highspeed:minimax-m2.7 Not-tested: desktop UI browser smoke and native release mode in this commit
86 lines
2.5 KiB
TypeScript
86 lines
2.5 KiB
TypeScript
#!/usr/bin/env bun
|
|
|
|
import { runQualityGate } from './runner'
|
|
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]')
|
|
}
|
|
|
|
function readBaselineTargets(args: Map<string, Array<string | boolean>>): BaselineTarget[] {
|
|
const values = (args.get('--provider-model') ?? [])
|
|
.filter((value): value is string => typeof value === 'string')
|
|
.flatMap((value) => value.split(','))
|
|
.map((value) => value.trim())
|
|
.filter(Boolean)
|
|
|
|
return values.map((value) => {
|
|
const [providerId, modelId, rawLabel] = value.split(':')
|
|
if (!providerId || !modelId) {
|
|
throw new Error(`Invalid --provider-model value "${value}". Expected providerId:modelId[:label].`)
|
|
}
|
|
return {
|
|
providerId,
|
|
modelId,
|
|
label: rawLabel || `${providerId.slice(0, 8)}-${modelId}`,
|
|
}
|
|
})
|
|
}
|
|
|
|
const args = parseArgs(process.argv.slice(2))
|
|
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)
|
|
}
|