|
| 1 | +#!/usr/bin/env node |
| 2 | +/** |
| 3 | + * Regression: the bundled CC template must always carry the interactive-only |
| 4 | + * tools (AskUserQuestion, EnterPlanMode, ExitPlanMode). |
| 5 | + * |
| 6 | + * Bug (v4.8.93): the bake captures CC headlessly (`claude --print -p hi`, see |
| 7 | + * live-fingerprint.ts). CC v2.1.187 stopped advertising the plan-mode / |
| 8 | + * clarification tools in `--print` mode, so an auto-rebake dropped them from |
| 9 | + * src/cc-template-data.json. Because buildCCRequest advertises only the |
| 10 | + * INTERSECTION of the client's declared tools and the bundled template, a full |
| 11 | + * CC client that declared AskUserQuestion no longer had it advertised — the |
| 12 | + * "advertise-respects-client" contract broke (tool-advertise-respects-client.mjs |
| 13 | + * caught it). These tools are not platform-scoped, so they must remain in the |
| 14 | + * template (and in CC_TOOL_DEFINITIONS) on EVERY host, the same way the bake |
| 15 | + * preserves win32-only PowerShell/Glob/Grep from the previous bundle. |
| 16 | + * |
| 17 | + * Fix: scripts/capture-and-bake.mjs preserves INTERACTIVE_ONLY_TOOLS from the |
| 18 | + * previous bundle on every bake; this test guards the resulting invariant so a |
| 19 | + * future headless bake (or manual edit) can't silently re-drop them. |
| 20 | + * |
| 21 | + * In-process — no proxy / OAuth / upstream. |
| 22 | + */ |
| 23 | + |
| 24 | +import { CC_TEMPLATE, CC_TOOL_DEFINITIONS, INTERACTIVE_ONLY_TOOLS } from '../dist/cc-template.js'; |
| 25 | + |
| 26 | +let pass = 0, fail = 0; |
| 27 | +function check(label, cond) { |
| 28 | + if (cond) { pass++; console.log(` ✅ ${label}`); } |
| 29 | + else { fail++; console.log(` ❌ ${label}`); } |
| 30 | +} |
| 31 | +function header(name) { console.log(`\n${'='.repeat(70)}\n ${name}\n${'='.repeat(70)}`); } |
| 32 | + |
| 33 | +header('bundled template carries every interactive-only tool'); |
| 34 | +{ |
| 35 | + check('INTERACTIVE_ONLY_TOOLS is non-empty', INTERACTIVE_ONLY_TOOLS.size > 0); |
| 36 | + |
| 37 | + const templateNames = new Set((CC_TEMPLATE.tools || []).map((t) => t.name)); |
| 38 | + for (const name of INTERACTIVE_ONLY_TOOLS) { |
| 39 | + check(`template tools[] contains ${name}`, templateNames.has(name)); |
| 40 | + const def = (CC_TEMPLATE.tools || []).find((t) => t.name === name); |
| 41 | + check(`${name} has a non-empty description`, |
| 42 | + !!def && typeof def.description === 'string' && def.description.length > 0); |
| 43 | + check(`${name} has an input_schema`, !!def && typeof def.input_schema === 'object' && def.input_schema !== null); |
| 44 | + } |
| 45 | +} |
| 46 | + |
| 47 | +header('interactive-only tools are NOT platform-filtered (present on every host)'); |
| 48 | +{ |
| 49 | + // CC_TOOL_DEFINITIONS is the current platform's filtered view of the bundle. |
| 50 | + // Interactive tools must survive that filter everywhere — unlike PowerShell/ |
| 51 | + // Glob/Grep, they are not registered in PLATFORM_ONLY_TOOLS. |
| 52 | + const ccNames = new Set(CC_TOOL_DEFINITIONS.map((t) => t.name)); |
| 53 | + for (const name of INTERACTIVE_ONLY_TOOLS) { |
| 54 | + check(`CC_TOOL_DEFINITIONS (platform ${process.platform}) contains ${name}`, ccNames.has(name)); |
| 55 | + } |
| 56 | +} |
| 57 | + |
| 58 | +console.log(`\ntemplate-interactive-tools: ${pass} passed, ${fail} failed`); |
| 59 | +if (fail > 0) process.exit(1); |
0 commit comments