fix(ambient): remove allowed-tools restriction so skills actually load#152
fix(ambient): remove allowed-tools restriction so skills actually load#152
Conversation
The ambient-router skill had `allowed-tools: Read, Grep, Glob` which prevented Claude from invoking the Skill tool to load skills for GUIDED/ORCHESTRATED classifications. Removes the restriction (router needs unrestricted access as the main-session orchestrator), adds an override note so loaded skills' allowed-tools don't restrict the main session, strengthens the hook preamble, and adds integration test helpers for skill loading verification.
The integration tests (claude -p) can't reliably trigger ambient classification since UserPromptSubmit hooks don't fire in non-interactive mode. Added 11 unit tests for the regex helpers (hasClassification, extractIntent, extractDepth, hasSkillLoading, extractLoadedSkills) that verify the parsing logic without API calls. Updated integration test docs to explain the -p mode limitation.
Adds --dry-run option that shows what would be removed (skills, agents, commands, extras) without performing any destructive operations. Works for both full and selective uninstalls. Includes formatDryRunPlan pure function with deduplication and 4 unit tests.
| }); | ||
|
|
||
| // Skill loading verification — GUIDED should show "Loading:" marker | ||
| it('loads skills for GUIDED classification', () => { |
There was a problem hiding this comment.
Issue: Flaky Integration Tests (85% confidence)
This test asserts on non-deterministic LLM classification in -p mode. The file acknowledges classification may not appear, yet the test uses hard expect(...).toBe(true) without conditional fallback.
Fix: Use it.skip() or conditional assertions:
if (hasClassification(output)) {
expect(hasSkillLoading(output)).toBe(true);
}Claude Code Review
| }); | ||
|
|
||
| // Skill loading verification — ORCHESTRATED should show "Loading:" marker | ||
| it('loads skills for ORCHESTRATED classification', () => { |
There was a problem hiding this comment.
Issue: Flaky Integration Tests (85% confidence)
Same as GUIDED test above — this assertion may fail in -p mode due to unreliable classification.
Fix: Skip or use conditional assertions (see line 73 comment).
Claude Code Review
| } | ||
| } | ||
|
|
||
| const AMBIENT_PREAMBLE = |
There was a problem hiding this comment.
Issue: Duplicated Magic String (82% confidence)
The AMBIENT_PREAMBLE is defined identically here and in scripts/hooks/ambient-prompt:42. These can drift if one is updated without the other.
Fix: Add a test that verifies the preamble substring exists in the hook script:
it('preamble matches ambient-prompt hook script', () => {
const hookScript = readFileSync(
path.join(__dirname, '../../scripts/hooks/ambient-prompt'),
'utf-8',
);
expect(hookScript).toContain(AMBIENT_PREAMBLE);
});Claude Code Review
| if (!isSelectiveUninstall) { | ||
| const docsDir = path.join(process.cwd(), '.docs'); | ||
| const memoryDir = path.join(process.cwd(), '.memory'); | ||
| try { await fs.access(docsDir); extras.push('.docs/'); } catch { /* noop */ } |
There was a problem hiding this comment.
Issue: Sequential I/O Performance (82% confidence)
The two fs.access checks for .docs/ and .memory/ are awaited sequentially. These are independent filesystem operations that could run in parallel.
Fix: Use Promise.allSettled:
const [docsResult, memoryResult] = await Promise.allSettled([
fs.access(docsDir),
fs.access(memoryDir),
]);
if (docsResult.status === 'fulfilled') extras.push('.docs/');
if (memoryResult.status === 'fulfilled') extras.push('.memory/');Claude Code Review
| }); | ||
| }); | ||
|
|
||
| describe('formatDryRunPlan', () => { |
There was a problem hiding this comment.
Issue: Untested Deduplication Logic (82% confidence)
The formatDryRunPlan function explicitly deduplicates inputs with [...new Set(assets.skills)], but no test verifies this behavior. If the dedup were removed, no test would catch it.
Fix: Add test case:
it('deduplicates repeated asset names', () => {
const plan = formatDryRunPlan({
skills: ['core-patterns', 'core-patterns', 'typescript'],
agents: ['coder', 'coder'],
commands: ['/implement'],
});
expect(plan).toContain('Skills (2)');
expect(plan).toContain('Agents (1)');
});Claude Code Review
Code Review SummaryBranch: fix/ambient-skill-loading → main Inline Comments Created (6)
Additional Findings (60-79% confidence, see details below)Documentation Issues:
Architecture/Design:
Test Coverage:
Performance/Robustness:
Security (Defense-in-Depth):
RecommendationAPPROVED WITH CONDITIONS Merge after addressing:
All three commits are atomic and well-tested. No blocking regressions. The ambient skill-loading fix is correct and necessary. Detailed Review Reports: |
- Skip flaky GUIDED/ORCHESTRATED integration tests (non-deterministic in -p mode) - Add security comment to ambient-router SKILL.md (no allowed-tools is deliberate) - Add --dry-run, --plugin, --verbose to README uninstall options table - Add CHANGELOG entries for --dry-run flag and ambient skill loading fix - Add SYNC cross-reference comments between ambient-prompt and helpers.ts - Add preamble drift-detection test (shell script vs helpers.ts) - Clarify dry-run scope log with "(dry-run shows all detected scopes)" - Add formatDryRunPlan deduplication test - Add EXPLORE/CHAT intent coverage to extractIntent tests - Replace echo with printf in ambient-prompt for POSIX correctness - Replace execSync with execFileSync to avoid shell injection - Fix CLAUDE.md wording: "has no" → "omits ... entirely"
Summary
ambient-routerskill hadallowed-tools: Read, Grep, Globwhich prevented Claude from invoking theSkilltool — skills were classified but never loadedallowed-toolsfrom ambient-router (unrestricted access as main-session orchestrator)<IMPORTANT>block so loaded skills'allowed-toolsdon't restrict main session capabilitieshasSkillLoading()andextractLoadedSkills()integration test helpers + 2 verification testsTest plan
npm run build— 35 skills distributed across 17 pluginsnpm test— 250/250 passnpm run test:integration— verify GUIDED/ORCHESTRATED prompts show "Loading:" marker (requires claude CLI)