Files
Dragan Spiridonov 6dbc1c377b fix: critical memory/learning system fixes for v1.9.4 (#89)
* SwarmMemoryManager Refactoring Plan

* fix: add missing directories to npm package (v1.9.3)

Added templates/, .claude/helpers/, and docs/reference/ to package.json
files array. These were missing from the npm distribution, causing
`aqe init` to fail with "Template file not found" errors.

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

Co-Authored-By: Claude <noreply@anthropic.com>

* checkpoint improvements

* fix: critical memory/learning system fixes for v1.9.4

## Critical Fixes
- Vector embeddings now stored correctly (was storing NULL)
- SQL parameter style fixed for agentdb's SqlJsDatabase wrapper
- HNSW index schema mismatch resolved with pattern_id column
- Hooks now save to correct database (.agentic-qe/agentdb.db)

## New Features
- Added 5 SwarmMemoryManager methods for learning data retrieval:
  - getBestAction() - Q-learning best action selection
  - getRecentLearningExperiences() - Recent experience retrieval
  - getLearningExperiencesByTaskType() - Task-filtered experiences
  - getHighRewardExperiences() - Successful experience extraction
  - getLearningStats() - Aggregate learning statistics
- Added comprehensive integration test for memory/learning loop
- Added RuVector pattern store integration (experimental)

## Technical Details
- Vector embeddings: 384 dimensions × 4 bytes = 1,536 bytes per pattern
- All 12 integration tests pass
- HNSW index: 150x faster vector search enabled

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

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: move ARM64-only ruvector packages to optionalDependencies

Fixes CI failures caused by platform-specific dependencies:
- @ruvector/node-linux-arm64-gnu (ARM64 only)
- ruvector-core-linux-arm64-gnu (ARM64 only)

These packages caused npm ci to fail on x64 CI runners with:
"EBADPLATFORM - wanted linux/arm64, current linux/x64"

Solution: Move ARM64 packages to optionalDependencies and add
x64 variants for cross-platform compatibility.

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

Co-Authored-By: Claude <noreply@anthropic.com>

* chore: regenerate package-lock.json with x64 ruvector packages

Updates lockfile to include optional x64 platform packages for
CI compatibility:
- @ruvector/node-linux-x64-gnu@0.1.17
- ruvector-core-linux-x64-gnu@0.1.15

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

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-30 20:52:44 +01:00

272 lines
9.6 KiB
YAML

name: Quality Gate
permissions:
contents: read
pull-requests: write
checks: write
statuses: write
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
workflow_dispatch:
inputs:
min_coverage:
description: 'Minimum coverage percentage'
required: false
default: '80'
min_pass_rate:
description: 'Minimum test pass rate (0-1)'
required: false
default: '0.95'
jobs:
quality-gate:
name: Quality Gate Evaluation
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build project
run: npm run build
- name: Run unit tests with coverage
id: unit-tests
run: npm run test:unit
continue-on-error: true
env:
NODE_OPTIONS: '--max-old-space-size=768'
- name: Run integration tests
id: integration-tests
run: npm run test:integration
continue-on-error: true
env:
NODE_OPTIONS: '--max-old-space-size=1024'
- name: Generate coverage report
if: always()
run: |
if [ -d "coverage" ]; then
echo "Coverage data found"
ls -la coverage/
else
echo "No coverage data generated"
fi
- name: Evaluate quality gates (TypeScript)
id: quality-gate-ts
if: always()
run: |
npx ts-node scripts/quality-gate.ts \
--min-coverage ${{ github.event.inputs.min_coverage || '80' }} \
--min-pass-rate ${{ github.event.inputs.min_pass_rate || '0.95' }} \
--max-critical-vulns 0 \
--max-high-vulns 2 \
--output-dir ./quality-reports \
--pr-comment
continue-on-error: true
env:
GITHUB_RUN_ID: ${{ github.run_id }}
GITHUB_RUN_NUMBER: ${{ github.run_number }}
GITHUB_REPOSITORY: ${{ github.repository }}
GITHUB_REF_NAME: ${{ github.ref_name }}
GITHUB_SHA: ${{ github.sha }}
GITHUB_SERVER_URL: ${{ github.server_url }}
- name: Evaluate quality gates (Bash)
id: quality-gate-bash
if: always() && steps.quality-gate-ts.outcome == 'failure'
run: |
bash scripts/quality-gate.sh \
--min-coverage ${{ github.event.inputs.min_coverage || '80' }} \
--min-pass-rate ${{ github.event.inputs.min_pass_rate || '0.95' }}
continue-on-error: true
env:
MIN_COVERAGE: ${{ github.event.inputs.min_coverage || '80' }}
MIN_PASS_RATE: ${{ github.event.inputs.min_pass_rate || '0.95' }}
QUALITY_REPORT_DIR: ./quality-reports
- name: Upload quality reports
uses: actions/upload-artifact@v4
if: always()
with:
name: quality-reports
path: |
quality-reports/
coverage/
retention-days: 30
- name: Post quality gate results to PR
uses: actions/github-script@v7
if: always() && github.event_name == 'pull_request'
with:
script: |
const fs = require('fs');
const path = require('path');
// Read PR comment if available
const prCommentPath = path.join(process.cwd(), 'quality-reports', 'pr-comment.md');
let comment = '';
if (fs.existsSync(prCommentPath)) {
comment = fs.readFileSync(prCommentPath, 'utf8');
} else {
// Fallback: Read control loop feedback
const feedbackPath = path.join(process.cwd(), 'quality-reports', 'control-loop-feedback.json');
if (fs.existsSync(feedbackPath)) {
const feedback = JSON.parse(fs.readFileSync(feedbackPath, 'utf8'));
const statusIcon = feedback.signals.canDeploy ? '✅' : '❌';
const statusText = feedback.signals.canDeploy ? 'PASSED' : 'FAILED';
comment = `## ${statusIcon} Quality Gate: ${statusText}\n\n`;
comment += '### 📊 Metrics\n\n';
comment += `- **Test Pass Rate**: ${(feedback.metrics.testPassRate * 100).toFixed(1)}%\n`;
comment += `- **Coverage**: ${feedback.metrics.coveragePercentage.toFixed(1)}%\n`;
comment += `- **Quality Score**: ${feedback.qualityScore}/100\n\n`;
if (feedback.violations.length > 0) {
comment += '### ⚠️ Violations\n\n';
for (const violation of feedback.violations) {
comment += `- **${violation.metric}**: ${violation.actualValue} (threshold: ${violation.threshold}) - ${violation.severity}\n`;
}
comment += '\n';
}
comment += '### 🚀 Deployment Decision\n\n';
if (feedback.signals.canDeploy) {
comment += '✅ **APPROVED** - All quality gates passed.\n';
} else {
comment += '❌ **BLOCKED** - Quality gates failed. Fix violations before deployment.\n';
}
} else {
comment = '## ⚠️ Quality Gate: Unable to evaluate\n\nNo quality gate data available.';
}
}
// Find existing quality gate comment
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const botComment = comments.find(comment =>
comment.user?.type === 'Bot' &&
comment.body?.includes('Quality Gate:')
);
const commentBody = `${comment}\n\n---\n*Quality gate evaluated at ${new Date().toISOString()}*`;
if (botComment) {
// Update existing comment
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: commentBody
});
} else {
// Create new comment
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: commentBody
});
}
- name: Update commit status
uses: actions/github-script@v7
if: always()
with:
script: |
const fs = require('fs');
const path = require('path');
// Read feedback
const feedbackPath = path.join(process.cwd(), 'quality-reports', 'control-loop-feedback.json');
let state = 'success';
let description = 'Quality gates passed';
if (fs.existsSync(feedbackPath)) {
const feedback = JSON.parse(fs.readFileSync(feedbackPath, 'utf8'));
if (!feedback.signals.canDeploy) {
state = 'failure';
description = `Quality gates failed: ${feedback.violations.length} violation(s)`;
}
} else {
state = 'error';
description = 'Unable to evaluate quality gates';
}
await github.rest.repos.createCommitStatus({
owner: context.repo.owner,
repo: context.repo.repo,
sha: context.sha,
state: state,
context: 'Quality Gate',
description: description,
target_url: `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`
});
- name: Fail workflow if quality gates failed
if: always()
run: |
if [ -f "quality-reports/control-loop-feedback.json" ]; then
CAN_DEPLOY=$(cat quality-reports/control-loop-feedback.json | jq -r '.signals.canDeploy')
if [ "$CAN_DEPLOY" = "true" ]; then
echo "✅ Quality gates passed - Deployment approved"
exit 0
else
echo "❌ Quality gates failed - Deployment blocked"
# Show violations
VIOLATIONS=$(cat quality-reports/control-loop-feedback.json | jq -r '.violations[] | " - \(.metric): \(.actualValue) (threshold: \(.threshold))"')
echo ""
echo "Violations:"
echo "$VIOLATIONS"
exit 1
fi
else
echo "⚠️ No quality gate feedback found - assuming failure"
exit 1
fi
# Optional: Deploy job that depends on quality gate
deploy:
name: Deploy to Environment
runs-on: ubuntu-latest
needs: quality-gate
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
steps:
- name: Deployment placeholder
run: |
echo "✅ Quality gates passed - Ready for deployment"
echo "This is where you would deploy to production"
echo "For example:"
echo " - Deploy to cloud platform"
echo " - Update infrastructure"
echo " - Notify stakeholders"