agentic-qe 3.8.4 → 3.8.6

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 (198) hide show
  1. package/.claude/skills/skills-manifest.json +1 -1
  2. package/CHANGELOG.md +12 -0
  3. package/dist/cli/bundle.js +694 -694
  4. package/dist/cli/commands/hooks-handlers/command-hooks.d.ts +12 -0
  5. package/dist/cli/commands/hooks-handlers/command-hooks.js +253 -0
  6. package/dist/cli/commands/hooks-handlers/editing-hooks.d.ts +12 -0
  7. package/dist/cli/commands/hooks-handlers/editing-hooks.js +161 -0
  8. package/dist/cli/commands/hooks-handlers/hooks-dream-learning.d.ts +57 -0
  9. package/dist/cli/commands/hooks-handlers/hooks-dream-learning.js +263 -0
  10. package/dist/cli/commands/hooks-handlers/hooks-shared.d.ts +52 -0
  11. package/dist/cli/commands/hooks-handlers/hooks-shared.js +223 -0
  12. package/dist/cli/commands/hooks-handlers/routing-hooks.d.ts +12 -0
  13. package/dist/cli/commands/hooks-handlers/routing-hooks.js +107 -0
  14. package/dist/cli/commands/hooks-handlers/session-hooks.d.ts +12 -0
  15. package/dist/cli/commands/hooks-handlers/session-hooks.js +171 -0
  16. package/dist/cli/commands/hooks-handlers/stats-hooks.d.ts +12 -0
  17. package/dist/cli/commands/hooks-handlers/stats-hooks.js +248 -0
  18. package/dist/cli/commands/hooks-handlers/task-hooks.d.ts +12 -0
  19. package/dist/cli/commands/hooks-handlers/task-hooks.js +152 -0
  20. package/dist/cli/commands/hooks.d.ts +3 -23
  21. package/dist/cli/commands/hooks.js +16 -1459
  22. package/dist/coordination/mincut/phase-executor.d.ts +27 -0
  23. package/dist/coordination/mincut/phase-executor.js +70 -0
  24. package/dist/coordination/mincut/time-crystal-analysis.d.ts +35 -0
  25. package/dist/coordination/mincut/time-crystal-analysis.js +237 -0
  26. package/dist/coordination/mincut/time-crystal-persistence.d.ts +35 -0
  27. package/dist/coordination/mincut/time-crystal-persistence.js +81 -0
  28. package/dist/coordination/mincut/time-crystal-scheduling.d.ts +34 -0
  29. package/dist/coordination/mincut/time-crystal-scheduling.js +213 -0
  30. package/dist/coordination/mincut/time-crystal-types.d.ts +278 -0
  31. package/dist/coordination/mincut/time-crystal-types.js +67 -0
  32. package/dist/coordination/mincut/time-crystal.d.ts +8 -438
  33. package/dist/coordination/mincut/time-crystal.js +87 -905
  34. package/dist/domains/base-domain-coordinator.d.ts +0 -15
  35. package/dist/domains/base-domain-coordinator.js +7 -5
  36. package/dist/domains/chaos-resilience/coordinator.d.ts +0 -4
  37. package/dist/domains/chaos-resilience/coordinator.js +24 -22
  38. package/dist/domains/chaos-resilience/services/chaos-engineer.d.ts +0 -4
  39. package/dist/domains/chaos-resilience/services/chaos-engineer.js +47 -45
  40. package/dist/domains/chaos-resilience/services/performance-profiler.d.ts +0 -4
  41. package/dist/domains/chaos-resilience/services/performance-profiler.js +10 -8
  42. package/dist/domains/code-intelligence/coordinator-consensus.d.ts +0 -3
  43. package/dist/domains/code-intelligence/coordinator-consensus.js +8 -6
  44. package/dist/domains/code-intelligence/coordinator-gnn.d.ts +0 -3
  45. package/dist/domains/code-intelligence/coordinator-gnn.js +8 -6
  46. package/dist/domains/code-intelligence/coordinator-hypergraph.d.ts +0 -3
  47. package/dist/domains/code-intelligence/coordinator-hypergraph.js +13 -11
  48. package/dist/domains/code-intelligence/coordinator.d.ts +0 -3
  49. package/dist/domains/code-intelligence/coordinator.js +21 -19
  50. package/dist/domains/code-intelligence/services/c4-model/index.d.ts +0 -3
  51. package/dist/domains/code-intelligence/services/c4-model/index.js +5 -3
  52. package/dist/domains/code-intelligence/services/knowledge-graph.d.ts +0 -6
  53. package/dist/domains/code-intelligence/services/knowledge-graph.js +4 -2
  54. package/dist/domains/code-intelligence/services/product-factors-bridge.d.ts +0 -5
  55. package/dist/domains/code-intelligence/services/product-factors-bridge.js +9 -7
  56. package/dist/domains/contract-testing/coordinator.d.ts +0 -6
  57. package/dist/domains/contract-testing/coordinator.js +25 -23
  58. package/dist/domains/contract-testing/services/contract-validator.d.ts +0 -4
  59. package/dist/domains/contract-testing/services/contract-validator.js +4 -2
  60. package/dist/domains/contract-testing/services/schema-validator.js +1 -1
  61. package/dist/domains/coverage-analysis/coordinator.js +13 -11
  62. package/dist/domains/coverage-analysis/services/coverage-analyzer.js +4 -2
  63. package/dist/domains/coverage-analysis/services/gap-detector.js +3 -1
  64. package/dist/domains/coverage-analysis/services/hnsw-index.d.ts +0 -15
  65. package/dist/domains/coverage-analysis/services/hnsw-index.js +3 -1
  66. package/dist/domains/coverage-analysis/services/sublinear-analyzer.d.ts +0 -26
  67. package/dist/domains/coverage-analysis/services/sublinear-analyzer.js +3 -1
  68. package/dist/domains/defect-intelligence/coordinator.d.ts +1 -10
  69. package/dist/domains/defect-intelligence/coordinator.js +5 -3
  70. package/dist/domains/defect-intelligence/services/causal-root-cause-analyzer.d.ts +0 -6
  71. package/dist/domains/defect-intelligence/services/causal-root-cause-analyzer.js +3 -1
  72. package/dist/domains/defect-intelligence/services/defect-predictor.d.ts +0 -6
  73. package/dist/domains/defect-intelligence/services/defect-predictor.js +5 -3
  74. package/dist/domains/defect-intelligence/services/pattern-learner.d.ts +0 -4
  75. package/dist/domains/defect-intelligence/services/pattern-learner.js +3 -1
  76. package/dist/domains/defect-intelligence/services/root-cause-analyzer.d.ts +0 -6
  77. package/dist/domains/defect-intelligence/services/root-cause-analyzer.js +3 -1
  78. package/dist/domains/enterprise-integration/coordinator.js +6 -4
  79. package/dist/domains/learning-optimization/coordinator-consensus.d.ts +0 -3
  80. package/dist/domains/learning-optimization/coordinator-consensus.js +8 -6
  81. package/dist/domains/learning-optimization/coordinator.d.ts +0 -3
  82. package/dist/domains/learning-optimization/coordinator.js +15 -13
  83. package/dist/domains/learning-optimization/services/learning-coordinator.d.ts +0 -4
  84. package/dist/domains/learning-optimization/services/learning-coordinator.js +4 -2
  85. package/dist/domains/quality-assessment/coordinator-claim-verifier.d.ts +0 -3
  86. package/dist/domains/quality-assessment/coordinator-claim-verifier.js +6 -4
  87. package/dist/domains/quality-assessment/coordinator-gate-evaluation.d.ts +0 -4
  88. package/dist/domains/quality-assessment/coordinator-gate-evaluation.js +9 -7
  89. package/dist/domains/quality-assessment/coordinator-rl-integration.d.ts +0 -3
  90. package/dist/domains/quality-assessment/coordinator-rl-integration.js +10 -8
  91. package/dist/domains/quality-assessment/coordinator.d.ts +0 -15
  92. package/dist/domains/quality-assessment/coordinator.js +14 -12
  93. package/dist/domains/quality-assessment/services/deployment-advisor.d.ts +0 -10
  94. package/dist/domains/quality-assessment/services/deployment-advisor.js +4 -2
  95. package/dist/domains/quality-assessment/services/quality-analyzer.d.ts +0 -6
  96. package/dist/domains/quality-assessment/services/quality-analyzer.js +4 -2
  97. package/dist/domains/requirements-validation/coordinator.d.ts +0 -3
  98. package/dist/domains/requirements-validation/coordinator.js +15 -13
  99. package/dist/domains/requirements-validation/services/product-factors-assessment/code-intelligence/codebase-analyzer.d.ts +0 -5
  100. package/dist/domains/requirements-validation/services/product-factors-assessment/code-intelligence/codebase-analyzer.js +15 -13
  101. package/dist/domains/requirements-validation/services/product-factors-assessment/product-factors-service.d.ts +0 -6
  102. package/dist/domains/requirements-validation/services/product-factors-assessment/product-factors-service.js +9 -7
  103. package/dist/domains/requirements-validation/services/requirements-validator.d.ts +0 -6
  104. package/dist/domains/requirements-validation/services/requirements-validator.js +4 -2
  105. package/dist/domains/security-compliance/coordinator.js +24 -22
  106. package/dist/domains/security-compliance/services/scanners/dast-scanner.d.ts +0 -21
  107. package/dist/domains/security-compliance/services/scanners/dast-scanner.js +4 -2
  108. package/dist/domains/security-compliance/services/scanners/sast-scanner.d.ts +0 -4
  109. package/dist/domains/security-compliance/services/scanners/sast-scanner.js +3 -1
  110. package/dist/domains/security-compliance/services/security-auditor-dast.d.ts +0 -4
  111. package/dist/domains/security-compliance/services/security-auditor-dast.js +3 -1
  112. package/dist/domains/security-compliance/services/security-auditor-sast.d.ts +0 -3
  113. package/dist/domains/security-compliance/services/security-auditor-sast.js +3 -1
  114. package/dist/domains/security-compliance/services/security-auditor-secrets.d.ts +0 -3
  115. package/dist/domains/security-compliance/services/security-auditor-secrets.js +3 -1
  116. package/dist/domains/security-compliance/services/security-auditor.js +11 -9
  117. package/dist/domains/test-execution/coordinator.js +11 -9
  118. package/dist/domains/test-execution/services/auth-state-manager.d.ts +0 -3
  119. package/dist/domains/test-execution/services/auth-state-manager.js +4 -2
  120. package/dist/domains/test-execution/services/e2e/e2e-coordinator.d.ts +0 -14
  121. package/dist/domains/test-execution/services/e2e/e2e-coordinator.js +3 -1
  122. package/dist/domains/test-execution/services/flaky-detector.js +4 -2
  123. package/dist/domains/test-execution/services/retry-handler.js +3 -1
  124. package/dist/domains/test-execution/services/test-executor.js +3 -1
  125. package/dist/domains/test-generation/coordinator.d.ts +0 -17
  126. package/dist/domains/test-generation/coordinator.js +33 -31
  127. package/dist/domains/test-generation/pattern-injection/edge-case-injector.d.ts +0 -5
  128. package/dist/domains/test-generation/pattern-injection/edge-case-injector.js +3 -1
  129. package/dist/domains/test-generation/services/code-transform-integration.d.ts +0 -7
  130. package/dist/domains/test-generation/services/code-transform-integration.js +3 -1
  131. package/dist/domains/test-generation/services/coherence-gate-service.d.ts +0 -3
  132. package/dist/domains/test-generation/services/coherence-gate-service.js +3 -1
  133. package/dist/domains/test-generation/services/test-generator.d.ts +0 -8
  134. package/dist/domains/test-generation/services/test-generator.js +5 -3
  135. package/dist/domains/visual-accessibility/coordinator.d.ts +0 -3
  136. package/dist/domains/visual-accessibility/coordinator.js +14 -12
  137. package/dist/domains/visual-accessibility/services/accessibility-tester-browser.d.ts +0 -3
  138. package/dist/domains/visual-accessibility/services/accessibility-tester-browser.js +52 -50
  139. package/dist/domains/visual-accessibility/services/accessibility-tester.d.ts +0 -4
  140. package/dist/domains/visual-accessibility/services/accessibility-tester.js +8 -6
  141. package/dist/domains/visual-accessibility/services/axe-core-integration.d.ts +0 -3
  142. package/dist/domains/visual-accessibility/services/axe-core-integration.js +20 -18
  143. package/dist/domains/visual-accessibility/services/browser-security-scanner.d.ts +0 -4
  144. package/dist/domains/visual-accessibility/services/browser-security-scanner.js +6 -4
  145. package/dist/domains/visual-accessibility/services/browser-swarm-coordinator.d.ts +0 -30
  146. package/dist/domains/visual-accessibility/services/browser-swarm-coordinator.js +5 -3
  147. package/dist/domains/visual-accessibility/services/viewport-capture.d.ts +0 -27
  148. package/dist/domains/visual-accessibility/services/viewport-capture.js +6 -4
  149. package/dist/domains/visual-accessibility/services/visual-regression.d.ts +0 -26
  150. package/dist/domains/visual-accessibility/services/visual-regression.js +4 -2
  151. package/dist/domains/visual-accessibility/services/visual-tester.d.ts +0 -4
  152. package/dist/domains/visual-accessibility/services/visual-tester.js +4 -2
  153. package/dist/governance/deterministic-gateway-integration.js +1 -1
  154. package/dist/learning/agent-routing.d.ts +53 -0
  155. package/dist/learning/agent-routing.js +142 -0
  156. package/dist/learning/embedding-utils.d.ts +34 -0
  157. package/dist/learning/embedding-utils.js +95 -0
  158. package/dist/learning/pattern-promotion.d.ts +63 -0
  159. package/dist/learning/pattern-promotion.js +187 -0
  160. package/dist/learning/pretrained-patterns.d.ts +14 -0
  161. package/dist/learning/pretrained-patterns.js +726 -0
  162. package/dist/learning/qe-reasoning-bank-types.d.ts +174 -0
  163. package/dist/learning/qe-reasoning-bank-types.js +24 -0
  164. package/dist/learning/qe-reasoning-bank.d.ts +9 -192
  165. package/dist/learning/qe-reasoning-bank.js +48 -1093
  166. package/dist/mcp/bundle.js +335 -335
  167. package/dist/mcp/security/validators/command-validator.d.ts +1 -40
  168. package/dist/mcp/security/validators/command-validator.js +2 -122
  169. package/dist/mcp/security/validators/crypto-validator.d.ts +1 -39
  170. package/dist/mcp/security/validators/crypto-validator.js +2 -71
  171. package/dist/mcp/security/validators/input-sanitizer.d.ts +1 -55
  172. package/dist/mcp/security/validators/input-sanitizer.js +2 -156
  173. package/dist/mcp/security/validators/interfaces.d.ts +1 -163
  174. package/dist/mcp/security/validators/interfaces.js +2 -5
  175. package/dist/mcp/security/validators/path-traversal-validator.d.ts +1 -49
  176. package/dist/mcp/security/validators/path-traversal-validator.js +2 -241
  177. package/dist/mcp/security/validators/regex-safety-validator.d.ts +1 -49
  178. package/dist/mcp/security/validators/regex-safety-validator.js +2 -182
  179. package/dist/mcp/security/validators/validation-orchestrator.d.ts +1 -65
  180. package/dist/mcp/security/validators/validation-orchestrator.js +2 -145
  181. package/dist/shared/io/file-reader.js +1 -1
  182. package/dist/shared/security/command-validator.d.ts +44 -0
  183. package/dist/shared/security/command-validator.js +126 -0
  184. package/dist/shared/security/crypto-validator.d.ts +43 -0
  185. package/dist/shared/security/crypto-validator.js +75 -0
  186. package/dist/shared/security/index.d.ts +7 -0
  187. package/dist/shared/security/index.js +15 -0
  188. package/dist/shared/security/input-sanitizer.d.ts +59 -0
  189. package/dist/shared/security/input-sanitizer.js +160 -0
  190. package/dist/shared/security/path-traversal-validator.d.ts +53 -0
  191. package/dist/shared/security/path-traversal-validator.js +245 -0
  192. package/dist/shared/security/regex-safety-validator.d.ts +53 -0
  193. package/dist/shared/security/regex-safety-validator.js +186 -0
  194. package/dist/shared/security/validation-orchestrator.d.ts +69 -0
  195. package/dist/shared/security/validation-orchestrator.js +149 -0
  196. package/dist/shared/security/validators-interfaces.d.ts +167 -0
  197. package/dist/shared/security/validators-interfaces.js +9 -0
  198. package/package.json +1 -1
