Files
proffesor-for-testing__agen…/scripts/hooks/capture-task-learning.js
Profa 93eefd6814 feat(learning): automatic learning capture via PostToolUse hook (v2.3.0)
This release fixes the long-standing issue where Task agents would not
reliably persist learning data. Now learnings are captured automatically.

## Major Feature: Automatic Learning Capture

- New `scripts/hooks/capture-task-learning.js` PostToolUse hook
  - Captures agent type, task output, duration, token usage
  - Calculates reward based on output quality indicators
  - Stores to `learning_experiences` table in memory.db
  - Deduplication: skips if agent already stored via MCP (60s window)

- Updated `aqe init` to copy hook scripts to user projects
  - New phase: "Hook Scripts" in initialization pipeline
  - Settings.json includes automatic learning capture hook

## Clean QE-Only Configuration

- Removed all claude-flow and agentdb dependencies from QE agents
- Updated settings.json with clean AQE env vars
- Removed agentdb.db references (deprecated)
- All persistence unified to `.agentic-qe/memory.db`

## Agent Learning Instructions Audit

- Added MANDATORY `<learning_protocol>` sections to all 30 QE agents
  - 19 main QE agents updated
  - 11 QE subagents updated
- Instructions include: query past learnings, store experiences, store patterns

## Files Changed

- 30 agent/subagent .md files with learning protocols
- scripts/hooks/capture-task-learning.js (new)
- src/cli/init/claude-config.ts - clean QE-only hooks
- src/cli/init/helpers.ts - copyHookScripts() function
- src/cli/init/index.ts - Hook Scripts phase
- .claude/settings.json - removed claude-flow hooks
- Version bumped to 2.3.0 in all required files

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-08 12:47:33 +00:00

192 lines
6.9 KiB
JavaScript
Executable File

