aicm 0.17.3 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,6 +3,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.writeAssetsToTargets = writeAssetsToTargets;
7
+ exports.writeCommandsToTargets = writeCommandsToTargets;
8
+ exports.warnPresetCommandCollisions = warnPresetCommandCollisions;
9
+ exports.dedupeCommandsForInstall = dedupeCommandsForInstall;
10
+ exports.writeMcpServersToFile = writeMcpServersToFile;
6
11
  exports.installPackage = installPackage;
7
12
  exports.install = install;
8
13
  exports.installCommand = installCommand;
@@ -10,14 +15,25 @@ const chalk_1 = __importDefault(require("chalk"));
10
15
  const fs_extra_1 = __importDefault(require("fs-extra"));
11
16
  const node_path_1 = __importDefault(require("node:path"));
12
17
  const config_1 = require("../utils/config");
18
+ const hooks_1 = require("../utils/hooks");
13
19
  const working_directory_1 = require("../utils/working-directory");
14
20
  const is_ci_1 = require("../utils/is-ci");
15
21
  const rules_file_writer_1 = require("../utils/rules-file-writer");
16
- const workspace_discovery_1 = require("../utils/workspace-discovery");
22
+ const install_workspaces_1 = require("./install-workspaces");
23
+ /**
24
+ * Rewrite asset references from source paths to installation paths
25
+ * Only rewrites the ../assets/ pattern - everything else is preserved
26
+ */
27
+ function rewriteAssetReferences(content) {
28
+ // Replace ../assets/ with ../../assets/aicm/
29
+ // Handles both forward slashes and backslashes for cross-platform compatibility
30
+ return content.replace(/\.\.[\\/]assets[\\/]/g, "../../assets/aicm/");
31
+ }
17
32
  function getTargetPaths() {
18
33
  const projectDir = process.cwd();
19
34
  return {
20
35
  cursor: node_path_1.default.join(projectDir, ".cursor", "rules", "aicm"),
36
+ assetsAicm: node_path_1.default.join(projectDir, ".cursor", "assets", "aicm"),
21
37
  aicm: node_path_1.default.join(projectDir, ".aicm"),
22
38
  };
23
39
  }
@@ -28,7 +44,7 @@ function writeCursorRules(rules, cursorRulesDir) {
28
44
  const ruleNameParts = rule.name.split(node_path_1.default.sep).filter(Boolean);
29
45
  if (rule.presetName) {
30
46
  // For rules from presets, create a namespaced directory structure
31
- const namespace = extractNamespaceFromPresetPath(rule.presetName);
47
+ const namespace = (0, config_1.extractNamespaceFromPresetPath)(rule.presetName);
32
48
  // Path will be: cursorRulesDir/namespace/rule-name.mdc
33
49
  rulePath = node_path_1.default.join(cursorRulesDir, ...namespace, ...ruleNameParts);
34
50
  }
@@ -38,10 +54,12 @@ function writeCursorRules(rules, cursorRulesDir) {
38
54
  }
39
55
  const ruleFile = rulePath + ".mdc";
40
56
  fs_extra_1.default.ensureDirSync(node_path_1.default.dirname(ruleFile));
41
- fs_extra_1.default.writeFileSync(ruleFile, rule.content);
57
+ // Rewrite asset references before writing
58
+ const content = rewriteAssetReferences(rule.content);
59
+ fs_extra_1.default.writeFileSync(ruleFile, content);
42
60
  }
43
61
  }