@@ -4,475 +4,17 @@
4
4
  * ADR-021: QE ReasoningBank for Pattern Learning
5
5
  *
6
6
  * Self-learning hooks system for pattern recognition and guidance generation.
7
- * This module provides CLI commands for the QE hooks system.
7
+ * This module composes all hooks subcommands from per-handler modules.
8
8
  */
9
- import { randomUUID } from 'crypto';
10
9
  import { Command } from 'commander';
11
- import chalk from 'chalk';
12
- import path from 'node:path';
13
- import { createQEReasoningBank, } from '../../learning/qe-reasoning-bank.js';
14
- import { safeJsonParse } from '../../shared/safe-json.js';
15
- import { QEHookRegistry, QE_HOOK_EVENTS, setupQEHooks, } from '../../learning/qe-hooks.js';
16
- import { HybridMemoryBackend } from '../../kernel/hybrid-backend.js';
17
- import { findProjectRoot } from '../../kernel/unified-memory.js';
18
- import { wasmLoader, createCoherenceService, } from '../../integrations/coherence/index.js';
19
- const state = {
20
- reasoningBank: null,
21
- hookRegistry: null,
22
- coherenceService: null,
23
- sessionId: null,
24
- initialized: false,
25
- initializationPromise: null,
26
- };
27
- /**
28
- * Get or create the hooks system with proper initialization
29
- */
30
- async function getHooksSystem() {
31
- // If already initializing, wait for it
32
- if (state.initializationPromise) {
33
- await state.initializationPromise;
34
- }
35
- // If already initialized, return
36
- if (state.initialized && state.reasoningBank && state.hookRegistry) {
37
- return {
38
- reasoningBank: state.reasoningBank,
39
- hookRegistry: state.hookRegistry,
40
- };
41
- }
42
- // Initialize with timeout protection
43
- state.initializationPromise = initializeHooksSystem();
44
- await state.initializationPromise;
45
- state.initializationPromise = null;
46
- if (!state.reasoningBank || !state.hookRegistry) {
47
- throw new Error('Failed to initialize hooks system');
48
- }
49
- return {
50
- reasoningBank: state.reasoningBank,
51
- hookRegistry: state.hookRegistry,
52
- };
53
- }
54
- /**
55
- * Initialize the hooks system
56
- */
57
- async function initializeHooksSystem() {
58
- if (state.initialized)
59
- return;
60
- try {
61
- // Create memory backend — always resolve to project root DB
62
- const projectRoot = findProjectRoot();
63
- const dataDir = path.join(projectRoot, '.agentic-qe');
64
- // Use hybrid backend with timeout protection
65
- const memoryBackend = await createHybridBackendWithTimeout(dataDir);
66
- // Initialize CoherenceService (optional - falls back to TypeScript implementation)
67
- try {
68
- state.coherenceService = await createCoherenceService(wasmLoader);
69
- console.log(chalk.dim('[hooks] CoherenceService initialized with WASM engines'));
70
- }
71
- catch (error) {
72
- // WASM not available - will use fallback
73
- console.log(chalk.dim(`[hooks] CoherenceService WASM unavailable, using fallback: ${error instanceof Error ? error.message : 'unknown'}`));
74
- }
75
- // Create reasoning bank with coherence service
76
- state.reasoningBank = createQEReasoningBank(memoryBackend, undefined, {
77
- enableLearning: true,
78
- enableGuidance: true,
79
- enableRouting: true,
80
- embeddingDimension: 384,
81
- useONNXEmbeddings: true, // Use real transformer embeddings (384-dim)
82
- }, state.coherenceService ?? undefined);
83
- // Initialize with timeout
84
- const initTimeout = 10000; // 10 seconds
85
- const initPromise = state.reasoningBank.initialize();
86
- const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('ReasoningBank init timeout')), initTimeout));
87
- await Promise.race([initPromise, timeoutPromise]);
88
- // Wire RVF dual-writer for vector replication (optional, best-effort)
89
- try {
90
- const { getSharedRvfDualWriter } = await import('../../integrations/ruvector/shared-rvf-dual-writer.js');
91
- const dualWriter = await getSharedRvfDualWriter();
92
- if (dualWriter)
93
- state.reasoningBank.setRvfDualWriter(dualWriter);
94
- }
95
- catch (e) {
96
- if (process.env.DEBUG)
97
- console.debug('[hooks] RVF wiring skipped:', e instanceof Error ? e.message : e);
98
- }
99
- // Setup hook registry
100
- state.hookRegistry = setupQEHooks(state.reasoningBank);
101
- state.initialized = true;
102
- console.log(chalk.dim('[hooks] System initialized'));
103
- }
104
- catch (error) {
105
- // Create minimal fallback state
106
- console.warn(chalk.yellow(`[hooks] Using fallback mode: ${error instanceof Error ? error.message : 'unknown error'}`));
107
- // Create in-memory fallback backend
108
- // NOTE: RVF dual-writer is intentionally NOT wired here — the fallback
109
- // uses an in-memory backend with no disk access, so RVF replication
110
- // (which requires the unified memory DB) is not meaningful.
111
- const fallbackBackend = createInMemoryBackend();
112
- state.reasoningBank = createQEReasoningBank(fallbackBackend, undefined, {
113
- enableLearning: true,
114
- enableGuidance: true,
115
- enableRouting: true,
116
- });
117
- // Skip full initialization for fallback
118
- state.hookRegistry = new QEHookRegistry();
119
- state.hookRegistry.initialize(state.reasoningBank);
120
- state.initialized = true;
121
- }
122
- }
123
- /**
124
- * Create hybrid backend with timeout protection
125
- *
126
- * ADR-046: Uses unified memory.db path for consistency with all other components.
127
- * HybridMemoryBackend delegates to UnifiedMemoryManager singleton.
128
- */
129
- async function createHybridBackendWithTimeout(dataDir) {
130
- const timeoutMs = 5000;
131
- // ADR-046: Use unified memory.db path - same as all other components
132
- // HybridMemoryBackend is a facade over UnifiedMemoryManager
133
- const backend = new HybridMemoryBackend({
134
- sqlite: {
135
- path: path.join(dataDir, 'memory.db'), // ADR-046: Unified storage
136
- walMode: true,
137
- poolSize: 3,
138
- busyTimeout: 5000,
139
- },
140
- // agentdb.path is ignored - vectors stored in unified memory.db
141
- enableFallback: true,
142
- defaultNamespace: 'qe-patterns',
143
- });
144
- const initPromise = backend.initialize();
145
- const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Backend init timeout')), timeoutMs));
146
- await Promise.race([initPromise, timeoutPromise]);
147
- return backend;
148
- }
149
- /**
150
- * Create in-memory fallback backend
151
- */
152
- function createInMemoryBackend() {
153
- const store = new Map();
154
- return {
155
- initialize: async () => { },
156
- dispose: async () => {
157
- store.clear();
158
- },
159
- get: async (key) => {
160
- const entry = store.get(key);
161
- return entry ? entry.value : undefined;
162
- },
163
- set: async (key, value, _options) => {
164
- store.set(key, { value });
165
- },
166
- delete: async (key) => {
167
- return store.delete(key);
168
- },
169
- has: async (key) => store.has(key),
170
- search: async (pattern, _limit) => {
171
- const regex = new RegExp(pattern.replace(/\*/g, '.*'));
172
- return Array.from(store.keys()).filter((k) => regex.test(k));
173
- },
174
- vectorSearch: async (_embedding, _k) => {
175
- return [];
176
- },
177
- storeVector: async (_key, _embedding, _metadata) => {
178
- // No-op for in-memory fallback
179
- },
180
- count: async (namespace) => {
181
- let count = 0;
182
- const prefix = `${namespace}:`;
183
- for (const key of store.keys()) {
184
- if (key.startsWith(prefix)) {
185
- count++;
186
- }
187
- }
188
- return count;
189
- },
190
- hasCodeIntelligenceIndex: async () => {
191
- const prefix = 'code-intelligence:kg:';
192
- for (const key of store.keys()) {
193
- if (key.startsWith(prefix)) {
194
- return true;
195
- }
196
- }
197
- return false;
198
- },
199
- };
200
- }
201
- // ============================================================================
202
- // Helper Functions
203
- // ============================================================================
204
- function printJson(data) {
205
- console.log(JSON.stringify(data, null, 2));
206
- }
207
- function printSuccess(message) {
208
- console.log(chalk.green('✓'), message);
209
- }
210
- function printError(message) {
211
- console.error(chalk.red('✗'), message);
212
- }
213
- // ============================================================================
214
- // Dream Scheduler State (persisted in kv_store between hook invocations)
215
- // ============================================================================
216
- const DREAM_STATE_KEY = 'dream-scheduler:hook-state';
217
- const DREAM_INTERVAL_MS = 3600000; // 1 hour between auto-dreams
218
- const DREAM_EXPERIENCE_THRESHOLD = 20; // experiences before triggering
219
- const DREAM_MIN_GAP_MS = 300000; // 5 minutes minimum between dreams
220
- /**
221
- * Check if a dream cycle should be triggered and run it if so.
222
- * Called from post-task hook after recording each experience.
223
- *
224
- * Trigger conditions (any of):
225
- * 1. Time-based: >1hr since last dream
226
- * 2. Experience-based: >20 experiences since last dream
227
- *
228
- * Guard: minimum 5 minutes between dreams
229
- */
230
- async function checkAndTriggerDream(memoryBackend) {
231
- try {
232
- // Load persisted dream state
233
- const dreamState = await memoryBackend.get(DREAM_STATE_KEY);
234
- if (!dreamState) {
235
- return { triggered: false, reason: 'no-state' };
236
- }
237
- const now = Date.now();
238
- const lastDreamTime = dreamState.lastDreamTime ? new Date(dreamState.lastDreamTime).getTime() : 0;
239
- const timeSinceLastDream = now - lastDreamTime;
240
- // Guard: minimum gap
241
- if (timeSinceLastDream < DREAM_MIN_GAP_MS) {
242
- return { triggered: false, reason: 'too-soon' };
243
- }
244
- // Check triggers
245
- const timeTriggered = timeSinceLastDream >= DREAM_INTERVAL_MS;
246
- const experienceTriggered = dreamState.experienceCount >= DREAM_EXPERIENCE_THRESHOLD;
247
- if (!timeTriggered && !experienceTriggered) {
248
- return { triggered: false, reason: 'conditions-not-met' };
249
- }
250
- const reason = timeTriggered ? 'time-interval' : 'experience-threshold';
251
- console.log(chalk.dim(`[hooks] Dream trigger: ${reason} (${dreamState.experienceCount} experiences, ${Math.round(timeSinceLastDream / 60000)}min since last dream)`));
252
- // Run a quick dream cycle
253
- const { createDreamEngine } = await import('../../learning/dream/index.js');
254
- const { createQEReasoningBank: createRB } = await import('../../learning/qe-reasoning-bank.js');
255
- const engine = createDreamEngine({
256
- maxDurationMs: 10000, // 10s for hook-triggered dreams
257
- minConceptsRequired: 3,
258
- });
259
- await engine.initialize();
260
- // Load patterns from ReasoningBank
261
- const rb = createRB(memoryBackend, undefined, {
262
- enableLearning: true,
263
- enableGuidance: false,
264
- enableRouting: false,
265
- embeddingDimension: 384,
266
- useONNXEmbeddings: true,
267
- });
268
- await rb.initialize();
269
- const patternsResult = await rb.searchPatterns('', { limit: 100, minConfidence: 0.3 });
270
- if (patternsResult.success && patternsResult.value.length > 0) {
271
- const importPatterns = patternsResult.value.map(r => ({
272
- id: r.pattern.id,
273
- name: r.pattern.name,
274
- description: r.pattern.description || `${r.pattern.patternType} pattern`,
275
- domain: r.pattern.qeDomain || 'learning-optimization',
276
- patternType: r.pattern.patternType,
277
- confidence: r.pattern.confidence,
278
- successRate: r.pattern.successRate || 0.5,
279
- }));
280
- await engine.loadPatternsAsConcepts(importPatterns);
281
- }
282
- const result = await engine.dream(10000);
283
- // Update state
284
- dreamState.lastDreamTime = new Date().toISOString();
285
- dreamState.experienceCount = 0;
286
- dreamState.totalDreamsThisSession++;
287
- await memoryBackend.set(DREAM_STATE_KEY, dreamState);
288
- await engine.close();
289
- return {
290
- triggered: true,
291
- reason,
292
- insightsGenerated: result.insights.length,
293
- };
294
- }
295
- catch (error) {
296
- console.error(chalk.dim(`[hooks] Dream trigger failed: ${error instanceof Error ? error.message : 'unknown'}`));
297
- return { triggered: false, reason: 'error' };
298
- }
299
- }
300
- /**
301
- * Increment the experience counter in dream state.
302
- * Called from post-task hook.
303
- */
304
- async function incrementDreamExperience(memoryBackend) {
305
- try {
306
- let dreamState = await memoryBackend.get(DREAM_STATE_KEY);
307
- if (!dreamState) {
308
- dreamState = {
309
- lastDreamTime: null,
310
- experienceCount: 0,
311
- sessionStartTime: new Date().toISOString(),
312
- totalDreamsThisSession: 0,
313
- };
314
- }
315
- dreamState.experienceCount++;
316
- await memoryBackend.set(DREAM_STATE_KEY, dreamState);
317
- return dreamState.experienceCount;
318
- }
319
- catch {
320
- return 0;
321
- }
322
- }
323
- /**
324
- * Persist a command/edit experience directly to the captured_experiences table.
325
- * CLI hooks cannot use the MCP middleware wrapper, so they write directly.
326
- */
327
- async function persistCommandExperience(opts) {
328
- try {
329
- const { getUnifiedMemory } = await import('../../kernel/unified-memory.js');
330
- const um = getUnifiedMemory();
331
- if (!um.isInitialized()) {
332
- await um.initialize();
333
- }
334
- const db = um.getDatabase();
335
- const id = `cli-${Date.now()}-${randomUUID().slice(0, 8)}`;
336
- // Compute quality based on context rather than binary success/fail.
337
- // Duration-aware: fast successful ops get higher quality.
338
- // Source-aware: post-task and post-edit are higher signal than post-command.
339
- const durationMs = opts.durationMs || 0;
340
- let quality;
341
- if (opts.success) {
342
- // Successful: base 0.7, bonus for fast execution (< 5s), bonus for high-signal sources
343
- const speedBonus = durationMs > 0 && durationMs < 5000 ? 0.1 : 0;
344
- const sourceBonus = opts.source.includes('post-task') ? 0.1 : opts.source.includes('post-edit') ? 0.05 : 0;
345
- quality = Math.min(0.95, 0.7 + speedBonus + sourceBonus);
346
- }
347
- else {
348
- // Failed: base 0.3, but higher for post-task (still learned something)
349
- const sourceBonus = opts.source.includes('post-task') ? 0.15 : opts.source.includes('post-edit') ? 0.1 : 0;
350
- quality = Math.min(0.6, 0.3 + sourceBonus);
351
- }
352
- db.prepare(`
353
- INSERT OR REPLACE INTO captured_experiences
354
- (id, task, agent, domain, success, quality, duration_ms,
355
- started_at, completed_at, source)
356
- VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'), ?)
357
- `).run(id, opts.task.slice(0, 500), opts.agent, opts.domain, opts.success ? 1 : 0, quality, durationMs, opts.source);
358
- }
359
- catch (error) {
360
- // Best-effort — don't fail the hook
361
- console.error(chalk.dim(`[hooks] persistCommandExperience: ${error instanceof Error ? error.message : 'unknown'}`));
362
- }
363
- }
364
- /**
365
- * Lightweight experience-to-pattern consolidation.
366
- * Aggregates captured_experiences by domain+agent, and for clusters that meet
367
- * quality thresholds, creates new qe_patterns entries.
368
- * Called at session-end so patterns grow with each session.
369
- */
370
- async function consolidateExperiencesToPatterns() {
371
- const { getUnifiedMemory } = await import('../../kernel/unified-memory.js');
372
- const um = getUnifiedMemory();
373
- if (!um.isInitialized()) {
374
- await um.initialize();
375
- }
376
- const db = um.getDatabase();
377
- // Ensure consolidation columns exist (may be missing on older DBs)
378
- const existingCols = new Set(db.prepare('PRAGMA table_info(captured_experiences)').all().map(c => c.name));
379
- const migrations = [
380
- ['consolidated_into', 'TEXT DEFAULT NULL'],
381
- ['consolidation_count', 'INTEGER DEFAULT 1'],
382
- ['quality_updated_at', 'TEXT DEFAULT NULL'],
383
- ['reuse_success_count', 'INTEGER DEFAULT 0'],
384
- ['reuse_failure_count', 'INTEGER DEFAULT 0'],
385
- ];
386
- for (const [col, def] of migrations) {
387
- if (!existingCols.has(col)) {
388
- db.exec(`ALTER TABLE captured_experiences ADD COLUMN ${col} ${def}`);
389
- }
390
- }
391
- // Aggregate unprocessed experiences by domain+agent with quality thresholds.
392
- // Exclude 'cli-hook' agent — these are low-quality hook telemetry events
393
- // (quality ~0.40, success_rate ~0.24) that flood the pipeline and block
394
- // real pattern creation. See issue #348.
395
- const aggregates = db.prepare(`
396
- SELECT
397
- domain,
398
- agent,
399
- COUNT(*) as cnt,
400
- AVG(quality) as avg_quality,
401
- SUM(success) as successes,
402
- CAST(SUM(success) AS REAL) / COUNT(*) as success_rate,
403
- AVG(duration_ms) as avg_duration,
404
- GROUP_CONCAT(DISTINCT source) as sources
405
- FROM captured_experiences
406
- WHERE application_count = 0
407
- AND agent != 'cli-hook'
408
- GROUP BY domain, agent
409
- HAVING cnt >= 3 AND avg_quality >= 0.5 AND success_rate >= 0.6
410
- ORDER BY avg_quality DESC
411
- LIMIT 50
412
- `).all();
413
- if (aggregates.length === 0)
414
- return 0;
415
- const { v4: uuidv4 } = await import('uuid');
416
- let created = 0;
417
- for (const agg of aggregates) {
418
- try {
419
- // Use date-bucketed names so new patterns emerge as usage evolves,
420
- // instead of silently reinforcing one static pattern forever.
421
- const dateBucket = new Date().toISOString().slice(0, 7); // YYYY-MM
422
- const patternName = `${agg.agent}-${agg.domain}-${dateBucket}`;
423
- // Check for existing pattern with same name this month
424
- const existing = db.prepare(`
425
- SELECT id FROM qe_patterns
426
- WHERE qe_domain = ? AND name = ?
427
- LIMIT 1
428
- `).get(agg.domain, patternName);
429
- if (existing) {
430
- // Reinforce existing monthly pattern
431
- db.prepare(`
432
- UPDATE qe_patterns
433
- SET usage_count = usage_count + ?,
434
- successful_uses = successful_uses + ?,
435
- confidence = MIN(0.99, confidence + 0.01),
436
- quality_score = MIN(0.99, quality_score + 0.005),
437
- updated_at = datetime('now')
438
- WHERE id = ?
439
- `).run(agg.cnt, agg.successes, existing.id);
440
- }
441
- else {
442
- const patternId = uuidv4();
443
- const confidence = Math.min(0.95, agg.avg_quality * 0.8 + agg.success_rate * 0.2);
444
- const qualityScore = confidence * 0.3 + (Math.min(agg.cnt, 100) / 100) * 0.2 + agg.success_rate * 0.5;
445
- db.prepare(`
446
- INSERT INTO qe_patterns (
447
- id, pattern_type, qe_domain, domain, name, description,
448
- confidence, usage_count, success_rate, quality_score, tier,
449
- template_json, context_json, created_at, successful_uses
450
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), ?)
451
- `).run(patternId, 'workflow', agg.domain, agg.domain, patternName, `Auto-consolidated from ${agg.cnt} experiences. Agent: ${agg.agent}, success rate: ${(agg.success_rate * 100).toFixed(0)}%`, confidence, agg.cnt, agg.success_rate, qualityScore, 'short-term', JSON.stringify({ type: 'workflow', content: `${agg.agent} pattern for ${agg.domain}`, variables: [] }), JSON.stringify({ tags: (agg.sources || '').split(','), sourceType: 'session-consolidation', extractedAt: new Date().toISOString() }), agg.successes);
452
- created++;
453
- }
454
- // Mark experiences as processed
455
- db.prepare(`
456
- UPDATE captured_experiences
457
- SET application_count = application_count + 1
458
- WHERE domain = ? AND agent = ? AND application_count = 0
459
- `).run(agg.domain, agg.agent);
460
- }
461
- catch {
462
- // Skip on error (e.g. constraint violations)
463
- }
464
- }
465
- return created;
466
- }
467
- function printGuidance(guidance) {
468
- if (guidance.length === 0) {
469
- console.log(chalk.dim(' No specific guidance'));
470
- return;
471
- }
472
- guidance.forEach((g, i) => {
473
- console.log(chalk.cyan(` ${i + 1}.`), g);
474
- });
475
- }
10
+ import { QE_HOOK_EVENTS } from '../../learning/qe-hooks.js';
11
+ import { getHooksSystem, state, } from './hooks-handlers/hooks-shared.js';
12
+ import { registerEditingHooks } from './hooks-handlers/editing-hooks.js';
13
+ import { registerRoutingHooks } from './hooks-handlers/routing-hooks.js';
14
+ import { registerStatsHooks } from './hooks-handlers/stats-hooks.js';
15
+ import { registerSessionHooks } from './hooks-handlers/session-hooks.js';
16
+ import { registerTaskHooks } from './hooks-handlers/task-hooks.js';
17
+ import { registerCommandHooks } from './hooks-handlers/command-hooks.js';
476
18
  // ============================================================================
