()
| 149 | // ── Main ────────────────────────────────────────────────────────────────────── |
| 150 | |
| 151 | function main() { |
| 152 | if (!skillFilter) { |
| 153 | console.error('Usage: node scripts/noop-ablate.js --skill <name> [--scenario <filter>] [--dry-run]'); |
| 154 | process.exit(1); |
| 155 | } |
| 156 | |
| 157 | const skillFile = path.join(SKILLS_DIR, skillFilter, 'SKILL.md'); |
| 158 | if (!fs.existsSync(skillFile)) { |
| 159 | console.error(`No SKILL.md for "${skillFilter}".`); |
| 160 | process.exit(1); |
| 161 | } |
| 162 | |
| 163 | const original = fs.readFileSync(skillFile, 'utf8'); |
| 164 | const rawCandidates = detectNoOps(original).map(c => ({ ...c, skill: skillFilter })); |
| 165 | const verdicts = loadLatestVerdicts(); |
| 166 | const edits = rawCandidates.map(c => resolveEdit(c, verdicts)).filter(e => e.action !== 'keep'); |
| 167 | |
| 168 | let scenarios = discoverScenarios().filter(s => s.skill === skillFilter && !s.skipExecute); |
| 169 | if (scenarioFilter) scenarios = scenarios.filter(s => s.name.includes(scenarioFilter)); |
| 170 | |
| 171 | if (rawCandidates.length === 0) { |
| 172 | console.log(`\nNo Tier 1 candidates in ${skillFilter}. Nothing to ablate.\n`); |
| 173 | process.exit(0); |
| 174 | } |
| 175 | if (edits.length === 0) { |
| 176 | console.log(`\nAll candidates in ${skillFilter} were judged "keep". Nothing to ablate.\n`); |
| 177 | process.exit(0); |
| 178 | } |
| 179 | if (scenarios.length === 0) { |
| 180 | console.log(`\n${skillFilter} has no runnable benchmark scenarios - cannot ablate empirically.`); |
| 181 | console.log('Add a scenario under skills/' + skillFilter + '/__benchmarks__/ to enable Tier 3.\n'); |
| 182 | process.exit(0); |
| 183 | } |
| 184 | |
| 185 | // ── Run-count math (the cost story) ─────────────────────────────────────── |
| 186 | const S = scenarios.length; |
| 187 | const K = edits.length; |
| 188 | const bisectWorst = K > 1 ? S * Math.ceil(Math.log2(K)) * 2 : 0; |
| 189 | const planCalls = S /*baseline*/ + S /*batch*/ + bisectWorst; |
| 190 | |
| 191 | console.log('\nNo-op Ablation (Tier 3, empirical)\n' + '='.repeat(44)); |
| 192 | console.log(`Skill: ${skillFilter}`); |
| 193 | console.log(`Candidates: ${K} (${edits.map(e => `L${e.lineNo}:${e.action}`).join(', ')})`); |
| 194 | console.log(`Scenarios: ${S} (${scenarios.map(s => s.name).join(', ')})`); |
| 195 | console.log(`Plan: ${S} baseline + ${S} batched ablation` + (K > 1 ? ` + ≤${bisectWorst} bisect` : '') + ` = ≤${planCalls} model call(s)`); |
| 196 | console.log('Method: compare assertion outcome vectors; same vector => behavior unchanged => no-op confirmed.'); |
| 197 | |
| 198 | if (DRY_RUN) { |
| 199 | console.log('\n[dry-run] No model calls made. Re-run without --dry-run to execute.\n'); |
| 200 | process.exit(0); |
| 201 | } |
| 202 | |
| 203 | const cli = findClaudeCLI(); |
| 204 | if (!cli) { |
| 205 | console.error('\nclaude CLI not found - cannot run ablation. Use --dry-run to see the plan.\n'); |
| 206 | process.exit(1); |
| 207 | } |
| 208 |
no test coverage detected