mirror of
https://github.com/proffesor-for-testing/agentic-qe.git
synced 2026-09-19 08:45:47 +08:00
2253d05618
Remove the nested v3/ subdirectory by moving all active source code, tests, configs, and assets directly to the project root. Archive v2/ (dead code, 847MB) to v2-archive.tar.gz. Key changes: - Move v3/src/ → src/, v3/tests/ → tests/, v3/packages/ → packages/ - Move v3/tsconfig.json, v3/vitest.config.ts to root - Merge v3/package.json into root package.json (ESM, scripts, deps, exports) - Merge v3/scripts/ into scripts/, v3/implementation/ into docs/implementation/ - Update all CI workflows to remove "cd v3 &&" and v3/ path prefixes - Fix all init installer path resolution (3-level-up → 2-level-up) - Fix hooks to use "node ./dist/cli/bundle.js" instead of bare "aqe" - Update settings.json ADR/DDD directories to docs/implementation/ - Fix helper scripts (statusline, adr-compliance, ddd-tracker, guidance-hooks) - Update 58 agent definitions: aqe/v3/ → aqe/ memory namespaces - Update 15 guidance shards: v3/src/ → src/ path references - Fix sync interfaces: remove v3/ and v2/ path references - Fix test path calculations (process.cwd() + '..' no longer needed) - Fix flaky perf test threshold (500ms → 1000ms for CI environments) - Fix complexity-analyzer test to match ADR-051 keyword-based eligibility Build passes, 17,901 tests pass, CLI and MCP bundles verified. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
307 lines
10 KiB
TypeScript
307 lines
10 KiB
TypeScript
#!/usr/bin/env npx tsx
|
|
/**
|
|
* SFDIPOT Assessment Validator
|
|
*
|
|
* Validates that Product Factors assessment HTML output passes quality gates.
|
|
*
|
|
* PHILOSOPHY: Priority percentages are INFORMATIONAL, not gates.
|
|
* Domain experts should determine priorities, not arbitrary percentages.
|
|
*
|
|
* Usage:
|
|
* npx tsx scripts/validate-sfdipot-assessment.ts <path-to-html>
|
|
* npx tsx scripts/validate-sfdipot-assessment.ts --all # Validate all assessments
|
|
*
|
|
* Exit codes:
|
|
* 0 = All gates pass
|
|
* 1 = One or more gates failed
|
|
*/
|
|
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
|
|
interface ValidationResult {
|
|
gate: string;
|
|
passed: boolean;
|
|
expected: string;
|
|
actual: string;
|
|
details?: string;
|
|
isHard: boolean; // Hard gates fail validation; soft gates are informational
|
|
}
|
|
|
|
interface AssessmentMetrics {
|
|
totalTests: number;
|
|
p0Count: number;
|
|
p1Count: number;
|
|
p2Count: number;
|
|
p3Count: number;
|
|
humanCount: number;
|
|
verifyCount: number;
|
|
humanWithReason: number;
|
|
humanWithExplore: number;
|
|
}
|
|
|
|
function extractMetrics(content: string): AssessmentMetrics {
|
|
// Count test IDs (TC-XXXX pattern)
|
|
const testIdMatches = content.match(/TC-[A-Z]{4}-[A-Z0-9]{8}/g) || [];
|
|
const totalTests = testIdMatches.length;
|
|
|
|
// Count priorities
|
|
const p0Count = (content.match(/priority-p0/g) || []).length;
|
|
const p1Count = (content.match(/priority-p1/g) || []).length;
|
|
const p2Count = (content.match(/priority-p2/g) || []).length;
|
|
const p3Count = (content.match(/priority-p3/g) || []).length;
|
|
|
|
// Count human exploration tests
|
|
const humanCount = (content.match(/automation-human/g) || []).length;
|
|
|
|
// Count "Verify" patterns in test ideas (case insensitive)
|
|
// Match "Verify" at the start of a test idea (after <td>)
|
|
const verifyMatches = content.match(/<td>Verify\s/gi) || [];
|
|
const verifyCount = verifyMatches.length;
|
|
|
|
// Count human tests with proper "Why Human Essential" reasoning
|
|
const humanWithReason = (content.match(/Why Human Essential:/g) || []).length;
|
|
|
|
// Count human tests with "Explore" in test idea column
|
|
const humanWithExplore = (content.match(/<td>Explore\s[^<]*;\s*assess\s/gi) || []).length;
|
|
|
|
return {
|
|
totalTests,
|
|
p0Count,
|
|
p1Count,
|
|
p2Count,
|
|
p3Count,
|
|
humanCount,
|
|
verifyCount,
|
|
humanWithReason,
|
|
humanWithExplore,
|
|
};
|
|
}
|
|
|
|
function validateAssessment(filePath: string): { passed: boolean; results: ValidationResult[] } {
|
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
const metrics = extractMetrics(content);
|
|
const results: ValidationResult[] = [];
|
|
|
|
// ============================================
|
|
// HARD GATES - These actually fail validation
|
|
// ============================================
|
|
|
|
// Gate 7: NO "Verify X" patterns (HARD REQUIREMENT)
|
|
const gate7 = metrics.verifyCount === 0;
|
|
results.push({
|
|
gate: 'Gate 7: NO "Verify X" patterns',
|
|
passed: gate7,
|
|
expected: '0',
|
|
actual: String(metrics.verifyCount),
|
|
details: gate7 ? undefined : `Found ${metrics.verifyCount} test ideas starting with "Verify"`,
|
|
isHard: true,
|
|
});
|
|
|
|
// Gate 5: Human >= 10% (HARD REQUIREMENT)
|
|
const total = metrics.totalTests || 1;
|
|
const humanPct = (metrics.humanCount / total) * 100;
|
|
const gate5 = humanPct >= 10;
|
|
results.push({
|
|
gate: 'Gate 5: Human >= 10%',
|
|
passed: gate5,
|
|
expected: '≥10%',
|
|
actual: `${humanPct.toFixed(1)}% (${metrics.humanCount}/${total})`,
|
|
details: gate5 ? undefined : `Human tests are ${(10 - humanPct).toFixed(1)}% under minimum`,
|
|
isHard: true,
|
|
});
|
|
|
|
// Gate 10a: Human tests have reasoning (HARD)
|
|
const gate10a = metrics.humanCount === 0 || metrics.humanWithReason >= metrics.humanCount * 0.9;
|
|
results.push({
|
|
gate: 'Gate 10a: Human tests have "Why Human Essential" reasoning',
|
|
passed: gate10a,
|
|
expected: `${metrics.humanCount} (all human tests)`,
|
|
actual: `${metrics.humanWithReason}`,
|
|
details: gate10a ? undefined : `${metrics.humanCount - metrics.humanWithReason} human tests missing reasoning`,
|
|
isHard: true,
|
|
});
|
|
|
|
// Gate 10b: Human tests use "Explore X; assess Y" format (HARD)
|
|
const gate10b = metrics.humanCount === 0 || metrics.humanWithExplore >= metrics.humanCount * 0.8;
|
|
results.push({
|
|
gate: 'Gate 10b: Human test ideas use "Explore X; assess Y" format',
|
|
passed: gate10b,
|
|
expected: `≥${Math.floor(metrics.humanCount * 0.8)} (80% of human tests)`,
|
|
actual: `${metrics.humanWithExplore}`,
|
|
details: gate10b ? undefined : `Only ${metrics.humanWithExplore} of ${metrics.humanCount} human tests use proper format`,
|
|
isHard: true,
|
|
});
|
|
|
|
// Gate 6: Minimum test count (HARD)
|
|
const gate6 = metrics.totalTests >= 50;
|
|
results.push({
|
|
gate: 'Gate 6: Minimum 50 test ideas',
|
|
passed: gate6,
|
|
expected: '≥50',
|
|
actual: `${metrics.totalTests}`,
|
|
details: gate6 ? undefined : `Only ${metrics.totalTests} tests generated`,
|
|
isHard: true,
|
|
});
|
|
|
|
// ============================================
|
|
// SOFT GATES - Informational only, for SME review
|
|
// Priority distribution is domain-specific, not a universal rule
|
|
// ============================================
|
|
|
|
const p0Pct = (metrics.p0Count / total) * 100;
|
|
const p1Pct = (metrics.p1Count / total) * 100;
|
|
const p2Pct = (metrics.p2Count / total) * 100;
|
|
const p3Pct = (metrics.p3Count / total) * 100;
|
|
|
|
// Info: P0 distribution (NOT a hard gate)
|
|
results.push({
|
|
gate: 'Info: P0 (Critical) distribution',
|
|
passed: true, // Always passes - informational only
|
|
expected: 'SME to review',
|
|
actual: `${p0Pct.toFixed(1)}% (${metrics.p0Count}/${total})`,
|
|
details: 'Domain expert should validate P0 assignments based on business context',
|
|
isHard: false,
|
|
});
|
|
|
|
// Info: P1 distribution (NOT a hard gate)
|
|
results.push({
|
|
gate: 'Info: P1 (High) distribution',
|
|
passed: true,
|
|
expected: 'SME to review',
|
|
actual: `${p1Pct.toFixed(1)}% (${metrics.p1Count}/${total})`,
|
|
details: 'Domain expert should validate P1 assignments based on business context',
|
|
isHard: false,
|
|
});
|
|
|
|
// Info: P2 distribution (NOT a hard gate)
|
|
results.push({
|
|
gate: 'Info: P2 (Medium) distribution',
|
|
passed: true,
|
|
expected: 'SME to review',
|
|
actual: `${p2Pct.toFixed(1)}% (${metrics.p2Count}/${total})`,
|
|
details: 'Domain expert should validate P2 assignments based on business context',
|
|
isHard: false,
|
|
});
|
|
|
|
// Info: P3 distribution (NOT a hard gate)
|
|
results.push({
|
|
gate: 'Info: P3 (Low) distribution',
|
|
passed: true,
|
|
expected: 'SME to review',
|
|
actual: `${p3Pct.toFixed(1)}% (${metrics.p3Count}/${total})`,
|
|
details: 'Domain expert should validate P3 assignments based on business context',
|
|
isHard: false,
|
|
});
|
|
|
|
// Only hard gates determine pass/fail
|
|
const allPassed = results.filter(r => r.isHard).every(r => r.passed);
|
|
return { passed: allPassed, results };
|
|
}
|
|
|
|
function printResults(filePath: string, validation: { passed: boolean; results: ValidationResult[] }): void {
|
|
const fileName = path.basename(filePath);
|
|
|
|
console.log('\n' + '='.repeat(70));
|
|
console.log(`SFDIPOT Assessment Validation: ${fileName}`);
|
|
console.log('='.repeat(70));
|
|
|
|
// Print hard gates first
|
|
console.log('\n--- HARD GATES (Must Pass) ---');
|
|
for (const result of validation.results.filter(r => r.isHard)) {
|
|
const status = result.passed ? '✅ PASS' : '❌ FAIL';
|
|
console.log(`\n${status} | ${result.gate}`);
|
|
console.log(` Expected: ${result.expected}`);
|
|
console.log(` Actual: ${result.actual}`);
|
|
if (result.details && !result.passed) {
|
|
console.log(` Details: ${result.details}`);
|
|
}
|
|
}
|
|
|
|
// Print soft gates (informational)
|
|
console.log('\n--- PRIORITY DISTRIBUTION (For SME Review) ---');
|
|
for (const result of validation.results.filter(r => !r.isHard)) {
|
|
console.log(`\n📊 INFO | ${result.gate}`);
|
|
console.log(` Value: ${result.actual}`);
|
|
console.log(` Note: ${result.details}`);
|
|
}
|
|
|
|
console.log('\n' + '-'.repeat(70));
|
|
const hardGates = validation.results.filter(r => r.isHard);
|
|
const passCount = hardGates.filter(r => r.passed).length;
|
|
const totalGates = hardGates.length;
|
|
|
|
if (validation.passed) {
|
|
console.log(`✅ ALL HARD GATES PASSED (${passCount}/${totalGates})`);
|
|
console.log(`⚠️ Priority distribution requires Domain Expert/SME review`);
|
|
} else {
|
|
console.log(`❌ VALIDATION FAILED (${passCount}/${totalGates} hard gates passed)`);
|
|
const failedGates = hardGates.filter(r => !r.passed).map(r => r.gate);
|
|
console.log(` Failed gates: ${failedGates.join(', ')}`);
|
|
}
|
|
console.log('-'.repeat(70));
|
|
}
|
|
|
|
async function main(): Promise<void> {
|
|
const args = process.argv.slice(2);
|
|
|
|
if (args.length === 0) {
|
|
console.error('Usage: npx tsx scripts/validate-sfdipot-assessment.ts <path-to-html>');
|
|
console.error(' npx tsx scripts/validate-sfdipot-assessment.ts --all');
|
|
process.exit(1);
|
|
}
|
|
|
|
let filesToValidate: string[] = [];
|
|
|
|
if (args[0] === '--all') {
|
|
// Validate all assessment files
|
|
const assessmentDir = '.agentic-qe/product-factors-assessments';
|
|
if (fs.existsSync(assessmentDir)) {
|
|
const files = fs.readdirSync(assessmentDir)
|
|
.filter(f => f.endsWith('.html'))
|
|
.map(f => path.join(assessmentDir, f));
|
|
filesToValidate = files;
|
|
}
|
|
} else {
|
|
filesToValidate = args.filter(f => fs.existsSync(f));
|
|
}
|
|
|
|
if (filesToValidate.length === 0) {
|
|
console.error('No valid files found to validate');
|
|
process.exit(1);
|
|
}
|
|
|
|
let allPassed = true;
|
|
const summary: { file: string; passed: boolean }[] = [];
|
|
|
|
for (const filePath of filesToValidate) {
|
|
const validation = validateAssessment(filePath);
|
|
printResults(filePath, validation);
|
|
summary.push({ file: path.basename(filePath), passed: validation.passed });
|
|
if (!validation.passed) {
|
|
allPassed = false;
|
|
}
|
|
}
|
|
|
|
// Print summary if multiple files
|
|
if (filesToValidate.length > 1) {
|
|
console.log('\n' + '='.repeat(70));
|
|
console.log('VALIDATION SUMMARY');
|
|
console.log('='.repeat(70));
|
|
for (const s of summary) {
|
|
const status = s.passed ? '✅' : '❌';
|
|
console.log(`${status} ${s.file}`);
|
|
}
|
|
console.log('-'.repeat(70));
|
|
const passedCount = summary.filter(s => s.passed).length;
|
|
console.log(`Total: ${passedCount}/${summary.length} assessments passed`);
|
|
}
|
|
|
|
process.exit(allPassed ? 0 : 1);
|
|
}
|
|
|
|
main().catch(err => {
|
|
console.error('Validation error:', err);
|
|
process.exit(1);
|
|
});
|