477
19
  // Hooks Command Creation
478
20
  // ============================================================================
@@ -507,998 +49,13 @@ Examples:
507
49
  aqe hooks stats
508
50
  aqe hooks list
509
51
  `);
510
- // -------------------------------------------------------------------------
511
- // pre-edit: Get guidance before editing a file
512
- // -------------------------------------------------------------------------
513
- hooks
514
- .command('pre-edit')
515
- .description('Get context and guidance before editing a file')
516
- .requiredOption('-f, --file <path>', 'File path to edit')
517
- .option('-o, --operation <type>', 'Operation type: create, update, delete, refactor', 'update')
518
- .option('--json', 'Output as JSON')
519
- .action(async (options) => {
520
- try {
521
- const { hookRegistry } = await getHooksSystem();
522
- const results = await hookRegistry.emit(QE_HOOK_EVENTS.PreTestGeneration, {
523
- targetFile: options.file,
524
- testType: 'unit',
525
- operation: options.operation,
526
- });
527
- const result = results[0] || { success: true, guidance: [], routing: null };
528
- if (options.json) {
529
- // Build additionalContext for Claude from guidance
530
- const guidanceLines = result.guidance || [];
531
- const agentHint = result.routing?.recommendedAgent
532
- ? `Recommended agent: ${result.routing.recommendedAgent} (${(result.routing.confidence * 100).toFixed(0)}% confidence).`
533
- : '';
534
- const contextStr = [
535
- agentHint,
536
- ...guidanceLines.map((g) => g),
537
- ].filter(Boolean).join(' ');
538
- printJson({
539
- hookSpecificOutput: {
540
- hookEventName: 'PreToolUse',
541
- additionalContext: contextStr || undefined,
542
- },
543
- file: options.file,
544
- operation: options.operation,
545
- patterns: result.routing?.patterns?.length || 0,
546
- });
547
- }
548
- else {
549
- console.log(chalk.bold('\n📝 Pre-Edit Analysis'));
550
- console.log(chalk.dim(` File: ${options.file}`));
551
- console.log(chalk.dim(` Operation: ${options.operation}`));
552
- if (result.routing) {
553
- console.log(chalk.bold('\n🎯 Recommended Agent:'), chalk.cyan(result.routing.recommendedAgent));
554
- console.log(chalk.dim(` Confidence: ${(result.routing.confidence * 100).toFixed(1)}%`));
555
- }
556
- console.log(chalk.bold('\n💡 Guidance:'));
557
- printGuidance(result.guidance || []);
558
- }
559
- return;
560
- }
561
- catch (error) {
562
- printError(`pre-edit failed: ${error instanceof Error ? error.message : 'unknown'}`);
563
- throw error;
564
- }
565
- });
566
- // -------------------------------------------------------------------------
567
- // post-edit: Record editing outcome for learning
568
- // -------------------------------------------------------------------------
569
- hooks
570
- .command('post-edit')
571
- .description('Record editing outcome for pattern learning')
572
- .requiredOption('-f, --file <path>', 'File path that was edited')
573
- .option('--success', 'Edit was successful')
574
- .option('--failure', 'Edit failed')
575
- .option('--pattern-id <id>', 'Pattern ID that was applied')
576
- .option('--json', 'Output as JSON')
577
- .action(async (options) => {
578
- try {
579
- const { hookRegistry } = await getHooksSystem();
580
- const success = options.success || !options.failure;
581
- // Generate synthetic patternId from file path if none provided
582
- const filePath = options.file || '';
583
- const fileName = filePath.split('/').pop() || 'unknown';
584
- const isTestFile = /\.(test|spec)\.(ts|js|tsx|jsx)$/.test(fileName);
585
- const domain = isTestFile ? 'test-generation' : 'code-intelligence';
586
- const syntheticPatternId = options.patternId || `edit:${domain}:${fileName}`;
587
- const results = await hookRegistry.emit(QE_HOOK_EVENTS.PostTestGeneration, {
588
- targetFile: options.file,
589
- success,
590
- patternId: syntheticPatternId,
591
- generatedTests: null,
592
- testCount: 0,
593
- });
594
- const result = results[0] || { success: true, patternsLearned: 0 };
595
- // Also explicitly call recordOutcome so qe_pattern_usage gets a row
596
- try {
597
- const { reasoningBank } = await getHooksSystem();
598
- await reasoningBank.recordOutcome({
599
- patternId: syntheticPatternId,
600
- success,
601
- metrics: { executionTimeMs: 0 },
602
- feedback: `Edit ${success ? 'succeeded' : 'failed'}: ${filePath}`,
603
- });
604
- }
605
- catch {
606
- // best-effort
607
- }
608
- // Persist as captured experience
609
- try {
610
- await persistCommandExperience({
611
- task: `edit: ${filePath}`,
612
- agent: 'cli-hook',
613
- domain,
614
- success,
615
- source: 'cli-hook-post-edit',
616
- });
617
- }
618
- catch {
619
- // best-effort
620
- }
621
- // Record experience for dream scheduler
622
- let dreamTriggered = false;
623
- try {
624
- const projectRoot = findProjectRoot();
625
- const dataDir = path.join(projectRoot, '.agentic-qe');
626
- const memoryBackend = await createHybridBackendWithTimeout(dataDir);
627
- await incrementDreamExperience(memoryBackend);
628
- }
629
- catch {
630
- // best-effort
631
- }
632
- if (options.json) {
633
- printJson({
634
- success: true,
635
- file: options.file,
636
- editSuccess: success,
637
- patternsLearned: result.patternsLearned || 0,
638
- dreamTriggered,
639
- });
640
- }
641
- else {
642
- printSuccess(`Recorded edit outcome for ${options.file}`);
643
- if (result.patternsLearned) {
644
- console.log(chalk.green(` Patterns learned: ${result.patternsLearned}`));
645
- }
646
- }
647
- return;
648
- }
649
- catch (error) {
650
- printError(`post-edit failed: ${error instanceof Error ? error.message : 'unknown'}`);
651
- throw error;
652
- }
653
- });
654
- // -------------------------------------------------------------------------
655
- // route: Route task to optimal agent
656
- // -------------------------------------------------------------------------
657
- hooks
658
- .command('route')
659
- .description('Route a task to the optimal QE agent')
660
- .requiredOption('-t, --task <description>', 'Task description')
661
- .option('-d, --domain <domain>', 'Target QE domain hint')
662
- .option('-c, --capabilities <caps...>', 'Required capabilities')
663
- .option('--json', 'Output as JSON')
664
- .action(async (options) => {
665
- try {
666
- const { reasoningBank } = await getHooksSystem();
667
- const request = {
668
- task: options.task,
669
- domain: options.domain,
670
- capabilities: options.capabilities,
671
- };
672
- const result = await reasoningBank.routeTask(request);
673
- if (!result.success) {
674
- throw new Error(result.error.message);
675
- }
676
- const routing = result.value;
677
- if (options.json) {
678
- printJson({
679
- recommendedAgent: routing.recommendedAgent,
680
- confidence: routing.confidence,
681
- alternatives: routing.alternatives,
682
- domains: routing.domains,
683
- patternCount: routing.patterns.length,
684
- guidance: routing.guidance,
685
- reasoning: routing.reasoning,
686
- });
687
- }
688
- else {
689
- console.log(chalk.bold('\n🎯 Task Routing Result'));
690
- console.log(chalk.dim(` Task: "${options.task}"`));
691
- console.log(chalk.bold('\n👤 Recommended Agent:'), chalk.cyan(routing.recommendedAgent));
692
- console.log(chalk.dim(` Confidence: ${(routing.confidence * 100).toFixed(1)}%`));
693
- if (routing.alternatives.length > 0) {
694
- console.log(chalk.bold('\n🔄 Alternatives:'));
695
- routing.alternatives.forEach((alt) => {
696
- console.log(chalk.dim(` - ${alt.agent}: ${(alt.score * 100).toFixed(1)}%`));
697
- });
698
- }
699
- console.log(chalk.bold('\n📂 Detected Domains:'), routing.domains.join(', '));
700
- console.log(chalk.bold('\n💡 Guidance:'));
701
- printGuidance(routing.guidance);
702
- console.log(chalk.bold('\n📖 Reasoning:'), chalk.dim(routing.reasoning));
703
- }
704
- // Persist routing decision for learning
705
- try {
706
- const { getUnifiedMemory } = await import('../../kernel/unified-memory.js');
707
- const um = getUnifiedMemory();
708
- if (!um.isInitialized()) {
709
- await um.initialize();
710
- }
711
- const db = um.getDatabase();
712
- const outcomeId = `route-${Date.now()}-${randomUUID().slice(0, 8)}`;
713
- db.prepare(`
714
- INSERT OR REPLACE INTO routing_outcomes (
715
- id, task_json, decision_json, used_agent,
716
- followed_recommendation, success, quality_score,
717
- duration_ms, error
718
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
719
- `).run(outcomeId, JSON.stringify({ description: options.task, domain: options.domain }), JSON.stringify({
720
- recommended: routing.recommendedAgent,
721
- confidence: routing.confidence,
722
- alternatives: routing.alternatives,
723
- }), routing.recommendedAgent, 1, // followed_recommendation = true (recommendation stage)
724
- 1, // success = true (routing itself succeeded)
725
- routing.confidence, 0, // duration not tracked at routing stage
726
- null);
727
- // Increment dream experience counter
728
- const projectRoot = findProjectRoot();
729
- const dataDir = path.join(projectRoot, '.agentic-qe');
730
- const memoryBackend = await createHybridBackendWithTimeout(dataDir);
731
- await incrementDreamExperience(memoryBackend);
732
- }
733
- catch (persistError) {
734
- // Best-effort — don't fail the hook
735
- console.error(chalk.dim(`[hooks] route persist: ${persistError instanceof Error ? persistError.message : 'unknown'}`));
736
- }
737
- return;
738
- }
739
- catch (error) {
740
- printError(`route failed: ${error instanceof Error ? error.message : 'unknown'}`);
741
- throw error;
742
- }
743
- });
744
- // -------------------------------------------------------------------------
745
- // stats: Get hooks system statistics
746
- // -------------------------------------------------------------------------
747
- hooks
748
- .command('stats')
749
- .description('Display hooks system statistics')
750
- .option('--json', 'Output as JSON')
751
- .action(async (options) => {
752
- try {
753
- const { reasoningBank } = await getHooksSystem();
754
- const stats = await reasoningBank.getStats();
755
- if (options.json) {
756
- printJson(stats);
757
- }
758
- else {
759
- console.log(chalk.bold('\n📊 Hooks System Statistics\n'));
760
- console.log(chalk.bold('Patterns:'));
761
- console.log(` Total: ${chalk.cyan(stats.totalPatterns)}`);
762
- console.log(` Short-term: ${stats.patternStoreStats.byTier.shortTerm}`);
763
- console.log(` Long-term: ${stats.patternStoreStats.byTier.longTerm}`);
764
- console.log(chalk.bold('\nBy Domain:'));
765
- for (const [domain, count] of Object.entries(stats.byDomain)) {
766
- if (count > 0) {
767
- console.log(` ${domain}: ${count}`);
768
- }
769
- }
770
- console.log(chalk.bold('\nRouting:'));
771
- console.log(` Requests: ${stats.routingRequests}`);
772
- console.log(` Avg Confidence: ${(stats.avgRoutingConfidence * 100).toFixed(1)}%`);
773
- console.log(chalk.bold('\nLearning:'));
774
- console.log(` Outcomes: ${stats.learningOutcomes}`);
775
- console.log(` Success Rate: ${(stats.patternSuccessRate * 100).toFixed(1)}%`);
776
- console.log(chalk.bold('\nSearch Performance:'));
777
- console.log(` Operations: ${stats.patternStoreStats.searchOperations}`);
778
- console.log(` Avg Latency: ${stats.patternStoreStats.avgSearchLatencyMs.toFixed(2)}ms`);
779
- console.log(` HNSW Native: ${stats.patternStoreStats.hnswStats.nativeAvailable ? '✓' : '✗'}`);
780
- }
781
- }
782
- catch (error) {
783
- printError(`stats failed: ${error instanceof Error ? error.message : 'unknown'}`);
784
- throw error;
785
- }
786
- });
787
- // -------------------------------------------------------------------------
788
- // list: List registered hook events
789
- // -------------------------------------------------------------------------
790
- hooks
791
- .command('list')
792
- .description('List all registered QE hook events')
793
- .option('--json', 'Output as JSON')
794
- .action(async (options) => {
795
- try {
796
- const { hookRegistry } = await getHooksSystem();
797
- const events = hookRegistry.getRegisteredEvents();
798
- if (options.json) {
799
- printJson({
800
- events,
801
- totalEvents: Object.keys(QE_HOOK_EVENTS).length,
802
- registeredEvents: events.length,
803
- });
804
- }
805
- else {
806
- console.log(chalk.bold('\n📋 Registered QE Hook Events\n'));
807
- console.log(chalk.bold('All Available Events:'));
808
- for (const [name, event] of Object.entries(QE_HOOK_EVENTS)) {
809
- const isRegistered = events.includes(event);
810
- const status = isRegistered ? chalk.green('✓') : chalk.dim('○');
811
- console.log(` ${status} ${name}: ${chalk.dim(event)}`);
812
- }
813
- console.log(chalk.dim(`\nRegistered: ${events.length}/${Object.keys(QE_HOOK_EVENTS).length}`));
814
- }
815
- }
816
- catch (error) {
817
- printError(`list failed: ${error instanceof Error ? error.message : 'unknown'}`);
818
- throw error;
819
- }
820
- });
821
- // -------------------------------------------------------------------------
822
- // emit: Emit a hook event (for testing/integration)
823
- // -------------------------------------------------------------------------
824
- hooks
825
- .command('emit')
826
- .description('Emit a QE hook event')
827
- .requiredOption('-e, --event <name>', 'Event name (e.g., qe:pattern-applied)')
828
- .option('-d, --data <json>', 'Event data as JSON', '{}')
829
- .option('--json', 'Output as JSON')
830
- .action(async (options) => {
831
- try {
832
- const { hookRegistry } = await getHooksSystem();
833
- let data;
834
- try {
835
- data = safeJsonParse(options.data);
836
- }
837
- catch {
838
- throw new Error(`Invalid JSON data: ${options.data}`);
839
- }
840
- const results = await hookRegistry.emit(options.event, data);
841
- if (options.json) {
842
- printJson({
843
- event: options.event,
844
- results,
845
- });
846
- }
847
- else {
848
- console.log(chalk.bold('\n📡 Hook Event Emitted'));
849
- console.log(chalk.dim(` Event: ${options.event}`));
850
- console.log(chalk.dim(` Handlers: ${results.length}`));
851
- results.forEach((result, i) => {
852
- const status = result.success ? chalk.green('✓') : chalk.red('✗');
853
- console.log(` ${status} Handler ${i + 1}: ${result.success ? 'success' : result.error}`);
854
- if (result.patternsLearned) {
855
- console.log(chalk.green(` Patterns learned: ${result.patternsLearned}`));
856
- }
857
- });
858
- }
859
- }
860
- catch (error) {
861
- printError(`emit failed: ${error instanceof Error ? error.message : 'unknown'}`);
862
- throw error;
863
- }
864
- });
865
- // -------------------------------------------------------------------------
866
- // learn: Store a new pattern for learning
867
- // -------------------------------------------------------------------------
868
- hooks
869
- .command('learn')
870
- .description('Store a new pattern in the reasoning bank')
871
- .requiredOption('-n, --name <name>', 'Pattern name')
872
- .requiredOption('-d, --description <desc>', 'Pattern description')
873
- .option('-t, --type <type>', 'Pattern type', 'test-template')
874
- .option('--domain <domain>', 'QE domain')
875
- .option('--tags <tags...>', 'Pattern tags')
876
- .option('--json', 'Output as JSON')
877
- .action(async (options) => {
878
- try {
879
- const { reasoningBank } = await getHooksSystem();
880
- const result = await reasoningBank.storePattern({
881
- patternType: options.type,
882
- name: options.name,
883
- description: options.description,
884
- template: {
885
- type: 'prompt',
886
- content: options.description,
887
- variables: [],
888
- },
889
- context: {
890
- tags: options.tags || [],
891
- },
892
- });
893
- if (!result.success) {
894
- throw new Error(result.error.message);
895
- }
896
- const pattern = result.value;
897
- if (options.json) {
898
- printJson({
899
- success: true,
900
- pattern: {
901
- id: pattern.id,
902
- name: pattern.name,
903
- type: pattern.patternType,
904
- domain: pattern.qeDomain,
905
- },
906
- });
907
- }
908
- else {
909
- printSuccess(`Pattern stored: ${pattern.name}`);
910
- console.log(chalk.dim(` ID: ${pattern.id}`));
911
- console.log(chalk.dim(` Domain: ${pattern.qeDomain}`));
912
- console.log(chalk.dim(` Tier: ${pattern.tier}`));
913
- }
914
- }
915
- catch (error) {
916
- printError(`learn failed: ${error instanceof Error ? error.message : 'unknown'}`);
917
- throw error;
918
- }
919
- });
920
- // -------------------------------------------------------------------------
921
- // search: Search for patterns
922
- // -------------------------------------------------------------------------
923
- hooks
924
- .command('search')
925
- .description('Search for patterns in the reasoning bank')
926
- .requiredOption('-q, --query <query>', 'Search query')
927
- .option('-l, --limit <n>', 'Maximum results', '10')
928
- .option('-d, --domain <domain>', 'Filter by domain')
929
- .option('--json', 'Output as JSON')
930
- .action(async (options) => {
931
- try {
932
- const { reasoningBank } = await getHooksSystem();
933
- const result = await reasoningBank.searchPatterns(options.query, {
934
- limit: parseInt(options.limit, 10),
935
- domain: options.domain,
936
- });
937
- if (!result.success) {
938
- throw new Error(result.error.message);
939
- }
940
- const patterns = result.value;
941
- if (options.json) {
942
- printJson({
943
- query: options.query,
944
- total: patterns.length,
945
- patterns: patterns.map((p) => ({
946
- id: p.pattern.id,
947
- name: p.pattern.name,
948
- score: p.score,
949
- domain: p.pattern.qeDomain,
950
- matchType: p.matchType,
951
- })),
952
- });
953
- }
954
- else {
955
- console.log(chalk.bold(`\n🔍 Search Results for "${options.query}"\n`));
956
- if (patterns.length === 0) {
957
- console.log(chalk.dim(' No patterns found'));
958
- }
959
- else {
960
- patterns.forEach((p, i) => {
961
- console.log(`${chalk.cyan(`${i + 1}.`)} ${p.pattern.name} ${chalk.dim(`(${(p.score * 100).toFixed(1)}%)`)}`);
962
- console.log(chalk.dim(` Domain: ${p.pattern.qeDomain}`));
963
- console.log(chalk.dim(` Match: ${p.matchType}`));
964
- console.log(chalk.dim(` ID: ${p.pattern.id}`));
965
- console.log();
966
- });
967
- }
968
- console.log(chalk.dim(`Found ${patterns.length} pattern(s)`));
969
- }
970
- }
971
- catch (error) {
972
- printError(`search failed: ${error instanceof Error ? error.message : 'unknown'}`);
973
- throw error;
974
- }
975
- });
976
- // -------------------------------------------------------------------------
977
- // session-start: Initialize session state (called by SessionStart hook)
978
- // -------------------------------------------------------------------------
979
- hooks
980
- .command('session-start')
981
- .description('Initialize session state when Claude Code session starts')
982
- .option('-s, --session-id <id>', 'Session ID')
983
- .option('--json', 'Output as JSON')
984
- .action(async (options) => {
985
- try {
986
- const sessionId = options.sessionId || `session-${Date.now()}`;
987
- state.sessionId = sessionId;
988
- // Initialize hooks system (lazy)
989
- const { reasoningBank } = await getHooksSystem();
990
- // Get initial stats for context
991
- const stats = await reasoningBank.getStats();
992
- // Initialize dream scheduler state for this session
993
- const projectRoot = findProjectRoot();
994
- const dataDir = path.join(projectRoot, '.agentic-qe');
995
- const memoryBackend = await createHybridBackendWithTimeout(dataDir);
996
- // Load existing dream state or create fresh one
997
- let dreamState = await memoryBackend.get(DREAM_STATE_KEY);
998
- const isNewSession = !dreamState || !dreamState.sessionStartTime;
999
- if (!dreamState) {
1000
- dreamState = {
1001
- lastDreamTime: null,
1002
- experienceCount: 0,
1003
- sessionStartTime: new Date().toISOString(),
1004
- totalDreamsThisSession: 0,
1005
- };
1006
- }
1007
- else {
1008
- // Reset session counters but preserve lastDreamTime across sessions
1009
- dreamState.sessionStartTime = new Date().toISOString();
1010
- dreamState.totalDreamsThisSession = 0;
1011
- // Don't reset experienceCount — carry over unfulfilled experiences
1012
- }
1013
- await memoryBackend.set(DREAM_STATE_KEY, dreamState);
1014
- // Build context injection for Claude
1015
- const contextParts = [];
1016
- contextParts.push(`AQE Learning: ${stats.totalPatterns} patterns loaded`);
1017
- // Top domains by pattern count
1018
- const domainEntries = Object.entries(stats.byDomain)
1019
- .filter(([, count]) => count > 0)
1020
- .sort(([, a], [, b]) => b - a)
1021
- .slice(0, 5);
1022
- if (domainEntries.length > 0) {
1023
- contextParts.push(`Top domains: ${domainEntries.map(([d, c]) => `${d}(${c})`).join(', ')}`);
1024
- }
1025
- if (stats.patternSuccessRate > 0) {
1026
- contextParts.push(`Pattern success rate: ${(stats.patternSuccessRate * 100).toFixed(0)}%`);
1027
- }
1028
- if (stats.routingRequests > 0) {
1029
- contextParts.push(`Routing confidence: ${(stats.avgRoutingConfidence * 100).toFixed(0)}% across ${stats.routingRequests} requests`);
1030
- }
1031
- const additionalContext = contextParts.join('. ') + '.';
1032
- if (options.json) {
1033
- printJson({
1034
- hookSpecificOutput: {
1035
- hookEventName: 'SessionStart',
1036
- additionalContext,
1037
- },
1038
- sessionId,
1039
- initialized: true,
1040
- patternsLoaded: stats.totalPatterns,
1041
- dreamScheduler: {
1042
- enabled: true,
1043
- lastDreamTime: dreamState.lastDreamTime,
1044
- pendingExperiences: dreamState.experienceCount,
1045
- },
1046
- });
1047
- }
1048
- else {
1049
- printSuccess(`Session started: ${sessionId}`);
1050
- console.log(chalk.dim(` Patterns loaded: ${stats.totalPatterns}`));
1051
- console.log(chalk.dim(` Dream scheduler: enabled (${dreamState.experienceCount} pending experiences)`));
1052
- }
1053
- return;
1054
- }
1055
- catch (error) {
1056
- // Don't fail the hook - just log and return cleanly
1057
- if (options.json) {
1058
- printJson({ success: false, error: error instanceof Error ? error.message : 'unknown' });
1059
- }
1060
- return; // Return cleanly even on error (continueOnError)
1061
- }
1062
- });
1063
- // -------------------------------------------------------------------------
1064
- // session-end: Save session state (called by Stop hook)
1065
- // -------------------------------------------------------------------------
1066
- hooks
1067
- .command('session-end')
1068
- .description('Save session state when Claude Code session ends')
1069
- .option('--save-state', 'Save learning state to disk')
1070
- .option('--export-metrics', 'Export session metrics')
1071
- .option('--json', 'Output as JSON')
1072
- .action(async (options) => {
1073
- try {
1074
- const sessionId = state.sessionId || 'unknown';
1075
- // Get final stats if system is already initialized (don't init just for shutdown)
1076
- let stats = null;
1077
- if (state.initialized && state.reasoningBank) {
1078
- try {
1079
- stats = await state.reasoningBank.getStats();
1080
- }
1081
- catch {
1082
- // Ignore - system may not be available during shutdown
1083
- }
1084
- }
1085
- // Run lightweight experience-to-pattern consolidation
1086
- let patternsCreated = 0;
1087
- try {
1088
- patternsCreated = await consolidateExperiencesToPatterns();
1089
- }
1090
- catch {
1091
- // Non-critical — don't block session end
1092
- }
1093
- if (options.json) {
1094
- const summary = stats
1095
- ? `Session complete: ${stats.totalPatterns} patterns, ${stats.routingRequests} routings, ${(stats.patternSuccessRate * 100).toFixed(0)}% success rate`
1096
- : 'Session complete';
1097
- // Stop hooks don't support hookSpecificOutput — only simple fields
1098
- printJson({
1099
- continue: true,
1100
- sessionId,
1101
- stateSaved: options.saveState || false,
1102
- metricsExported: options.exportMetrics || false,
1103
- patternsConsolidated: patternsCreated,
1104
- finalStats: stats ? {
1105
- patternsLearned: stats.totalPatterns,
1106
- routingRequests: stats.routingRequests,
1107
- successRate: stats.patternSuccessRate,
1108
- } : null,
1109
- });
1110
- }
1111
- else {
1112
- printSuccess(`Session ended: ${sessionId}`);
1113
- if (stats) {
1114
- console.log(chalk.dim(` Patterns: ${stats.totalPatterns}`));
1115
- console.log(chalk.dim(` Routing requests: ${stats.routingRequests}`));
1116
- }
1117
- if (patternsCreated > 0) {
1118
- console.log(chalk.dim(` Patterns consolidated: ${patternsCreated}`));
1119
- }
1120
- }
1121
- return;
1122
- }
1123
- catch (error) {
1124
- // Don't fail the hook - just return cleanly
1125
- if (options.json) {
1126
- printJson({ success: false, error: error instanceof Error ? error.message : 'unknown' });
1127
- }
1128
- return;
1129
- }
1130
- });
1131
- // -------------------------------------------------------------------------
1132
- // pre-task: Get guidance before spawning a Task (called by PreToolUse hook)
1133
- // -------------------------------------------------------------------------
1134
- hooks
1135
- .command('pre-task')
1136
- .description('Get context and guidance before spawning a Task agent')
1137
- .option('--task-id <id>', 'Task identifier')
1138
- .option('-d, --description <desc>', 'Task description')
1139
- .option('--json', 'Output as JSON')
1140
- .action(async (options) => {
1141
- try {
1142
- const { reasoningBank } = await getHooksSystem();
1143
- // Route the task to get agent recommendation
1144
- let routing = null;
1145
- if (options.description) {
1146
- const result = await reasoningBank.routeTask({
1147
- task: options.description,
1148
- });
1149
- if (result.success) {
1150
- routing = result.value;
1151
- }
1152
- }
1153
- if (options.json) {
1154
- printJson({
1155
- success: true,
1156
- taskId: options.taskId,
1157
- description: options.description,
1158
- recommendedAgent: routing?.recommendedAgent,
1159
- confidence: routing?.confidence,
1160
- guidance: routing?.guidance || [],
1161
- });
1162
- }
1163
- else {
1164
- console.log(chalk.bold('\n🚀 Pre-Task Analysis'));
1165
- console.log(chalk.dim(` Task ID: ${options.taskId || 'N/A'}`));
1166
- if (routing) {
1167
- console.log(chalk.bold('\n🎯 Recommended:'), chalk.cyan(routing.recommendedAgent));
1168
- console.log(chalk.dim(` Confidence: ${(routing.confidence * 100).toFixed(1)}%`));
1169
- }
1170
- }
1171
- return;
1172
- }
1173
- catch (error) {
1174
- if (options.json) {
1175
- printJson({ success: false, error: error instanceof Error ? error.message : 'unknown' });
1176
- }
1177
- return;
1178
- }
1179
- });
1180
- // -------------------------------------------------------------------------
1181
- // post-task: Record task outcome for learning (called by PostToolUse hook)
1182
- // -------------------------------------------------------------------------
1183
- hooks
1184
- .command('post-task')
1185
- .description('Record task outcome for pattern learning')
1186
- .option('--task-id <id>', 'Task identifier')
1187
- .option('--success <bool>', 'Whether task succeeded', 'true')
1188
- .option('--agent <name>', 'Agent that executed the task')
1189
- .option('--duration <ms>', 'Task duration in milliseconds')
1190
- .option('--json', 'Output as JSON')
1191
- .action(async (options) => {
1192
- try {
1193
- const success = options.success === 'true' || options.success === true;
1194
- // Initialize hooks system and record learning outcome
1195
- // BUG FIX: Must call getHooksSystem() FIRST to initialize, not check state.initialized
1196
- let patternsLearned = 0;
1197
- let dreamResult = { triggered: false };
1198
- try {
1199
- // Initialize system (creates ReasoningBank and HookRegistry)
1200
- const { hookRegistry, reasoningBank } = await getHooksSystem();
1201
- // Emit learning event for task completion
1202
- const results = await hookRegistry.emit(QE_HOOK_EVENTS.QEAgentCompletion, {
1203
- taskId: options.taskId,
1204
- success,
1205
- agent: options.agent,
1206
- duration: options.duration ? parseInt(options.duration, 10) : undefined,
1207
- timestamp: Date.now(),
1208
- });
1209
- patternsLearned = results.reduce((sum, r) => sum + (r.patternsLearned || 0), 0);
1210
- // Record as learning experience for every post-task invocation
1211
- if (options.taskId) {
1212
- const agent = options.agent || 'unknown';
1213
- await reasoningBank.recordOutcome({
1214
- patternId: `task:${agent}:${options.taskId}`,
1215
- success,
1216
- metrics: {
1217
- executionTimeMs: options.duration ? parseInt(options.duration, 10) : 0,
1218
- },
1219
- feedback: `Agent: ${agent}, Task: ${options.taskId}`,
1220
- });
1221
- }
1222
- // Record experience for dream scheduler and check if dream should trigger
1223
- const projectRoot = findProjectRoot();
1224
- const dataDir = path.join(projectRoot, '.agentic-qe');
1225
- const memoryBackend = await createHybridBackendWithTimeout(dataDir);
1226
- const expCount = await incrementDreamExperience(memoryBackend);
1227
- // Check if dream cycle should be triggered
1228
- // Always check — time-based triggers need every invocation, and the
1229
- // check itself is lightweight (just reads state + compares timestamps)
1230
- dreamResult = await checkAndTriggerDream(memoryBackend);
1231
- }
1232
- catch (initError) {
1233
- // Log but don't fail - learning is best-effort
1234
- console.error(chalk.dim(`[hooks] Learning init: ${initError instanceof Error ? initError.message : 'unknown'}`));
1235
- }
1236
- if (options.json) {
1237
- printJson({
1238
- success: true,
1239
- taskId: options.taskId,
1240
- taskSuccess: success,
1241
- patternsLearned,
1242
- dreamTriggered: dreamResult.triggered,
1243
- dreamReason: dreamResult.reason,
1244
- dreamInsights: dreamResult.insightsGenerated,
1245
- });
1246
- }
1247
- else {
1248
- printSuccess(`Task completed: ${options.taskId || 'unknown'}`);
1249
- console.log(chalk.dim(` Success: ${success}`));
1250
- if (patternsLearned > 0) {
1251
- console.log(chalk.green(` Patterns learned: ${patternsLearned}`));
1252
- }
1253
- if (dreamResult.triggered) {
1254
- console.log(chalk.blue(` 🌙 Dream cycle triggered (${dreamResult.reason}): ${dreamResult.insightsGenerated} insights`));
1255
- }
1256
- }
1257
- return;
1258
- }
1259
- catch (error) {
1260
- if (options.json) {
1261
- printJson({ success: false, error: error instanceof Error ? error.message : 'unknown' });
1262
- }
1263
- return;
1264
- }
1265
- });
1266
- // -------------------------------------------------------------------------
1267
- // guard: File guardian - block edits to protected files (PreToolUse)
1268
- // -------------------------------------------------------------------------
1269
- hooks
1270
- .command('guard')
1271
- .description('File guardian - block edits to protected files')
1272
- .requiredOption('-f, --file <path>', 'File path to check')
1273
- .option('--json', 'Output as JSON (required for hook API)')
1274
- .action(async (options) => {
1275
- try {
1276
- const filePath = options.file || '';
1277
- const normalizedPath = filePath.replace(/\\/g, '/');
1278
- // Protected file patterns
1279
- const protectedPatterns = [
1280
- { pattern: /^\.env($|\.)/, reason: 'Environment file contains secrets' },
1281
- { pattern: /\.env\.[a-zA-Z]+$/, reason: 'Environment file contains secrets' },
1282
- { pattern: /\.lock$/, reason: 'Lock files are auto-generated' },
1283
- { pattern: /(^|\/)node_modules\//, reason: 'node_modules is managed by package manager' },
1284
- { pattern: /(^|\/)\.agentic-qe\/memory\.db/, reason: 'AQE memory database must not be directly edited' },
1285
- { pattern: /(^|\/)\.agentic-qe\/memory\.db-wal$/, reason: 'AQE WAL file must not be directly edited' },
1286
- { pattern: /(^|\/)\.agentic-qe\/memory\.db-shm$/, reason: 'AQE shared memory file must not be directly edited' },
1287
- ];
1288
- const match = protectedPatterns.find(p => p.pattern.test(normalizedPath));
1289
- if (match) {
1290
- // Deny - use Claude Code hookSpecificOutput API format
1291
- if (options.json) {
1292
- printJson({
1293
- hookSpecificOutput: {
1294
- hookEventName: 'PreToolUse',
1295
- permissionDecision: 'deny',
1296
- permissionDecisionReason: `Protected file: ${match.reason} (${filePath})`,
1297
- },
1298
- });
1299
- }
1300
- else {
1301
- printError(`Blocked: ${match.reason} (${filePath})`);
1302
- }
1303
- }
1304
- else {
1305
- // Allow
1306
- if (options.json) {
1307
- printJson({
1308
- hookSpecificOutput: {
1309
- hookEventName: 'PreToolUse',
1310
- permissionDecision: 'allow',
1311
- },
1312
- });
1313
- }
1314
- else {
1315
- printSuccess(`Allowed: ${filePath}`);
1316
- }
1317
- }
1318
- return;
1319
- }
1320
- catch (error) {
1321
- // On error, allow (fail-open for non-critical guard)
1322
- if (options.json) {
1323
- printJson({
1324
- hookSpecificOutput: {
1325
- hookEventName: 'PreToolUse',
1326
- permissionDecision: 'allow',
1327
- },
1328
- });
1329
- }
1330
- return;
1331
- }
1332
- });
1333
- // -------------------------------------------------------------------------
1334
- // pre-command: Get guidance before Bash command (called by PreToolUse hook)
1335
- // -------------------------------------------------------------------------
1336
- hooks
1337
- .command('pre-command')
1338
- .description('Get context before executing a Bash command')
1339
- .option('-c, --command <cmd>', 'Command to be executed')
1340
- .option('--json', 'Output as JSON')
1341
- .action(async (options) => {
1342
- try {
1343
- const command = options.command || '';
1344
- // Dangerous command patterns that should be BLOCKED
1345
- const dangerousPatterns = [
1346
- { pattern: /rm\s+(-[a-zA-Z]*f[a-zA-Z]*\s+)?-[a-zA-Z]*r[a-zA-Z]*\s+\/(?!\w)/, reason: 'Recursive delete of root filesystem' },
1347
- { pattern: /rm\s+(-[a-zA-Z]*r[a-zA-Z]*\s+)?-[a-zA-Z]*f[a-zA-Z]*\s+\/(?!\w)/, reason: 'Recursive delete of root filesystem' },
1348
- { pattern: /rm\s+-rf\s+~/, reason: 'Recursive delete of home directory' },
1349
- { pattern: /DROP\s+(TABLE|DATABASE|SCHEMA)/i, reason: 'Destructive SQL operation' },
1350
- { pattern: /git\s+push\s+.*--force(?!-)/, reason: 'Force push can overwrite remote history' },
1351
- { pattern: /git\s+reset\s+--hard/, reason: 'Hard reset discards uncommitted changes' },
1352
- { pattern: />\s*\/dev\/sd[a-z]/, reason: 'Direct write to block device' },
1353
- { pattern: /dd\s+if=.*of=\/dev\/sd/, reason: 'Direct disk write via dd' },
1354
- { pattern: /chmod\s+777\s/, reason: 'World-writable permissions are a security risk' },
1355
- { pattern: /:\(\)\s*\{\s*:\|\s*:&\s*\}\s*;?\s*:/, reason: 'Fork bomb detected' },
1356
- { pattern: /mkfs\./, reason: 'Filesystem format operation' },
1357
- { pattern: />\s*\/dev\/null\s*2>&1\s*&\s*disown/, reason: 'Stealth background process' },
1358
- ];
1359
- // Warning patterns (inform but don't block)
1360
- const warningPatterns = [
1361
- { pattern: /\.agentic-qe.*rm/, reason: 'Deleting AQE data files' },
1362
- { pattern: /rm\s+-rf\s/, reason: 'Recursive force delete' },
1363
- { pattern: /git\s+clean\s+-[a-zA-Z]*f/, reason: 'Force cleaning untracked files' },
1364
- ];
1365
- const dangerMatch = dangerousPatterns.find(p => p.pattern.test(command));
1366
- const warnings = warningPatterns
1367
- .filter(p => p.pattern.test(command))
1368
- .map(p => p.reason);
1369
- if (dangerMatch) {
1370
- // BLOCK the command
1371
- if (options.json) {
1372
- printJson({
1373
- hookSpecificOutput: {
1374
- hookEventName: 'PreToolUse',
1375
- permissionDecision: 'deny',
1376
- permissionDecisionReason: `Dangerous command blocked: ${dangerMatch.reason}`,
1377
- },
1378
- });
1379
- }
1380
- else {
1381
- printError(`Blocked: ${dangerMatch.reason}`);
1382
- }
1383
- }
1384
- else {
1385
- // Allow (with optional warnings as context)
1386
- if (options.json) {
1387
- const result = {
1388
- hookSpecificOutput: {
1389
- hookEventName: 'PreToolUse',
1390
- permissionDecision: 'allow',
1391
- },
1392
- };
1393
- if (warnings.length > 0) {
1394
- result.hookSpecificOutput.additionalContext =
1395
- `Warnings: ${warnings.join('; ')}`;
1396
- }
1397
- printJson(result);
1398
- }
1399
- else if (warnings.length > 0) {
1400
- console.log(chalk.yellow('\n⚠️ Command Warnings:'));
1401
- warnings.forEach(w => console.log(chalk.yellow(` - ${w}`)));
1402
- }
1403
- }
1404
- return;
1405
- }
1406
- catch (error) {
1407
- // Fail-open on error
1408
- if (options.json) {
1409
- printJson({
1410
- hookSpecificOutput: {
1411
- hookEventName: 'PreToolUse',
1412
- permissionDecision: 'allow',
1413
- },
1414
- });
1415
- }
1416
- return;
1417
- }
1418
- });
1419
- // -------------------------------------------------------------------------
1420
- // post-command: Record command outcome (called by PostToolUse hook)
1421
- // -------------------------------------------------------------------------
1422
- hooks
1423
- .command('post-command')
1424
- .description('Record Bash command outcome')
1425
- .option('-c, --command <cmd>', 'Command that was executed')
1426
- .option('--success <bool>', 'Whether command succeeded', 'true')
1427
- .option('--exit-code <code>', 'Command exit code')
1428
- .option('--json', 'Output as JSON')
1429
- .action(async (options) => {
1430
- try {
1431
- const success = options.success === 'true' || options.success === true;
1432
- const exitCode = options.exitCode ? parseInt(options.exitCode, 10) : (success ? 0 : 1);
1433
- const command = (options.command || '').substring(0, 200);
1434
- // Determine if this is a test/build/lint command for richer learning
1435
- const isTestCmd = /\b(test|vitest|jest|pytest|mocha)\b/i.test(command);
1436
- const isBuildCmd = /\b(build|compile|tsc)\b/i.test(command);
1437
- const isLintCmd = /\b(lint|eslint|prettier)\b/i.test(command);
1438
- let patternsLearned = 0;
1439
- let experienceRecorded = false;
1440
- try {
1441
- const { reasoningBank } = await getHooksSystem();
1442
- // For test commands, emit TestExecutionResult for pattern learning
1443
- if (isTestCmd) {
1444
- const { hookRegistry } = await getHooksSystem();
1445
- await hookRegistry.emit(QE_HOOK_EVENTS.TestExecutionResult, {
1446
- runId: `cmd-${Date.now()}`,
1447
- patternId: `cmd:test:${command.split(/\s+/).slice(0, 3).join('-')}`,
1448
- passed: success ? 1 : 0,
1449
- failed: success ? 0 : 1,
1450
- duration: 0,
1451
- flaky: false,
1452
- });
1453
- }
1454
- // Record outcome for all commands
1455
- const cmdSlug = command.replace(/[^a-zA-Z0-9]/g, '-').slice(0, 80);
1456
- const domain = isTestCmd ? 'test-execution' : isBuildCmd ? 'code-intelligence' : isLintCmd ? 'quality-assessment' : 'code-intelligence';
1457
- await reasoningBank.recordOutcome({
1458
- patternId: `cmd:${cmdSlug}`,
1459
- success,
1460
- metrics: { executionTimeMs: 0 },
1461
- feedback: `Command: ${command}, exit: ${exitCode}`,
1462
- });
1463
- patternsLearned = 1;
1464
- // Persist as captured experience
1465
- await persistCommandExperience({
1466
- task: `bash: ${command}`,
1467
- agent: 'cli-hook',
1468
- domain,
1469
- success,
1470
- source: 'cli-hook-post-command',
1471
- });
1472
- experienceRecorded = true;
1473
- // Increment dream experience counter
1474
- const projectRoot = findProjectRoot();
1475
- const dataDir = path.join(projectRoot, '.agentic-qe');
1476
- const memoryBackend = await createHybridBackendWithTimeout(dataDir);
1477
- await incrementDreamExperience(memoryBackend);
1478
- }
1479
- catch (initError) {
1480
- console.error(chalk.dim(`[hooks] post-command learning: ${initError instanceof Error ? initError.message : 'unknown'}`));
1481
- }
1482
- if (options.json) {
1483
- printJson({
1484
- success: true,
1485
- command: command.substring(0, 100),
1486
- commandSuccess: success,
1487
- exitCode,
1488
- patternsLearned,
1489
- experienceRecorded,
1490
- });
1491
- }
1492
- // Silent in non-JSON mode to avoid cluttering output
1493
- return;
1494
- }
1495
- catch (error) {
1496
- if (options.json) {
1497
- printJson({ success: false, error: error instanceof Error ? error.message : 'unknown' });
1498
- }
1499
- return;
1500
- }
1501
- });
52
+ // Register all handler groups
53
+ registerEditingHooks(hooks);
54
+ registerRoutingHooks(hooks);
55
+ registerStatsHooks(hooks);
56
+ registerSessionHooks(hooks);
57
+ registerTaskHooks(hooks);
58
+ registerCommandHooks(hooks);
1502
59
  return hooks;
1503
60
  }
1504
61
  // ============================================================================