codingbuddy 5.1.3 → 5.2.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.
Files changed (128) hide show
  1. package/README.md +9 -0
  2. package/dist/src/app.module.js +2 -0
  3. package/dist/src/app.module.js.map +1 -1
  4. package/dist/src/cli/cli.d.ts +3 -3
  5. package/dist/src/cli/cli.js +118 -1
  6. package/dist/src/cli/cli.js.map +1 -1
  7. package/dist/src/cli/cli.types.d.ts +14 -0
  8. package/dist/src/cli/init/init.constants.js +0 -1
  9. package/dist/src/cli/init/init.constants.js.map +1 -1
  10. package/dist/src/cli/plugin/install.command.d.ts +10 -0
  11. package/dist/src/cli/plugin/install.command.js +31 -0
  12. package/dist/src/cli/plugin/install.command.js.map +1 -0
  13. package/dist/src/cli/plugin/plugins.command.d.ts +9 -0
  14. package/dist/src/cli/plugin/plugins.command.js +51 -0
  15. package/dist/src/cli/plugin/plugins.command.js.map +1 -0
  16. package/dist/src/cli/plugin/search.command.d.ts +9 -0
  17. package/dist/src/cli/plugin/search.command.js +53 -0
  18. package/dist/src/cli/plugin/search.command.js.map +1 -0
  19. package/dist/src/cli/plugin/uninstall.command.d.ts +12 -0
  20. package/dist/src/cli/plugin/uninstall.command.js +79 -0
  21. package/dist/src/cli/plugin/uninstall.command.js.map +1 -0
  22. package/dist/src/cli/plugin/update.command.d.ts +11 -0
  23. package/dist/src/cli/plugin/update.command.js +103 -0
  24. package/dist/src/cli/plugin/update.command.js.map +1 -0
  25. package/dist/src/context/briefing-loader.service.d.ts +27 -0
  26. package/dist/src/context/briefing-loader.service.js +124 -0
  27. package/dist/src/context/briefing-loader.service.js.map +1 -0
  28. package/dist/src/context/briefing.service.d.ts +27 -0
  29. package/dist/src/context/briefing.service.js +181 -0
  30. package/dist/src/context/briefing.service.js.map +1 -0
  31. package/dist/src/context/briefing.types.d.ts +13 -0
  32. package/dist/src/context/briefing.types.js +6 -0
  33. package/dist/src/context/briefing.types.js.map +1 -0
  34. package/dist/src/context/context.module.js +16 -2
  35. package/dist/src/context/context.module.js.map +1 -1
  36. package/dist/src/context/index.d.ts +2 -0
  37. package/dist/src/context/index.js +2 -0
  38. package/dist/src/context/index.js.map +1 -1
  39. package/dist/src/main.js +0 -17
  40. package/dist/src/main.js.map +1 -1
  41. package/dist/src/mcp/handlers/briefing.handler.d.ts +12 -0
  42. package/dist/src/mcp/handlers/briefing.handler.js +71 -0
  43. package/dist/src/mcp/handlers/briefing.handler.js.map +1 -0
  44. package/dist/src/mcp/handlers/index.d.ts +4 -0
  45. package/dist/src/mcp/handlers/index.js +9 -1
  46. package/dist/src/mcp/handlers/index.js.map +1 -1
  47. package/dist/src/mcp/handlers/quality-report.handler.d.ts +12 -0
  48. package/dist/src/mcp/handlers/quality-report.handler.js +75 -0
  49. package/dist/src/mcp/handlers/quality-report.handler.js.map +1 -0
  50. package/dist/src/mcp/handlers/resume.handler.d.ts +12 -0
  51. package/dist/src/mcp/handlers/resume.handler.js +63 -0
  52. package/dist/src/mcp/handlers/resume.handler.js.map +1 -0
  53. package/dist/src/mcp/handlers/rule-impact.handler.d.ts +23 -0
  54. package/dist/src/mcp/handlers/rule-impact.handler.js +241 -0
  55. package/dist/src/mcp/handlers/rule-impact.handler.js.map +1 -0
  56. package/dist/src/mcp/handlers/skill.handler.js +2 -2
  57. package/dist/src/mcp/handlers/skill.handler.js.map +1 -1
  58. package/dist/src/mcp/mcp.module.js +6 -0
  59. package/dist/src/mcp/mcp.module.js.map +1 -1
  60. package/dist/src/plugin/plugin-installer.service.d.ts +41 -0
  61. package/dist/src/plugin/plugin-installer.service.js +183 -0
  62. package/dist/src/plugin/plugin-installer.service.js.map +1 -0
  63. package/dist/src/plugin/plugin-manifest.schema.d.ts +21 -0
  64. package/dist/src/plugin/plugin-manifest.schema.js +45 -0
  65. package/dist/src/plugin/plugin-manifest.schema.js.map +1 -0
  66. package/dist/src/plugin/plugin.module.d.ts +2 -0
  67. package/dist/src/plugin/plugin.module.js +17 -0
  68. package/dist/src/plugin/plugin.module.js.map +1 -0
  69. package/dist/src/plugin/plugin.types.d.ts +7 -0
  70. package/dist/src/plugin/plugin.types.js +3 -0
  71. package/dist/src/plugin/plugin.types.js.map +1 -0
  72. package/dist/src/plugin/registry-client.d.ts +24 -0
  73. package/dist/src/plugin/registry-client.js +45 -0
  74. package/dist/src/plugin/registry-client.js.map +1 -0
  75. package/dist/src/rules/rule-event-collector.d.ts +7 -0
  76. package/dist/src/rules/rule-event-collector.js +38 -0
  77. package/dist/src/rules/rule-event-collector.js.map +1 -0
  78. package/dist/src/rules/rule-event.types.d.ts +9 -0
  79. package/dist/src/rules/rule-event.types.js +10 -0
  80. package/dist/src/rules/rule-event.types.js.map +1 -0
  81. package/dist/src/rules/rule-stats-writer.d.ts +23 -0
  82. package/dist/src/rules/rule-stats-writer.js +138 -0
  83. package/dist/src/rules/rule-stats-writer.js.map +1 -0
  84. package/dist/src/rules/rules.module.js +4 -2
  85. package/dist/src/rules/rules.module.js.map +1 -1
  86. package/dist/src/rules/rules.service.d.ts +2 -1
  87. package/dist/src/rules/rules.service.js.map +1 -1
  88. package/dist/src/rules/skill.schema.d.ts +5 -0
  89. package/dist/src/rules/skill.schema.js +6 -0
  90. package/dist/src/rules/skill.schema.js.map +1 -1
  91. package/dist/src/shared/rules-core.d.ts +2 -1
  92. package/dist/src/shared/rules-core.js +1 -0
  93. package/dist/src/shared/rules-core.js.map +1 -1
  94. package/dist/src/shared/version.d.ts +1 -1
  95. package/dist/src/shared/version.js +1 -1
  96. package/dist/src/ship/file-specialist-mapper.d.ts +9 -0
  97. package/dist/src/ship/file-specialist-mapper.js +56 -0
  98. package/dist/src/ship/file-specialist-mapper.js.map +1 -0
  99. package/dist/src/ship/quality-report.service.d.ts +6 -0
  100. package/dist/src/ship/quality-report.service.js +56 -0
  101. package/dist/src/ship/quality-report.service.js.map +1 -0
  102. package/dist/src/ship/quality-report.types.d.ts +20 -0
  103. package/dist/src/ship/quality-report.types.js +3 -0
  104. package/dist/src/ship/quality-report.types.js.map +1 -0
  105. package/dist/src/ship/ship.module.d.ts +2 -0
  106. package/dist/src/ship/ship.module.js +21 -0
  107. package/dist/src/ship/ship.module.js.map +1 -0
  108. package/dist/src/skill/skill-recommendation.service.d.ts +9 -2
  109. package/dist/src/skill/skill-recommendation.service.js +94 -16
  110. package/dist/src/skill/skill-recommendation.service.js.map +1 -1
  111. package/dist/src/skill/skill.module.js +2 -0
  112. package/dist/src/skill/skill.module.js.map +1 -1
  113. package/dist/src/tui/__perf__/memory-stability.spec.js +6 -6
  114. package/dist/src/tui/__perf__/memory-stability.spec.js.map +1 -1
  115. package/dist/src/tui/__perf__/rendering-performance.spec.js +5 -8
  116. package/dist/src/tui/__perf__/rendering-performance.spec.js.map +1 -1
  117. package/dist/src/tui/dashboard-app.spec.js +15 -15
  118. package/dist/src/tui/dashboard-app.spec.js.map +1 -1
  119. package/dist/src/tui/eventbus-ui.integration.spec.js +45 -45
  120. package/dist/src/tui/eventbus-ui.integration.spec.js.map +1 -1
  121. package/dist/src/tui/testing/tui-test-utils.d.ts +2 -0
  122. package/dist/src/tui/testing/tui-test-utils.js +25 -0
  123. package/dist/src/tui/testing/tui-test-utils.js.map +1 -0
  124. package/dist/src/tui/transport-tui.integration.spec.js +8 -8
  125. package/dist/src/tui/transport-tui.integration.spec.js.map +1 -1
  126. package/dist/src/tui-bundle.mjs +197 -36
  127. package/dist/tsconfig.build.tsbuildinfo +1 -1
  128. package/package.json +2 -2