44
- function writeCursorCommands(commands, cursorCommandsDir, assets) {
62
+ function writeCursorCommands(commands, cursorCommandsDir) {
45
63
  fs_extra_1.default.removeSync(cursorCommandsDir);
46
64
  for (const command of commands) {
47
65
  const commandNameParts = command.name
@@ -51,34 +69,11 @@ function writeCursorCommands(commands, cursorCommandsDir, assets) {
51
69
  const commandPath = node_path_1.default.join(cursorCommandsDir, ...commandNameParts);
52
70
  const commandFile = commandPath + ".md";
53
71
  fs_extra_1.default.ensureDirSync(node_path_1.default.dirname(commandFile));
54
- // If the command file references assets in the rules directory, we need to rewrite the links.
55
- // Commands are installed in .cursor/commands/aicm/
56
- // Rules/assets are installed in .cursor/rules/aicm/
57
- // So a link like "../rules/asset.json" in source (from commands/ to rules/)
58
- // needs to become "../../rules/aicm/asset.json" in target (from .cursor/commands/aicm/ to .cursor/rules/aicm/)
59
- const content = rewriteCommandRelativeLinks(command.content, command.sourcePath, assets);
72
+ // Rewrite asset references before writing
73
+ const content = rewriteAssetReferences(command.content);
60
74
  fs_extra_1.default.writeFileSync(commandFile, content);
61
75
  }
62
76
  }
63
- function rewriteCommandRelativeLinks(content, commandSourcePath, assets) {
64
- const commandDir = node_path_1.default.dirname(commandSourcePath);
65
- const assetMap = new Map(assets.map((a) => [node_path_1.default.normalize(a.sourcePath), a.name]));
66
- return content.replace(/\.\.[/\\][\w\-/\\.]+/g, (match) => {
67
- const resolved = node_path_1.default.normalize(node_path_1.default.resolve(commandDir, match));
68
- return assetMap.has(resolved)
69
- ? `../../rules/aicm/${assetMap.get(resolved)}`
70
- : match;
71
- });
72
- }
73
- function extractNamespaceFromPresetPath(presetPath) {
74
- // Special case: npm package names always use forward slashes, regardless of platform
75
- if (presetPath.startsWith("@")) {
76
- // For scoped packages like @scope/package/subdir, create nested directories
77
- return presetPath.split("/");
78
- }
79
- const parts = presetPath.split(node_path_1.default.sep);
80
- return parts.filter((part) => part.length > 0 && part !== "." && part !== "..");
81
- }
82
77
  /**
83
78
  * Write rules to a shared directory and update the given rules file
84
79
  */
@@ -89,7 +84,7 @@ function writeRulesForFile(rules, assets, ruleDir, rulesFile) {
89
84
  const ruleNameParts = rule.name.split(node_path_1.default.sep).filter(Boolean);
90
85
  if (rule.presetName) {
91
86
  // For rules from presets, create a namespaced directory structure
92
- const namespace = extractNamespaceFromPresetPath(rule.presetName);
87
+ const namespace = (0, config_1.extractNamespaceFromPresetPath)(rule.presetName);
93
88
  // Path will be: ruleDir/namespace/rule-name.md
94
89
  rulePath = node_path_1.default.join(ruleDir, ...namespace, ...ruleNameParts);
95
90
  }
@@ -97,7 +92,8 @@ function writeRulesForFile(rules, assets, ruleDir, rulesFile) {
97
92
  // For local rules, maintain the original flat structure
98
93
  rulePath = node_path_1.default.join(ruleDir, ...ruleNameParts);
99
94
  }
100
- const content = rule.content;
95
+ // Rewrite asset references before writing
96
+ const content = rewriteAssetReferences(rule.content);
101
97
  const physicalRulePath = rulePath + ".md";
102
98
  fs_extra_1.default.ensureDirSync(node_path_1.default.dirname(physicalRulePath));
103
99
  fs_extra_1.default.writeFileSync(physicalRulePath, content);
@@ -105,7 +101,7 @@ function writeRulesForFile(rules, assets, ruleDir, rulesFile) {
105
101
  // For the rules file, maintain the same structure
106
102
  let windsurfPath;
107
103
  if (rule.presetName) {
108
- const namespace = extractNamespaceFromPresetPath(rule.presetName);
104
+ const namespace = (0, config_1.extractNamespaceFromPresetPath)(rule.presetName);
109
105
  windsurfPath =
110
106
  node_path_1.default.join(relativeRuleDir, ...namespace, ...ruleNameParts) + ".md";
111
107
  }
@@ -129,7 +125,7 @@ function writeAssetsToTargets(assets, targets) {
129
125
  let targetDir;
130
126
  switch (target) {
131
127
  case "cursor":
132
- targetDir = targetPaths.cursor;
128
+ targetDir = targetPaths.assetsAicm;
133
129
  break;
134
130
  case "windsurf":
135
131
  case "codex":
@@ -142,7 +138,7 @@ function writeAssetsToTargets(assets, targets) {
142
138
  for (const asset of assets) {
143
139
  let assetPath;
144
140
  if (asset.presetName) {
145
- const namespace = extractNamespaceFromPresetPath(asset.presetName);
141
+ const namespace = (0, config_1.extractNamespaceFromPresetPath)(asset.presetName);
146
142
  assetPath = node_path_1.default.join(targetDir, ...namespace, asset.name);
147
143
  }
148
144
  else {
@@ -185,13 +181,13 @@ function writeRulesToTargets(rules, assets, targets) {
185
181
  // Write assets after rules so they don't get wiped by emptyDirSync
186
182
  writeAssetsToTargets(assets, targets);
187
183
  }
188
- function writeCommandsToTargets(commands, assets, targets) {
184
+ function writeCommandsToTargets(commands, targets) {
189
185
  const projectDir = process.cwd();
190
186
  const cursorRoot = node_path_1.default.join(projectDir, ".cursor");
191
187
  for (const target of targets) {
192
188
  if (target === "cursor") {
193
189
  const commandsDir = node_path_1.default.join(cursorRoot, "commands", "aicm");
194
- writeCursorCommands(commands, commandsDir, assets);
190
+ writeCursorCommands(commands, commandsDir);
195
191
  }
196
192
  // Other targets do not support commands yet
197
193
  }
@@ -227,37 +223,6 @@ function dedupeCommandsForInstall(commands) {
227
223
  }
228
224
  return Array.from(unique.values());
229
225
  }
230
- function mergeWorkspaceCommands(packages) {
231
- var _a;
232
- const commands = [];
233
- const seenPresetCommands = new Set();
234
- for (const pkg of packages) {
235
- const hasCursorTarget = pkg.config.config.targets.includes("cursor");
236
- if (!hasCursorTarget) {
237
- continue;
238
- }
239
- for (const command of (_a = pkg.config.commands) !== null && _a !== void 0 ? _a : []) {
240
- if (command.presetName) {
241
- const presetKey = `${command.presetName}::${command.name}`;
242
- if (seenPresetCommands.has(presetKey)) {
243
- continue;
244
- }
245
- seenPresetCommands.add(presetKey);
246
- }
247
- commands.push(command);
248
- }
249
- }
250
- return commands;
251
- }
252
- function collectWorkspaceCommandTargets(packages) {
253
- const targets = new Set();
254
- for (const pkg of packages) {
255
- if (pkg.config.config.targets.includes("cursor")) {
256
- targets.add("cursor");
257
- }
258
- }
259
- return Array.from(targets);
260
- }
261
226
  /**
262
227
  * Write MCP servers configuration to IDE targets
263
228
  */
@@ -272,6 +237,21 @@ function writeMcpServersToTargets(mcpServers, targets, cwd) {
272
237
  // Windsurf and Codex do not support project mcpServers, so skip
273
238
  }
274
239
  }
240
+ /**
241
+ * Write hooks to IDE targets
242
+ */
243
+ function writeHooksToTargets(hooksConfig, hookFiles, targets, cwd) {
244
+ const hasHooks = hooksConfig.hooks && Object.keys(hooksConfig.hooks).length > 0;
245
+ if (!hasHooks && hookFiles.length === 0) {
246
+ return;
247
+ }
248
+ for (const target of targets) {
249
+ if (target === "cursor") {
250
+ (0, hooks_1.writeHooksToCursor)(hooksConfig, hookFiles, cwd);
251
+ }
252
+ // Other targets do not support hooks yet
253
+ }
254
+ }
275
255
  /**
276
256
  * Write MCP servers configuration to a specific file
277
257
  */
@@ -311,37 +291,6 @@ function writeMcpServersToFile(mcpServers, mcpPath) {
311
291
  };
312
292
  fs_extra_1.default.writeJsonSync(mcpPath, mergedConfig, { spaces: 2 });
313
293
  }
314
- function mergeWorkspaceMcpServers(packages) {
315
- const merged = {};
316
- const info = {};
317
- for (const pkg of packages) {
318
- for (const [key, value] of Object.entries(pkg.config.mcpServers)) {
319
- if (value === false)
320
- continue;
321
- const json = JSON.stringify(value);
322
- if (!info[key]) {
323
- info[key] = {
324
- configs: new Set([json]),
325
- packages: [pkg.relativePath],
326
- chosen: pkg.relativePath,
327
- };
328
- }
329
- else {
330
- info[key].packages.push(pkg.relativePath);
331
- info[key].configs.add(json);
332
- info[key].chosen = pkg.relativePath;
333
- }
334
- merged[key] = value;
335
- }
336
- }
337
- const conflicts = [];
338
- for (const [key, data] of Object.entries(info)) {
339
- if (data.configs.size > 1) {
340
- conflicts.push({ key, packages: data.packages, chosen: data.chosen });
341
- }
342
- }
343
- return { merged, conflicts };
344
- }
345
294
  /**
346
295
  * Install rules for a single package (used within workspaces and standalone installs)
347
296
  */
@@ -362,16 +311,18 @@ async function installPackage(options = {}) {
362
311
  installedRuleCount: 0,
363
312
  installedCommandCount: 0,
364
313
  installedAssetCount: 0,
314
+ installedHookCount: 0,
365
315
  packagesCount: 0,
366
316
  };
367
317
  }
368
- const { config, rules, commands, assets, mcpServers } = resolvedConfig;
318
+ const { config, rules, commands, assets, mcpServers, hooks, hookFiles } = resolvedConfig;
369
319
  if (config.skipInstall === true) {
370
320
  return {
371
321
  success: true,
372
322
  installedRuleCount: 0,
373
323
  installedCommandCount: 0,
374
324
  installedAssetCount: 0,
325
+ installedHookCount: 0,
375
326
  packagesCount: 0,
376
327
  };
377
328
  }
@@ -380,18 +331,23 @@ async function installPackage(options = {}) {
380
331
  try {
381
332
  if (!options.dryRun) {
382
333
  writeRulesToTargets(rules, assets, config.targets);
383
- writeCommandsToTargets(commandsToInstall, assets, config.targets);
334
+ writeCommandsToTargets(commandsToInstall, config.targets);
384
335
  if (mcpServers && Object.keys(mcpServers).length > 0) {
385
336
  writeMcpServersToTargets(mcpServers, config.targets, cwd);
386
337
  }
338
+ if (hooks && ((0, hooks_1.countHooks)(hooks) > 0 || hookFiles.length > 0)) {
339
+ writeHooksToTargets(hooks, hookFiles, config.targets, cwd);
340
+ }
387
341
  }
388
342
  const uniqueRuleCount = new Set(rules.map((rule) => rule.name)).size;
389
343
  const uniqueCommandCount = new Set(commandsToInstall.map((command) => command.name)).size;
344
+ const uniqueHookCount = (0, hooks_1.countHooks)(hooks);
390
345
  return {
391
346
  success: true,
392
347
  installedRuleCount: uniqueRuleCount,
393
348
  installedCommandCount: uniqueCommandCount,
394
349
  installedAssetCount: assets.length,
350
+ installedHookCount: uniqueHookCount,
395
351
  packagesCount: 1,
396
352
  };
397
353
  }
@@ -402,171 +358,12 @@ async function installPackage(options = {}) {
402
358
  installedRuleCount: 0,
403
359
  installedCommandCount: 0,
404
360
  installedAssetCount: 0,
361
+ installedHookCount: 0,
405
362
  packagesCount: 0,
406
363
  };
407
364
  }
408
365
  });
409
366
  }
410
- /**
411
- * Install aicm configurations for all packages in a workspace
412
- */
413
- async function installWorkspacesPackages(packages, options = {}) {
414
- const results = [];
415
- let totalRuleCount = 0;
416
- let totalCommandCount = 0;
417
- let totalAssetCount = 0;
418
- // Install packages sequentially for now (can be parallelized later)
419
- for (const pkg of packages) {
420
- const packagePath = pkg.absolutePath;
421
- try {
422
- const result = await installPackage({
423
- ...options,
424
- cwd: packagePath,
425
- config: pkg.config,
426
- });
427
- totalRuleCount += result.installedRuleCount;
428
- totalCommandCount += result.installedCommandCount;
429
- totalAssetCount += result.installedAssetCount;
430
- results.push({
431
- path: pkg.relativePath,
432
- success: result.success,
433
- error: result.error,
434
- installedRuleCount: result.installedRuleCount,
435
- installedCommandCount: result.installedCommandCount,
436
- installedAssetCount: result.installedAssetCount,
437
- });
438
- }
439
- catch (error) {
440
- results.push({
441
- path: pkg.relativePath,
442
- success: false,
443
- error: error instanceof Error ? error : new Error(String(error)),
444
- installedRuleCount: 0,
445
- installedCommandCount: 0,
446
- installedAssetCount: 0,
447
- });
448
- }
449
- }
450
- const failedPackages = results.filter((r) => !r.success);
451
- return {
452
- success: failedPackages.length === 0,
453
- packages: results,
454
- totalRuleCount,
455
- totalCommandCount,
456
- totalAssetCount,
457
- };
458
- }
459
- /**
460
- * Install rules across multiple packages in a workspace
461
- */
462
- async function installWorkspaces(cwd, installOnCI, verbose = false, dryRun = false) {
463
- return (0, working_directory_1.withWorkingDirectory)(cwd, async () => {
464
- if (verbose) {
465
- console.log(chalk_1.default.blue("🔍 Discovering packages..."));
466
- }
467
- const allPackages = await (0, workspace_discovery_1.discoverPackagesWithAicm)(cwd);
468
- const packages = allPackages.filter((pkg) => {
469
- if (pkg.config.config.skipInstall === true) {
470
- return false;
471
- }
472
- const isRoot = pkg.relativePath === ".";
473
- if (!isRoot)
474
- return true;
475
- // For root directories, only keep if it has rules, commands, or presets
476
- const hasRules = pkg.config.rules && pkg.config.rules.length > 0;
477
- const hasCommands = pkg.config.commands && pkg.config.commands.length > 0;
478
- const hasPresets = pkg.config.config.presets && pkg.config.config.presets.length > 0;
479
- return hasRules || hasCommands || hasPresets;
480
- });
481
- if (packages.length === 0) {
482
- return {
483
- success: false,
484
- error: new Error("No packages with aicm configurations found"),
485
- installedRuleCount: 0,
486
- installedCommandCount: 0,
487
- installedAssetCount: 0,
488
- packagesCount: 0,
489
- };
490
- }
491
- if (verbose) {
492
- console.log(chalk_1.default.blue(`Found ${packages.length} packages with aicm configurations:`));
493
- packages.forEach((pkg) => {
494
- console.log(chalk_1.default.gray(` - ${pkg.relativePath}`));
495
- });
496
- console.log(chalk_1.default.blue(`📦 Installing configurations...`));
497
- }
498
- const result = await installWorkspacesPackages(packages, {
499
- installOnCI,
500
- verbose,
501
- dryRun,
502
- });
503
- const workspaceCommands = mergeWorkspaceCommands(packages);
504
- const workspaceCommandTargets = collectWorkspaceCommandTargets(packages);
505
- if (workspaceCommands.length > 0) {
506
- warnPresetCommandCollisions(workspaceCommands);
507
- }
508
- if (!dryRun &&
509
- workspaceCommands.length > 0 &&
510
- workspaceCommandTargets.length > 0) {
511
- const dedupedWorkspaceCommands = dedupeCommandsForInstall(workspaceCommands);
512
- // Collect all assets from packages for command path rewriting
513
- const allAssets = packages.flatMap((pkg) => { var _a; return (_a = pkg.config.assets) !== null && _a !== void 0 ? _a : []; });
514
- writeCommandsToTargets(dedupedWorkspaceCommands, allAssets, workspaceCommandTargets);
515
- }
516
- const { merged: rootMcp, conflicts } = mergeWorkspaceMcpServers(packages);
517
- const hasCursorTarget = packages.some((p) => p.config.config.targets.includes("cursor"));
518
- if (!dryRun && hasCursorTarget && Object.keys(rootMcp).length > 0) {
519
- const mcpPath = node_path_1.default.join(cwd, ".cursor", "mcp.json");
520
- writeMcpServersToFile(rootMcp, mcpPath);
521
- }
522
- for (const conflict of conflicts) {
523
- console.warn(`Warning: MCP configuration conflict detected\n Key: "${conflict.key}"\n Packages: ${conflict.packages.join(", ")}\n Using configuration from: ${conflict.chosen}`);
524
- }
525
- if (verbose) {
526
- result.packages.forEach((pkg) => {
527
- if (pkg.success) {
528
- const summaryParts = [`${pkg.installedRuleCount} rules`];
529
- if (pkg.installedCommandCount > 0) {
530
- summaryParts.push(`${pkg.installedCommandCount} command${pkg.installedCommandCount === 1 ? "" : "s"}`);
531
- }
532
- console.log(chalk_1.default.green(`✅ ${pkg.path} (${summaryParts.join(", ")})`));
533
- }
534
- else {
535
- console.log(chalk_1.default.red(`❌ ${pkg.path}: ${pkg.error}`));
536
- }
537
- });
538
- }
539
- const failedPackages = result.packages.filter((r) => !r.success);
540
- if (failedPackages.length > 0) {
541
- console.log(chalk_1.default.yellow(`Installation completed with errors`));
542
- if (verbose) {
543
- const commandSummary = result.totalCommandCount > 0
544
- ? `, ${result.totalCommandCount} command${result.totalCommandCount === 1 ? "" : "s"} total`
545
- : "";
546
- console.log(chalk_1.default.green(`Successfully installed: ${result.packages.length - failedPackages.length}/${result.packages.length} packages (${result.totalRuleCount} rule${result.totalRuleCount === 1 ? "" : "s"} total${commandSummary})`));
547
- console.log(chalk_1.default.red(`Failed packages: ${failedPackages.map((p) => p.path).join(", ")}`));
548
- }
549
- const errorDetails = failedPackages
550
- .map((p) => `${p.path}: ${p.error}`)
551
- .join("; ");
552
- return {
553
- success: false,
554
- error: new Error(`Package installation failed for ${failedPackages.length} package(s): ${errorDetails}`),
555
- installedRuleCount: result.totalRuleCount,
556
- installedCommandCount: result.totalCommandCount,
557
- installedAssetCount: result.totalAssetCount,
558
- packagesCount: result.packages.length,
559
- };
560
- }
561
- return {
562
- success: true,
563
- installedRuleCount: result.totalRuleCount,
564
- installedCommandCount: result.totalCommandCount,
565
- installedAssetCount: result.totalAssetCount,
566
- packagesCount: result.packages.length,
567
- };
568
- });
569
- }
570
367
  /**
571
368
  * Core implementation of the rule installation logic
572
369
  */
@@ -581,6 +378,7 @@ async function install(options = {}) {
581
378
  installedRuleCount: 0,
582
379
  installedCommandCount: 0,
583
380
  installedAssetCount: 0,
381
+ installedHookCount: 0,
584
382
  packagesCount: 0,
585
383
  };
586
384
  }
@@ -595,7 +393,7 @@ async function install(options = {}) {
595
393
  const shouldUseWorkspaces = (resolvedConfig === null || resolvedConfig === void 0 ? void 0 : resolvedConfig.config.workspaces) ||
596
394
  (!resolvedConfig && (0, config_1.detectWorkspacesFromPackageJson)(cwd));
597
395
  if (shouldUseWorkspaces) {
598
- return await installWorkspaces(cwd, installOnCI, options.verbose, options.dryRun);
396
+ return await (0, install_workspaces_1.installWorkspaces)(cwd, installOnCI, options.verbose, options.dryRun);
599
397
  }
600
398
  return installPackage(options);
601
399
  });
@@ -612,10 +410,12 @@ async function installCommand(installOnCI, verbose, dryRun) {
612
410
  else {
613
411
  const ruleCount = result.installedRuleCount;
614
412
  const commandCount = result.installedCommandCount;
413
+ const hookCount = result.installedHookCount;
615
414
  const ruleMessage = ruleCount > 0 ? `${ruleCount} rule${ruleCount === 1 ? "" : "s"}` : null;
616
415
  const commandMessage = commandCount > 0
617
416
  ? `${commandCount} command${commandCount === 1 ? "" : "s"}`
618
417
  : null;
418
+ const hookMessage = hookCount > 0 ? `${hookCount} hook${hookCount === 1 ? "" : "s"}` : null;
619
419
  const countsParts = [];
620
420
  if (ruleMessage) {
621
421
  countsParts.push(ruleMessage);
@@ -623,7 +423,12 @@ async function installCommand(installOnCI, verbose, dryRun) {
623
423
  if (commandMessage) {
624
424
  countsParts.push(commandMessage);
625
425
  }
626
- const countsMessage = countsParts.length > 0 ? countsParts.join(" and ") : "0 rules";
426
+ if (hookMessage) {
427
+ countsParts.push(hookMessage);
428
+ }
429
+ const countsMessage = countsParts.length > 0
430
+ ? countsParts.join(", ").replace(/, ([^,]*)$/, " and $1")
431
+ : "0 rules";
627
432
  if (dryRun) {
628
433
  if (result.packagesCount > 1) {
629
434
  console.log(`Dry run: validated ${countsMessage} across ${result.packagesCount} packages`);
@@ -632,8 +437,8 @@ async function installCommand(installOnCI, verbose, dryRun) {
632
437
  console.log(`Dry run: validated ${countsMessage}`);
633
438
  }
634
439
  }
635
- else if (ruleCount === 0 && commandCount === 0) {
636
- console.log("No rules or commands installed");
440
+ else if (ruleCount === 0 && commandCount === 0 && hookCount === 0) {
441
+ console.log("No rules, commands, or hooks installed");
637
442
  }
638
443
  else if (result.packagesCount > 1) {
639
444
  console.log(`Successfully installed ${countsMessage} across ${result.packagesCount} packages`);
@@ -1,20 +1,17 @@
1
1
  import { CosmiconfigResult } from "cosmiconfig";
2
+ import { HooksJson, HookFile } from "./hooks";
2
3
  export interface RawConfig {
3
- rulesDir?: string;
4
- commandsDir?: string;
4
+ rootDir?: string;
5
5
  targets?: string[];
6
6
  presets?: string[];
7
- overrides?: Record<string, string | false>;
8
7
  mcpServers?: MCPServers;
9
8
  workspaces?: boolean;
10
9
  skipInstall?: boolean;
11
10
  }
12
11
  export interface Config {
13
- rulesDir?: string;
14
- commandsDir?: string;
12
+ rootDir?: string;
15
13
  targets: string[];
16
14
  presets?: string[];
17
- overrides?: Record<string, string | false>;
18
15
  mcpServers?: MCPServers;
19
16
  workspaces?: boolean;
20
17
  skipInstall?: boolean;
@@ -58,30 +55,37 @@ export interface ResolvedConfig {
58
55
  commands: CommandFile[];
59
56
  assets: AssetFile[];
60
57
  mcpServers: MCPServers;
58
+ hooks: HooksJson;
59
+ hookFiles: HookFile[];
61
60
  }
62
- export declare const ALLOWED_CONFIG_KEYS: readonly ["rulesDir", "commandsDir", "targets", "presets", "overrides", "mcpServers", "workspaces", "skipInstall"];
61
+ export declare const ALLOWED_CONFIG_KEYS: readonly ["rootDir", "targets", "presets", "mcpServers", "workspaces", "skipInstall"];
63
62
  export declare const SUPPORTED_TARGETS: readonly ["cursor", "windsurf", "codex", "claude"];
64
63
  export type SupportedTarget = (typeof SUPPORTED_TARGETS)[number];
65
64
  export declare function detectWorkspacesFromPackageJson(cwd: string): boolean;
66
65
  export declare function resolveWorkspaces(config: unknown, configFilePath: string, cwd: string): boolean;
67
66
  export declare function applyDefaults(config: RawConfig, workspaces: boolean): Config;
68
67
  export declare function validateConfig(config: unknown, configFilePath: string, cwd: string, isWorkspaceMode?: boolean): asserts config is Config;
69
- export declare function loadRulesFromDirectory(rulesDir: string, source: "local" | "preset", presetName?: string): Promise<RuleFile[]>;
70
- export declare function loadCommandsFromDirectory(commandsDir: string, source: "local" | "preset", presetName?: string): Promise<CommandFile[]>;
71
- export declare function loadAssetsFromDirectory(rulesDir: string, source: "local" | "preset", presetName?: string): Promise<AssetFile[]>;
68
+ export declare function loadRulesFromDirectory(directoryPath: string, source: "local" | "preset", presetName?: string): Promise<RuleFile[]>;
69
+ export declare function loadCommandsFromDirectory(directoryPath: string, source: "local" | "preset", presetName?: string): Promise<CommandFile[]>;
70
+ export declare function loadAssetsFromDirectory(directoryPath: string, source: "local" | "preset", presetName?: string): Promise<AssetFile[]>;
71
+ /**
72
+ * Extract namespace from preset path for directory structure
73
+ * Handles both npm packages and local paths consistently
74
+ */
75
+ export declare function extractNamespaceFromPresetPath(presetPath: string): string[];
72
76
  export declare function resolvePresetPath(presetPath: string, cwd: string): string | null;
73
77
  export declare function loadPreset(presetPath: string, cwd: string): Promise<{
74
78
  config: Config;
75
- rulesDir?: string;
76
- commandsDir?: string;
79
+ rootDir: string;
77
80
  }>;
78
81
  export declare function loadAllRules(config: Config, cwd: string): Promise<{
79
82
  rules: RuleFile[];
80
83
  commands: CommandFile[];
81
84
  assets: AssetFile[];
82
85
  mcpServers: MCPServers;
86
+ hooks: HooksJson;
87
+ hookFiles: HookFile[];
83
88
  }>;
84
- export declare function applyOverrides<T extends ManagedFile>(files: T[], overrides: Record<string, string | false>, cwd: string): T[];
85
89
  export declare function loadConfigFile(searchFrom?: string): Promise<CosmiconfigResult>;
86
90
  export declare function loadConfig(cwd?: string): Promise<ResolvedConfig | null>;
87
91
  export declare function saveConfig(config: Config, cwd?: string): boolean;