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.
- package/.claude/skills/skills-manifest.json +1 -1
- package/CHANGELOG.md +12 -0
- package/dist/cli/bundle.js +694 -694
- package/dist/cli/commands/hooks-handlers/command-hooks.d.ts +12 -0
- package/dist/cli/commands/hooks-handlers/command-hooks.js +253 -0
- package/dist/cli/commands/hooks-handlers/editing-hooks.d.ts +12 -0
- package/dist/cli/commands/hooks-handlers/editing-hooks.js +161 -0
- package/dist/cli/commands/hooks-handlers/hooks-dream-learning.d.ts +57 -0
- package/dist/cli/commands/hooks-handlers/hooks-dream-learning.js +263 -0
- package/dist/cli/commands/hooks-handlers/hooks-shared.d.ts +52 -0
- package/dist/cli/commands/hooks-handlers/hooks-shared.js +223 -0
- package/dist/cli/commands/hooks-handlers/routing-hooks.d.ts +12 -0
- package/dist/cli/commands/hooks-handlers/routing-hooks.js +107 -0
- package/dist/cli/commands/hooks-handlers/session-hooks.d.ts +12 -0
- package/dist/cli/commands/hooks-handlers/session-hooks.js +171 -0
- package/dist/cli/commands/hooks-handlers/stats-hooks.d.ts +12 -0
- package/dist/cli/commands/hooks-handlers/stats-hooks.js +248 -0
- package/dist/cli/commands/hooks-handlers/task-hooks.d.ts +12 -0
- package/dist/cli/commands/hooks-handlers/task-hooks.js +152 -0
- package/dist/cli/commands/hooks.d.ts +3 -23
- package/dist/cli/commands/hooks.js +16 -1459
- package/dist/coordination/mincut/phase-executor.d.ts +27 -0
- package/dist/coordination/mincut/phase-executor.js +70 -0
- package/dist/coordination/mincut/time-crystal-analysis.d.ts +35 -0
- package/dist/coordination/mincut/time-crystal-analysis.js +237 -0
- package/dist/coordination/mincut/time-crystal-persistence.d.ts +35 -0
- package/dist/coordination/mincut/time-crystal-persistence.js +81 -0
- package/dist/coordination/mincut/time-crystal-scheduling.d.ts +34 -0
- package/dist/coordination/mincut/time-crystal-scheduling.js +213 -0
- package/dist/coordination/mincut/time-crystal-types.d.ts +278 -0
- package/dist/coordination/mincut/time-crystal-types.js +67 -0
- package/dist/coordination/mincut/time-crystal.d.ts +8 -438
- package/dist/coordination/mincut/time-crystal.js +87 -905
- package/dist/domains/base-domain-coordinator.d.ts +0 -15
- package/dist/domains/base-domain-coordinator.js +7 -5
- package/dist/domains/chaos-resilience/coordinator.d.ts +0 -4
- package/dist/domains/chaos-resilience/coordinator.js +24 -22
- package/dist/domains/chaos-resilience/services/chaos-engineer.d.ts +0 -4
- package/dist/domains/chaos-resilience/services/chaos-engineer.js +47 -45
- package/dist/domains/chaos-resilience/services/performance-profiler.d.ts +0 -4
- package/dist/domains/chaos-resilience/services/performance-profiler.js +10 -8
- package/dist/domains/code-intelligence/coordinator-consensus.d.ts +0 -3
- package/dist/domains/code-intelligence/coordinator-consensus.js +8 -6
- package/dist/domains/code-intelligence/coordinator-gnn.d.ts +0 -3
- package/dist/domains/code-intelligence/coordinator-gnn.js +8 -6
- package/dist/domains/code-intelligence/coordinator-hypergraph.d.ts +0 -3
- package/dist/domains/code-intelligence/coordinator-hypergraph.js +13 -11
- package/dist/domains/code-intelligence/coordinator.d.ts +0 -3
- package/dist/domains/code-intelligence/coordinator.js +21 -19
- package/dist/domains/code-intelligence/services/c4-model/index.d.ts +0 -3
- package/dist/domains/code-intelligence/services/c4-model/index.js +5 -3
- package/dist/domains/code-intelligence/services/knowledge-graph.d.ts +0 -6
- package/dist/domains/code-intelligence/services/knowledge-graph.js +4 -2
- package/dist/domains/code-intelligence/services/product-factors-bridge.d.ts +0 -5
- package/dist/domains/code-intelligence/services/product-factors-bridge.js +9 -7
- package/dist/domains/contract-testing/coordinator.d.ts +0 -6
- package/dist/domains/contract-testing/coordinator.js +25 -23
- package/dist/domains/contract-testing/services/contract-validator.d.ts +0 -4
- package/dist/domains/contract-testing/services/contract-validator.js +4 -2
- package/dist/domains/contract-testing/services/schema-validator.js +1 -1
- package/dist/domains/coverage-analysis/coordinator.js +13 -11
- package/dist/domains/coverage-analysis/services/coverage-analyzer.js +4 -2
- package/dist/domains/coverage-analysis/services/gap-detector.js +3 -1
- package/dist/domains/coverage-analysis/services/hnsw-index.d.ts +0 -15
- package/dist/domains/coverage-analysis/services/hnsw-index.js +3 -1
- package/dist/domains/coverage-analysis/services/sublinear-analyzer.d.ts +0 -26
- package/dist/domains/coverage-analysis/services/sublinear-analyzer.js +3 -1
- package/dist/domains/defect-intelligence/coordinator.d.ts +1 -10
- package/dist/domains/defect-intelligence/coordinator.js +5 -3
- package/dist/domains/defect-intelligence/services/causal-root-cause-analyzer.d.ts +0 -6
- package/dist/domains/defect-intelligence/services/causal-root-cause-analyzer.js +3 -1
- package/dist/domains/defect-intelligence/services/defect-predictor.d.ts +0 -6
- package/dist/domains/defect-intelligence/services/defect-predictor.js +5 -3
- package/dist/domains/defect-intelligence/services/pattern-learner.d.ts +0 -4
- package/dist/domains/defect-intelligence/services/pattern-learner.js +3 -1
- package/dist/domains/defect-intelligence/services/root-cause-analyzer.d.ts +0 -6
- package/dist/domains/defect-intelligence/services/root-cause-analyzer.js +3 -1
- package/dist/domains/enterprise-integration/coordinator.js +6 -4
- package/dist/domains/learning-optimization/coordinator-consensus.d.ts +0 -3
- package/dist/domains/learning-optimization/coordinator-consensus.js +8 -6
- package/dist/domains/learning-optimization/coordinator.d.ts +0 -3
- package/dist/domains/learning-optimization/coordinator.js +15 -13
- package/dist/domains/learning-optimization/services/learning-coordinator.d.ts +0 -4
- package/dist/domains/learning-optimization/services/learning-coordinator.js +4 -2
- package/dist/domains/quality-assessment/coordinator-claim-verifier.d.ts +0 -3
- package/dist/domains/quality-assessment/coordinator-claim-verifier.js +6 -4
- package/dist/domains/quality-assessment/coordinator-gate-evaluation.d.ts +0 -4
- package/dist/domains/quality-assessment/coordinator-gate-evaluation.js +9 -7
- package/dist/domains/quality-assessment/coordinator-rl-integration.d.ts +0 -3
- package/dist/domains/quality-assessment/coordinator-rl-integration.js +10 -8
- package/dist/domains/quality-assessment/coordinator.d.ts +0 -15
- package/dist/domains/quality-assessment/coordinator.js +14 -12
- package/dist/domains/quality-assessment/services/deployment-advisor.d.ts +0 -10
- package/dist/domains/quality-assessment/services/deployment-advisor.js +4 -2
- package/dist/domains/quality-assessment/services/quality-analyzer.d.ts +0 -6
- package/dist/domains/quality-assessment/services/quality-analyzer.js +4 -2
- package/dist/domains/requirements-validation/coordinator.d.ts +0 -3
- package/dist/domains/requirements-validation/coordinator.js +15 -13
- package/dist/domains/requirements-validation/services/product-factors-assessment/code-intelligence/codebase-analyzer.d.ts +0 -5
- package/dist/domains/requirements-validation/services/product-factors-assessment/code-intelligence/codebase-analyzer.js +15 -13
- package/dist/domains/requirements-validation/services/product-factors-assessment/product-factors-service.d.ts +0 -6
- package/dist/domains/requirements-validation/services/product-factors-assessment/product-factors-service.js +9 -7
- package/dist/domains/requirements-validation/services/requirements-validator.d.ts +0 -6
- package/dist/domains/requirements-validation/services/requirements-validator.js +4 -2
- package/dist/domains/security-compliance/coordinator.js +24 -22
- package/dist/domains/security-compliance/services/scanners/dast-scanner.d.ts +0 -21
- package/dist/domains/security-compliance/services/scanners/dast-scanner.js +4 -2
- package/dist/domains/security-compliance/services/scanners/sast-scanner.d.ts +0 -4
- package/dist/domains/security-compliance/services/scanners/sast-scanner.js +3 -1
- package/dist/domains/security-compliance/services/security-auditor-dast.d.ts +0 -4
- package/dist/domains/security-compliance/services/security-auditor-dast.js +3 -1
- package/dist/domains/security-compliance/services/security-auditor-sast.d.ts +0 -3
- package/dist/domains/security-compliance/services/security-auditor-sast.js +3 -1
- package/dist/domains/security-compliance/services/security-auditor-secrets.d.ts +0 -3
- package/dist/domains/security-compliance/services/security-auditor-secrets.js +3 -1
- package/dist/domains/security-compliance/services/security-auditor.js +11 -9
- package/dist/domains/test-execution/coordinator.js +11 -9
- package/dist/domains/test-execution/services/auth-state-manager.d.ts +0 -3
- package/dist/domains/test-execution/services/auth-state-manager.js +4 -2
- package/dist/domains/test-execution/services/e2e/e2e-coordinator.d.ts +0 -14
- package/dist/domains/test-execution/services/e2e/e2e-coordinator.js +3 -1
- package/dist/domains/test-execution/services/flaky-detector.js +4 -2
- package/dist/domains/test-execution/services/retry-handler.js +3 -1
- package/dist/domains/test-execution/services/test-executor.js +3 -1
- package/dist/domains/test-generation/coordinator.d.ts +0 -17
- package/dist/domains/test-generation/coordinator.js +33 -31
- package/dist/domains/test-generation/pattern-injection/edge-case-injector.d.ts +0 -5
- package/dist/domains/test-generation/pattern-injection/edge-case-injector.js +3 -1
- package/dist/domains/test-generation/services/code-transform-integration.d.ts +0 -7
- package/dist/domains/test-generation/services/code-transform-integration.js +3 -1
- package/dist/domains/test-generation/services/coherence-gate-service.d.ts +0 -3
- package/dist/domains/test-generation/services/coherence-gate-service.js +3 -1
- package/dist/domains/test-generation/services/test-generator.d.ts +0 -8
- package/dist/domains/test-generation/services/test-generator.js +5 -3
- package/dist/domains/visual-accessibility/coordinator.d.ts +0 -3
- package/dist/domains/visual-accessibility/coordinator.js +14 -12
- package/dist/domains/visual-accessibility/services/accessibility-tester-browser.d.ts +0 -3
- package/dist/domains/visual-accessibility/services/accessibility-tester-browser.js +52 -50
- package/dist/domains/visual-accessibility/services/accessibility-tester.d.ts +0 -4
- package/dist/domains/visual-accessibility/services/accessibility-tester.js +8 -6
- package/dist/domains/visual-accessibility/services/axe-core-integration.d.ts +0 -3
- package/dist/domains/visual-accessibility/services/axe-core-integration.js +20 -18
- package/dist/domains/visual-accessibility/services/browser-security-scanner.d.ts +0 -4
- package/dist/domains/visual-accessibility/services/browser-security-scanner.js +6 -4
- package/dist/domains/visual-accessibility/services/browser-swarm-coordinator.d.ts +0 -30
- package/dist/domains/visual-accessibility/services/browser-swarm-coordinator.js +5 -3
- package/dist/domains/visual-accessibility/services/viewport-capture.d.ts +0 -27
- package/dist/domains/visual-accessibility/services/viewport-capture.js +6 -4
- package/dist/domains/visual-accessibility/services/visual-regression.d.ts +0 -26
- package/dist/domains/visual-accessibility/services/visual-regression.js +4 -2
- package/dist/domains/visual-accessibility/services/visual-tester.d.ts +0 -4
- package/dist/domains/visual-accessibility/services/visual-tester.js +4 -2
- package/dist/governance/deterministic-gateway-integration.js +1 -1
- package/dist/learning/agent-routing.d.ts +53 -0
- package/dist/learning/agent-routing.js +142 -0
- package/dist/learning/embedding-utils.d.ts +34 -0
- package/dist/learning/embedding-utils.js +95 -0
- package/dist/learning/pattern-promotion.d.ts +63 -0
- package/dist/learning/pattern-promotion.js +187 -0
- package/dist/learning/pretrained-patterns.d.ts +14 -0
- package/dist/learning/pretrained-patterns.js +726 -0
- package/dist/learning/qe-reasoning-bank-types.d.ts +174 -0
- package/dist/learning/qe-reasoning-bank-types.js +24 -0
- package/dist/learning/qe-reasoning-bank.d.ts +9 -192
- package/dist/learning/qe-reasoning-bank.js +48 -1093
- package/dist/mcp/bundle.js +335 -335
- package/dist/mcp/security/validators/command-validator.d.ts +1 -40
- package/dist/mcp/security/validators/command-validator.js +2 -122
- package/dist/mcp/security/validators/crypto-validator.d.ts +1 -39
- package/dist/mcp/security/validators/crypto-validator.js +2 -71
- package/dist/mcp/security/validators/input-sanitizer.d.ts +1 -55
- package/dist/mcp/security/validators/input-sanitizer.js +2 -156
- package/dist/mcp/security/validators/interfaces.d.ts +1 -163
- package/dist/mcp/security/validators/interfaces.js +2 -5
- package/dist/mcp/security/validators/path-traversal-validator.d.ts +1 -49
- package/dist/mcp/security/validators/path-traversal-validator.js +2 -241
- package/dist/mcp/security/validators/regex-safety-validator.d.ts +1 -49
- package/dist/mcp/security/validators/regex-safety-validator.js +2 -182
- package/dist/mcp/security/validators/validation-orchestrator.d.ts +1 -65
- package/dist/mcp/security/validators/validation-orchestrator.js +2 -145
- package/dist/shared/io/file-reader.js +1 -1
- package/dist/shared/security/command-validator.d.ts +44 -0
- package/dist/shared/security/command-validator.js +126 -0
- package/dist/shared/security/crypto-validator.d.ts +43 -0
- package/dist/shared/security/crypto-validator.js +75 -0
- package/dist/shared/security/index.d.ts +7 -0
- package/dist/shared/security/index.js +15 -0
- package/dist/shared/security/input-sanitizer.d.ts +59 -0
- package/dist/shared/security/input-sanitizer.js +160 -0
- package/dist/shared/security/path-traversal-validator.d.ts +53 -0
- package/dist/shared/security/path-traversal-validator.js +245 -0
- package/dist/shared/security/regex-safety-validator.d.ts +53 -0
- package/dist/shared/security/regex-safety-validator.js +186 -0
- package/dist/shared/security/validation-orchestrator.d.ts +69 -0
- package/dist/shared/security/validation-orchestrator.js +149 -0
- package/dist/shared/security/validators-interfaces.d.ts +167 -0
- package/dist/shared/security/validators-interfaces.js +9 -0
- 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
|
|
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
|
|
12
|
-
import
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
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
|
-
|
|
512
|
-
|
|
513
|
-
hooks
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
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
|
// ============================================================================
|