Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .aiox-core/install-manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
# - File types for categorization
#
version: 5.0.3
generated_at: "2026-03-11T15:04:09.395Z"
generated_at: "2026-03-13T03:34:29.010Z"
generator: scripts/generate-install-manifest.js
file_count: 1090
files:
Expand Down
33 changes: 26 additions & 7 deletions packages/installer/src/wizard/ide-config-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -652,9 +652,29 @@ function showSuccessSummary(result) {
console.log(' 4. Use * commands to interact with agents\n');
}

/**
* Hooks disponíveis para todos os tiers (free + pro)
* @type {string[]}
*/
const HOOKS_FREE = [
'synapse-engine.cjs',
'synapse-wrapper.cjs',
'precompact-wrapper.cjs',
'README.md',
];
Comment on lines +659 to +664
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

code-intel-pretool.cjs is never copied, so its mapped hook can’t be installed.

HOOK_EVENT_MAP still defines code-intel-pretool.cjs, but the new tier allowlists omit it entirely. That silently drops PreToolUse coverage in installed projects.

💡 Proposed fix
 const HOOKS_FREE = [
   'synapse-engine.cjs',
+  'code-intel-pretool.cjs',
   'synapse-wrapper.cjs',
   'precompact-wrapper.cjs',
   'README.md',
 ];