#!/usr/bin/env node
/**
* PostToolUse Hook: Automatic Task Learning Capture
*
* This hook automatically captures learnings from completed Task agents
* and persists them to memory.db. It provides a safety net ensuring
* learnings are captured even when agents don't explicitly call MCP tools.
*
* Input (via stdin): PostToolUse JSON with tool_input and tool_response
* Output: Stores learning_experience record to .agentic-qe/memory.db
*
* @module hooks/capture-task-learning
*/
const fs = require('fs');
const path = require('path');
// Read stdin
let input = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', chunk => input += chunk);
process.stdin.on('end', () => processTaskLearning(input));
async function processTaskLearning(jsonInput) {
try {
const data = JSON.parse(jsonInput);
// Only process completed Task tools
if (data.tool_name !== 'Task' || data.tool_response?.status !== 'completed') {
return;
}
// Extract key information
const agentType = data.tool_input?.subagent_type || 'unknown';
const taskDescription = data.tool_input?.description || '';
const prompt = data.tool_input?.prompt || '';
const agentOutput = data.tool_response?.content?.[0]?.text || '';
const durationMs = data.tool_response?.totalDurationMs || 0;
const totalTokens = data.tool_response?.totalTokens || 0;
const toolUseCount = data.tool_response?.totalToolUseCount || 0;
const agentId = data.tool_response?.agentId || 'unknown';
const cwd = data.cwd || process.cwd();
// Skip if no meaningful output
if (!agentOutput || agentOutput.length < 20) {
return;
}
// Determine task type from agent type
const taskTypeMap = {
'qe-test-generator': 'test-generation',
'qe-coverage-analyzer': 'coverage-analysis',
'qe-security-scanner': 'security-scan',
'qe-performance-tester': 'performance-test',
'qe-flaky-test-hunter': 'flaky-detection',
'qe-chaos-engineer': 'chaos-testing',
'qe-code-complexity': 'complexity-analysis',
'qe-quality-gate': 'quality-gate',
'qe-regression-risk-analyzer': 'regression-analysis',
'qe-requirements-validator': 'requirements-validation',
'qe-test-data-architect': 'test-data-generation',
'qe-visual-tester': 'visual-testing',
'qe-api-contract-validator': 'contract-validation',
'qe-fleet-commander': 'fleet-coordination',
'qe-test-executor': 'test-execution',
'qe-quality-analyzer': 'quality-analysis',
'qe-deployment-readiness': 'deployment-readiness',
'qe-production-intelligence': 'production-intelligence',
'qx-partner': 'qx-analysis',
'researcher': 'research',
'coder': 'implementation',
'tester': 'testing',
'reviewer': 'code-review'
};
const taskType = taskTypeMap[agentType] || agentType;
// Calculate reward based on output quality indicators
let reward = 0.7; // Base reward for completion
// Increase reward for comprehensive output
if (agentOutput.length > 500) reward += 0.05;
if (agentOutput.length > 1000) reward += 0.05;
if (agentOutput.length > 2000) reward += 0.05;
// Increase reward for tool usage (indicates thorough work)
if (toolUseCount > 0) reward += 0.05;
if (toolUseCount > 5) reward += 0.05;
// Check for success indicators in output
const successIndicators = ['✓', '✅', 'success', 'complete', 'passed', 'found', 'created', 'generated'];
const failureIndicators = ['❌', 'failed', 'error', 'could not', 'unable to'];
const hasSuccess = successIndicators.some(ind => agentOutput.toLowerCase().includes(ind.toLowerCase()));
const hasFailure = failureIndicators.some(ind => agentOutput.toLowerCase().includes(ind.toLowerCase()));
if (hasSuccess && !hasFailure) reward += 0.1;
if (hasFailure) reward -= 0.2;
// Cap reward between 0.1 and 1.0
reward = Math.max(0.1, Math.min(1.0, reward));
// Find memory.db path
const dbPath = path.join(cwd, '.agentic-qe', 'memory.db');
// Check if database exists
if (!fs.existsSync(dbPath)) {
// Try to find it relative to this script
const altPath = path.join(__dirname, '../../.agentic-qe/memory.db');
if (!fs.existsSync(altPath)) {
console.error('💡 Learning: memory.db not found, skipping capture');
return;
}
}
// Store learning experience
const Database = require('better-sqlite3');
const db = new Database(dbPath);
// Note: Table should already exist from aqe init
// Schema: id, agent_id, task_id, task_type, state, action, reward, next_state, episode_id, metadata, created_at, timestamp
// DEDUPLICATION: Check if agent already stored learning via MCP in last 60 seconds
// This prevents double-storing when agents properly call MCP learning tools
const recentLearning = db.prepare(`
SELECT id, metadata FROM learning_experiences
WHERE agent_id = ?
AND created_at > datetime('now', '-60 seconds')
ORDER BY created_at DESC
LIMIT 1
`).get(agentType);
if (recentLearning) {
// Check if it was stored by the agent (not by hook)
try {
const meta = JSON.parse(recentLearning.metadata || '{}');
if (meta.capturedBy !== 'PostToolUse-hook') {
// Agent already stored learning via MCP - skip duplicate
console.log(`📚 Learning: Agent ${agentType} already stored learning via MCP - skipping hook capture`);
db.close();
return;
}
} catch { /* ignore parse errors */ }
}
// Extract outcome summary (first 500 chars of output)
const outcomeSummary = agentOutput.substring(0, 500).replace(/\n/g, ' ').trim();
// Insert learning experience (matching actual schema)
const stmt = db.prepare(`
INSERT INTO learning_experiences (agent_id, task_id, task_type, state, action, reward, next_state, episode_id, metadata)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
`);
const metadata = JSON.stringify({
taskDescription,
durationMs,
totalTokens,
toolUseCount,
outputLength: agentOutput.length,
outputSummary: outcomeSummary,
success: hasSuccess && !hasFailure,
capturedBy: 'PostToolUse-hook',
sessionAgentId: agentId
});
const taskId = `task-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
const episodeId = `episode-${new Date().toISOString().slice(0, 10)}`;
stmt.run(
agentType,
taskId,
taskType,
'task-started',
'execute-task',
reward,
'task-completed',
episodeId,
metadata
);
db.close();
// Output confirmation (visible in hook output)
console.log(`📚 Learning captured: ${agentType} → ${taskType} (reward: ${reward.toFixed(2)})`);
} catch (error) {
// Silently fail - don't break the workflow
// Uncomment for debugging:
// console.error('Hook error:', error.message);
}
}