diff --git a/cli/bin/cli.js b/cli/bin/cli.js index 2f10dea69..16459e4e4 100755 --- a/cli/bin/cli.js +++ b/cli/bin/cli.js @@ -77,6 +77,12 @@ Compatibility: process.argv = [process.argv[0], process.argv[1], ...args]; const { detectCli } = await import('../engine/detect-antipatterns.mjs'); await detectCli(); + } else if (command === 'init') { + // The follow-up mistake from issue #472: `/impeccable init` belongs in an AI + // coding agent's chat, and a user who typed it into their shell is likely to + // retry it here as `npx impeccable init`. + console.error(`"init" is not a CLI command. Type /impeccable init in your AI coding agent's chat (Claude Code, Cursor, Codex, ...), not in this terminal.`); + process.exit(1); } else { // An unknown bareword: a mistyped command (or an old cached version run // against newer docs). Fail loudly instead of silently statting it as a path. diff --git a/cli/bin/commands/skills.mjs b/cli/bin/commands/skills.mjs index 741629027..03ce12454 100644 --- a/cli/bin/commands/skills.mjs +++ b/cli/bin/commands/skills.mjs @@ -1944,7 +1944,7 @@ async function install(flags) { reportProviderAgents(agentResults); if (hookTargets.length > 0) console.log(`Installed hooks into: ${hookTargets.join(', ')}`); - console.log('\nDone! Run /impeccable init in your AI harness to set up design context.\n'); + console.log('\nDone! Now type /impeccable init in your AI coding agent\'s chat (not in this terminal) to set up design context.\n'); } // ─── skills update ──────────────────────────────────────────────────────────── diff --git a/tests/skills-cli.test.js b/tests/skills-cli.test.js index a93423caf..c51ef7680 100644 --- a/tests/skills-cli.test.js +++ b/tests/skills-cli.test.js @@ -719,6 +719,64 @@ describe('skills install/update: local universal bundle e2e', () => { rmSync(home, { recursive: true, force: true }); }, 15000); + test('install completion says /impeccable init runs in the agent chat, not the terminal (#472)', () => { + const tmp = mkdtempSync(join(tmpdir(), 'imp-test-install-472-msg-')); + const home = mkdtempSync(join(tmpdir(), 'imp-home-install-472-msg-')); + execSync('git init', { cwd: tmp }); + const bundleRoot = createFakeUniversalBundle(tmp, ['.claude']); + + const output = run('install -y --providers=claude --no-hooks', { + cwd: tmp, + env: { ...process.env, HOME: home, IMPECCABLE_BUNDLE_PATH: bundleRoot }, + }); + + expect(output).toContain("type /impeccable init in your AI coding agent's chat (not in this terminal)"); + + rmSync(tmp, { recursive: true, force: true }); + rmSync(home, { recursive: true, force: true }); + }, 15000); + + test('`impeccable init` in the shell points at the agent chat instead of "Unknown command" (#472)', () => { + const tmp = mkdtempSync(join(tmpdir(), 'imp-test-init-472-')); + + let error; + try { + run('init', { cwd: tmp, stdio: 'pipe' }); + } catch (e) { + error = e; + } + + expect(error).toBeDefined(); + expect(error.status).toBe(1); + const stderr = String(error.stderr); + expect(stderr).toContain("Type /impeccable init in your AI coding agent's chat"); + expect(stderr).not.toContain('Unknown command'); + + rmSync(tmp, { recursive: true, force: true }); + }, 15000); + + test('a real path named init still routes to detect, not the #472 guidance', () => { + const tmp = mkdtempSync(join(tmpdir(), 'imp-test-init-path-472-')); + mkdirSync(join(tmp, 'init'), { recursive: true }); + writeFileSync(join(tmp, 'init', 'page.html'), '
hello
\n'); + + // Detect exits 0 on a clean scan and 2 when findings surface; either way it + // must be the detector answering, not the init redirect. --json makes that + // positive: the detector always prints a JSON findings array. + let output = ''; + try { + output = run('init --json', { cwd: tmp, stdio: 'pipe' }); + } catch (e) { + output = `${e.stdout || ''}${e.stderr || ''}`; + } + + expect(output.trim().startsWith('[')).toBe(true); + expect(output).not.toContain('is not a CLI command'); + expect(output).not.toContain('Unknown command'); + + rmSync(tmp, { recursive: true, force: true }); + }, 60000); + test('formats detected harnesses as concise source-to-target rows', () => { const tmp = mkdtempSync(join(tmpdir(), 'imp-test-detect-lines-')); const home = mkdtempSync(join(tmpdir(), 'imp-home-detect-lines-'));