@@ -38,6 +38,7 @@ var __decorateClass = (decorators, target, key, kind) => {
38
38
  if (kind && result) __defProp(target, key, result);
39
39
  return result;
40
40
  };
41
+ var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
41
42
 
42
43
  // node_modules/eventemitter2/lib/eventemitter2.js
43
44
  var require_eventemitter2 = __commonJS({
@@ -2302,9 +2303,14 @@ var SkillSchemaError = class extends Error {
2302
2303
  this.name = "SkillSchemaError";
2303
2304
  }
2304
2305
  };
2306
+ var SkillFrontmatterTriggerSchema = z2.object({
2307
+ pattern: z2.string().min(1),
2308
+ confidence: z2.enum(["high", "medium", "low"])
2309
+ });
2305
2310
  var SkillFrontmatterSchema = z2.object({
2306
2311
  name: z2.string().min(1).regex(/^[a-z0-9-]+$/, "Skill name must be lowercase alphanumeric with hyphens only"),
2307
- description: z2.string().min(1).max(500)
2312
+ description: z2.string().min(1).max(500),
2313
+ triggers: z2.array(SkillFrontmatterTriggerSchema).optional()
2308
2314
  });
2309
2315
  var FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
2310
2316
  function parseFrontmatter(content) {
@@ -2343,7 +2349,8 @@ function parseSkill(content, filePath) {
2343
2349
  name: result.data.name,
2344
2350
  description: result.data.description,
2345
2351
  content: body,
2346
- path: filePath
2352
+ path: filePath,
2353
+ triggers: result.data.triggers
2347
2354
  };
2348
2355
  }
2349
2356
 
@@ -2366,7 +2373,7 @@ function getReadFile(deps) {
2366
2373
  function getReaddir(deps) {
2367
2374
  return deps?.readdir ?? defaultReaddir;
2368
2375
  }
2369
- function resolveRulesDir(dirname3, options) {
2376
+ function resolveRulesDir(dirname4, options) {
2370
2377
  if (options?.envRulesDir) {
2371
2378
  return options.envRulesDir;
2372
2379
  }
@@ -2374,15 +2381,15 @@ function resolveRulesDir(dirname3, options) {
2374
2381
  return options.packageRulesPath;
2375
2382
  }
2376
2383
  const candidates = [
2377
- path2.resolve(dirname3, "../../../../packages/rules/.ai-rules"),
2378
- path2.resolve(dirname3, "../../../packages/rules/.ai-rules"),
2379
- path2.resolve(dirname3, "../../../../.ai-rules"),
2380
- path2.resolve(dirname3, "../../../.ai-rules")
2384
+ path2.resolve(dirname4, "../../../../packages/rules/.ai-rules"),
2385
+ path2.resolve(dirname4, "../../../packages/rules/.ai-rules"),
2386
+ path2.resolve(dirname4, "../../../../.ai-rules"),
2387
+ path2.resolve(dirname4, "../../../.ai-rules")
2381
2388
  ];
2382
- const existsSync7 = options?.existsSync;
2383
- if (existsSync7) {
2389
+ const existsSync8 = options?.existsSync;
2390
+ if (existsSync8) {
2384
2391
  for (const candidate of candidates) {
2385
- if (existsSync7(candidate)) {
2392
+ if (existsSync8(candidate)) {
2386
2393
  return candidate;
2387
2394
  }
2388
2395
  }
@@ -2394,9 +2401,9 @@ async function readRuleContent(rulesDir, relativePath, deps) {
2394
2401
  throw new Error("Access denied: Invalid path");
2395
2402
  }
2396
2403
  const fullPath = path2.join(rulesDir, relativePath);
2397
- const readFile3 = getReadFile(deps);
2404
+ const readFile4 = getReadFile(deps);
2398
2405
  try {
2399
- return await readFile3(fullPath, "utf-8");
2406
+ return await readFile4(fullPath, "utf-8");
2400
2407
  } catch {
2401
2408
  throw new Error(`Failed to read rule file: ${relativePath}`);
2402
2409
  }
@@ -2450,7 +2457,7 @@ async function searchInRuleFiles(rulesDir, query, files, deps) {
2450
2457
  async function listSkillSummaries(rulesDir, deps) {
2451
2458
  const skillsDir = path2.join(rulesDir, "skills");
2452
2459
  const readdir2 = getReaddir(deps);
2453
- const readFile3 = getReadFile(deps);
2460
+ const readFile4 = getReadFile(deps);
2454
2461
  const summaries = [];
2455
2462
  try {
2456
2463
  const entries = await readdir2(skillsDir, {
@@ -2460,11 +2467,12 @@ async function listSkillSummaries(rulesDir, deps) {
2460
2467
  if (entry.isDirectory()) {
2461
2468
  const skillPath = path2.join(skillsDir, entry.name, "SKILL.md");
2462
2469
  try {
2463
- const content = await readFile3(skillPath, "utf-8");
2470
+ const content = await readFile4(skillPath, "utf-8");
2464
2471
  const skill = parseSkill(content, `skills/${entry.name}/SKILL.md`);
2465
2472
  summaries.push({
2466
2473
  name: skill.name,
2467
- description: skill.description
2474
+ description: skill.description,
2475
+ triggers: skill.triggers
2468
2476
  });
2469
2477
  } catch {
2470
2478
  }
@@ -2483,9 +2491,9 @@ async function loadSkill(rulesDir, name, deps) {
2483
2491
  throw new Error("Access denied: Invalid path");
2484
2492
  }
2485
2493
  const fullPath = path2.join(rulesDir, skillPath);
2486
- const readFile3 = getReadFile(deps);
2494
+ const readFile4 = getReadFile(deps);
2487
2495
  try {
2488
- const content = await readFile3(fullPath, "utf-8");
2496
+ const content = await readFile4(fullPath, "utf-8");
2489
2497
  return parseSkill(content, skillPath);
2490
2498
  } catch (error) {
2491
2499
  if (error instanceof SkillSchemaError) {
@@ -2629,7 +2637,7 @@ var RulesService = class {
2629
2637
  // ============================================================================
2630
2638
  /**
2631
2639
  * List all available skills from the skills directory
2632
- * @returns Array of skill summaries with name and description
2640
+ * @returns Array of skill summaries with name, description, and optional triggers
2633
2641
  */
2634
2642
  async listSkillsFromDir() {
2635
2643
  try {
@@ -2761,11 +2769,164 @@ RuleInsightsService = __decorateClass([
2761
2769
  Injectable5()
2762
2770
  ], RuleInsightsService);
2763
2771
 
2772
+ // src/rules/rule-event-collector.ts
2773
+ import { Injectable as Injectable6 } from "@nestjs/common";
2774
+
2775
+ // src/rules/rule-event.types.ts
2776
+ var RULE_EVENT_TYPES = [
2777
+ "mode_activated",
2778
+ "checklist_generated",
2779
+ "specialist_dispatched",
2780
+ "violation_caught"
2781
+ ];
2782
+
2783
+ // src/rules/rule-event-collector.ts
2784
+ var RuleEventCollector = class {
2785
+ constructor() {
2786
+ this.events = [];
2787
+ }
2788
+ record(event) {
2789
+ if (!RULE_EVENT_TYPES.includes(event.type)) {
2790
+ throw new Error(`Invalid rule event type: ${event.type}`);
2791
+ }
2792
+ this.events.push({
2793
+ ...event,
2794
+ timestamp: event.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
2795
+ });
2796
+ }
2797
+ getEvents() {
2798
+ return [...this.events];
2799
+ }
2800
+ flush() {
2801
+ const flushed = [...this.events];
2802
+ this.events = [];
2803
+ return flushed;
2804
+ }
2805
+ };
2806
+ RuleEventCollector = __decorateClass([
2807
+ Injectable6()
2808
+ ], RuleEventCollector);
2809
+
2810
+ // src/rules/rule-stats-writer.ts
2811
+ import { Injectable as Injectable7, Inject, Optional } from "@nestjs/common";
2812
+ import { readFile, writeFile, rename, mkdir } from "fs/promises";
2813
+ import { existsSync as existsSync2 } from "fs";
2814
+ import { dirname, join as join2 } from "path";
2815
+ var STATS_FILE_PATH = /* @__PURE__ */ Symbol("STATS_FILE_PATH");
2816
+ var DEFAULT_STATS_RELATIVE_PATH = "docs/codingbuddy/rule-stats.json";
2817
+ function createEmptyStats() {
2818
+ return {
2819
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
2820
+ rules: {},
2821
+ domains: {}
2822
+ };
2823
+ }
2824
+ function aggregateEvents(events) {
2825
+ const stats = createEmptyStats();
2826
+ for (const event of events) {
2827
+ switch (event.type) {
2828
+ case "mode_activated": {
2829
+ if (!event.rule) break;
2830
+ if (!stats.rules[event.rule]) {
2831
+ stats.rules[event.rule] = { applied: 0, sessions: 0 };
2832
+ }
2833
+ stats.rules[event.rule].applied += 1;
2834
+ stats.rules[event.rule].sessions += 1;
2835
+ break;
2836
+ }
2837
+ case "checklist_generated":
2838
+ case "specialist_dispatched": {
2839
+ if (!event.domain) break;
2840
+ if (!stats.domains[event.domain]) {
2841
+ stats.domains[event.domain] = { checks: 0, issues_prevented: 0 };
2842
+ }
2843
+ stats.domains[event.domain].checks += 1;
2844
+ break;
2845
+ }
2846
+ case "violation_caught": {
2847
+ if (!event.domain) break;
2848
+ if (!stats.domains[event.domain]) {
2849
+ stats.domains[event.domain] = { checks: 0, issues_prevented: 0 };
2850
+ }
2851
+ stats.domains[event.domain].issues_prevented += 1;
2852
+ break;
2853
+ }
2854
+ }
2855
+ }
2856
+ return stats;
2857
+ }
2858
+ function mergeStats(existing, incoming) {
2859
+ const merged = {
2860
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
2861
+ rules: { ...existing.rules },
2862
+ domains: { ...existing.domains }
2863
+ };
2864
+ for (const [name, entry] of Object.entries(incoming.rules)) {
2865
+ if (merged.rules[name]) {
2866
+ merged.rules[name] = {
2867
+ applied: merged.rules[name].applied + entry.applied,
2868
+ sessions: merged.rules[name].sessions + entry.sessions
2869
+ };
2870
+ } else {
2871
+ merged.rules[name] = { ...entry };
2872
+ }
2873
+ }
2874
+ for (const [name, entry] of Object.entries(incoming.domains)) {
2875
+ if (merged.domains[name]) {
2876
+ merged.domains[name] = {
2877
+ checks: merged.domains[name].checks + entry.checks,
2878
+ issues_prevented: merged.domains[name].issues_prevented + entry.issues_prevented
2879
+ };
2880
+ } else {
2881
+ merged.domains[name] = { ...entry };
2882
+ }
2883
+ }
2884
+ return merged;
2885
+ }
2886
+ var RuleStatsWriter = class {
2887
+ constructor(collector, statsPath) {
2888
+ this.collector = collector;
2889
+ this.statsPath = statsPath ?? join2(process.cwd(), DEFAULT_STATS_RELATIVE_PATH);
2890
+ }
2891
+ async flush() {
2892
+ const events = this.collector.flush();
2893
+ if (events.length === 0) {
2894
+ return;
2895
+ }
2896
+ const incoming = aggregateEvents(events);
2897
+ const existing = await this.readExistingStats();
2898
+ const merged = existing ? mergeStats(existing, incoming) : incoming;
2899
+ await this.atomicWrite(merged);
2900
+ }
2901
+ async readExistingStats() {
2902
+ try {
2903
+ const raw = await readFile(this.statsPath, "utf-8");
2904
+ return JSON.parse(raw);
2905
+ } catch {
2906
+ return null;
2907
+ }
2908
+ }
2909
+ async atomicWrite(stats) {
2910
+ const dir = dirname(this.statsPath);
2911
+ if (!existsSync2(dir)) {
2912
+ await mkdir(dir, { recursive: true });
2913
+ }
2914
+ const tmpPath = this.statsPath + ".tmp";
2915
+ await writeFile(tmpPath, JSON.stringify(stats, null, 2), "utf-8");
2916
+ await rename(tmpPath, this.statsPath);
2917
+ }
2918
+ };
2919
+ RuleStatsWriter = __decorateClass([
2920
+ Injectable7(),
2921
+ __decorateParam(1, Optional()),
2922
+ __decorateParam(1, Inject(STATS_FILE_PATH))
2923
+ ], RuleStatsWriter);
2924
+
2764
2925
  // src/custom/custom.module.ts
2765
2926
  import { Module } from "@nestjs/common";
2766
2927
 
2767
2928
  // src/custom/custom.service.ts
2768
- import { Injectable as Injectable6, Logger as Logger5 } from "@nestjs/common";
2929
+ import { Injectable as Injectable8, Logger as Logger5 } from "@nestjs/common";
2769
2930
  import * as fs from "fs/promises";
2770
2931
  import * as path3 from "path";
2771
2932
 
@@ -2885,7 +3046,7 @@ var CustomService = class {
2885
3046
  }
2886
3047
  };
2887
3048
  CustomService = __decorateClass([
2888
- Injectable6()
3049
+ Injectable8()
2889
3050
  ], CustomService);
2890
3051
 
2891
3052
  // src/custom/custom.module.ts
@@ -2902,14 +3063,14 @@ CustomModule = __decorateClass([
2902
3063
  import { Module as Module2 } from "@nestjs/common";
2903
3064
 
2904
3065
  // src/config/config.service.ts
2905
- import { Injectable as Injectable7, Logger as Logger6 } from "@nestjs/common";
2906
- import { existsSync as existsSync5, statSync } from "fs";
3066
+ import { Injectable as Injectable9, Logger as Logger6 } from "@nestjs/common";
3067
+ import { existsSync as existsSync6, statSync } from "fs";
2907
3068
  import { stat, realpath } from "fs/promises";
2908
3069
  import * as path7 from "path";
2909
3070
 
2910
3071
  // src/config/config.loader.ts
2911
3072
  import * as fs2 from "fs/promises";
2912
- import { existsSync as existsSync2 } from "fs";
3073
+ import { existsSync as existsSync3 } from "fs";
2913
3074
  import * as path4 from "path";
2914
3075
 
2915
3076
  // src/config/config.schema.ts
@@ -3128,7 +3289,7 @@ var ConfigLoadError = class extends Error {
3128
3289
  function findConfigFile(projectRoot) {
3129
3290
  for (const fileName of CONFIG_FILE_NAMES) {
3130
3291
  const filePath = path4.join(projectRoot, fileName);
3131
- if (existsSync2(filePath)) {
3292
+ if (existsSync3(filePath)) {
3132
3293
  return filePath;
3133
3294
  }
3134
3295
  }
@@ -3138,7 +3299,7 @@ function findDeprecatedConfigFiles(projectRoot) {
3138
3299
  const found = [];
3139
3300
  for (const fileName of DEPRECATED_CONFIG_FILE_NAMES) {
3140
3301
  const filePath = path4.join(projectRoot, fileName);
3141
- if (existsSync2(filePath)) {
3302
+ if (existsSync3(filePath)) {
3142
3303
  found.push(filePath);
3143
3304
  }
3144
3305
  }
@@ -3195,7 +3356,7 @@ function findProjectRoot(startDir) {
3195
3356
  return currentDir;
3196
3357
  }
3197
3358
  const packageJsonPath = path4.join(currentDir, "package.json");
3198
- if (existsSync2(packageJsonPath) && firstPackageJsonDir === null) {
3359
+ if (existsSync3(packageJsonPath) && firstPackageJsonDir === null) {
3199
3360
  firstPackageJsonDir = currentDir;
3200
3361
  }
3201
3362
  const parentDir = path4.dirname(currentDir);
@@ -3271,7 +3432,7 @@ async function loadConfig(projectRoot) {
3271
3432
  }
3272
3433
 
3273
3434
  // src/config/ignore.parser.ts
3274
- import { existsSync as existsSync3 } from "fs";
3435
+ import { existsSync as existsSync4 } from "fs";
3275
3436
  import * as path5 from "path";
3276
3437
 
3277
3438
  // src/shared/file.utils.ts
@@ -3457,7 +3618,7 @@ function parseIgnoreContent(content) {
3457
3618
  }
3458
3619
  async function loadIgnoreFile(projectRoot) {
3459
3620
  const ignorePath = path5.join(projectRoot, IGNORE_FILE_NAME);
3460
- if (!existsSync3(ignorePath)) {
3621
+ if (!existsSync4(ignorePath)) {
3461
3622
  return {
3462
3623
  patterns: [],
3463
3624
  source: null
@@ -3547,7 +3708,7 @@ function getDefaultIgnorePatterns() {
3547
3708
  }
3548
3709
 
3549
3710
  // src/config/context.loader.ts
3550
- import { existsSync as existsSync4 } from "fs";
3711
+ import { existsSync as existsSync5 } from "fs";
3551
3712
  import * as path6 from "path";
3552
3713
  var CONTEXT_DIR_NAME = ".codingbuddy";
3553
3714
  var KNOWN_SUBDIRS = {
@@ -3614,7 +3775,7 @@ async function loadContextFile(contextDir, relativePath) {
3614
3775
  }
3615
3776
  async function loadContextFiles(projectRoot) {
3616
3777
  const contextDir = path6.join(projectRoot, CONTEXT_DIR_NAME);
3617
- if (!existsSync4(contextDir)) {
3778
+ if (!existsSync5(contextDir)) {
3618
3779
  return {
3619
3780
  files: [],
3620
3781
  source: null,
@@ -3707,7 +3868,7 @@ var ConfigService = class {
3707
3868
  const envRoot = process.env.CODINGBUDDY_PROJECT_ROOT;
3708
3869
  if (envRoot) {
3709
3870
  const normalizedPath = path7.resolve(envRoot);
3710
- if (existsSync5(normalizedPath)) {
3871
+ if (existsSync6(normalizedPath)) {
3711
3872
  try {
3712
3873
  const stat2 = statSync(normalizedPath);
3713
3874
  if (stat2.isDirectory()) {
@@ -3930,11 +4091,11 @@ var ConfigService = class {
3930
4091
  }
3931
4092
  };
3932
4093
  ConfigService = __decorateClass([
3933
- Injectable7()
4094
+ Injectable9()
3934
4095
  ], ConfigService);
3935
4096
 
3936
4097
  // src/config/config-diff.service.ts
3937
- import { Injectable as Injectable8 } from "@nestjs/common";
4098
+ import { Injectable as Injectable10 } from "@nestjs/common";
3938
4099
  var ConfigDiffService = class {
3939
4100
  /**
3940
4101
  * Compare project analysis with current config
@@ -4106,7 +4267,7 @@ var ConfigDiffService = class {
4106
4267
  }
4107
4268
  };
4108
4269
  ConfigDiffService = __decorateClass([
4109
- Injectable8()
4270
+ Injectable10()
4110
4271
  ], ConfigDiffService);
4111
4272
 
4112
4273
  // src/config/config.module.ts
@@ -4125,8 +4286,8 @@ var RulesModule = class {
4125
4286
  RulesModule = __decorateClass([
4126
4287
  Module3({
4127
4288
  imports: [CustomModule, CodingBuddyConfigModule],
4128
- providers: [RulesService, RuleInsightsService],
4129
- exports: [RulesService, RuleInsightsService]
4289
+ providers: [RulesService, RuleInsightsService, RuleEventCollector, RuleStatsWriter],
4290
+ exports: [RulesService, RuleInsightsService, RuleEventCollector, RuleStatsWriter]
4130
4291
  })
4131
4292
  ], RulesModule);
4132
4293