Also applies to: 670-672

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/installer/src/wizard/ide-config-generator.js` around lines 659 -
664, HOOK_EVENT_MAP still references "code-intel-pretool.cjs" but the new
HOOKS_FREE/tier allowlists removed it, so the file never gets copied and its
hook can't be installed; update the arrays used to determine which hook files
are copied (the HOOKS_FREE constant and the tier allowlist arrays referenced
around the same area) to include "code-intel-pretool.cjs" (or alternatively
ensure the tier allowlist logic includes it) so that the file is copied and the
mapping in HOOK_EVENT_MAP can be installed correctly.


/**
* Hooks exclusivos do tier pro (requerem aios-pro)
* @type {string[]}
*/
const HOOKS_PRO_ONLY = [
'precompact-session-digest.cjs',
];

/**
* BUG-3 fix (INS-1): Copy .claude/hooks/ folder during installation
* Only copies JS hooks that work without external dependencies (Python, etc.)
* Pro-only hooks (precompact-session-digest) are skipped when pro/ is unavailable (#544)
* @param {string} projectRoot - Project root directory
* @returns {Promise<string[]>} List of copied files
*/
Expand All @@ -674,13 +694,12 @@ async function copyClaudeHooksFolder(projectRoot) {

await fs.ensureDir(targetDir);

// Only copy JS hooks that work standalone (no Python/shell deps)
const HOOKS_TO_COPY = [
'synapse-engine.cjs',
'code-intel-pretool.cjs',
'precompact-session-digest.cjs',
'README.md',
];
// Detecta se pro/ está disponível para decidir quais hooks copiar (#544)
const { isProAvailable } = require('../../../../bin/utils/pro-detector');
const isPro = isProAvailable();
const HOOKS_TO_COPY = isPro
? [...HOOKS_FREE, ...HOOKS_PRO_ONLY]
: HOOKS_FREE;
Comment on lines +697 to +702
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard pro detection failures and default to free hooks.

If pro-detector throws, this path currently fails hard during installer execution. Safer behavior is fail-open to free-tier hook copy.

💡 Proposed fix
-  const { isProAvailable } = require('../../../../bin/utils/pro-detector');
-  const isPro = isProAvailable();
+  let isPro = false;
+  try {
+    const { isProAvailable } = require('../../../../bin/utils/pro-detector');
+    isPro = Boolean(isProAvailable());
+  } catch (_) {
+    isPro = false;
+  }
   const HOOKS_TO_COPY = isPro
     ? [...HOOKS_FREE, ...HOOKS_PRO_ONLY]
     : HOOKS_FREE;

As per coding guidelines **/*.js: Verify error handling is comprehensive.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Detecta se pro/ está disponível para decidir quais hooks copiar (#544)
const { isProAvailable } = require('../../../../bin/utils/pro-detector');
const isPro = isProAvailable();
const HOOKS_TO_COPY = isPro
? [...HOOKS_FREE, ...HOOKS_PRO_ONLY]
: HOOKS_FREE;
// Detecta se pro/ está disponível para decidir quais hooks copiar (`#544`)
let isPro = false;
try {
const { isProAvailable } = require('../../../../bin/utils/pro-detector');
isPro = Boolean(isProAvailable());
} catch (_) {
isPro = false;
}
const HOOKS_TO_COPY = isPro
? [...HOOKS_FREE, ...HOOKS_PRO_ONLY]
: HOOKS_FREE;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/installer/src/wizard/ide-config-generator.js` around lines 697 -
702, Wrap the pro-detection require and call in a try/catch so any thrown error
from require('../../../../bin/utils/pro-detector') or isProAvailable() is caught
and the code falls back to free hooks; specifically, protect the isProAvailable
import and invocation (used to compute isPro and HOOKS_TO_COPY) by defaulting
isPro to false on error, optionally logging a warning, and then compute
HOOKS_TO_COPY from HOOKS_FREE and HOOKS_PRO_ONLY as before.


const files = await fs.readdir(sourceDir);

Expand Down
79 changes: 79 additions & 0 deletions tests/installer/conditional-hooks.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* Testes para instalação condicional de hooks por tier (#544)
*
* Valida que hooks pro-only não são copiados quando pro/ não está disponível.
*
* @see packages/installer/src/wizard/ide-config-generator.js
* @issue #544
*/

'use strict';

const path = require('path');
const fs = require('fs-extra');
const os = require('os');

// O módulo sob teste
const {
copyClaudeHooksFolder,
} = require('../../packages/installer/src/wizard/ide-config-generator');

// Mock do pro-detector
jest.mock('../../bin/utils/pro-detector');
const proDetector = require('../../bin/utils/pro-detector');

function makeTempDir() {
return fs.mkdtempSync(path.join(os.tmpdir(), 'hooks-test-'));
}

describe('copyClaudeHooksFolder — tier-aware (#544)', () => {
let tmpDir;

beforeEach(() => {
tmpDir = makeTempDir();
});

afterEach(() => {
fs.rmSync(tmpDir, { recursive: true, force: true });
});

it('deve copiar apenas hooks free quando pro não está disponível', async () => {
proDetector.isProAvailable.mockReturnValue(false);

const copiedFiles = await copyClaudeHooksFolder(tmpDir);
const fileNames = copiedFiles.map((f) => path.basename(f));

expect(fileNames).toContain('synapse-engine.cjs');
expect(fileNames).toContain('README.md');
expect(fileNames).not.toContain('precompact-session-digest.cjs');
});

it('deve copiar todos os hooks incluindo pro quando pro está disponível', async () => {
proDetector.isProAvailable.mockReturnValue(true);

const copiedFiles = await copyClaudeHooksFolder(tmpDir);
const fileNames = copiedFiles.map((f) => path.basename(f));

expect(fileNames).toContain('synapse-engine.cjs');
expect(fileNames).toContain('precompact-session-digest.cjs');
expect(fileNames).toContain('README.md');
});

it('deve retornar array vazio quando source === target (framework-dev)', async () => {
proDetector.isProAvailable.mockReturnValue(false);

// Aponta projectRoot para o próprio framework root
const frameworkRoot = path.resolve(__dirname, '..', '..');
const result = await copyClaudeHooksFolder(frameworkRoot);
expect(result).toEqual([]);
});

it('deve sempre copiar README.md independente do tier', async () => {
proDetector.isProAvailable.mockReturnValue(false);

const copiedFiles = await copyClaudeHooksFolder(tmpDir);
const fileNames = copiedFiles.map((f) => path.basename(f));

expect(fileNames).toContain('README.md');
});
});
Loading