feat(auth): add test bypass code 202511 for bind_email_with_verification
Some checks failed
Build docker and publish / build (20.15.1) (push) Failing after 5m30s
Some checks failed
Build docker and publish / build (20.15.1) (push) Failing after 5m30s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
d748a7e75d
commit
110c97ada4
@ -1,38 +0,0 @@
|
|||||||
# .agents Directory
|
|
||||||
|
|
||||||
This directory contains agent configuration and skills for OpenAI Codex CLI.
|
|
||||||
|
|
||||||
## Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
.agents/
|
|
||||||
config.toml # Main configuration file
|
|
||||||
skills/ # Skill definitions
|
|
||||||
skill-name/
|
|
||||||
SKILL.md # Skill instructions
|
|
||||||
scripts/ # Optional scripts
|
|
||||||
docs/ # Optional documentation
|
|
||||||
README.md # This file
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
The `config.toml` file controls:
|
|
||||||
- Model selection
|
|
||||||
- Approval policies
|
|
||||||
- Sandbox modes
|
|
||||||
- MCP server connections
|
|
||||||
- Skills configuration
|
|
||||||
|
|
||||||
## Skills
|
|
||||||
|
|
||||||
Skills are invoked using `$skill-name` syntax. Each skill has:
|
|
||||||
- YAML frontmatter with metadata
|
|
||||||
- Trigger and skip conditions
|
|
||||||
- Commands and examples
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
- Main instructions: `AGENTS.md` (project root)
|
|
||||||
- Local overrides: `.codex/AGENTS.override.md` (gitignored)
|
|
||||||
- Claude Flow: https://github.com/ruvnet/claude-flow
|
|
||||||
@ -1,298 +0,0 @@
|
|||||||
# =============================================================================
|
|
||||||
# Claude Flow V3 - Codex Configuration
|
|
||||||
# =============================================================================
|
|
||||||
# Generated by: @claude-flow/codex
|
|
||||||
# Documentation: https://github.com/ruvnet/claude-flow
|
|
||||||
#
|
|
||||||
# This file configures the Codex CLI for Claude Flow integration.
|
|
||||||
# Place in .agents/config.toml (project) or .codex/config.toml (user).
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Core Settings
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
# Model selection - the AI model to use for code generation
|
|
||||||
# Options: gpt-5.3-codex, gpt-4o, claude-sonnet, claude-opus
|
|
||||||
model = "gpt-5.3-codex"
|
|
||||||
|
|
||||||
# Approval policy determines when human approval is required
|
|
||||||
# - untrusted: Always require approval
|
|
||||||
# - on-failure: Require approval only after failures
|
|
||||||
# - on-request: Require approval for significant changes
|
|
||||||
# - never: Auto-approve all actions (use with caution)
|
|
||||||
approval_policy = "on-request"
|
|
||||||
|
|
||||||
# Sandbox mode controls file system access
|
|
||||||
# - read-only: Can only read files, no modifications
|
|
||||||
# - workspace-write: Can write within workspace directory
|
|
||||||
# - danger-full-access: Full file system access (dangerous)
|
|
||||||
sandbox_mode = "workspace-write"
|
|
||||||
|
|
||||||
# Web search enables internet access for research
|
|
||||||
# - disabled: No web access
|
|
||||||
# - cached: Use cached results when available
|
|
||||||
# - live: Always fetch fresh results
|
|
||||||
web_search = "cached"
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Project Documentation
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
# Maximum bytes to read from AGENTS.md files
|
|
||||||
project_doc_max_bytes = 65536
|
|
||||||
|
|
||||||
# Fallback filenames if AGENTS.md not found
|
|
||||||
project_doc_fallback_filenames = [
|
|
||||||
"AGENTS.md",
|
|
||||||
"TEAM_GUIDE.md",
|
|
||||||
".agents.md"
|
|
||||||
]
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Features
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
[features]
|
|
||||||
# Enable child AGENTS.md guidance
|
|
||||||
child_agents_md = true
|
|
||||||
|
|
||||||
# Cache shell environment for faster repeated commands
|
|
||||||
shell_snapshot = true
|
|
||||||
|
|
||||||
# Smart approvals based on request context
|
|
||||||
request_rule = true
|
|
||||||
|
|
||||||
# Enable remote compaction for large histories
|
|
||||||
remote_compaction = true
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# MCP Servers
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
[mcp_servers.claude-flow]
|
|
||||||
command = "npx"
|
|
||||||
args = ["-y", "@claude-flow/cli@latest"]
|
|
||||||
enabled = true
|
|
||||||
tool_timeout_sec = 120
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Skills Configuration
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
[[skills.config]]
|
|
||||||
path = ".agents/skills/swarm-orchestration"
|
|
||||||
enabled = true
|
|
||||||
|
|
||||||
[[skills.config]]
|
|
||||||
path = ".agents/skills/memory-management"
|
|
||||||
enabled = true
|
|
||||||
|
|
||||||
[[skills.config]]
|
|
||||||
path = ".agents/skills/sparc-methodology"
|
|
||||||
enabled = true
|
|
||||||
|
|
||||||
[[skills.config]]
|
|
||||||
path = ".agents/skills/security-audit"
|
|
||||||
enabled = true
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Profiles
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
# Development profile - more permissive for local work
|
|
||||||
[profiles.dev]
|
|
||||||
approval_policy = "never"
|
|
||||||
sandbox_mode = "danger-full-access"
|
|
||||||
web_search = "live"
|
|
||||||
|
|
||||||
# Safe profile - maximum restrictions
|
|
||||||
[profiles.safe]
|
|
||||||
approval_policy = "untrusted"
|
|
||||||
sandbox_mode = "read-only"
|
|
||||||
web_search = "disabled"
|
|
||||||
|
|
||||||
# CI profile - for automated pipelines
|
|
||||||
[profiles.ci]
|
|
||||||
approval_policy = "never"
|
|
||||||
sandbox_mode = "workspace-write"
|
|
||||||
web_search = "cached"
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# History
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
[history]
|
|
||||||
# Save all session transcripts
|
|
||||||
persistence = "save-all"
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Shell Environment
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
[shell_environment_policy]
|
|
||||||
# Inherit environment variables
|
|
||||||
inherit = "core"
|
|
||||||
|
|
||||||
# Exclude sensitive variables
|
|
||||||
exclude = ["*_KEY", "*_SECRET", "*_TOKEN", "*_PASSWORD"]
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Sandbox Workspace Write Settings
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
[sandbox_workspace_write]
|
|
||||||
# Additional writable paths beyond workspace
|
|
||||||
writable_roots = []
|
|
||||||
|
|
||||||
# Allow network access
|
|
||||||
network_access = true
|
|
||||||
|
|
||||||
# Exclude temp directories
|
|
||||||
exclude_slash_tmp = false
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Security Settings
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
[security]
|
|
||||||
# Enable input validation for all user inputs
|
|
||||||
input_validation = true
|
|
||||||
|
|
||||||
# Prevent directory traversal attacks
|
|
||||||
path_traversal_prevention = true
|
|
||||||
|
|
||||||
# Scan for hardcoded secrets
|
|
||||||
secret_scanning = true
|
|
||||||
|
|
||||||
# Scan dependencies for known CVEs
|
|
||||||
cve_scanning = true
|
|
||||||
|
|
||||||
# Maximum file size for operations (bytes)
|
|
||||||
max_file_size = 10485760
|
|
||||||
|
|
||||||
# Allowed file extensions (empty = allow all)
|
|
||||||
allowed_extensions = []
|
|
||||||
|
|
||||||
# Blocked file patterns (regex)
|
|
||||||
blocked_patterns = ["\\.env$", "credentials\\.json$", "\\.pem$", "\\.key$"]
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Performance Settings
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
[performance]
|
|
||||||
# Maximum concurrent agents
|
|
||||||
max_agents = 8
|
|
||||||
|
|
||||||
# Task timeout in seconds
|
|
||||||
task_timeout = 300
|
|
||||||
|
|
||||||
# Memory limit per agent
|
|
||||||
memory_limit = "512MB"
|
|
||||||
|
|
||||||
# Enable response caching
|
|
||||||
cache_enabled = true
|
|
||||||
|
|
||||||
# Cache TTL in seconds
|
|
||||||
cache_ttl = 3600
|
|
||||||
|
|
||||||
# Enable parallel task execution
|
|
||||||
parallel_execution = true
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Logging Settings
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
[logging]
|
|
||||||
# Log level: debug, info, warn, error
|
|
||||||
level = "info"
|
|
||||||
|
|
||||||
# Log format: json, text, pretty
|
|
||||||
format = "pretty"
|
|
||||||
|
|
||||||
# Log destination: stdout, file, both
|
|
||||||
destination = "stdout"
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Neural Intelligence Settings
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
[neural]
|
|
||||||
# Enable SONA (Self-Optimizing Neural Architecture)
|
|
||||||
sona_enabled = true
|
|
||||||
|
|
||||||
# Enable HNSW vector search
|
|
||||||
hnsw_enabled = true
|
|
||||||
|
|
||||||
# HNSW index parameters
|
|
||||||
hnsw_m = 16
|
|
||||||
hnsw_ef_construction = 200
|
|
||||||
hnsw_ef_search = 100
|
|
||||||
|
|
||||||
# Enable pattern learning
|
|
||||||
pattern_learning = true
|
|
||||||
|
|
||||||
# Learning rate for neural adaptation
|
|
||||||
learning_rate = 0.01
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Swarm Orchestration Settings
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
[swarm]
|
|
||||||
# Default topology: hierarchical, mesh, ring, star
|
|
||||||
default_topology = "hierarchical"
|
|
||||||
|
|
||||||
# Default strategy: balanced, specialized, adaptive
|
|
||||||
default_strategy = "specialized"
|
|
||||||
|
|
||||||
# Consensus algorithm: raft, byzantine, gossip
|
|
||||||
consensus = "raft"
|
|
||||||
|
|
||||||
# Enable anti-drift measures
|
|
||||||
anti_drift = true
|
|
||||||
|
|
||||||
# Checkpoint interval (tasks)
|
|
||||||
checkpoint_interval = 10
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Hooks Configuration
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
[hooks]
|
|
||||||
# Enable lifecycle hooks
|
|
||||||
enabled = true
|
|
||||||
|
|
||||||
# Pre-task hook
|
|
||||||
pre_task = true
|
|
||||||
|
|
||||||
# Post-task hook (for learning)
|
|
||||||
post_task = true
|
|
||||||
|
|
||||||
# Enable neural training on post-edit
|
|
||||||
train_on_edit = true
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Background Workers
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
[workers]
|
|
||||||
# Enable background workers
|
|
||||||
enabled = true
|
|
||||||
|
|
||||||
# Worker configuration
|
|
||||||
[workers.audit]
|
|
||||||
enabled = true
|
|
||||||
priority = "critical"
|
|
||||||
interval = 300
|
|
||||||
|
|
||||||
[workers.optimize]
|
|
||||||
enabled = true
|
|
||||||
priority = "high"
|
|
||||||
interval = 600
|
|
||||||
|
|
||||||
[workers.consolidate]
|
|
||||||
enabled = true
|
|
||||||
priority = "low"
|
|
||||||
interval = 1800
|
|
||||||
@ -1,550 +0,0 @@
|
|||||||
---
|
|
||||||
name: "AgentDB Advanced Features"
|
|
||||||
description: "Master advanced AgentDB features including QUIC synchronization, multi-database management, custom distance metrics, hybrid search, and distributed systems integration. Use when building distributed AI systems, multi-agent coordination, or advanced vector search applications."
|
|
||||||
---
|
|
||||||
|
|
||||||
# AgentDB Advanced Features
|
|
||||||
|
|
||||||
## What This Skill Does
|
|
||||||
|
|
||||||
Covers advanced AgentDB capabilities for distributed systems, multi-database coordination, custom distance metrics, hybrid search (vector + metadata), QUIC synchronization, and production deployment patterns. Enables building sophisticated AI systems with sub-millisecond cross-node communication and advanced search capabilities.
|
|
||||||
|
|
||||||
**Performance**: <1ms QUIC sync, hybrid search with filters, custom distance metrics.
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- Node.js 18+
|
|
||||||
- AgentDB v1.0.7+ (via agentic-flow)
|
|
||||||
- Understanding of distributed systems (for QUIC sync)
|
|
||||||
- Vector search fundamentals
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## QUIC Synchronization
|
|
||||||
|
|
||||||
### What is QUIC Sync?
|
|
||||||
|
|
||||||
QUIC (Quick UDP Internet Connections) enables sub-millisecond latency synchronization between AgentDB instances across network boundaries with automatic retry, multiplexing, and encryption.
|
|
||||||
|
|
||||||
**Benefits**:
|
|
||||||
- <1ms latency between nodes
|
|
||||||
- Multiplexed streams (multiple operations simultaneously)
|
|
||||||
- Built-in encryption (TLS 1.3)
|
|
||||||
- Automatic retry and recovery
|
|
||||||
- Event-based broadcasting
|
|
||||||
|
|
||||||
### Enable QUIC Sync
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { createAgentDBAdapter } from 'agentic-flow/reasoningbank';
|
|
||||||
|
|
||||||
// Initialize with QUIC synchronization
|
|
||||||
const adapter = await createAgentDBAdapter({
|
|
||||||
dbPath: '.agentdb/distributed.db',
|
|
||||||
enableQUICSync: true,
|
|
||||||
syncPort: 4433,
|
|
||||||
syncPeers: [
|
|
||||||
'192.168.1.10:4433',
|
|
||||||
'192.168.1.11:4433',
|
|
||||||
'192.168.1.12:4433',
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
// Patterns automatically sync across all peers
|
|
||||||
await adapter.insertPattern({
|
|
||||||
// ... pattern data
|
|
||||||
});
|
|
||||||
|
|
||||||
// Available on all peers within ~1ms
|
|
||||||
```
|
|
||||||
|
|
||||||
### QUIC Configuration
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const adapter = await createAgentDBAdapter({
|
|
||||||
enableQUICSync: true,
|
|
||||||
syncPort: 4433, // QUIC server port
|
|
||||||
syncPeers: ['host1:4433'], // Peer addresses
|
|
||||||
syncInterval: 1000, // Sync interval (ms)
|
|
||||||
syncBatchSize: 100, // Patterns per batch
|
|
||||||
maxRetries: 3, // Retry failed syncs
|
|
||||||
compression: true, // Enable compression
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Multi-Node Deployment
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Node 1 (192.168.1.10)
|
|
||||||
AGENTDB_QUIC_SYNC=true \
|
|
||||||
AGENTDB_QUIC_PORT=4433 \
|
|
||||||
AGENTDB_QUIC_PEERS=192.168.1.11:4433,192.168.1.12:4433 \
|
|
||||||
node server.js
|
|
||||||
|
|
||||||
# Node 2 (192.168.1.11)
|
|
||||||
AGENTDB_QUIC_SYNC=true \
|
|
||||||
AGENTDB_QUIC_PORT=4433 \
|
|
||||||
AGENTDB_QUIC_PEERS=192.168.1.10:4433,192.168.1.12:4433 \
|
|
||||||
node server.js
|
|
||||||
|
|
||||||
# Node 3 (192.168.1.12)
|
|
||||||
AGENTDB_QUIC_SYNC=true \
|
|
||||||
AGENTDB_QUIC_PORT=4433 \
|
|
||||||
AGENTDB_QUIC_PEERS=192.168.1.10:4433,192.168.1.11:4433 \
|
|
||||||
node server.js
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Distance Metrics
|
|
||||||
|
|
||||||
### Cosine Similarity (Default)
|
|
||||||
|
|
||||||
Best for normalized vectors, semantic similarity:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# CLI
|
|
||||||
npx agentdb@latest query ./vectors.db "[0.1,0.2,...]" -m cosine
|
|
||||||
|
|
||||||
# API
|
|
||||||
const result = await adapter.retrieveWithReasoning(queryEmbedding, {
|
|
||||||
metric: 'cosine',
|
|
||||||
k: 10,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
**Use Cases**:
|
|
||||||
- Text embeddings (BERT, GPT, etc.)
|
|
||||||
- Semantic search
|
|
||||||
- Document similarity
|
|
||||||
- Most general-purpose applications
|
|
||||||
|
|
||||||
**Formula**: `cos(θ) = (A · B) / (||A|| × ||B||)`
|
|
||||||
**Range**: [-1, 1] (1 = identical, -1 = opposite)
|
|
||||||
|
|
||||||
### Euclidean Distance (L2)
|
|
||||||
|
|
||||||
Best for spatial data, geometric similarity:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# CLI
|
|
||||||
npx agentdb@latest query ./vectors.db "[0.1,0.2,...]" -m euclidean
|
|
||||||
|
|
||||||
# API
|
|
||||||
const result = await adapter.retrieveWithReasoning(queryEmbedding, {
|
|
||||||
metric: 'euclidean',
|
|
||||||
k: 10,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
**Use Cases**:
|
|
||||||
- Image embeddings
|
|
||||||
- Spatial data
|
|
||||||
- Computer vision
|
|
||||||
- When vector magnitude matters
|
|
||||||
|
|
||||||
**Formula**: `d = √(Σ(ai - bi)²)`
|
|
||||||
**Range**: [0, ∞] (0 = identical, ∞ = very different)
|
|
||||||
|
|
||||||
### Dot Product
|
|
||||||
|
|
||||||
Best for pre-normalized vectors, fast computation:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# CLI
|
|
||||||
npx agentdb@latest query ./vectors.db "[0.1,0.2,...]" -m dot
|
|
||||||
|
|
||||||
# API
|
|
||||||
const result = await adapter.retrieveWithReasoning(queryEmbedding, {
|
|
||||||
metric: 'dot',
|
|
||||||
k: 10,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
**Use Cases**:
|
|
||||||
- Pre-normalized embeddings
|
|
||||||
- Fast similarity computation
|
|
||||||
- When vectors are already unit-length
|
|
||||||
|
|
||||||
**Formula**: `dot = Σ(ai × bi)`
|
|
||||||
**Range**: [-∞, ∞] (higher = more similar)
|
|
||||||
|
|
||||||
### Custom Distance Metrics
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Implement custom distance function
|
|
||||||
function customDistance(vec1: number[], vec2: number[]): number {
|
|
||||||
// Weighted Euclidean distance
|
|
||||||
const weights = [1.0, 2.0, 1.5, ...];
|
|
||||||
let sum = 0;
|
|
||||||
for (let i = 0; i < vec1.length; i++) {
|
|
||||||
sum += weights[i] * Math.pow(vec1[i] - vec2[i], 2);
|
|
||||||
}
|
|
||||||
return Math.sqrt(sum);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use in search (requires custom implementation)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Hybrid Search (Vector + Metadata)
|
|
||||||
|
|
||||||
### Basic Hybrid Search
|
|
||||||
|
|
||||||
Combine vector similarity with metadata filtering:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Store documents with metadata
|
|
||||||
await adapter.insertPattern({
|
|
||||||
id: '',
|
|
||||||
type: 'document',
|
|
||||||
domain: 'research-papers',
|
|
||||||
pattern_data: JSON.stringify({
|
|
||||||
embedding: documentEmbedding,
|
|
||||||
text: documentText,
|
|
||||||
metadata: {
|
|
||||||
author: 'Jane Smith',
|
|
||||||
year: 2025,
|
|
||||||
category: 'machine-learning',
|
|
||||||
citations: 150,
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
confidence: 1.0,
|
|
||||||
usage_count: 0,
|
|
||||||
success_count: 0,
|
|
||||||
created_at: Date.now(),
|
|
||||||
last_used: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Hybrid search: vector similarity + metadata filters
|
|
||||||
const result = await adapter.retrieveWithReasoning(queryEmbedding, {
|
|
||||||
domain: 'research-papers',
|
|
||||||
k: 20,
|
|
||||||
filters: {
|
|
||||||
year: { $gte: 2023 }, // Published 2023 or later
|
|
||||||
category: 'machine-learning', // ML papers only
|
|
||||||
citations: { $gte: 50 }, // Highly cited
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Advanced Filtering
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Complex metadata queries
|
|
||||||
const result = await adapter.retrieveWithReasoning(queryEmbedding, {
|
|
||||||
domain: 'products',
|
|
||||||
k: 50,
|
|
||||||
filters: {
|
|
||||||
price: { $gte: 10, $lte: 100 }, // Price range
|
|
||||||
category: { $in: ['electronics', 'gadgets'] }, // Multiple categories
|
|
||||||
rating: { $gte: 4.0 }, // High rated
|
|
||||||
inStock: true, // Available
|
|
||||||
tags: { $contains: 'wireless' }, // Has tag
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Weighted Hybrid Search
|
|
||||||
|
|
||||||
Combine vector and metadata scores:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const result = await adapter.retrieveWithReasoning(queryEmbedding, {
|
|
||||||
domain: 'content',
|
|
||||||
k: 20,
|
|
||||||
hybridWeights: {
|
|
||||||
vectorSimilarity: 0.7, // 70% weight on semantic similarity
|
|
||||||
metadataScore: 0.3, // 30% weight on metadata match
|
|
||||||
},
|
|
||||||
filters: {
|
|
||||||
category: 'technology',
|
|
||||||
recency: { $gte: Date.now() - 30 * 24 * 3600000 }, // Last 30 days
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Multi-Database Management
|
|
||||||
|
|
||||||
### Multiple Databases
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Separate databases for different domains
|
|
||||||
const knowledgeDB = await createAgentDBAdapter({
|
|
||||||
dbPath: '.agentdb/knowledge.db',
|
|
||||||
});
|
|
||||||
|
|
||||||
const conversationDB = await createAgentDBAdapter({
|
|
||||||
dbPath: '.agentdb/conversations.db',
|
|
||||||
});
|
|
||||||
|
|
||||||
const codeDB = await createAgentDBAdapter({
|
|
||||||
dbPath: '.agentdb/code.db',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Use appropriate database for each task
|
|
||||||
await knowledgeDB.insertPattern({ /* knowledge */ });
|
|
||||||
await conversationDB.insertPattern({ /* conversation */ });
|
|
||||||
await codeDB.insertPattern({ /* code */ });
|
|
||||||
```
|
|
||||||
|
|
||||||
### Database Sharding
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Shard by domain for horizontal scaling
|
|
||||||
const shards = {
|
|
||||||
'domain-a': await createAgentDBAdapter({ dbPath: '.agentdb/shard-a.db' }),
|
|
||||||
'domain-b': await createAgentDBAdapter({ dbPath: '.agentdb/shard-b.db' }),
|
|
||||||
'domain-c': await createAgentDBAdapter({ dbPath: '.agentdb/shard-c.db' }),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Route queries to appropriate shard
|
|
||||||
function getDBForDomain(domain: string) {
|
|
||||||
const shardKey = domain.split('-')[0]; // Extract shard key
|
|
||||||
return shards[shardKey] || shards['domain-a'];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert to correct shard
|
|
||||||
const db = getDBForDomain('domain-a-task');
|
|
||||||
await db.insertPattern({ /* ... */ });
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## MMR (Maximal Marginal Relevance)
|
|
||||||
|
|
||||||
Retrieve diverse results to avoid redundancy:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Without MMR: Similar results may be redundant
|
|
||||||
const standardResults = await adapter.retrieveWithReasoning(queryEmbedding, {
|
|
||||||
k: 10,
|
|
||||||
useMMR: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
// With MMR: Diverse, non-redundant results
|
|
||||||
const diverseResults = await adapter.retrieveWithReasoning(queryEmbedding, {
|
|
||||||
k: 10,
|
|
||||||
useMMR: true,
|
|
||||||
mmrLambda: 0.5, // Balance relevance (0) vs diversity (1)
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
**MMR Parameters**:
|
|
||||||
- `mmrLambda = 0`: Maximum relevance (may be redundant)
|
|
||||||
- `mmrLambda = 0.5`: Balanced (default)
|
|
||||||
- `mmrLambda = 1`: Maximum diversity (may be less relevant)
|
|
||||||
|
|
||||||
**Use Cases**:
|
|
||||||
- Search result diversification
|
|
||||||
- Recommendation systems
|
|
||||||
- Avoiding echo chambers
|
|
||||||
- Exploratory search
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Context Synthesis
|
|
||||||
|
|
||||||
Generate rich context from multiple memories:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const result = await adapter.retrieveWithReasoning(queryEmbedding, {
|
|
||||||
domain: 'problem-solving',
|
|
||||||
k: 10,
|
|
||||||
synthesizeContext: true, // Enable context synthesis
|
|
||||||
});
|
|
||||||
|
|
||||||
// ContextSynthesizer creates coherent narrative
|
|
||||||
console.log('Synthesized Context:', result.context);
|
|
||||||
// "Based on 10 similar problem-solving attempts, the most effective
|
|
||||||
// approach involves: 1) analyzing root cause, 2) brainstorming solutions,
|
|
||||||
// 3) evaluating trade-offs, 4) implementing incrementally. Success rate: 85%"
|
|
||||||
|
|
||||||
console.log('Patterns:', result.patterns);
|
|
||||||
// Extracted common patterns across memories
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Production Patterns
|
|
||||||
|
|
||||||
### Connection Pooling
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Singleton pattern for shared adapter
|
|
||||||
class AgentDBPool {
|
|
||||||
private static instance: AgentDBAdapter;
|
|
||||||
|
|
||||||
static async getInstance() {
|
|
||||||
if (!this.instance) {
|
|
||||||
this.instance = await createAgentDBAdapter({
|
|
||||||
dbPath: '.agentdb/production.db',
|
|
||||||
quantizationType: 'scalar',
|
|
||||||
cacheSize: 2000,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return this.instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use in application
|
|
||||||
const db = await AgentDBPool.getInstance();
|
|
||||||
const results = await db.retrieveWithReasoning(queryEmbedding, { k: 10 });
|
|
||||||
```
|
|
||||||
|
|
||||||
### Error Handling
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
async function safeRetrieve(queryEmbedding: number[], options: any) {
|
|
||||||
try {
|
|
||||||
const result = await adapter.retrieveWithReasoning(queryEmbedding, options);
|
|
||||||
return result;
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code === 'DIMENSION_MISMATCH') {
|
|
||||||
console.error('Query embedding dimension mismatch');
|
|
||||||
// Handle dimension error
|
|
||||||
} else if (error.code === 'DATABASE_LOCKED') {
|
|
||||||
// Retry with exponential backoff
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 100));
|
|
||||||
return safeRetrieve(queryEmbedding, options);
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Monitoring and Logging
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Performance monitoring
|
|
||||||
const startTime = Date.now();
|
|
||||||
const result = await adapter.retrieveWithReasoning(queryEmbedding, { k: 10 });
|
|
||||||
const latency = Date.now() - startTime;
|
|
||||||
|
|
||||||
if (latency > 100) {
|
|
||||||
console.warn('Slow query detected:', latency, 'ms');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log statistics
|
|
||||||
const stats = await adapter.getStats();
|
|
||||||
console.log('Database Stats:', {
|
|
||||||
totalPatterns: stats.totalPatterns,
|
|
||||||
dbSize: stats.dbSize,
|
|
||||||
cacheHitRate: stats.cacheHitRate,
|
|
||||||
avgSearchLatency: stats.avgSearchLatency,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## CLI Advanced Operations
|
|
||||||
|
|
||||||
### Database Import/Export
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Export with compression
|
|
||||||
npx agentdb@latest export ./vectors.db ./backup.json.gz --compress
|
|
||||||
|
|
||||||
# Import from backup
|
|
||||||
npx agentdb@latest import ./backup.json.gz --decompress
|
|
||||||
|
|
||||||
# Merge databases
|
|
||||||
npx agentdb@latest merge ./db1.sqlite ./db2.sqlite ./merged.sqlite
|
|
||||||
```
|
|
||||||
|
|
||||||
### Database Optimization
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Vacuum database (reclaim space)
|
|
||||||
sqlite3 .agentdb/vectors.db "VACUUM;"
|
|
||||||
|
|
||||||
# Analyze for query optimization
|
|
||||||
sqlite3 .agentdb/vectors.db "ANALYZE;"
|
|
||||||
|
|
||||||
# Rebuild indices
|
|
||||||
npx agentdb@latest reindex ./vectors.db
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Environment Variables
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# AgentDB configuration
|
|
||||||
AGENTDB_PATH=.agentdb/reasoningbank.db
|
|
||||||
AGENTDB_ENABLED=true
|
|
||||||
|
|
||||||
# Performance tuning
|
|
||||||
AGENTDB_QUANTIZATION=binary # binary|scalar|product|none
|
|
||||||
AGENTDB_CACHE_SIZE=2000
|
|
||||||
AGENTDB_HNSW_M=16
|
|
||||||
AGENTDB_HNSW_EF=100
|
|
||||||
|
|
||||||
# Learning plugins
|
|
||||||
AGENTDB_LEARNING=true
|
|
||||||
|
|
||||||
# Reasoning agents
|
|
||||||
AGENTDB_REASONING=true
|
|
||||||
|
|
||||||
# QUIC synchronization
|
|
||||||
AGENTDB_QUIC_SYNC=true
|
|
||||||
AGENTDB_QUIC_PORT=4433
|
|
||||||
AGENTDB_QUIC_PEERS=host1:4433,host2:4433
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Issue: QUIC sync not working
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Check firewall allows UDP port 4433
|
|
||||||
sudo ufw allow 4433/udp
|
|
||||||
|
|
||||||
# Verify peers are reachable
|
|
||||||
ping host1
|
|
||||||
|
|
||||||
# Check QUIC logs
|
|
||||||
DEBUG=agentdb:quic node server.js
|
|
||||||
```
|
|
||||||
|
|
||||||
### Issue: Hybrid search returns no results
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Relax filters
|
|
||||||
const result = await adapter.retrieveWithReasoning(queryEmbedding, {
|
|
||||||
k: 100, // Increase k
|
|
||||||
filters: {
|
|
||||||
// Remove or relax filters
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Issue: Memory consolidation too aggressive
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Disable automatic optimization
|
|
||||||
const result = await adapter.retrieveWithReasoning(queryEmbedding, {
|
|
||||||
optimizeMemory: false, // Disable auto-consolidation
|
|
||||||
k: 10,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Learn More
|
|
||||||
|
|
||||||
- **QUIC Protocol**: docs/quic-synchronization.pdf
|
|
||||||
- **Hybrid Search**: docs/hybrid-search-guide.md
|
|
||||||
- **GitHub**: https://github.com/ruvnet/agentic-flow/tree/main/packages/agentdb
|
|
||||||
- **Website**: https://agentdb.ruv.io
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Category**: Advanced / Distributed Systems
|
|
||||||
**Difficulty**: Advanced
|
|
||||||
**Estimated Time**: 45-60 minutes
|
|
||||||
@ -1,545 +0,0 @@
|
|||||||
---
|
|
||||||
name: "AgentDB Learning Plugins"
|
|
||||||
description: "Create and train AI learning plugins with AgentDB's 9 reinforcement learning algorithms. Includes Decision Transformer, Q-Learning, SARSA, Actor-Critic, and more. Use when building self-learning agents, implementing RL, or optimizing agent behavior through experience."
|
|
||||||
---
|
|
||||||
|
|
||||||
# AgentDB Learning Plugins
|
|
||||||
|
|
||||||
## What This Skill Does
|
|
||||||
|
|
||||||
Provides access to 9 reinforcement learning algorithms via AgentDB's plugin system. Create, train, and deploy learning plugins for autonomous agents that improve through experience. Includes offline RL (Decision Transformer), value-based learning (Q-Learning), policy gradients (Actor-Critic), and advanced techniques.
|
|
||||||
|
|
||||||
**Performance**: Train models 10-100x faster with WASM-accelerated neural inference.
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- Node.js 18+
|
|
||||||
- AgentDB v1.0.7+ (via agentic-flow)
|
|
||||||
- Basic understanding of reinforcement learning (recommended)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Quick Start with CLI
|
|
||||||
|
|
||||||
### Create Learning Plugin
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Interactive wizard
|
|
||||||
npx agentdb@latest create-plugin
|
|
||||||
|
|
||||||
# Use specific template
|
|
||||||
npx agentdb@latest create-plugin -t decision-transformer -n my-agent
|
|
||||||
|
|
||||||
# Preview without creating
|
|
||||||
npx agentdb@latest create-plugin -t q-learning --dry-run
|
|
||||||
|
|
||||||
# Custom output directory
|
|
||||||
npx agentdb@latest create-plugin -t actor-critic -o ./plugins
|
|
||||||
```
|
|
||||||
|
|
||||||
### List Available Templates
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Show all plugin templates
|
|
||||||
npx agentdb@latest list-templates
|
|
||||||
|
|
||||||
# Available templates:
|
|
||||||
# - decision-transformer (sequence modeling RL - recommended)
|
|
||||||
# - q-learning (value-based learning)
|
|
||||||
# - sarsa (on-policy TD learning)
|
|
||||||
# - actor-critic (policy gradient with baseline)
|
|
||||||
# - curiosity-driven (exploration-based)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Manage Plugins
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# List installed plugins
|
|
||||||
npx agentdb@latest list-plugins
|
|
||||||
|
|
||||||
# Get plugin information
|
|
||||||
npx agentdb@latest plugin-info my-agent
|
|
||||||
|
|
||||||
# Shows: algorithm, configuration, training status
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Quick Start with API
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { createAgentDBAdapter } from 'agentic-flow/reasoningbank';
|
|
||||||
|
|
||||||
// Initialize with learning enabled
|
|
||||||
const adapter = await createAgentDBAdapter({
|
|
||||||
dbPath: '.agentdb/learning.db',
|
|
||||||
enableLearning: true, // Enable learning plugins
|
|
||||||
enableReasoning: true,
|
|
||||||
cacheSize: 1000,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Store training experience
|
|
||||||
await adapter.insertPattern({
|
|
||||||
id: '',
|
|
||||||
type: 'experience',
|
|
||||||
domain: 'game-playing',
|
|
||||||
pattern_data: JSON.stringify({
|
|
||||||
embedding: await computeEmbedding('state-action-reward'),
|
|
||||||
pattern: {
|
|
||||||
state: [0.1, 0.2, 0.3],
|
|
||||||
action: 2,
|
|
||||||
reward: 1.0,
|
|
||||||
next_state: [0.15, 0.25, 0.35],
|
|
||||||
done: false
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
confidence: 0.9,
|
|
||||||
usage_count: 1,
|
|
||||||
success_count: 1,
|
|
||||||
created_at: Date.now(),
|
|
||||||
last_used: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Train learning model
|
|
||||||
const metrics = await adapter.train({
|
|
||||||
epochs: 50,
|
|
||||||
batchSize: 32,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Training Loss:', metrics.loss);
|
|
||||||
console.log('Duration:', metrics.duration, 'ms');
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Available Learning Algorithms (9 Total)
|
|
||||||
|
|
||||||
### 1. Decision Transformer (Recommended)
|
|
||||||
|
|
||||||
**Type**: Offline Reinforcement Learning
|
|
||||||
**Best For**: Learning from logged experiences, imitation learning
|
|
||||||
**Strengths**: No online interaction needed, stable training
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx agentdb@latest create-plugin -t decision-transformer -n dt-agent
|
|
||||||
```
|
|
||||||
|
|
||||||
**Use Cases**:
|
|
||||||
- Learn from historical data
|
|
||||||
- Imitation learning from expert demonstrations
|
|
||||||
- Safe learning without environment interaction
|
|
||||||
- Sequence modeling tasks
|
|
||||||
|
|
||||||
**Configuration**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"algorithm": "decision-transformer",
|
|
||||||
"model_size": "base",
|
|
||||||
"context_length": 20,
|
|
||||||
"embed_dim": 128,
|
|
||||||
"n_heads": 8,
|
|
||||||
"n_layers": 6
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Q-Learning
|
|
||||||
|
|
||||||
**Type**: Value-Based RL (Off-Policy)
|
|
||||||
**Best For**: Discrete action spaces, sample efficiency
|
|
||||||
**Strengths**: Proven, simple, works well for small/medium problems
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx agentdb@latest create-plugin -t q-learning -n q-agent
|
|
||||||
```
|
|
||||||
|
|
||||||
**Use Cases**:
|
|
||||||
- Grid worlds, board games
|
|
||||||
- Navigation tasks
|
|
||||||
- Resource allocation
|
|
||||||
- Discrete decision-making
|
|
||||||
|
|
||||||
**Configuration**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"algorithm": "q-learning",
|
|
||||||
"learning_rate": 0.001,
|
|
||||||
"gamma": 0.99,
|
|
||||||
"epsilon": 0.1,
|
|
||||||
"epsilon_decay": 0.995
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. SARSA
|
|
||||||
|
|
||||||
**Type**: Value-Based RL (On-Policy)
|
|
||||||
**Best For**: Safe exploration, risk-sensitive tasks
|
|
||||||
**Strengths**: More conservative than Q-Learning, better for safety
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx agentdb@latest create-plugin -t sarsa -n sarsa-agent
|
|
||||||
```
|
|
||||||
|
|
||||||
**Use Cases**:
|
|
||||||
- Safety-critical applications
|
|
||||||
- Risk-sensitive decision-making
|
|
||||||
- Online learning with exploration
|
|
||||||
|
|
||||||
**Configuration**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"algorithm": "sarsa",
|
|
||||||
"learning_rate": 0.001,
|
|
||||||
"gamma": 0.99,
|
|
||||||
"epsilon": 0.1
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Actor-Critic
|
|
||||||
|
|
||||||
**Type**: Policy Gradient with Value Baseline
|
|
||||||
**Best For**: Continuous actions, variance reduction
|
|
||||||
**Strengths**: Stable, works for continuous/discrete actions
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx agentdb@latest create-plugin -t actor-critic -n ac-agent
|
|
||||||
```
|
|
||||||
|
|
||||||
**Use Cases**:
|
|
||||||
- Continuous control (robotics, simulations)
|
|
||||||
- Complex action spaces
|
|
||||||
- Multi-agent coordination
|
|
||||||
|
|
||||||
**Configuration**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"algorithm": "actor-critic",
|
|
||||||
"actor_lr": 0.001,
|
|
||||||
"critic_lr": 0.002,
|
|
||||||
"gamma": 0.99,
|
|
||||||
"entropy_coef": 0.01
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. Active Learning
|
|
||||||
|
|
||||||
**Type**: Query-Based Learning
|
|
||||||
**Best For**: Label-efficient learning, human-in-the-loop
|
|
||||||
**Strengths**: Minimizes labeling cost, focuses on uncertain samples
|
|
||||||
|
|
||||||
**Use Cases**:
|
|
||||||
- Human feedback incorporation
|
|
||||||
- Label-efficient training
|
|
||||||
- Uncertainty sampling
|
|
||||||
- Annotation cost reduction
|
|
||||||
|
|
||||||
### 6. Adversarial Training
|
|
||||||
|
|
||||||
**Type**: Robustness Enhancement
|
|
||||||
**Best For**: Safety, robustness to perturbations
|
|
||||||
**Strengths**: Improves model robustness, adversarial defense
|
|
||||||
|
|
||||||
**Use Cases**:
|
|
||||||
- Security applications
|
|
||||||
- Robust decision-making
|
|
||||||
- Adversarial defense
|
|
||||||
- Safety testing
|
|
||||||
|
|
||||||
### 7. Curriculum Learning
|
|
||||||
|
|
||||||
**Type**: Progressive Difficulty Training
|
|
||||||
**Best For**: Complex tasks, faster convergence
|
|
||||||
**Strengths**: Stable learning, faster convergence on hard tasks
|
|
||||||
|
|
||||||
**Use Cases**:
|
|
||||||
- Complex multi-stage tasks
|
|
||||||
- Hard exploration problems
|
|
||||||
- Skill composition
|
|
||||||
- Transfer learning
|
|
||||||
|
|
||||||
### 8. Federated Learning
|
|
||||||
|
|
||||||
**Type**: Distributed Learning
|
|
||||||
**Best For**: Privacy, distributed data
|
|
||||||
**Strengths**: Privacy-preserving, scalable
|
|
||||||
|
|
||||||
**Use Cases**:
|
|
||||||
- Multi-agent systems
|
|
||||||
- Privacy-sensitive data
|
|
||||||
- Distributed training
|
|
||||||
- Collaborative learning
|
|
||||||
|
|
||||||
### 9. Multi-Task Learning
|
|
||||||
|
|
||||||
**Type**: Transfer Learning
|
|
||||||
**Best For**: Related tasks, knowledge sharing
|
|
||||||
**Strengths**: Faster learning on new tasks, better generalization
|
|
||||||
|
|
||||||
**Use Cases**:
|
|
||||||
- Task families
|
|
||||||
- Transfer learning
|
|
||||||
- Domain adaptation
|
|
||||||
- Meta-learning
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Training Workflow
|
|
||||||
|
|
||||||
### 1. Collect Experiences
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Store experiences during agent execution
|
|
||||||
for (let i = 0; i < numEpisodes; i++) {
|
|
||||||
const episode = runEpisode();
|
|
||||||
|
|
||||||
for (const step of episode.steps) {
|
|
||||||
await adapter.insertPattern({
|
|
||||||
id: '',
|
|
||||||
type: 'experience',
|
|
||||||
domain: 'task-domain',
|
|
||||||
pattern_data: JSON.stringify({
|
|
||||||
embedding: await computeEmbedding(JSON.stringify(step)),
|
|
||||||
pattern: {
|
|
||||||
state: step.state,
|
|
||||||
action: step.action,
|
|
||||||
reward: step.reward,
|
|
||||||
next_state: step.next_state,
|
|
||||||
done: step.done
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
confidence: step.reward > 0 ? 0.9 : 0.5,
|
|
||||||
usage_count: 1,
|
|
||||||
success_count: step.reward > 0 ? 1 : 0,
|
|
||||||
created_at: Date.now(),
|
|
||||||
last_used: Date.now(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Train Model
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Train on collected experiences
|
|
||||||
const trainingMetrics = await adapter.train({
|
|
||||||
epochs: 100,
|
|
||||||
batchSize: 64,
|
|
||||||
learningRate: 0.001,
|
|
||||||
validationSplit: 0.2,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Training Metrics:', trainingMetrics);
|
|
||||||
// {
|
|
||||||
// loss: 0.023,
|
|
||||||
// valLoss: 0.028,
|
|
||||||
// duration: 1523,
|
|
||||||
// epochs: 100
|
|
||||||
// }
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Evaluate Performance
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Retrieve similar successful experiences
|
|
||||||
const testQuery = await computeEmbedding(JSON.stringify(testState));
|
|
||||||
const result = await adapter.retrieveWithReasoning(testQuery, {
|
|
||||||
domain: 'task-domain',
|
|
||||||
k: 10,
|
|
||||||
synthesizeContext: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Evaluate action quality
|
|
||||||
const suggestedAction = result.memories[0].pattern.action;
|
|
||||||
const confidence = result.memories[0].similarity;
|
|
||||||
|
|
||||||
console.log('Suggested Action:', suggestedAction);
|
|
||||||
console.log('Confidence:', confidence);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Advanced Training Techniques
|
|
||||||
|
|
||||||
### Experience Replay
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Store experiences in buffer
|
|
||||||
const replayBuffer = [];
|
|
||||||
|
|
||||||
// Sample random batch for training
|
|
||||||
const batch = sampleRandomBatch(replayBuffer, batchSize: 32);
|
|
||||||
|
|
||||||
// Train on batch
|
|
||||||
await adapter.train({
|
|
||||||
data: batch,
|
|
||||||
epochs: 1,
|
|
||||||
batchSize: 32,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Prioritized Experience Replay
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Store experiences with priority (TD error)
|
|
||||||
await adapter.insertPattern({
|
|
||||||
// ... standard fields
|
|
||||||
confidence: tdError, // Use TD error as confidence/priority
|
|
||||||
// ...
|
|
||||||
});
|
|
||||||
|
|
||||||
// Retrieve high-priority experiences
|
|
||||||
const highPriority = await adapter.retrieveWithReasoning(queryEmbedding, {
|
|
||||||
domain: 'task-domain',
|
|
||||||
k: 32,
|
|
||||||
minConfidence: 0.7, // Only high TD-error experiences
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Multi-Agent Training
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Collect experiences from multiple agents
|
|
||||||
for (const agent of agents) {
|
|
||||||
const experience = await agent.step();
|
|
||||||
|
|
||||||
await adapter.insertPattern({
|
|
||||||
// ... store experience with agent ID
|
|
||||||
domain: `multi-agent/${agent.id}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Train shared model
|
|
||||||
await adapter.train({
|
|
||||||
epochs: 50,
|
|
||||||
batchSize: 64,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Performance Optimization
|
|
||||||
|
|
||||||
### Batch Training
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Collect batch of experiences
|
|
||||||
const experiences = collectBatch(size: 1000);
|
|
||||||
|
|
||||||
// Batch insert (500x faster)
|
|
||||||
for (const exp of experiences) {
|
|
||||||
await adapter.insertPattern({ /* ... */ });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Train on batch
|
|
||||||
await adapter.train({
|
|
||||||
epochs: 10,
|
|
||||||
batchSize: 128, // Larger batch for efficiency
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Incremental Learning
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Train incrementally as new data arrives
|
|
||||||
setInterval(async () => {
|
|
||||||
const newExperiences = getNewExperiences();
|
|
||||||
|
|
||||||
if (newExperiences.length > 100) {
|
|
||||||
await adapter.train({
|
|
||||||
epochs: 5,
|
|
||||||
batchSize: 32,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, 60000); // Every minute
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Integration with Reasoning Agents
|
|
||||||
|
|
||||||
Combine learning with reasoning for better performance:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Train learning model
|
|
||||||
await adapter.train({ epochs: 50, batchSize: 32 });
|
|
||||||
|
|
||||||
// Use reasoning agents for inference
|
|
||||||
const result = await adapter.retrieveWithReasoning(queryEmbedding, {
|
|
||||||
domain: 'decision-making',
|
|
||||||
k: 10,
|
|
||||||
useMMR: true, // Diverse experiences
|
|
||||||
synthesizeContext: true, // Rich context
|
|
||||||
optimizeMemory: true, // Consolidate patterns
|
|
||||||
});
|
|
||||||
|
|
||||||
// Make decision based on learned experiences + reasoning
|
|
||||||
const decision = result.context.suggestedAction;
|
|
||||||
const confidence = result.memories[0].similarity;
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## CLI Operations
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Create plugin
|
|
||||||
npx agentdb@latest create-plugin -t decision-transformer -n my-plugin
|
|
||||||
|
|
||||||
# List plugins
|
|
||||||
npx agentdb@latest list-plugins
|
|
||||||
|
|
||||||
# Get plugin info
|
|
||||||
npx agentdb@latest plugin-info my-plugin
|
|
||||||
|
|
||||||
# List templates
|
|
||||||
npx agentdb@latest list-templates
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Issue: Training not converging
|
|
||||||
```typescript
|
|
||||||
// Reduce learning rate
|
|
||||||
await adapter.train({
|
|
||||||
epochs: 100,
|
|
||||||
batchSize: 32,
|
|
||||||
learningRate: 0.0001, // Lower learning rate
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Issue: Overfitting
|
|
||||||
```typescript
|
|
||||||
// Use validation split
|
|
||||||
await adapter.train({
|
|
||||||
epochs: 50,
|
|
||||||
batchSize: 64,
|
|
||||||
validationSplit: 0.2, // 20% validation
|
|
||||||
});
|
|
||||||
|
|
||||||
// Enable memory optimization
|
|
||||||
await adapter.retrieveWithReasoning(queryEmbedding, {
|
|
||||||
optimizeMemory: true, // Consolidate, reduce overfitting
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Issue: Slow training
|
|
||||||
```bash
|
|
||||||
# Enable quantization for faster inference
|
|
||||||
# Use binary quantization (32x faster)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Learn More
|
|
||||||
|
|
||||||
- **Algorithm Papers**: See docs/algorithms/ for detailed papers
|
|
||||||
- **GitHub**: https://github.com/ruvnet/agentic-flow/tree/main/packages/agentdb
|
|
||||||
- **MCP Integration**: `npx agentdb@latest mcp`
|
|
||||||
- **Website**: https://agentdb.ruv.io
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Category**: Machine Learning / Reinforcement Learning
|
|
||||||
**Difficulty**: Intermediate to Advanced
|
|
||||||
**Estimated Time**: 30-60 minutes
|
|
||||||
@ -1,339 +0,0 @@
|
|||||||
---
|
|
||||||
name: "AgentDB Memory Patterns"
|
|
||||||
description: "Implement persistent memory patterns for AI agents using AgentDB. Includes session memory, long-term storage, pattern learning, and context management. Use when building stateful agents, chat systems, or intelligent assistants."
|
|
||||||
---
|
|
||||||
|
|
||||||
# AgentDB Memory Patterns
|
|
||||||
|
|
||||||
## What This Skill Does
|
|
||||||
|
|
||||||
Provides memory management patterns for AI agents using AgentDB's persistent storage and ReasoningBank integration. Enables agents to remember conversations, learn from interactions, and maintain context across sessions.
|
|
||||||
|
|
||||||
**Performance**: 150x-12,500x faster than traditional solutions with 100% backward compatibility.
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- Node.js 18+
|
|
||||||
- AgentDB v1.0.7+ (via agentic-flow or standalone)
|
|
||||||
- Understanding of agent architectures
|
|
||||||
|
|
||||||
## Quick Start with CLI
|
|
||||||
|
|
||||||
### Initialize AgentDB
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Initialize vector database
|
|
||||||
npx agentdb@latest init ./agents.db
|
|
||||||
|
|
||||||
# Or with custom dimensions
|
|
||||||
npx agentdb@latest init ./agents.db --dimension 768
|
|
||||||
|
|
||||||
# Use preset configurations
|
|
||||||
npx agentdb@latest init ./agents.db --preset large
|
|
||||||
|
|
||||||
# In-memory database for testing
|
|
||||||
npx agentdb@latest init ./memory.db --in-memory
|
|
||||||
```
|
|
||||||
|
|
||||||
### Start MCP Server for Codex
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Start MCP server (integrates with Codex)
|
|
||||||
npx agentdb@latest mcp
|
|
||||||
|
|
||||||
# Add to Codex (one-time setup)
|
|
||||||
Codex mcp add agentdb npx agentdb@latest mcp
|
|
||||||
```
|
|
||||||
|
|
||||||
### Create Learning Plugin
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Interactive plugin wizard
|
|
||||||
npx agentdb@latest create-plugin
|
|
||||||
|
|
||||||
# Use template directly
|
|
||||||
npx agentdb@latest create-plugin -t decision-transformer -n my-agent
|
|
||||||
|
|
||||||
# Available templates:
|
|
||||||
# - decision-transformer (sequence modeling RL)
|
|
||||||
# - q-learning (value-based learning)
|
|
||||||
# - sarsa (on-policy TD learning)
|
|
||||||
# - actor-critic (policy gradient)
|
|
||||||
# - curiosity-driven (exploration-based)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Quick Start with API
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { createAgentDBAdapter } from 'agentic-flow/reasoningbank';
|
|
||||||
|
|
||||||
// Initialize with default configuration
|
|
||||||
const adapter = await createAgentDBAdapter({
|
|
||||||
dbPath: '.agentdb/reasoningbank.db',
|
|
||||||
enableLearning: true, // Enable learning plugins
|
|
||||||
enableReasoning: true, // Enable reasoning agents
|
|
||||||
quantizationType: 'scalar', // binary | scalar | product | none
|
|
||||||
cacheSize: 1000, // In-memory cache
|
|
||||||
});
|
|
||||||
|
|
||||||
// Store interaction memory
|
|
||||||
const patternId = await adapter.insertPattern({
|
|
||||||
id: '',
|
|
||||||
type: 'pattern',
|
|
||||||
domain: 'conversation',
|
|
||||||
pattern_data: JSON.stringify({
|
|
||||||
embedding: await computeEmbedding('What is the capital of France?'),
|
|
||||||
pattern: {
|
|
||||||
user: 'What is the capital of France?',
|
|
||||||
assistant: 'The capital of France is Paris.',
|
|
||||||
timestamp: Date.now()
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
confidence: 0.95,
|
|
||||||
usage_count: 1,
|
|
||||||
success_count: 1,
|
|
||||||
created_at: Date.now(),
|
|
||||||
last_used: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Retrieve context with reasoning
|
|
||||||
const context = await adapter.retrieveWithReasoning(queryEmbedding, {
|
|
||||||
domain: 'conversation',
|
|
||||||
k: 10,
|
|
||||||
useMMR: true, // Maximal Marginal Relevance
|
|
||||||
synthesizeContext: true, // Generate rich context
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Memory Patterns
|
|
||||||
|
|
||||||
### 1. Session Memory
|
|
||||||
```typescript
|
|
||||||
class SessionMemory {
|
|
||||||
async storeMessage(role: string, content: string) {
|
|
||||||
return await db.storeMemory({
|
|
||||||
sessionId: this.sessionId,
|
|
||||||
role,
|
|
||||||
content,
|
|
||||||
timestamp: Date.now()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async getSessionHistory(limit = 20) {
|
|
||||||
return await db.query({
|
|
||||||
filters: { sessionId: this.sessionId },
|
|
||||||
orderBy: 'timestamp',
|
|
||||||
limit
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Long-Term Memory
|
|
||||||
```typescript
|
|
||||||
// Store important facts
|
|
||||||
await db.storeFact({
|
|
||||||
category: 'user_preference',
|
|
||||||
key: 'language',
|
|
||||||
value: 'English',
|
|
||||||
confidence: 1.0,
|
|
||||||
source: 'explicit'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Retrieve facts
|
|
||||||
const prefs = await db.getFacts({
|
|
||||||
category: 'user_preference'
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Pattern Learning
|
|
||||||
```typescript
|
|
||||||
// Learn from successful interactions
|
|
||||||
await db.storePattern({
|
|
||||||
trigger: 'user_asks_time',
|
|
||||||
response: 'provide_formatted_time',
|
|
||||||
success: true,
|
|
||||||
context: { timezone: 'UTC' }
|
|
||||||
});
|
|
||||||
|
|
||||||
// Apply learned patterns
|
|
||||||
const pattern = await db.matchPattern(currentContext);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Advanced Patterns
|
|
||||||
|
|
||||||
### Hierarchical Memory
|
|
||||||
```typescript
|
|
||||||
// Organize memory in hierarchy
|
|
||||||
await memory.organize({
|
|
||||||
immediate: recentMessages, // Last 10 messages
|
|
||||||
shortTerm: sessionContext, // Current session
|
|
||||||
longTerm: importantFacts, // Persistent facts
|
|
||||||
semantic: embeddedKnowledge // Vector search
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Memory Consolidation
|
|
||||||
```typescript
|
|
||||||
// Periodically consolidate memories
|
|
||||||
await memory.consolidate({
|
|
||||||
strategy: 'importance', // Keep important memories
|
|
||||||
maxSize: 10000, // Size limit
|
|
||||||
minScore: 0.5 // Relevance threshold
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## CLI Operations
|
|
||||||
|
|
||||||
### Query Database
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Query with vector embedding
|
|
||||||
npx agentdb@latest query ./agents.db "[0.1,0.2,0.3,...]"
|
|
||||||
|
|
||||||
# Top-k results
|
|
||||||
npx agentdb@latest query ./agents.db "[0.1,0.2,0.3]" -k 10
|
|
||||||
|
|
||||||
# With similarity threshold
|
|
||||||
npx agentdb@latest query ./agents.db "0.1 0.2 0.3" -t 0.75
|
|
||||||
|
|
||||||
# JSON output
|
|
||||||
npx agentdb@latest query ./agents.db "[...]" -f json
|
|
||||||
```
|
|
||||||
|
|
||||||
### Import/Export Data
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Export vectors to file
|
|
||||||
npx agentdb@latest export ./agents.db ./backup.json
|
|
||||||
|
|
||||||
# Import vectors from file
|
|
||||||
npx agentdb@latest import ./backup.json
|
|
||||||
|
|
||||||
# Get database statistics
|
|
||||||
npx agentdb@latest stats ./agents.db
|
|
||||||
```
|
|
||||||
|
|
||||||
### Performance Benchmarks
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Run performance benchmarks
|
|
||||||
npx agentdb@latest benchmark
|
|
||||||
|
|
||||||
# Results show:
|
|
||||||
# - Pattern Search: 150x faster (100µs vs 15ms)
|
|
||||||
# - Batch Insert: 500x faster (2ms vs 1s)
|
|
||||||
# - Large-scale Query: 12,500x faster (8ms vs 100s)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Integration with ReasoningBank
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { createAgentDBAdapter, migrateToAgentDB } from 'agentic-flow/reasoningbank';
|
|
||||||
|
|
||||||
// Migrate from legacy ReasoningBank
|
|
||||||
const result = await migrateToAgentDB(
|
|
||||||
'.swarm/memory.db', // Source (legacy)
|
|
||||||
'.agentdb/reasoningbank.db' // Destination (AgentDB)
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log(`✅ Migrated ${result.patternsMigrated} patterns`);
|
|
||||||
|
|
||||||
// Train learning model
|
|
||||||
const adapter = await createAgentDBAdapter({
|
|
||||||
enableLearning: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
await adapter.train({
|
|
||||||
epochs: 50,
|
|
||||||
batchSize: 32,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get optimal strategy with reasoning
|
|
||||||
const result = await adapter.retrieveWithReasoning(queryEmbedding, {
|
|
||||||
domain: 'task-planning',
|
|
||||||
synthesizeContext: true,
|
|
||||||
optimizeMemory: true,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Learning Plugins
|
|
||||||
|
|
||||||
### Available Algorithms (9 Total)
|
|
||||||
|
|
||||||
1. **Decision Transformer** - Sequence modeling RL (recommended)
|
|
||||||
2. **Q-Learning** - Value-based learning
|
|
||||||
3. **SARSA** - On-policy TD learning
|
|
||||||
4. **Actor-Critic** - Policy gradient with baseline
|
|
||||||
5. **Active Learning** - Query selection
|
|
||||||
6. **Adversarial Training** - Robustness
|
|
||||||
7. **Curriculum Learning** - Progressive difficulty
|
|
||||||
8. **Federated Learning** - Distributed learning
|
|
||||||
9. **Multi-task Learning** - Transfer learning
|
|
||||||
|
|
||||||
### List and Manage Plugins
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# List available plugins
|
|
||||||
npx agentdb@latest list-plugins
|
|
||||||
|
|
||||||
# List plugin templates
|
|
||||||
npx agentdb@latest list-templates
|
|
||||||
|
|
||||||
# Get plugin info
|
|
||||||
npx agentdb@latest plugin-info <name>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Reasoning Agents (4 Modules)
|
|
||||||
|
|
||||||
1. **PatternMatcher** - Find similar patterns with HNSW indexing
|
|
||||||
2. **ContextSynthesizer** - Generate rich context from multiple sources
|
|
||||||
3. **MemoryOptimizer** - Consolidate similar patterns, prune low-quality
|
|
||||||
4. **ExperienceCurator** - Quality-based experience filtering
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
1. **Enable quantization**: Use scalar/binary for 4-32x memory reduction
|
|
||||||
2. **Use caching**: 1000 pattern cache for <1ms retrieval
|
|
||||||
3. **Batch operations**: 500x faster than individual inserts
|
|
||||||
4. **Train regularly**: Update learning models with new experiences
|
|
||||||
5. **Enable reasoning**: Automatic context synthesis and optimization
|
|
||||||
6. **Monitor metrics**: Use `stats` command to track performance
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Issue: Memory growing too large
|
|
||||||
```bash
|
|
||||||
# Check database size
|
|
||||||
npx agentdb@latest stats ./agents.db
|
|
||||||
|
|
||||||
# Enable quantization
|
|
||||||
# Use 'binary' (32x smaller) or 'scalar' (4x smaller)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Issue: Slow search performance
|
|
||||||
```bash
|
|
||||||
# Enable HNSW indexing and caching
|
|
||||||
# Results: <100µs search time
|
|
||||||
```
|
|
||||||
|
|
||||||
### Issue: Migration from legacy ReasoningBank
|
|
||||||
```bash
|
|
||||||
# Automatic migration with validation
|
|
||||||
npx agentdb@latest migrate --source .swarm/memory.db
|
|
||||||
```
|
|
||||||
|
|
||||||
## Performance Characteristics
|
|
||||||
|
|
||||||
- **Vector Search**: <100µs (HNSW indexing)
|
|
||||||
- **Pattern Retrieval**: <1ms (with cache)
|
|
||||||
- **Batch Insert**: 2ms for 100 patterns
|
|
||||||
- **Memory Efficiency**: 4-32x reduction with quantization
|
|
||||||
- **Backward Compatibility**: 100% compatible with ReasoningBank API
|
|
||||||
|
|
||||||
## Learn More
|
|
||||||
|
|
||||||
- GitHub: https://github.com/ruvnet/agentic-flow/tree/main/packages/agentdb
|
|
||||||
- Documentation: node_modules/agentic-flow/docs/AGENTDB_INTEGRATION.md
|
|
||||||
- MCP Integration: `npx agentdb@latest mcp` for Codex
|
|
||||||
- Website: https://agentdb.ruv.io
|
|
||||||
@ -1,509 +0,0 @@
|
|||||||
---
|
|
||||||
name: "AgentDB Performance Optimization"
|
|
||||||
description: "Optimize AgentDB performance with quantization (4-32x memory reduction), HNSW indexing (150x faster search), caching, and batch operations. Use when optimizing memory usage, improving search speed, or scaling to millions of vectors."
|
|
||||||
---
|
|
||||||
|
|
||||||
# AgentDB Performance Optimization
|
|
||||||
|
|
||||||
## What This Skill Does
|
|
||||||
|
|
||||||
Provides comprehensive performance optimization techniques for AgentDB vector databases. Achieve 150x-12,500x performance improvements through quantization, HNSW indexing, caching strategies, and batch operations. Reduce memory usage by 4-32x while maintaining accuracy.
|
|
||||||
|
|
||||||
**Performance**: <100µs vector search, <1ms pattern retrieval, 2ms batch insert for 100 vectors.
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- Node.js 18+
|
|
||||||
- AgentDB v1.0.7+ (via agentic-flow)
|
|
||||||
- Existing AgentDB database or application
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
### Run Performance Benchmarks
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Comprehensive performance benchmarking
|
|
||||||
npx agentdb@latest benchmark
|
|
||||||
|
|
||||||
# Results show:
|
|
||||||
# ✅ Pattern Search: 150x faster (100µs vs 15ms)
|
|
||||||
# ✅ Batch Insert: 500x faster (2ms vs 1s for 100 vectors)
|
|
||||||
# ✅ Large-scale Query: 12,500x faster (8ms vs 100s at 1M vectors)
|
|
||||||
# ✅ Memory Efficiency: 4-32x reduction with quantization
|
|
||||||
```
|
|
||||||
|
|
||||||
### Enable Optimizations
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { createAgentDBAdapter } from 'agentic-flow/reasoningbank';
|
|
||||||
|
|
||||||
// Optimized configuration
|
|
||||||
const adapter = await createAgentDBAdapter({
|
|
||||||
dbPath: '.agentdb/optimized.db',
|
|
||||||
quantizationType: 'binary', // 32x memory reduction
|
|
||||||
cacheSize: 1000, // In-memory cache
|
|
||||||
enableLearning: true,
|
|
||||||
enableReasoning: true,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Quantization Strategies
|
|
||||||
|
|
||||||
### 1. Binary Quantization (32x Reduction)
|
|
||||||
|
|
||||||
**Best For**: Large-scale deployments (1M+ vectors), memory-constrained environments
|
|
||||||
**Trade-off**: ~2-5% accuracy loss, 32x memory reduction, 10x faster
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const adapter = await createAgentDBAdapter({
|
|
||||||
quantizationType: 'binary',
|
|
||||||
// 768-dim float32 (3072 bytes) → 96 bytes binary
|
|
||||||
// 1M vectors: 3GB → 96MB
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
**Use Cases**:
|
|
||||||
- Mobile/edge deployment
|
|
||||||
- Large-scale vector storage (millions of vectors)
|
|
||||||
- Real-time search with memory constraints
|
|
||||||
|
|
||||||
**Performance**:
|
|
||||||
- Memory: 32x smaller
|
|
||||||
- Search Speed: 10x faster (bit operations)
|
|
||||||
- Accuracy: 95-98% of original
|
|
||||||
|
|
||||||
### 2. Scalar Quantization (4x Reduction)
|
|
||||||
|
|
||||||
**Best For**: Balanced performance/accuracy, moderate datasets
|
|
||||||
**Trade-off**: ~1-2% accuracy loss, 4x memory reduction, 3x faster
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const adapter = await createAgentDBAdapter({
|
|
||||||
quantizationType: 'scalar',
|
|
||||||
// 768-dim float32 (3072 bytes) → 768 bytes (uint8)
|
|
||||||
// 1M vectors: 3GB → 768MB
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
**Use Cases**:
|
|
||||||
- Production applications requiring high accuracy
|
|
||||||
- Medium-scale deployments (10K-1M vectors)
|
|
||||||
- General-purpose optimization
|
|
||||||
|
|
||||||
**Performance**:
|
|
||||||
- Memory: 4x smaller
|
|
||||||
- Search Speed: 3x faster
|
|
||||||
- Accuracy: 98-99% of original
|
|
||||||
|
|
||||||
### 3. Product Quantization (8-16x Reduction)
|
|
||||||
|
|
||||||
**Best For**: High-dimensional vectors, balanced compression
|
|
||||||
**Trade-off**: ~3-7% accuracy loss, 8-16x memory reduction, 5x faster
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const adapter = await createAgentDBAdapter({
|
|
||||||
quantizationType: 'product',
|
|
||||||
// 768-dim float32 (3072 bytes) → 48-96 bytes
|
|
||||||
// 1M vectors: 3GB → 192MB
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
**Use Cases**:
|
|
||||||
- High-dimensional embeddings (>512 dims)
|
|
||||||
- Image/video embeddings
|
|
||||||
- Large-scale similarity search
|
|
||||||
|
|
||||||
**Performance**:
|
|
||||||
- Memory: 8-16x smaller
|
|
||||||
- Search Speed: 5x faster
|
|
||||||
- Accuracy: 93-97% of original
|
|
||||||
|
|
||||||
### 4. No Quantization (Full Precision)
|
|
||||||
|
|
||||||
**Best For**: Maximum accuracy, small datasets
|
|
||||||
**Trade-off**: No accuracy loss, full memory usage
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const adapter = await createAgentDBAdapter({
|
|
||||||
quantizationType: 'none',
|
|
||||||
// Full float32 precision
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## HNSW Indexing
|
|
||||||
|
|
||||||
**Hierarchical Navigable Small World** - O(log n) search complexity
|
|
||||||
|
|
||||||
### Automatic HNSW
|
|
||||||
|
|
||||||
AgentDB automatically builds HNSW indices:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const adapter = await createAgentDBAdapter({
|
|
||||||
dbPath: '.agentdb/vectors.db',
|
|
||||||
// HNSW automatically enabled
|
|
||||||
});
|
|
||||||
|
|
||||||
// Search with HNSW (100µs vs 15ms linear scan)
|
|
||||||
const results = await adapter.retrieveWithReasoning(queryEmbedding, {
|
|
||||||
k: 10,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### HNSW Parameters
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Advanced HNSW configuration
|
|
||||||
const adapter = await createAgentDBAdapter({
|
|
||||||
dbPath: '.agentdb/vectors.db',
|
|
||||||
hnswM: 16, // Connections per layer (default: 16)
|
|
||||||
hnswEfConstruction: 200, // Build quality (default: 200)
|
|
||||||
hnswEfSearch: 100, // Search quality (default: 100)
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
**Parameter Tuning**:
|
|
||||||
- **M** (connections): Higher = better recall, more memory
|
|
||||||
- Small datasets (<10K): M = 8
|
|
||||||
- Medium datasets (10K-100K): M = 16
|
|
||||||
- Large datasets (>100K): M = 32
|
|
||||||
- **efConstruction**: Higher = better index quality, slower build
|
|
||||||
- Fast build: 100
|
|
||||||
- Balanced: 200 (default)
|
|
||||||
- High quality: 400
|
|
||||||
- **efSearch**: Higher = better recall, slower search
|
|
||||||
- Fast search: 50
|
|
||||||
- Balanced: 100 (default)
|
|
||||||
- High recall: 200
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Caching Strategies
|
|
||||||
|
|
||||||
### In-Memory Pattern Cache
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const adapter = await createAgentDBAdapter({
|
|
||||||
cacheSize: 1000, // Cache 1000 most-used patterns
|
|
||||||
});
|
|
||||||
|
|
||||||
// First retrieval: ~2ms (database)
|
|
||||||
// Subsequent: <1ms (cache hit)
|
|
||||||
const result = await adapter.retrieveWithReasoning(queryEmbedding, {
|
|
||||||
k: 10,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
**Cache Tuning**:
|
|
||||||
- Small applications: 100-500 patterns
|
|
||||||
- Medium applications: 500-2000 patterns
|
|
||||||
- Large applications: 2000-5000 patterns
|
|
||||||
|
|
||||||
### LRU Cache Behavior
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Cache automatically evicts least-recently-used patterns
|
|
||||||
// Most frequently accessed patterns stay in cache
|
|
||||||
|
|
||||||
// Monitor cache performance
|
|
||||||
const stats = await adapter.getStats();
|
|
||||||
console.log('Cache Hit Rate:', stats.cacheHitRate);
|
|
||||||
// Aim for >80% hit rate
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Batch Operations
|
|
||||||
|
|
||||||
### Batch Insert (500x Faster)
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// ❌ SLOW: Individual inserts
|
|
||||||
for (const doc of documents) {
|
|
||||||
await adapter.insertPattern({ /* ... */ }); // 1s for 100 docs
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ FAST: Batch insert
|
|
||||||
const patterns = documents.map(doc => ({
|
|
||||||
id: '',
|
|
||||||
type: 'document',
|
|
||||||
domain: 'knowledge',
|
|
||||||
pattern_data: JSON.stringify({
|
|
||||||
embedding: doc.embedding,
|
|
||||||
text: doc.text,
|
|
||||||
}),
|
|
||||||
confidence: 1.0,
|
|
||||||
usage_count: 0,
|
|
||||||
success_count: 0,
|
|
||||||
created_at: Date.now(),
|
|
||||||
last_used: Date.now(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Insert all at once (2ms for 100 docs)
|
|
||||||
for (const pattern of patterns) {
|
|
||||||
await adapter.insertPattern(pattern);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Batch Retrieval
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Retrieve multiple queries efficiently
|
|
||||||
const queries = [queryEmbedding1, queryEmbedding2, queryEmbedding3];
|
|
||||||
|
|
||||||
// Parallel retrieval
|
|
||||||
const results = await Promise.all(
|
|
||||||
queries.map(q => adapter.retrieveWithReasoning(q, { k: 5 }))
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Memory Optimization
|
|
||||||
|
|
||||||
### Automatic Consolidation
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Enable automatic pattern consolidation
|
|
||||||
const result = await adapter.retrieveWithReasoning(queryEmbedding, {
|
|
||||||
domain: 'documents',
|
|
||||||
optimizeMemory: true, // Consolidate similar patterns
|
|
||||||
k: 10,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Optimizations:', result.optimizations);
|
|
||||||
// {
|
|
||||||
// consolidated: 15, // Merged 15 similar patterns
|
|
||||||
// pruned: 3, // Removed 3 low-quality patterns
|
|
||||||
// improved_quality: 0.12 // 12% quality improvement
|
|
||||||
// }
|
|
||||||
```
|
|
||||||
|
|
||||||
### Manual Optimization
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Manually trigger optimization
|
|
||||||
await adapter.optimize();
|
|
||||||
|
|
||||||
// Get statistics
|
|
||||||
const stats = await adapter.getStats();
|
|
||||||
console.log('Before:', stats.totalPatterns);
|
|
||||||
console.log('After:', stats.totalPatterns); // Reduced by ~10-30%
|
|
||||||
```
|
|
||||||
|
|
||||||
### Pruning Strategies
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Prune low-confidence patterns
|
|
||||||
await adapter.prune({
|
|
||||||
minConfidence: 0.5, // Remove confidence < 0.5
|
|
||||||
minUsageCount: 2, // Remove usage_count < 2
|
|
||||||
maxAge: 30 * 24 * 3600, // Remove >30 days old
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Performance Monitoring
|
|
||||||
|
|
||||||
### Database Statistics
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Get comprehensive stats
|
|
||||||
npx agentdb@latest stats .agentdb/vectors.db
|
|
||||||
|
|
||||||
# Output:
|
|
||||||
# Total Patterns: 125,430
|
|
||||||
# Database Size: 47.2 MB (with binary quantization)
|
|
||||||
# Avg Confidence: 0.87
|
|
||||||
# Domains: 15
|
|
||||||
# Cache Hit Rate: 84%
|
|
||||||
# Index Type: HNSW
|
|
||||||
```
|
|
||||||
|
|
||||||
### Runtime Metrics
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const stats = await adapter.getStats();
|
|
||||||
|
|
||||||
console.log('Performance Metrics:');
|
|
||||||
console.log('Total Patterns:', stats.totalPatterns);
|
|
||||||
console.log('Database Size:', stats.dbSize);
|
|
||||||
console.log('Avg Confidence:', stats.avgConfidence);
|
|
||||||
console.log('Cache Hit Rate:', stats.cacheHitRate);
|
|
||||||
console.log('Search Latency (avg):', stats.avgSearchLatency);
|
|
||||||
console.log('Insert Latency (avg):', stats.avgInsertLatency);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Optimization Recipes
|
|
||||||
|
|
||||||
### Recipe 1: Maximum Speed (Sacrifice Accuracy)
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const adapter = await createAgentDBAdapter({
|
|
||||||
quantizationType: 'binary', // 32x memory reduction
|
|
||||||
cacheSize: 5000, // Large cache
|
|
||||||
hnswM: 8, // Fewer connections = faster
|
|
||||||
hnswEfSearch: 50, // Low search quality = faster
|
|
||||||
});
|
|
||||||
|
|
||||||
// Expected: <50µs search, 90-95% accuracy
|
|
||||||
```
|
|
||||||
|
|
||||||
### Recipe 2: Balanced Performance
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const adapter = await createAgentDBAdapter({
|
|
||||||
quantizationType: 'scalar', // 4x memory reduction
|
|
||||||
cacheSize: 1000, // Standard cache
|
|
||||||
hnswM: 16, // Balanced connections
|
|
||||||
hnswEfSearch: 100, // Balanced quality
|
|
||||||
});
|
|
||||||
|
|
||||||
// Expected: <100µs search, 98-99% accuracy
|
|
||||||
```
|
|
||||||
|
|
||||||
### Recipe 3: Maximum Accuracy
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const adapter = await createAgentDBAdapter({
|
|
||||||
quantizationType: 'none', // No quantization
|
|
||||||
cacheSize: 2000, // Large cache
|
|
||||||
hnswM: 32, // Many connections
|
|
||||||
hnswEfSearch: 200, // High search quality
|
|
||||||
});
|
|
||||||
|
|
||||||
// Expected: <200µs search, 100% accuracy
|
|
||||||
```
|
|
||||||
|
|
||||||
### Recipe 4: Memory-Constrained (Mobile/Edge)
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const adapter = await createAgentDBAdapter({
|
|
||||||
quantizationType: 'binary', // 32x memory reduction
|
|
||||||
cacheSize: 100, // Small cache
|
|
||||||
hnswM: 8, // Minimal connections
|
|
||||||
});
|
|
||||||
|
|
||||||
// Expected: <100µs search, ~10MB for 100K vectors
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Scaling Strategies
|
|
||||||
|
|
||||||
### Small Scale (<10K vectors)
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const adapter = await createAgentDBAdapter({
|
|
||||||
quantizationType: 'none', // Full precision
|
|
||||||
cacheSize: 500,
|
|
||||||
hnswM: 8,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Medium Scale (10K-100K vectors)
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const adapter = await createAgentDBAdapter({
|
|
||||||
quantizationType: 'scalar', // 4x reduction
|
|
||||||
cacheSize: 1000,
|
|
||||||
hnswM: 16,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Large Scale (100K-1M vectors)
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const adapter = await createAgentDBAdapter({
|
|
||||||
quantizationType: 'binary', // 32x reduction
|
|
||||||
cacheSize: 2000,
|
|
||||||
hnswM: 32,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Massive Scale (>1M vectors)
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const adapter = await createAgentDBAdapter({
|
|
||||||
quantizationType: 'product', // 8-16x reduction
|
|
||||||
cacheSize: 5000,
|
|
||||||
hnswM: 48,
|
|
||||||
hnswEfConstruction: 400,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Issue: High memory usage
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Check database size
|
|
||||||
npx agentdb@latest stats .agentdb/vectors.db
|
|
||||||
|
|
||||||
# Enable quantization
|
|
||||||
# Use 'binary' for 32x reduction
|
|
||||||
```
|
|
||||||
|
|
||||||
### Issue: Slow search performance
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Increase cache size
|
|
||||||
const adapter = await createAgentDBAdapter({
|
|
||||||
cacheSize: 2000, // Increase from 1000
|
|
||||||
});
|
|
||||||
|
|
||||||
// Reduce search quality (faster)
|
|
||||||
const result = await adapter.retrieveWithReasoning(queryEmbedding, {
|
|
||||||
k: 5, // Reduce from 10
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Issue: Low accuracy
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Disable or use lighter quantization
|
|
||||||
const adapter = await createAgentDBAdapter({
|
|
||||||
quantizationType: 'scalar', // Instead of 'binary'
|
|
||||||
hnswEfSearch: 200, // Higher search quality
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Performance Benchmarks
|
|
||||||
|
|
||||||
**Test System**: AMD Ryzen 9 5950X, 64GB RAM
|
|
||||||
|
|
||||||
| Operation | Vector Count | No Optimization | Optimized | Improvement |
|
|
||||||
|-----------|-------------|-----------------|-----------|-------------|
|
|
||||||
| Search | 10K | 15ms | 100µs | 150x |
|
|
||||||
| Search | 100K | 150ms | 120µs | 1,250x |
|
|
||||||
| Search | 1M | 100s | 8ms | 12,500x |
|
|
||||||
| Batch Insert (100) | - | 1s | 2ms | 500x |
|
|
||||||
| Memory Usage | 1M | 3GB | 96MB | 32x (binary) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Learn More
|
|
||||||
|
|
||||||
- **Quantization Paper**: docs/quantization-techniques.pdf
|
|
||||||
- **HNSW Algorithm**: docs/hnsw-index.pdf
|
|
||||||
- **GitHub**: https://github.com/ruvnet/agentic-flow/tree/main/packages/agentdb
|
|
||||||
- **Website**: https://agentdb.ruv.io
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Category**: Performance / Optimization
|
|
||||||
**Difficulty**: Intermediate
|
|
||||||
**Estimated Time**: 20-30 minutes
|
|
||||||
@ -1,339 +0,0 @@
|
|||||||
---
|
|
||||||
name: "AgentDB Vector Search"
|
|
||||||
description: "Implement semantic vector search with AgentDB for intelligent document retrieval, similarity matching, and context-aware querying. Use when building RAG systems, semantic search engines, or intelligent knowledge bases."
|
|
||||||
---
|
|
||||||
|
|
||||||
# AgentDB Vector Search
|
|
||||||
|
|
||||||
## What This Skill Does
|
|
||||||
|
|
||||||
Implements vector-based semantic search using AgentDB's high-performance vector database with **150x-12,500x faster** operations than traditional solutions. Features HNSW indexing, quantization, and sub-millisecond search (<100µs).
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- Node.js 18+
|
|
||||||
- AgentDB v1.0.7+ (via agentic-flow or standalone)
|
|
||||||
- OpenAI API key (for embeddings) or custom embedding model
|
|
||||||
|
|
||||||
## Quick Start with CLI
|
|
||||||
|
|
||||||
### Initialize Vector Database
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Initialize with default dimensions (1536 for OpenAI ada-002)
|
|
||||||
npx agentdb@latest init ./vectors.db
|
|
||||||
|
|
||||||
# Custom dimensions for different embedding models
|
|
||||||
npx agentdb@latest init ./vectors.db --dimension 768 # sentence-transformers
|
|
||||||
npx agentdb@latest init ./vectors.db --dimension 384 # all-MiniLM-L6-v2
|
|
||||||
|
|
||||||
# Use preset configurations
|
|
||||||
npx agentdb@latest init ./vectors.db --preset small # <10K vectors
|
|
||||||
npx agentdb@latest init ./vectors.db --preset medium # 10K-100K vectors
|
|
||||||
npx agentdb@latest init ./vectors.db --preset large # >100K vectors
|
|
||||||
|
|
||||||
# In-memory database for testing
|
|
||||||
npx agentdb@latest init ./vectors.db --in-memory
|
|
||||||
```
|
|
||||||
|
|
||||||
### Query Vector Database
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Basic similarity search
|
|
||||||
npx agentdb@latest query ./vectors.db "[0.1,0.2,0.3,...]"
|
|
||||||
|
|
||||||
# Top-k results
|
|
||||||
npx agentdb@latest query ./vectors.db "[0.1,0.2,0.3]" -k 10
|
|
||||||
|
|
||||||
# With similarity threshold (cosine similarity)
|
|
||||||
npx agentdb@latest query ./vectors.db "0.1 0.2 0.3" -t 0.75 -m cosine
|
|
||||||
|
|
||||||
# Different distance metrics
|
|
||||||
npx agentdb@latest query ./vectors.db "[...]" -m euclidean # L2 distance
|
|
||||||
npx agentdb@latest query ./vectors.db "[...]" -m dot # Dot product
|
|
||||||
|
|
||||||
# JSON output for automation
|
|
||||||
npx agentdb@latest query ./vectors.db "[...]" -f json -k 5
|
|
||||||
|
|
||||||
# Verbose output with distances
|
|
||||||
npx agentdb@latest query ./vectors.db "[...]" -v
|
|
||||||
```
|
|
||||||
|
|
||||||
### Import/Export Vectors
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Export vectors to JSON
|
|
||||||
npx agentdb@latest export ./vectors.db ./backup.json
|
|
||||||
|
|
||||||
# Import vectors from JSON
|
|
||||||
npx agentdb@latest import ./backup.json
|
|
||||||
|
|
||||||
# Get database statistics
|
|
||||||
npx agentdb@latest stats ./vectors.db
|
|
||||||
```
|
|
||||||
|
|
||||||
## Quick Start with API
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { createAgentDBAdapter, computeEmbedding } from 'agentic-flow/reasoningbank';
|
|
||||||
|
|
||||||
// Initialize with vector search optimizations
|
|
||||||
const adapter = await createAgentDBAdapter({
|
|
||||||
dbPath: '.agentdb/vectors.db',
|
|
||||||
enableLearning: false, // Vector search only
|
|
||||||
enableReasoning: true, // Enable semantic matching
|
|
||||||
quantizationType: 'binary', // 32x memory reduction
|
|
||||||
cacheSize: 1000, // Fast retrieval
|
|
||||||
});
|
|
||||||
|
|
||||||
// Store document with embedding
|
|
||||||
const text = "The quantum computer achieved 100 qubits";
|
|
||||||
const embedding = await computeEmbedding(text);
|
|
||||||
|
|
||||||
await adapter.insertPattern({
|
|
||||||
id: '',
|
|
||||||
type: 'document',
|
|
||||||
domain: 'technology',
|
|
||||||
pattern_data: JSON.stringify({
|
|
||||||
embedding,
|
|
||||||
text,
|
|
||||||
metadata: { category: "quantum", date: "2025-01-15" }
|
|
||||||
}),
|
|
||||||
confidence: 1.0,
|
|
||||||
usage_count: 0,
|
|
||||||
success_count: 0,
|
|
||||||
created_at: Date.now(),
|
|
||||||
last_used: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Semantic search with MMR (Maximal Marginal Relevance)
|
|
||||||
const queryEmbedding = await computeEmbedding("quantum computing advances");
|
|
||||||
const results = await adapter.retrieveWithReasoning(queryEmbedding, {
|
|
||||||
domain: 'technology',
|
|
||||||
k: 10,
|
|
||||||
useMMR: true, // Diverse results
|
|
||||||
synthesizeContext: true, // Rich context
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Core Features
|
|
||||||
|
|
||||||
### 1. Vector Storage
|
|
||||||
```typescript
|
|
||||||
// Store with automatic embedding
|
|
||||||
await db.storeWithEmbedding({
|
|
||||||
content: "Your document text",
|
|
||||||
metadata: { source: "docs", page: 42 }
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Similarity Search
|
|
||||||
```typescript
|
|
||||||
// Find similar documents
|
|
||||||
const similar = await db.findSimilar("quantum computing", {
|
|
||||||
limit: 5,
|
|
||||||
minScore: 0.75
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Hybrid Search (Vector + Metadata)
|
|
||||||
```typescript
|
|
||||||
// Combine vector similarity with metadata filtering
|
|
||||||
const results = await db.hybridSearch({
|
|
||||||
query: "machine learning models",
|
|
||||||
filters: {
|
|
||||||
category: "research",
|
|
||||||
date: { $gte: "2024-01-01" }
|
|
||||||
},
|
|
||||||
limit: 20
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Advanced Usage
|
|
||||||
|
|
||||||
### RAG (Retrieval Augmented Generation)
|
|
||||||
```typescript
|
|
||||||
// Build RAG pipeline
|
|
||||||
async function ragQuery(question: string) {
|
|
||||||
// 1. Get relevant context
|
|
||||||
const context = await db.searchSimilar(
|
|
||||||
await embed(question),
|
|
||||||
{ limit: 5, threshold: 0.7 }
|
|
||||||
);
|
|
||||||
|
|
||||||
// 2. Generate answer with context
|
|
||||||
const prompt = `Context: ${context.map(c => c.text).join('\n')}
|
|
||||||
Question: ${question}`;
|
|
||||||
|
|
||||||
return await llm.generate(prompt);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Batch Operations
|
|
||||||
```typescript
|
|
||||||
// Efficient batch storage
|
|
||||||
await db.batchStore(documents.map(doc => ({
|
|
||||||
text: doc.content,
|
|
||||||
embedding: doc.vector,
|
|
||||||
metadata: doc.meta
|
|
||||||
})));
|
|
||||||
```
|
|
||||||
|
|
||||||
## MCP Server Integration
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Start AgentDB MCP server for Codex
|
|
||||||
npx agentdb@latest mcp
|
|
||||||
|
|
||||||
# Add to Codex (one-time setup)
|
|
||||||
Codex mcp add agentdb npx agentdb@latest mcp
|
|
||||||
|
|
||||||
# Now use MCP tools in Codex:
|
|
||||||
# - agentdb_query: Semantic vector search
|
|
||||||
# - agentdb_store: Store documents with embeddings
|
|
||||||
# - agentdb_stats: Database statistics
|
|
||||||
```
|
|
||||||
|
|
||||||
## Performance Benchmarks
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Run comprehensive benchmarks
|
|
||||||
npx agentdb@latest benchmark
|
|
||||||
|
|
||||||
# Results:
|
|
||||||
# ✅ Pattern Search: 150x faster (100µs vs 15ms)
|
|
||||||
# ✅ Batch Insert: 500x faster (2ms vs 1s for 100 vectors)
|
|
||||||
# ✅ Large-scale Query: 12,500x faster (8ms vs 100s at 1M vectors)
|
|
||||||
# ✅ Memory Efficiency: 4-32x reduction with quantization
|
|
||||||
```
|
|
||||||
|
|
||||||
## Quantization Options
|
|
||||||
|
|
||||||
AgentDB provides multiple quantization strategies for memory efficiency:
|
|
||||||
|
|
||||||
### Binary Quantization (32x reduction)
|
|
||||||
```typescript
|
|
||||||
const adapter = await createAgentDBAdapter({
|
|
||||||
quantizationType: 'binary', // 768-dim → 96 bytes
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Scalar Quantization (4x reduction)
|
|
||||||
```typescript
|
|
||||||
const adapter = await createAgentDBAdapter({
|
|
||||||
quantizationType: 'scalar', // 768-dim → 768 bytes
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Product Quantization (8-16x reduction)
|
|
||||||
```typescript
|
|
||||||
const adapter = await createAgentDBAdapter({
|
|
||||||
quantizationType: 'product', // 768-dim → 48-96 bytes
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Distance Metrics
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Cosine similarity (default, best for most use cases)
|
|
||||||
npx agentdb@latest query ./db.sqlite "[...]" -m cosine
|
|
||||||
|
|
||||||
# Euclidean distance (L2 norm)
|
|
||||||
npx agentdb@latest query ./db.sqlite "[...]" -m euclidean
|
|
||||||
|
|
||||||
# Dot product (for normalized vectors)
|
|
||||||
npx agentdb@latest query ./db.sqlite "[...]" -m dot
|
|
||||||
```
|
|
||||||
|
|
||||||
## Advanced Features
|
|
||||||
|
|
||||||
### HNSW Indexing
|
|
||||||
- **O(log n) search complexity**
|
|
||||||
- **Sub-millisecond retrieval** (<100µs)
|
|
||||||
- **Automatic index building**
|
|
||||||
|
|
||||||
### Caching
|
|
||||||
- **1000 pattern in-memory cache**
|
|
||||||
- **<1ms pattern retrieval**
|
|
||||||
- **Automatic cache invalidation**
|
|
||||||
|
|
||||||
### MMR (Maximal Marginal Relevance)
|
|
||||||
- **Diverse result sets**
|
|
||||||
- **Avoid redundancy**
|
|
||||||
- **Balance relevance and diversity**
|
|
||||||
|
|
||||||
## Performance Tips
|
|
||||||
|
|
||||||
1. **Enable HNSW indexing**: Automatic with AgentDB, 10-100x faster
|
|
||||||
2. **Use quantization**: Binary (32x), Scalar (4x), Product (8-16x) memory reduction
|
|
||||||
3. **Batch operations**: 500x faster for bulk inserts
|
|
||||||
4. **Match dimensions**: 1536 (OpenAI), 768 (sentence-transformers), 384 (MiniLM)
|
|
||||||
5. **Similarity threshold**: Start at 0.7 for quality, adjust based on use case
|
|
||||||
6. **Enable caching**: 1000 pattern cache for frequent queries
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Issue: Slow search performance
|
|
||||||
```bash
|
|
||||||
# Check if HNSW indexing is enabled (automatic)
|
|
||||||
npx agentdb@latest stats ./vectors.db
|
|
||||||
|
|
||||||
# Expected: <100µs search time
|
|
||||||
```
|
|
||||||
|
|
||||||
### Issue: High memory usage
|
|
||||||
```bash
|
|
||||||
# Enable binary quantization (32x reduction)
|
|
||||||
# Use in adapter: quantizationType: 'binary'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Issue: Poor relevance
|
|
||||||
```bash
|
|
||||||
# Adjust similarity threshold
|
|
||||||
npx agentdb@latest query ./db.sqlite "[...]" -t 0.8 # Higher threshold
|
|
||||||
|
|
||||||
# Or use MMR for diverse results
|
|
||||||
# Use in adapter: useMMR: true
|
|
||||||
```
|
|
||||||
|
|
||||||
### Issue: Wrong dimensions
|
|
||||||
```bash
|
|
||||||
# Check embedding model dimensions:
|
|
||||||
# - OpenAI ada-002: 1536
|
|
||||||
# - sentence-transformers: 768
|
|
||||||
# - all-MiniLM-L6-v2: 384
|
|
||||||
|
|
||||||
npx agentdb@latest init ./db.sqlite --dimension 768
|
|
||||||
```
|
|
||||||
|
|
||||||
## Database Statistics
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Get comprehensive stats
|
|
||||||
npx agentdb@latest stats ./vectors.db
|
|
||||||
|
|
||||||
# Shows:
|
|
||||||
# - Total patterns/vectors
|
|
||||||
# - Database size
|
|
||||||
# - Average confidence
|
|
||||||
# - Domains distribution
|
|
||||||
# - Index status
|
|
||||||
```
|
|
||||||
|
|
||||||
## Performance Characteristics
|
|
||||||
|
|
||||||
- **Vector Search**: <100µs (HNSW indexing)
|
|
||||||
- **Pattern Retrieval**: <1ms (with cache)
|
|
||||||
- **Batch Insert**: 2ms for 100 vectors
|
|
||||||
- **Memory Efficiency**: 4-32x reduction with quantization
|
|
||||||
- **Scalability**: Handles 1M+ vectors efficiently
|
|
||||||
- **Latency**: Sub-millisecond for most operations
|
|
||||||
|
|
||||||
## Learn More
|
|
||||||
|
|
||||||
- GitHub: https://github.com/ruvnet/agentic-flow/tree/main/packages/agentdb
|
|
||||||
- Documentation: node_modules/agentic-flow/docs/AGENTDB_INTEGRATION.md
|
|
||||||
- MCP Integration: `npx agentdb@latest mcp` for Codex
|
|
||||||
- Website: https://agentdb.ruv.io
|
|
||||||
- CLI Help: `npx agentdb@latest --help`
|
|
||||||
- Command Help: `npx agentdb@latest help <command>`
|
|
||||||
@ -1,204 +0,0 @@
|
|||||||
---
|
|
||||||
name: browser
|
|
||||||
description: Web browser automation with AI-optimized snapshots for Codex-flow agents
|
|
||||||
version: 1.0.0
|
|
||||||
triggers:
|
|
||||||
- /browser
|
|
||||||
- browse
|
|
||||||
- web automation
|
|
||||||
- scrape
|
|
||||||
- navigate
|
|
||||||
- screenshot
|
|
||||||
tools:
|
|
||||||
- browser/open
|
|
||||||
- browser/snapshot
|
|
||||||
- browser/click
|
|
||||||
- browser/fill
|
|
||||||
- browser/screenshot
|
|
||||||
- browser/close
|
|
||||||
---
|
|
||||||
|
|
||||||
# Browser Automation Skill
|
|
||||||
|
|
||||||
Web browser automation using agent-browser with AI-optimized snapshots. Reduces context by 93% using element refs (@e1, @e2) instead of full DOM.
|
|
||||||
|
|
||||||
## Core Workflow
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. Navigate to page
|
|
||||||
agent-browser open <url>
|
|
||||||
|
|
||||||
# 2. Get accessibility tree with element refs
|
|
||||||
agent-browser snapshot -i # -i = interactive elements only
|
|
||||||
|
|
||||||
# 3. Interact using refs from snapshot
|
|
||||||
agent-browser click @e2
|
|
||||||
agent-browser fill @e3 "text"
|
|
||||||
|
|
||||||
# 4. Re-snapshot after page changes
|
|
||||||
agent-browser snapshot -i
|
|
||||||
```
|
|
||||||
|
|
||||||
## Quick Reference
|
|
||||||
|
|
||||||
### Navigation
|
|
||||||
| Command | Description |
|
|
||||||
|---------|-------------|
|
|
||||||
| `open <url>` | Navigate to URL |
|
|
||||||
| `back` | Go back |
|
|
||||||
| `forward` | Go forward |
|
|
||||||
| `reload` | Reload page |
|
|
||||||
| `close` | Close browser |
|
|
||||||
|
|
||||||
### Snapshots (AI-Optimized)
|
|
||||||
| Command | Description |
|
|
||||||
|---------|-------------|
|
|
||||||
| `snapshot` | Full accessibility tree |
|
|
||||||
| `snapshot -i` | Interactive elements only (buttons, links, inputs) |
|
|
||||||
| `snapshot -c` | Compact (remove empty elements) |
|
|
||||||
| `snapshot -d 3` | Limit depth to 3 levels |
|
|
||||||
| `screenshot [path]` | Capture screenshot (base64 if no path) |
|
|
||||||
|
|
||||||
### Interaction
|
|
||||||
| Command | Description |
|
|
||||||
|---------|-------------|
|
|
||||||
| `click <sel>` | Click element |
|
|
||||||
| `fill <sel> <text>` | Clear and fill input |
|
|
||||||
| `type <sel> <text>` | Type with key events |
|
|
||||||
| `press <key>` | Press key (Enter, Tab, etc.) |
|
|
||||||
| `hover <sel>` | Hover element |
|
|
||||||
| `select <sel> <val>` | Select dropdown option |
|
|
||||||
| `check/uncheck <sel>` | Toggle checkbox |
|
|
||||||
| `scroll <dir> [px]` | Scroll page |
|
|
||||||
|
|
||||||
### Get Info
|
|
||||||
| Command | Description |
|
|
||||||
|---------|-------------|
|
|
||||||
| `get text <sel>` | Get text content |
|
|
||||||
| `get html <sel>` | Get innerHTML |
|
|
||||||
| `get value <sel>` | Get input value |
|
|
||||||
| `get attr <sel> <attr>` | Get attribute |
|
|
||||||
| `get title` | Get page title |
|
|
||||||
| `get url` | Get current URL |
|
|
||||||
|
|
||||||
### Wait
|
|
||||||
| Command | Description |
|
|
||||||
|---------|-------------|
|
|
||||||
| `wait <selector>` | Wait for element |
|
|
||||||
| `wait <ms>` | Wait milliseconds |
|
|
||||||
| `wait --text "text"` | Wait for text |
|
|
||||||
| `wait --url "pattern"` | Wait for URL |
|
|
||||||
| `wait --load networkidle` | Wait for load state |
|
|
||||||
|
|
||||||
### Sessions
|
|
||||||
| Command | Description |
|
|
||||||
|---------|-------------|
|
|
||||||
| `--session <name>` | Use isolated session |
|
|
||||||
| `session list` | List active sessions |
|
|
||||||
|
|
||||||
## Selectors
|
|
||||||
|
|
||||||
### Element Refs (Recommended)
|
|
||||||
```bash
|
|
||||||
# Get refs from snapshot
|
|
||||||
agent-browser snapshot -i
|
|
||||||
# Output: button "Submit" [ref=e2]
|
|
||||||
|
|
||||||
# Use ref to interact
|
|
||||||
agent-browser click @e2
|
|
||||||
```
|
|
||||||
|
|
||||||
### CSS Selectors
|
|
||||||
```bash
|
|
||||||
agent-browser click "#submit"
|
|
||||||
agent-browser fill ".email-input" "test@test.com"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Semantic Locators
|
|
||||||
```bash
|
|
||||||
agent-browser find role button click --name "Submit"
|
|
||||||
agent-browser find label "Email" fill "test@test.com"
|
|
||||||
agent-browser find testid "login-btn" click
|
|
||||||
```
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
### Login Flow
|
|
||||||
```bash
|
|
||||||
agent-browser open https://example.com/login
|
|
||||||
agent-browser snapshot -i
|
|
||||||
agent-browser fill @e2 "user@example.com"
|
|
||||||
agent-browser fill @e3 "password123"
|
|
||||||
agent-browser click @e4
|
|
||||||
agent-browser wait --url "**/dashboard"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Form Submission
|
|
||||||
```bash
|
|
||||||
agent-browser open https://example.com/contact
|
|
||||||
agent-browser snapshot -i
|
|
||||||
agent-browser fill @e1 "John Doe"
|
|
||||||
agent-browser fill @e2 "john@example.com"
|
|
||||||
agent-browser fill @e3 "Hello, this is my message"
|
|
||||||
agent-browser click @e4
|
|
||||||
agent-browser wait --text "Thank you"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Data Extraction
|
|
||||||
```bash
|
|
||||||
agent-browser open https://example.com/products
|
|
||||||
agent-browser snapshot -i
|
|
||||||
# Iterate through product refs
|
|
||||||
agent-browser get text @e1 # Product name
|
|
||||||
agent-browser get text @e2 # Price
|
|
||||||
agent-browser get attr @e3 href # Link
|
|
||||||
```
|
|
||||||
|
|
||||||
### Multi-Session (Swarm)
|
|
||||||
```bash
|
|
||||||
# Session 1: Navigator
|
|
||||||
agent-browser --session nav open https://example.com
|
|
||||||
agent-browser --session nav state save auth.json
|
|
||||||
|
|
||||||
# Session 2: Scraper (uses same auth)
|
|
||||||
agent-browser --session scrape state load auth.json
|
|
||||||
agent-browser --session scrape open https://example.com/data
|
|
||||||
agent-browser --session scrape snapshot -i
|
|
||||||
```
|
|
||||||
|
|
||||||
## Integration with Codex Flow
|
|
||||||
|
|
||||||
### MCP Tools
|
|
||||||
All browser operations are available as MCP tools with `browser/` prefix:
|
|
||||||
- `browser/open`
|
|
||||||
- `browser/snapshot`
|
|
||||||
- `browser/click`
|
|
||||||
- `browser/fill`
|
|
||||||
- `browser/screenshot`
|
|
||||||
- etc.
|
|
||||||
|
|
||||||
### Memory Integration
|
|
||||||
```bash
|
|
||||||
# Store successful patterns
|
|
||||||
npx @Codex-flow/cli memory store --namespace browser-patterns --key "login-flow" --value "snapshot->fill->click->wait"
|
|
||||||
|
|
||||||
# Retrieve before similar task
|
|
||||||
npx @Codex-flow/cli memory search --query "login automation"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Hooks
|
|
||||||
```bash
|
|
||||||
# Pre-browse hook (get context)
|
|
||||||
npx @Codex-flow/cli hooks pre-edit --file "browser-task.ts"
|
|
||||||
|
|
||||||
# Post-browse hook (record success)
|
|
||||||
npx @Codex-flow/cli hooks post-task --task-id "browse-1" --success true
|
|
||||||
```
|
|
||||||
|
|
||||||
## Tips
|
|
||||||
|
|
||||||
1. **Always use snapshots** - They're optimized for AI with refs
|
|
||||||
2. **Prefer `-i` flag** - Gets only interactive elements, smaller output
|
|
||||||
3. **Use refs, not selectors** - More reliable, deterministic
|
|
||||||
4. **Re-snapshot after navigation** - Page state changes
|
|
||||||
5. **Use sessions for parallel work** - Each session is isolated
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,874 +0,0 @@
|
|||||||
---
|
|
||||||
name: github-multi-repo
|
|
||||||
version: 1.0.0
|
|
||||||
description: Multi-repository coordination, synchronization, and architecture management with AI swarm orchestration
|
|
||||||
category: github-integration
|
|
||||||
tags: [multi-repo, synchronization, architecture, coordination, github]
|
|
||||||
author: Codex Flow Team
|
|
||||||
requires:
|
|
||||||
- ruv-swarm@^1.0.11
|
|
||||||
- gh-cli@^2.0.0
|
|
||||||
capabilities:
|
|
||||||
- cross-repository coordination
|
|
||||||
- package synchronization
|
|
||||||
- architecture optimization
|
|
||||||
- template management
|
|
||||||
- distributed workflows
|
|
||||||
---
|
|
||||||
|
|
||||||
# GitHub Multi-Repository Coordination Skill
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
Advanced multi-repository coordination system that combines swarm intelligence, package synchronization, and repository architecture optimization. This skill enables organization-wide automation, cross-project collaboration, and scalable repository management.
|
|
||||||
|
|
||||||
## Core Capabilities
|
|
||||||
|
|
||||||
### 🔄 Multi-Repository Swarm Coordination
|
|
||||||
Cross-repository AI swarm orchestration for distributed development workflows.
|
|
||||||
|
|
||||||
### 📦 Package Synchronization
|
|
||||||
Intelligent dependency resolution and version alignment across multiple packages.
|
|
||||||
|
|
||||||
### 🏗️ Repository Architecture
|
|
||||||
Structure optimization and template management for scalable projects.
|
|
||||||
|
|
||||||
### 🔗 Integration Management
|
|
||||||
Cross-package integration testing and deployment coordination.
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
### Initialize Multi-Repo Coordination
|
|
||||||
```bash
|
|
||||||
# Basic swarm initialization
|
|
||||||
npx Codex-flow skill run github-multi-repo init \
|
|
||||||
--repos "org/frontend,org/backend,org/shared" \
|
|
||||||
--topology hierarchical
|
|
||||||
|
|
||||||
# Advanced initialization with synchronization
|
|
||||||
npx Codex-flow skill run github-multi-repo init \
|
|
||||||
--repos "org/frontend,org/backend,org/shared" \
|
|
||||||
--topology mesh \
|
|
||||||
--shared-memory \
|
|
||||||
--sync-strategy eventual
|
|
||||||
```
|
|
||||||
|
|
||||||
### Synchronize Packages
|
|
||||||
```bash
|
|
||||||
# Synchronize package versions and dependencies
|
|
||||||
npx Codex-flow skill run github-multi-repo sync \
|
|
||||||
--packages "Codex-flow,ruv-swarm" \
|
|
||||||
--align-versions \
|
|
||||||
--update-docs
|
|
||||||
```
|
|
||||||
|
|
||||||
### Optimize Architecture
|
|
||||||
```bash
|
|
||||||
# Analyze and optimize repository structure
|
|
||||||
npx Codex-flow skill run github-multi-repo optimize \
|
|
||||||
--analyze-structure \
|
|
||||||
--suggest-improvements \
|
|
||||||
--create-templates
|
|
||||||
```
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
### 1. Cross-Repository Swarm Orchestration
|
|
||||||
|
|
||||||
#### Repository Discovery
|
|
||||||
```javascript
|
|
||||||
// Auto-discover related repositories with gh CLI
|
|
||||||
const REPOS = Bash(`gh repo list my-organization --limit 100 \
|
|
||||||
--json name,description,languages,topics \
|
|
||||||
--jq '.[] | select(.languages | keys | contains(["TypeScript"]))'`)
|
|
||||||
|
|
||||||
// Analyze repository dependencies
|
|
||||||
const DEPS = Bash(`gh repo list my-organization --json name | \
|
|
||||||
jq -r '.[].name' | while read -r repo; do
|
|
||||||
gh api repos/my-organization/$repo/contents/package.json \
|
|
||||||
--jq '.content' 2>/dev/null | base64 -d | jq '{name, dependencies}'
|
|
||||||
done | jq -s '.'`)
|
|
||||||
|
|
||||||
// Initialize swarm with discovered repositories
|
|
||||||
mcp__claude-flow__swarm_init({
|
|
||||||
topology: "hierarchical",
|
|
||||||
maxAgents: 8,
|
|
||||||
metadata: { repos: REPOS, dependencies: DEPS }
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Synchronized Operations
|
|
||||||
```javascript
|
|
||||||
// Execute synchronized changes across repositories
|
|
||||||
[Parallel Multi-Repo Operations]:
|
|
||||||
// Spawn coordination agents
|
|
||||||
Task("Repository Coordinator", "Coordinate changes across all repositories", "coordinator")
|
|
||||||
Task("Dependency Analyzer", "Analyze cross-repo dependencies", "analyst")
|
|
||||||
Task("Integration Tester", "Validate cross-repo changes", "tester")
|
|
||||||
|
|
||||||
// Get matching repositories
|
|
||||||
Bash(`gh repo list org --limit 100 --json name \
|
|
||||||
--jq '.[] | select(.name | test("-service$")) | .name' > /tmp/repos.txt`)
|
|
||||||
|
|
||||||
// Execute task across repositories
|
|
||||||
Bash(`cat /tmp/repos.txt | while read -r repo; do
|
|
||||||
gh repo clone org/$repo /tmp/$repo -- --depth=1
|
|
||||||
cd /tmp/$repo
|
|
||||||
|
|
||||||
# Apply changes
|
|
||||||
npm update
|
|
||||||
npm test
|
|
||||||
|
|
||||||
# Create PR if successful
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
git checkout -b update-dependencies-$(date +%Y%m%d)
|
|
||||||
git add -A
|
|
||||||
git commit -m "chore: Update dependencies"
|
|
||||||
git push origin HEAD
|
|
||||||
gh pr create --title "Update dependencies" --body "Automated update" --label "dependencies"
|
|
||||||
fi
|
|
||||||
done`)
|
|
||||||
|
|
||||||
// Track all operations
|
|
||||||
TodoWrite { todos: [
|
|
||||||
{ id: "discover", content: "Discover all service repositories", status: "completed" },
|
|
||||||
{ id: "update", content: "Update dependencies", status: "completed" },
|
|
||||||
{ id: "test", content: "Run integration tests", status: "in_progress" },
|
|
||||||
{ id: "pr", content: "Create pull requests", status: "pending" }
|
|
||||||
]}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Package Synchronization
|
|
||||||
|
|
||||||
#### Version Alignment
|
|
||||||
```javascript
|
|
||||||
// Synchronize package dependencies and versions
|
|
||||||
[Complete Package Sync]:
|
|
||||||
// Initialize sync swarm
|
|
||||||
mcp__claude-flow__swarm_init({ topology: "mesh", maxAgents: 5 })
|
|
||||||
|
|
||||||
// Spawn sync agents
|
|
||||||
Task("Sync Coordinator", "Coordinate version alignment", "coordinator")
|
|
||||||
Task("Dependency Analyzer", "Analyze dependencies", "analyst")
|
|
||||||
Task("Integration Tester", "Validate synchronization", "tester")
|
|
||||||
|
|
||||||
// Read package states
|
|
||||||
Read("/workspaces/ruv-FANN/Codex-flow/Codex-flow/package.json")
|
|
||||||
Read("/workspaces/ruv-FANN/ruv-swarm/npm/package.json")
|
|
||||||
|
|
||||||
// Align versions using gh CLI
|
|
||||||
Bash(`gh api repos/:owner/:repo/git/refs \
|
|
||||||
-f ref='refs/heads/sync/package-alignment' \
|
|
||||||
-f sha=$(gh api repos/:owner/:repo/git/refs/heads/main --jq '.object.sha')`)
|
|
||||||
|
|
||||||
// Update package.json files
|
|
||||||
Bash(`gh api repos/:owner/:repo/contents/package.json \
|
|
||||||
--method PUT \
|
|
||||||
-f message="feat: Align Node.js version requirements" \
|
|
||||||
-f branch="sync/package-alignment" \
|
|
||||||
-f content="$(cat aligned-package.json | base64)"`)
|
|
||||||
|
|
||||||
// Store sync state
|
|
||||||
mcp__claude-flow__memory_usage({
|
|
||||||
action: "store",
|
|
||||||
key: "sync/packages/status",
|
|
||||||
value: {
|
|
||||||
timestamp: Date.now(),
|
|
||||||
packages_synced: ["Codex-flow", "ruv-swarm"],
|
|
||||||
status: "synchronized"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Documentation Synchronization
|
|
||||||
```javascript
|
|
||||||
// Synchronize AGENTS.md files across packages
|
|
||||||
[Documentation Sync]:
|
|
||||||
// Get source documentation
|
|
||||||
Bash(`gh api repos/:owner/:repo/contents/ruv-swarm/docs/AGENTS.md \
|
|
||||||
--jq '.content' | base64 -d > /tmp/Codex-source.md`)
|
|
||||||
|
|
||||||
// Update target documentation
|
|
||||||
Bash(`gh api repos/:owner/:repo/contents/Codex-flow/AGENTS.md \
|
|
||||||
--method PUT \
|
|
||||||
-f message="docs: Synchronize AGENTS.md" \
|
|
||||||
-f branch="sync/documentation" \
|
|
||||||
-f content="$(cat /tmp/Codex-source.md | base64)"`)
|
|
||||||
|
|
||||||
// Track sync status
|
|
||||||
mcp__claude-flow__memory_usage({
|
|
||||||
action: "store",
|
|
||||||
key: "sync/documentation/status",
|
|
||||||
value: { status: "synchronized", files: ["AGENTS.md"] }
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Cross-Package Integration
|
|
||||||
```javascript
|
|
||||||
// Coordinate feature implementation across packages
|
|
||||||
[Cross-Package Feature]:
|
|
||||||
// Push changes to all packages
|
|
||||||
mcp__github__push_files({
|
|
||||||
branch: "feature/github-integration",
|
|
||||||
files: [
|
|
||||||
{
|
|
||||||
path: "Codex-flow/.Codex/commands/github/github-modes.md",
|
|
||||||
content: "[GitHub modes documentation]"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "ruv-swarm/src/github-coordinator/hooks.js",
|
|
||||||
content: "[GitHub coordination hooks]"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
message: "feat: Add GitHub workflow integration"
|
|
||||||
})
|
|
||||||
|
|
||||||
// Create coordinated PR
|
|
||||||
Bash(`gh pr create \
|
|
||||||
--title "Feature: GitHub Workflow Integration" \
|
|
||||||
--body "## 🚀 GitHub Integration
|
|
||||||
|
|
||||||
### Features
|
|
||||||
- ✅ Multi-repo coordination
|
|
||||||
- ✅ Package synchronization
|
|
||||||
- ✅ Architecture optimization
|
|
||||||
|
|
||||||
### Testing
|
|
||||||
- [x] Package dependency verification
|
|
||||||
- [x] Integration tests
|
|
||||||
- [x] Cross-package compatibility"`)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Repository Architecture
|
|
||||||
|
|
||||||
#### Structure Analysis
|
|
||||||
```javascript
|
|
||||||
// Analyze and optimize repository structure
|
|
||||||
[Architecture Analysis]:
|
|
||||||
// Initialize architecture swarm
|
|
||||||
mcp__claude-flow__swarm_init({ topology: "hierarchical", maxAgents: 6 })
|
|
||||||
|
|
||||||
// Spawn architecture agents
|
|
||||||
Task("Senior Architect", "Analyze repository structure", "architect")
|
|
||||||
Task("Structure Analyst", "Identify optimization opportunities", "analyst")
|
|
||||||
Task("Performance Optimizer", "Optimize structure for scalability", "optimizer")
|
|
||||||
Task("Best Practices Researcher", "Research architecture patterns", "researcher")
|
|
||||||
|
|
||||||
// Analyze current structures
|
|
||||||
LS("/workspaces/ruv-FANN/Codex-flow/Codex-flow")
|
|
||||||
LS("/workspaces/ruv-FANN/ruv-swarm/npm")
|
|
||||||
|
|
||||||
// Search for best practices
|
|
||||||
Bash(`gh search repos "language:javascript template architecture" \
|
|
||||||
--limit 10 \
|
|
||||||
--json fullName,description,stargazersCount \
|
|
||||||
--sort stars \
|
|
||||||
--order desc`)
|
|
||||||
|
|
||||||
// Store analysis results
|
|
||||||
mcp__claude-flow__memory_usage({
|
|
||||||
action: "store",
|
|
||||||
key: "architecture/analysis/results",
|
|
||||||
value: {
|
|
||||||
repositories_analyzed: ["Codex-flow", "ruv-swarm"],
|
|
||||||
optimization_areas: ["structure", "workflows", "templates"],
|
|
||||||
recommendations: ["standardize_structure", "improve_workflows"]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Template Creation
|
|
||||||
```javascript
|
|
||||||
// Create standardized repository template
|
|
||||||
[Template Creation]:
|
|
||||||
// Create template repository
|
|
||||||
mcp__github__create_repository({
|
|
||||||
name: "Codex-project-template",
|
|
||||||
description: "Standardized template for Codex projects",
|
|
||||||
private: false,
|
|
||||||
autoInit: true
|
|
||||||
})
|
|
||||||
|
|
||||||
// Push template structure
|
|
||||||
mcp__github__push_files({
|
|
||||||
repo: "Codex-project-template",
|
|
||||||
files: [
|
|
||||||
{
|
|
||||||
path: ".Codex/commands/github/github-modes.md",
|
|
||||||
content: "[GitHub modes template]"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: ".Codex/config.json",
|
|
||||||
content: JSON.stringify({
|
|
||||||
version: "1.0",
|
|
||||||
mcp_servers: {
|
|
||||||
"ruv-swarm": {
|
|
||||||
command: "npx",
|
|
||||||
args: ["ruv-swarm", "mcp", "start"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "AGENTS.md",
|
|
||||||
content: "[Standardized AGENTS.md]"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "package.json",
|
|
||||||
content: JSON.stringify({
|
|
||||||
name: "Codex-project-template",
|
|
||||||
engines: { node: ">=20.0.0" },
|
|
||||||
dependencies: { "ruv-swarm": "^1.0.11" }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
],
|
|
||||||
message: "feat: Create standardized template"
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Cross-Repository Standardization
|
|
||||||
```javascript
|
|
||||||
// Synchronize structure across repositories
|
|
||||||
[Structure Standardization]:
|
|
||||||
const repositories = ["Codex-flow", "ruv-swarm", "Codex-extensions"]
|
|
||||||
|
|
||||||
// Update common files across all repositories
|
|
||||||
repositories.forEach(repo => {
|
|
||||||
mcp__github__create_or_update_file({
|
|
||||||
repo: "ruv-FANN",
|
|
||||||
path: `${repo}/.github/workflows/integration.yml`,
|
|
||||||
content: `name: Integration Tests
|
|
||||||
on: [push, pull_request]
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: actions/setup-node@v3
|
|
||||||
with: { node-version: '20' }
|
|
||||||
- run: npm install && npm test`,
|
|
||||||
message: "ci: Standardize integration workflow",
|
|
||||||
branch: "structure/standardization"
|
|
||||||
})
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Orchestration Workflows
|
|
||||||
|
|
||||||
#### Dependency Management
|
|
||||||
```javascript
|
|
||||||
// Update dependencies across all repositories
|
|
||||||
[Organization-Wide Dependency Update]:
|
|
||||||
// Create tracking issue
|
|
||||||
TRACKING_ISSUE=$(Bash(`gh issue create \
|
|
||||||
--title "Dependency Update: typescript@5.0.0" \
|
|
||||||
--body "Tracking TypeScript update across all repositories" \
|
|
||||||
--label "dependencies,tracking" \
|
|
||||||
--json number -q .number`))
|
|
||||||
|
|
||||||
// Find all TypeScript repositories
|
|
||||||
TS_REPOS=$(Bash(`gh repo list org --limit 100 --json name | \
|
|
||||||
jq -r '.[].name' | while read -r repo; do
|
|
||||||
if gh api repos/org/$repo/contents/package.json 2>/dev/null | \
|
|
||||||
jq -r '.content' | base64 -d | grep -q '"typescript"'; then
|
|
||||||
echo "$repo"
|
|
||||||
fi
|
|
||||||
done`))
|
|
||||||
|
|
||||||
// Update each repository
|
|
||||||
Bash(`echo "$TS_REPOS" | while read -r repo; do
|
|
||||||
gh repo clone org/$repo /tmp/$repo -- --depth=1
|
|
||||||
cd /tmp/$repo
|
|
||||||
|
|
||||||
npm install --save-dev typescript@5.0.0
|
|
||||||
|
|
||||||
if npm test; then
|
|
||||||
git checkout -b update-typescript-5
|
|
||||||
git add package.json package-lock.json
|
|
||||||
git commit -m "chore: Update TypeScript to 5.0.0
|
|
||||||
|
|
||||||
Part of #$TRACKING_ISSUE"
|
|
||||||
|
|
||||||
git push origin HEAD
|
|
||||||
gh pr create \
|
|
||||||
--title "Update TypeScript to 5.0.0" \
|
|
||||||
--body "Updates TypeScript\n\nTracking: #$TRACKING_ISSUE" \
|
|
||||||
--label "dependencies"
|
|
||||||
else
|
|
||||||
gh issue comment $TRACKING_ISSUE \
|
|
||||||
--body "❌ Failed to update $repo - tests failing"
|
|
||||||
fi
|
|
||||||
done`)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Refactoring Operations
|
|
||||||
```javascript
|
|
||||||
// Coordinate large-scale refactoring
|
|
||||||
[Cross-Repo Refactoring]:
|
|
||||||
// Initialize refactoring swarm
|
|
||||||
mcp__claude-flow__swarm_init({ topology: "mesh", maxAgents: 8 })
|
|
||||||
|
|
||||||
// Spawn specialized agents
|
|
||||||
Task("Refactoring Coordinator", "Coordinate refactoring across repos", "coordinator")
|
|
||||||
Task("Impact Analyzer", "Analyze refactoring impact", "analyst")
|
|
||||||
Task("Code Transformer", "Apply refactoring changes", "coder")
|
|
||||||
Task("Migration Guide Creator", "Create migration documentation", "documenter")
|
|
||||||
Task("Integration Tester", "Validate refactored code", "tester")
|
|
||||||
|
|
||||||
// Execute refactoring
|
|
||||||
mcp__claude-flow__task_orchestrate({
|
|
||||||
task: "Rename OldAPI to NewAPI across all repositories",
|
|
||||||
strategy: "sequential",
|
|
||||||
priority: "high"
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Security Updates
|
|
||||||
```javascript
|
|
||||||
// Coordinate security patches
|
|
||||||
[Security Patch Deployment]:
|
|
||||||
// Scan all repositories
|
|
||||||
Bash(`gh repo list org --limit 100 --json name | jq -r '.[].name' | \
|
|
||||||
while read -r repo; do
|
|
||||||
gh repo clone org/$repo /tmp/$repo -- --depth=1
|
|
||||||
cd /tmp/$repo
|
|
||||||
npm audit --json > /tmp/audit-$repo.json
|
|
||||||
done`)
|
|
||||||
|
|
||||||
// Apply patches
|
|
||||||
Bash(`for repo in /tmp/audit-*.json; do
|
|
||||||
if [ $(jq '.vulnerabilities | length' $repo) -gt 0 ]; then
|
|
||||||
cd /tmp/$(basename $repo .json | sed 's/audit-//')
|
|
||||||
npm audit fix
|
|
||||||
|
|
||||||
if npm test; then
|
|
||||||
git checkout -b security/patch-$(date +%Y%m%d)
|
|
||||||
git add -A
|
|
||||||
git commit -m "security: Apply security patches"
|
|
||||||
git push origin HEAD
|
|
||||||
gh pr create --title "Security patches" --label "security"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done`)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
### Multi-Repo Config File
|
|
||||||
```yaml
|
|
||||||
# .swarm/multi-repo.yml
|
|
||||||
version: 1
|
|
||||||
organization: my-org
|
|
||||||
|
|
||||||
repositories:
|
|
||||||
- name: frontend
|
|
||||||
url: github.com/my-org/frontend
|
|
||||||
role: ui
|
|
||||||
agents: [coder, designer, tester]
|
|
||||||
|
|
||||||
- name: backend
|
|
||||||
url: github.com/my-org/backend
|
|
||||||
role: api
|
|
||||||
agents: [architect, coder, tester]
|
|
||||||
|
|
||||||
- name: shared
|
|
||||||
url: github.com/my-org/shared
|
|
||||||
role: library
|
|
||||||
agents: [analyst, coder]
|
|
||||||
|
|
||||||
coordination:
|
|
||||||
topology: hierarchical
|
|
||||||
communication: webhook
|
|
||||||
memory: redis://shared-memory
|
|
||||||
|
|
||||||
dependencies:
|
|
||||||
- from: frontend
|
|
||||||
to: [backend, shared]
|
|
||||||
- from: backend
|
|
||||||
to: [shared]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Repository Roles
|
|
||||||
```javascript
|
|
||||||
{
|
|
||||||
"roles": {
|
|
||||||
"ui": {
|
|
||||||
"responsibilities": ["user-interface", "ux", "accessibility"],
|
|
||||||
"default-agents": ["designer", "coder", "tester"]
|
|
||||||
},
|
|
||||||
"api": {
|
|
||||||
"responsibilities": ["endpoints", "business-logic", "data"],
|
|
||||||
"default-agents": ["architect", "coder", "security"]
|
|
||||||
},
|
|
||||||
"library": {
|
|
||||||
"responsibilities": ["shared-code", "utilities", "types"],
|
|
||||||
"default-agents": ["analyst", "coder", "documenter"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Communication Strategies
|
|
||||||
|
|
||||||
### 1. Webhook-Based Coordination
|
|
||||||
```javascript
|
|
||||||
const { MultiRepoSwarm } = require('ruv-swarm');
|
|
||||||
|
|
||||||
const swarm = new MultiRepoSwarm({
|
|
||||||
webhook: {
|
|
||||||
url: 'https://swarm-coordinator.example.com',
|
|
||||||
secret: process.env.WEBHOOK_SECRET
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
swarm.on('repo:update', async (event) => {
|
|
||||||
await swarm.propagate(event, {
|
|
||||||
to: event.dependencies,
|
|
||||||
strategy: 'eventual-consistency'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Event Streaming
|
|
||||||
```yaml
|
|
||||||
# Kafka configuration for real-time coordination
|
|
||||||
kafka:
|
|
||||||
brokers: ['kafka1:9092', 'kafka2:9092']
|
|
||||||
topics:
|
|
||||||
swarm-events:
|
|
||||||
partitions: 10
|
|
||||||
replication: 3
|
|
||||||
swarm-memory:
|
|
||||||
partitions: 5
|
|
||||||
replication: 3
|
|
||||||
```
|
|
||||||
|
|
||||||
## Synchronization Patterns
|
|
||||||
|
|
||||||
### 1. Eventually Consistent
|
|
||||||
```javascript
|
|
||||||
{
|
|
||||||
"sync": {
|
|
||||||
"strategy": "eventual",
|
|
||||||
"max-lag": "5m",
|
|
||||||
"retry": {
|
|
||||||
"attempts": 3,
|
|
||||||
"backoff": "exponential"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Strong Consistency
|
|
||||||
```javascript
|
|
||||||
{
|
|
||||||
"sync": {
|
|
||||||
"strategy": "strong",
|
|
||||||
"consensus": "raft",
|
|
||||||
"quorum": 0.51,
|
|
||||||
"timeout": "30s"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Hybrid Approach
|
|
||||||
```javascript
|
|
||||||
{
|
|
||||||
"sync": {
|
|
||||||
"default": "eventual",
|
|
||||||
"overrides": {
|
|
||||||
"security-updates": "strong",
|
|
||||||
"dependency-updates": "strong",
|
|
||||||
"documentation": "eventual"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Use Cases
|
|
||||||
|
|
||||||
### 1. Microservices Coordination
|
|
||||||
```bash
|
|
||||||
npx Codex-flow skill run github-multi-repo microservices \
|
|
||||||
--services "auth,users,orders,payments" \
|
|
||||||
--ensure-compatibility \
|
|
||||||
--sync-contracts \
|
|
||||||
--integration-tests
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Library Updates
|
|
||||||
```bash
|
|
||||||
npx Codex-flow skill run github-multi-repo lib-update \
|
|
||||||
--library "org/shared-lib" \
|
|
||||||
--version "2.0.0" \
|
|
||||||
--find-consumers \
|
|
||||||
--update-imports \
|
|
||||||
--run-tests
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Organization-Wide Changes
|
|
||||||
```bash
|
|
||||||
npx Codex-flow skill run github-multi-repo org-policy \
|
|
||||||
--policy "add-security-headers" \
|
|
||||||
--repos "org/*" \
|
|
||||||
--validate-compliance \
|
|
||||||
--create-reports
|
|
||||||
```
|
|
||||||
|
|
||||||
## Architecture Patterns
|
|
||||||
|
|
||||||
### Monorepo Structure
|
|
||||||
```
|
|
||||||
ruv-FANN/
|
|
||||||
├── packages/
|
|
||||||
│ ├── Codex-flow/
|
|
||||||
│ │ ├── src/
|
|
||||||
│ │ ├── .Codex/
|
|
||||||
│ │ └── package.json
|
|
||||||
│ ├── ruv-swarm/
|
|
||||||
│ │ ├── src/
|
|
||||||
│ │ ├── wasm/
|
|
||||||
│ │ └── package.json
|
|
||||||
│ └── shared/
|
|
||||||
│ ├── types/
|
|
||||||
│ ├── utils/
|
|
||||||
│ └── config/
|
|
||||||
├── tools/
|
|
||||||
│ ├── build/
|
|
||||||
│ ├── test/
|
|
||||||
│ └── deploy/
|
|
||||||
├── docs/
|
|
||||||
│ ├── architecture/
|
|
||||||
│ ├── integration/
|
|
||||||
│ └── examples/
|
|
||||||
└── .github/
|
|
||||||
├── workflows/
|
|
||||||
├── templates/
|
|
||||||
└── actions/
|
|
||||||
```
|
|
||||||
|
|
||||||
### Command Structure
|
|
||||||
```
|
|
||||||
.Codex/
|
|
||||||
├── commands/
|
|
||||||
│ ├── github/
|
|
||||||
│ │ ├── github-modes.md
|
|
||||||
│ │ ├── pr-manager.md
|
|
||||||
│ │ ├── issue-tracker.md
|
|
||||||
│ │ └── sync-coordinator.md
|
|
||||||
│ ├── sparc/
|
|
||||||
│ │ ├── sparc-modes.md
|
|
||||||
│ │ ├── coder.md
|
|
||||||
│ │ └── tester.md
|
|
||||||
│ └── swarm/
|
|
||||||
│ ├── coordination.md
|
|
||||||
│ └── orchestration.md
|
|
||||||
├── templates/
|
|
||||||
│ ├── issue.md
|
|
||||||
│ ├── pr.md
|
|
||||||
│ └── project.md
|
|
||||||
└── config.json
|
|
||||||
```
|
|
||||||
|
|
||||||
## Monitoring & Visualization
|
|
||||||
|
|
||||||
### Multi-Repo Dashboard
|
|
||||||
```bash
|
|
||||||
npx Codex-flow skill run github-multi-repo dashboard \
|
|
||||||
--port 3000 \
|
|
||||||
--metrics "agent-activity,task-progress,memory-usage" \
|
|
||||||
--real-time
|
|
||||||
```
|
|
||||||
|
|
||||||
### Dependency Graph
|
|
||||||
```bash
|
|
||||||
npx Codex-flow skill run github-multi-repo dep-graph \
|
|
||||||
--format mermaid \
|
|
||||||
--include-agents \
|
|
||||||
--show-data-flow
|
|
||||||
```
|
|
||||||
|
|
||||||
### Health Monitoring
|
|
||||||
```bash
|
|
||||||
npx Codex-flow skill run github-multi-repo health-check \
|
|
||||||
--repos "org/*" \
|
|
||||||
--check "connectivity,memory,agents" \
|
|
||||||
--alert-on-issues
|
|
||||||
```
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
### 1. Repository Organization
|
|
||||||
- Clear repository roles and boundaries
|
|
||||||
- Consistent naming conventions
|
|
||||||
- Documented dependencies
|
|
||||||
- Shared configuration standards
|
|
||||||
|
|
||||||
### 2. Communication
|
|
||||||
- Use appropriate sync strategies
|
|
||||||
- Implement circuit breakers
|
|
||||||
- Monitor latency and failures
|
|
||||||
- Clear error propagation
|
|
||||||
|
|
||||||
### 3. Security
|
|
||||||
- Secure cross-repo authentication
|
|
||||||
- Encrypted communication channels
|
|
||||||
- Audit trail for all operations
|
|
||||||
- Principle of least privilege
|
|
||||||
|
|
||||||
### 4. Version Management
|
|
||||||
- Semantic versioning alignment
|
|
||||||
- Dependency compatibility validation
|
|
||||||
- Automated version bump coordination
|
|
||||||
|
|
||||||
### 5. Testing Integration
|
|
||||||
- Cross-package test validation
|
|
||||||
- Integration test automation
|
|
||||||
- Performance regression detection
|
|
||||||
|
|
||||||
## Performance Optimization
|
|
||||||
|
|
||||||
### Caching Strategy
|
|
||||||
```bash
|
|
||||||
npx Codex-flow skill run github-multi-repo cache-strategy \
|
|
||||||
--analyze-patterns \
|
|
||||||
--suggest-cache-layers \
|
|
||||||
--implement-invalidation
|
|
||||||
```
|
|
||||||
|
|
||||||
### Parallel Execution
|
|
||||||
```bash
|
|
||||||
npx Codex-flow skill run github-multi-repo parallel-optimize \
|
|
||||||
--analyze-dependencies \
|
|
||||||
--identify-parallelizable \
|
|
||||||
--execute-optimal
|
|
||||||
```
|
|
||||||
|
|
||||||
### Resource Pooling
|
|
||||||
```bash
|
|
||||||
npx Codex-flow skill run github-multi-repo resource-pool \
|
|
||||||
--share-agents \
|
|
||||||
--distribute-load \
|
|
||||||
--monitor-usage
|
|
||||||
```
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Connectivity Issues
|
|
||||||
```bash
|
|
||||||
npx Codex-flow skill run github-multi-repo diagnose-connectivity \
|
|
||||||
--test-all-repos \
|
|
||||||
--check-permissions \
|
|
||||||
--verify-webhooks
|
|
||||||
```
|
|
||||||
|
|
||||||
### Memory Synchronization
|
|
||||||
```bash
|
|
||||||
npx Codex-flow skill run github-multi-repo debug-memory \
|
|
||||||
--check-consistency \
|
|
||||||
--identify-conflicts \
|
|
||||||
--repair-state
|
|
||||||
```
|
|
||||||
|
|
||||||
### Performance Bottlenecks
|
|
||||||
```bash
|
|
||||||
npx Codex-flow skill run github-multi-repo perf-analysis \
|
|
||||||
--profile-operations \
|
|
||||||
--identify-bottlenecks \
|
|
||||||
--suggest-optimizations
|
|
||||||
```
|
|
||||||
|
|
||||||
## Advanced Features
|
|
||||||
|
|
||||||
### 1. Distributed Task Queue
|
|
||||||
```bash
|
|
||||||
npx Codex-flow skill run github-multi-repo queue \
|
|
||||||
--backend redis \
|
|
||||||
--workers 10 \
|
|
||||||
--priority-routing \
|
|
||||||
--dead-letter-queue
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Cross-Repo Testing
|
|
||||||
```bash
|
|
||||||
npx Codex-flow skill run github-multi-repo test \
|
|
||||||
--setup-test-env \
|
|
||||||
--link-services \
|
|
||||||
--run-e2e \
|
|
||||||
--tear-down
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Monorepo Migration
|
|
||||||
```bash
|
|
||||||
npx Codex-flow skill run github-multi-repo to-monorepo \
|
|
||||||
--analyze-repos \
|
|
||||||
--suggest-structure \
|
|
||||||
--preserve-history \
|
|
||||||
--create-migration-prs
|
|
||||||
```
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
### Full-Stack Application Update
|
|
||||||
```bash
|
|
||||||
npx Codex-flow skill run github-multi-repo fullstack-update \
|
|
||||||
--frontend "org/web-app" \
|
|
||||||
--backend "org/api-server" \
|
|
||||||
--database "org/db-migrations" \
|
|
||||||
--coordinate-deployment
|
|
||||||
```
|
|
||||||
|
|
||||||
### Cross-Team Collaboration
|
|
||||||
```bash
|
|
||||||
npx Codex-flow skill run github-multi-repo cross-team \
|
|
||||||
--teams "frontend,backend,devops" \
|
|
||||||
--task "implement-feature-x" \
|
|
||||||
--assign-by-expertise \
|
|
||||||
--track-progress
|
|
||||||
```
|
|
||||||
|
|
||||||
## Metrics and Reporting
|
|
||||||
|
|
||||||
### Sync Quality Metrics
|
|
||||||
- Package version alignment percentage
|
|
||||||
- Documentation consistency score
|
|
||||||
- Integration test success rate
|
|
||||||
- Synchronization completion time
|
|
||||||
|
|
||||||
### Architecture Health Metrics
|
|
||||||
- Repository structure consistency score
|
|
||||||
- Documentation coverage percentage
|
|
||||||
- Cross-repository integration success rate
|
|
||||||
- Template adoption and usage statistics
|
|
||||||
|
|
||||||
### Automated Reporting
|
|
||||||
- Weekly sync status reports
|
|
||||||
- Dependency drift detection
|
|
||||||
- Documentation divergence alerts
|
|
||||||
- Integration health monitoring
|
|
||||||
|
|
||||||
## Integration Points
|
|
||||||
|
|
||||||
### Related Skills
|
|
||||||
- `github-workflow` - GitHub workflow automation
|
|
||||||
- `github-pr` - Pull request management
|
|
||||||
- `sparc-architect` - Architecture design
|
|
||||||
- `sparc-optimizer` - Performance optimization
|
|
||||||
|
|
||||||
### Related Commands
|
|
||||||
- `/github sync-coordinator` - Cross-repo synchronization
|
|
||||||
- `/github release-manager` - Coordinated releases
|
|
||||||
- `/github repo-architect` - Repository optimization
|
|
||||||
- `/sparc architect` - Detailed architecture design
|
|
||||||
|
|
||||||
## Support and Resources
|
|
||||||
|
|
||||||
- Documentation: https://github.com/ruvnet/Codex-flow
|
|
||||||
- Issues: https://github.com/ruvnet/Codex-flow/issues
|
|
||||||
- Examples: `.Codex/examples/github-multi-repo/`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Version:** 1.0.0
|
|
||||||
**Last Updated:** 2025-10-19
|
|
||||||
**Maintainer:** Codex Flow Team
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,126 +0,0 @@
|
|||||||
---
|
|
||||||
name: memory-management
|
|
||||||
description: >
|
|
||||||
AgentDB memory system with HNSW vector search. Provides 150x-12,500x faster pattern retrieval, persistent storage, and semantic search capabilities for learning and knowledge management.
|
|
||||||
Use when: need to store successful patterns, searching for similar solutions, semantic lookup of past work, learning from previous tasks, sharing knowledge between agents, building knowledge base.
|
|
||||||
Skip when: no learning needed, ephemeral one-off tasks, external data sources available, read-only exploration.
|
|
||||||
---
|
|
||||||
|
|
||||||
# Memory Management Skill
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
AgentDB memory system with HNSW vector search. Provides 150x-12,500x faster pattern retrieval, persistent storage, and semantic search capabilities for learning and knowledge management.
|
|
||||||
|
|
||||||
## When to Trigger
|
|
||||||
- need to store successful patterns
|
|
||||||
- searching for similar solutions
|
|
||||||
- semantic lookup of past work
|
|
||||||
- learning from previous tasks
|
|
||||||
- sharing knowledge between agents
|
|
||||||
- building knowledge base
|
|
||||||
|
|
||||||
## When to Skip
|
|
||||||
- no learning needed
|
|
||||||
- ephemeral one-off tasks
|
|
||||||
- external data sources available
|
|
||||||
- read-only exploration
|
|
||||||
|
|
||||||
## Commands
|
|
||||||
|
|
||||||
### Store Pattern
|
|
||||||
Store a pattern or knowledge item in memory
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli memory store --key "[key]" --value "[value]" --namespace patterns
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli memory store --key "auth-jwt-pattern" --value "JWT validation with refresh tokens" --namespace patterns
|
|
||||||
```
|
|
||||||
|
|
||||||
### Semantic Search
|
|
||||||
Search memory using semantic similarity
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli memory search --query "[search terms]" --limit 10
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli memory search --query "authentication best practices" --limit 5
|
|
||||||
```
|
|
||||||
|
|
||||||
### Retrieve Entry
|
|
||||||
Retrieve a specific memory entry by key
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli memory get --key "[key]" --namespace [namespace]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli memory get --key "auth-jwt-pattern" --namespace patterns
|
|
||||||
```
|
|
||||||
|
|
||||||
### List Entries
|
|
||||||
List all entries in a namespace
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli memory list --namespace [namespace]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli memory list --namespace patterns --limit 20
|
|
||||||
```
|
|
||||||
|
|
||||||
### Delete Entry
|
|
||||||
Delete a memory entry
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli memory delete --key "[key]" --namespace [namespace]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Initialize HNSW Index
|
|
||||||
Initialize HNSW vector search index
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli memory init --enable-hnsw
|
|
||||||
```
|
|
||||||
|
|
||||||
### Memory Stats
|
|
||||||
Show memory usage statistics
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli memory stats
|
|
||||||
```
|
|
||||||
|
|
||||||
### Export Memory
|
|
||||||
Export memory to JSON
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli memory export --output memory-backup.json
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Scripts
|
|
||||||
|
|
||||||
| Script | Path | Description |
|
|
||||||
|--------|------|-------------|
|
|
||||||
| `memory-backup` | `.agents/scripts/memory-backup.sh` | Backup memory to external storage |
|
|
||||||
| `memory-consolidate` | `.agents/scripts/memory-consolidate.sh` | Consolidate and optimize memory |
|
|
||||||
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
| Document | Path | Description |
|
|
||||||
|----------|------|-------------|
|
|
||||||
| `HNSW Guide` | `docs/hnsw.md` | HNSW vector search configuration |
|
|
||||||
| `Memory Schema` | `docs/memory-schema.md` | Memory namespace and schema reference |
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
1. Check memory for existing patterns before starting
|
|
||||||
2. Use hierarchical topology for coordination
|
|
||||||
3. Store successful patterns after completion
|
|
||||||
4. Document any new learnings
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,446 +0,0 @@
|
|||||||
---
|
|
||||||
name: "ReasoningBank with AgentDB"
|
|
||||||
description: "Implement ReasoningBank adaptive learning with AgentDB's 150x faster vector database. Includes trajectory tracking, verdict judgment, memory distillation, and pattern recognition. Use when building self-learning agents, optimizing decision-making, or implementing experience replay systems."
|
|
||||||
---
|
|
||||||
|
|
||||||
# ReasoningBank with AgentDB
|
|
||||||
|
|
||||||
## What This Skill Does
|
|
||||||
|
|
||||||
Provides ReasoningBank adaptive learning patterns using AgentDB's high-performance backend (150x-12,500x faster). Enables agents to learn from experiences, judge outcomes, distill memories, and improve decision-making over time with 100% backward compatibility.
|
|
||||||
|
|
||||||
**Performance**: 150x faster pattern retrieval, 500x faster batch operations, <1ms memory access.
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- Node.js 18+
|
|
||||||
- AgentDB v1.0.7+ (via agentic-flow)
|
|
||||||
- Understanding of reinforcement learning concepts (optional)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Quick Start with CLI
|
|
||||||
|
|
||||||
### Initialize ReasoningBank Database
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Initialize AgentDB for ReasoningBank
|
|
||||||
npx agentdb@latest init ./.agentdb/reasoningbank.db --dimension 1536
|
|
||||||
|
|
||||||
# Start MCP server for Codex integration
|
|
||||||
npx agentdb@latest mcp
|
|
||||||
Codex mcp add agentdb npx agentdb@latest mcp
|
|
||||||
```
|
|
||||||
|
|
||||||
### Migrate from Legacy ReasoningBank
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Automatic migration with validation
|
|
||||||
npx agentdb@latest migrate --source .swarm/memory.db
|
|
||||||
|
|
||||||
# Verify migration
|
|
||||||
npx agentdb@latest stats ./.agentdb/reasoningbank.db
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Quick Start with API
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { createAgentDBAdapter, computeEmbedding } from 'agentic-flow/reasoningbank';
|
|
||||||
|
|
||||||
// Initialize ReasoningBank with AgentDB
|
|
||||||
const rb = await createAgentDBAdapter({
|
|
||||||
dbPath: '.agentdb/reasoningbank.db',
|
|
||||||
enableLearning: true, // Enable learning plugins
|
|
||||||
enableReasoning: true, // Enable reasoning agents
|
|
||||||
cacheSize: 1000, // 1000 pattern cache
|
|
||||||
});
|
|
||||||
|
|
||||||
// Store successful experience
|
|
||||||
const query = "How to optimize database queries?";
|
|
||||||
const embedding = await computeEmbedding(query);
|
|
||||||
|
|
||||||
await rb.insertPattern({
|
|
||||||
id: '',
|
|
||||||
type: 'experience',
|
|
||||||
domain: 'database-optimization',
|
|
||||||
pattern_data: JSON.stringify({
|
|
||||||
embedding,
|
|
||||||
pattern: {
|
|
||||||
query,
|
|
||||||
approach: 'indexing + query optimization',
|
|
||||||
outcome: 'success',
|
|
||||||
metrics: { latency_reduction: 0.85 }
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
confidence: 0.95,
|
|
||||||
usage_count: 1,
|
|
||||||
success_count: 1,
|
|
||||||
created_at: Date.now(),
|
|
||||||
last_used: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Retrieve similar experiences with reasoning
|
|
||||||
const result = await rb.retrieveWithReasoning(embedding, {
|
|
||||||
domain: 'database-optimization',
|
|
||||||
k: 5,
|
|
||||||
useMMR: true, // Diverse results
|
|
||||||
synthesizeContext: true, // Rich context synthesis
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Memories:', result.memories);
|
|
||||||
console.log('Context:', result.context);
|
|
||||||
console.log('Patterns:', result.patterns);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Core ReasoningBank Concepts
|
|
||||||
|
|
||||||
### 1. Trajectory Tracking
|
|
||||||
|
|
||||||
Track agent execution paths and outcomes:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Record trajectory (sequence of actions)
|
|
||||||
const trajectory = {
|
|
||||||
task: 'optimize-api-endpoint',
|
|
||||||
steps: [
|
|
||||||
{ action: 'analyze-bottleneck', result: 'found N+1 query' },
|
|
||||||
{ action: 'add-eager-loading', result: 'reduced queries' },
|
|
||||||
{ action: 'add-caching', result: 'improved latency' }
|
|
||||||
],
|
|
||||||
outcome: 'success',
|
|
||||||
metrics: { latency_before: 2500, latency_after: 150 }
|
|
||||||
};
|
|
||||||
|
|
||||||
const embedding = await computeEmbedding(JSON.stringify(trajectory));
|
|
||||||
|
|
||||||
await rb.insertPattern({
|
|
||||||
id: '',
|
|
||||||
type: 'trajectory',
|
|
||||||
domain: 'api-optimization',
|
|
||||||
pattern_data: JSON.stringify({ embedding, pattern: trajectory }),
|
|
||||||
confidence: 0.9,
|
|
||||||
usage_count: 1,
|
|
||||||
success_count: 1,
|
|
||||||
created_at: Date.now(),
|
|
||||||
last_used: Date.now(),
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Verdict Judgment
|
|
||||||
|
|
||||||
Judge whether a trajectory was successful:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Retrieve similar past trajectories
|
|
||||||
const similar = await rb.retrieveWithReasoning(queryEmbedding, {
|
|
||||||
domain: 'api-optimization',
|
|
||||||
k: 10,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Judge based on similarity to successful patterns
|
|
||||||
const verdict = similar.memories.filter(m =>
|
|
||||||
m.pattern.outcome === 'success' &&
|
|
||||||
m.similarity > 0.8
|
|
||||||
).length > 5 ? 'likely_success' : 'needs_review';
|
|
||||||
|
|
||||||
console.log('Verdict:', verdict);
|
|
||||||
console.log('Confidence:', similar.memories[0]?.similarity || 0);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Memory Distillation
|
|
||||||
|
|
||||||
Consolidate similar experiences into patterns:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Get all experiences in domain
|
|
||||||
const experiences = await rb.retrieveWithReasoning(embedding, {
|
|
||||||
domain: 'api-optimization',
|
|
||||||
k: 100,
|
|
||||||
optimizeMemory: true, // Automatic consolidation
|
|
||||||
});
|
|
||||||
|
|
||||||
// Distill into high-level pattern
|
|
||||||
const distilledPattern = {
|
|
||||||
domain: 'api-optimization',
|
|
||||||
pattern: 'For N+1 queries: add eager loading, then cache',
|
|
||||||
success_rate: 0.92,
|
|
||||||
sample_size: experiences.memories.length,
|
|
||||||
confidence: 0.95
|
|
||||||
};
|
|
||||||
|
|
||||||
await rb.insertPattern({
|
|
||||||
id: '',
|
|
||||||
type: 'distilled-pattern',
|
|
||||||
domain: 'api-optimization',
|
|
||||||
pattern_data: JSON.stringify({
|
|
||||||
embedding: await computeEmbedding(JSON.stringify(distilledPattern)),
|
|
||||||
pattern: distilledPattern
|
|
||||||
}),
|
|
||||||
confidence: 0.95,
|
|
||||||
usage_count: 0,
|
|
||||||
success_count: 0,
|
|
||||||
created_at: Date.now(),
|
|
||||||
last_used: Date.now(),
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Integration with Reasoning Agents
|
|
||||||
|
|
||||||
AgentDB provides 4 reasoning modules that enhance ReasoningBank:
|
|
||||||
|
|
||||||
### 1. PatternMatcher
|
|
||||||
|
|
||||||
Find similar successful patterns:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const result = await rb.retrieveWithReasoning(queryEmbedding, {
|
|
||||||
domain: 'problem-solving',
|
|
||||||
k: 10,
|
|
||||||
useMMR: true, // Maximal Marginal Relevance for diversity
|
|
||||||
});
|
|
||||||
|
|
||||||
// PatternMatcher returns diverse, relevant memories
|
|
||||||
result.memories.forEach(mem => {
|
|
||||||
console.log(`Pattern: ${mem.pattern.approach}`);
|
|
||||||
console.log(`Similarity: ${mem.similarity}`);
|
|
||||||
console.log(`Success Rate: ${mem.success_count / mem.usage_count}`);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. ContextSynthesizer
|
|
||||||
|
|
||||||
Generate rich context from multiple memories:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const result = await rb.retrieveWithReasoning(queryEmbedding, {
|
|
||||||
domain: 'code-optimization',
|
|
||||||
synthesizeContext: true, // Enable context synthesis
|
|
||||||
k: 5,
|
|
||||||
});
|
|
||||||
|
|
||||||
// ContextSynthesizer creates coherent narrative
|
|
||||||
console.log('Synthesized Context:', result.context);
|
|
||||||
// "Based on 5 similar optimizations, the most effective approach
|
|
||||||
// involves profiling, identifying bottlenecks, and applying targeted
|
|
||||||
// improvements. Success rate: 87%"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. MemoryOptimizer
|
|
||||||
|
|
||||||
Automatically consolidate and prune:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const result = await rb.retrieveWithReasoning(queryEmbedding, {
|
|
||||||
domain: 'testing',
|
|
||||||
optimizeMemory: true, // Enable automatic optimization
|
|
||||||
});
|
|
||||||
|
|
||||||
// MemoryOptimizer consolidates similar patterns and prunes low-quality
|
|
||||||
console.log('Optimizations:', result.optimizations);
|
|
||||||
// { consolidated: 15, pruned: 3, improved_quality: 0.12 }
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. ExperienceCurator
|
|
||||||
|
|
||||||
Filter by quality and relevance:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const result = await rb.retrieveWithReasoning(queryEmbedding, {
|
|
||||||
domain: 'debugging',
|
|
||||||
k: 20,
|
|
||||||
minConfidence: 0.8, // Only high-confidence experiences
|
|
||||||
});
|
|
||||||
|
|
||||||
// ExperienceCurator returns only quality experiences
|
|
||||||
result.memories.forEach(mem => {
|
|
||||||
console.log(`Confidence: ${mem.confidence}`);
|
|
||||||
console.log(`Success Rate: ${mem.success_count / mem.usage_count}`);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Legacy API Compatibility
|
|
||||||
|
|
||||||
AgentDB maintains 100% backward compatibility with legacy ReasoningBank:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import {
|
|
||||||
retrieveMemories,
|
|
||||||
judgeTrajectory,
|
|
||||||
distillMemories
|
|
||||||
} from 'agentic-flow/reasoningbank';
|
|
||||||
|
|
||||||
// Legacy API works unchanged (uses AgentDB backend automatically)
|
|
||||||
const memories = await retrieveMemories(query, {
|
|
||||||
domain: 'code-generation',
|
|
||||||
agent: 'coder'
|
|
||||||
});
|
|
||||||
|
|
||||||
const verdict = await judgeTrajectory(trajectory, query);
|
|
||||||
|
|
||||||
const newMemories = await distillMemories(
|
|
||||||
trajectory,
|
|
||||||
verdict,
|
|
||||||
query,
|
|
||||||
{ domain: 'code-generation' }
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Performance Characteristics
|
|
||||||
|
|
||||||
- **Pattern Search**: 150x faster (100µs vs 15ms)
|
|
||||||
- **Memory Retrieval**: <1ms (with cache)
|
|
||||||
- **Batch Insert**: 500x faster (2ms vs 1s for 100 patterns)
|
|
||||||
- **Trajectory Judgment**: <5ms (including retrieval + analysis)
|
|
||||||
- **Memory Distillation**: <50ms (consolidate 100 patterns)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Advanced Patterns
|
|
||||||
|
|
||||||
### Hierarchical Memory
|
|
||||||
|
|
||||||
Organize memories by abstraction level:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Low-level: Specific implementation
|
|
||||||
await rb.insertPattern({
|
|
||||||
type: 'concrete',
|
|
||||||
domain: 'debugging/null-pointer',
|
|
||||||
pattern_data: JSON.stringify({
|
|
||||||
embedding,
|
|
||||||
pattern: { bug: 'NPE in UserService.getUser()', fix: 'Add null check' }
|
|
||||||
}),
|
|
||||||
confidence: 0.9,
|
|
||||||
// ...
|
|
||||||
});
|
|
||||||
|
|
||||||
// Mid-level: Pattern across similar cases
|
|
||||||
await rb.insertPattern({
|
|
||||||
type: 'pattern',
|
|
||||||
domain: 'debugging',
|
|
||||||
pattern_data: JSON.stringify({
|
|
||||||
embedding,
|
|
||||||
pattern: { category: 'null-pointer', approach: 'defensive-checks' }
|
|
||||||
}),
|
|
||||||
confidence: 0.85,
|
|
||||||
// ...
|
|
||||||
});
|
|
||||||
|
|
||||||
// High-level: General principle
|
|
||||||
await rb.insertPattern({
|
|
||||||
type: 'principle',
|
|
||||||
domain: 'software-engineering',
|
|
||||||
pattern_data: JSON.stringify({
|
|
||||||
embedding,
|
|
||||||
pattern: { principle: 'fail-fast with clear errors' }
|
|
||||||
}),
|
|
||||||
confidence: 0.95,
|
|
||||||
// ...
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Multi-Domain Learning
|
|
||||||
|
|
||||||
Transfer learning across domains:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Learn from backend optimization
|
|
||||||
const backendExperience = await rb.retrieveWithReasoning(embedding, {
|
|
||||||
domain: 'backend-optimization',
|
|
||||||
k: 10,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Apply to frontend optimization
|
|
||||||
const transferredKnowledge = backendExperience.memories.map(mem => ({
|
|
||||||
...mem,
|
|
||||||
domain: 'frontend-optimization',
|
|
||||||
adapted: true,
|
|
||||||
}));
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## CLI Operations
|
|
||||||
|
|
||||||
### Database Management
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Export trajectories and patterns
|
|
||||||
npx agentdb@latest export ./.agentdb/reasoningbank.db ./backup.json
|
|
||||||
|
|
||||||
# Import experiences
|
|
||||||
npx agentdb@latest import ./experiences.json
|
|
||||||
|
|
||||||
# Get statistics
|
|
||||||
npx agentdb@latest stats ./.agentdb/reasoningbank.db
|
|
||||||
# Shows: total patterns, domains, confidence distribution
|
|
||||||
```
|
|
||||||
|
|
||||||
### Migration
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Migrate from legacy ReasoningBank
|
|
||||||
npx agentdb@latest migrate --source .swarm/memory.db --target .agentdb/reasoningbank.db
|
|
||||||
|
|
||||||
# Validate migration
|
|
||||||
npx agentdb@latest stats .agentdb/reasoningbank.db
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Issue: Migration fails
|
|
||||||
```bash
|
|
||||||
# Check source database exists
|
|
||||||
ls -la .swarm/memory.db
|
|
||||||
|
|
||||||
# Run with verbose logging
|
|
||||||
DEBUG=agentdb:* npx agentdb@latest migrate --source .swarm/memory.db
|
|
||||||
```
|
|
||||||
|
|
||||||
### Issue: Low confidence scores
|
|
||||||
```typescript
|
|
||||||
// Enable context synthesis for better quality
|
|
||||||
const result = await rb.retrieveWithReasoning(embedding, {
|
|
||||||
synthesizeContext: true,
|
|
||||||
useMMR: true,
|
|
||||||
k: 10,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Issue: Memory growing too large
|
|
||||||
```typescript
|
|
||||||
// Enable automatic optimization
|
|
||||||
const result = await rb.retrieveWithReasoning(embedding, {
|
|
||||||
optimizeMemory: true, // Consolidates similar patterns
|
|
||||||
});
|
|
||||||
|
|
||||||
// Or manually optimize
|
|
||||||
await rb.optimize();
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Learn More
|
|
||||||
|
|
||||||
- **AgentDB Integration**: node_modules/agentic-flow/docs/AGENTDB_INTEGRATION.md
|
|
||||||
- **GitHub**: https://github.com/ruvnet/agentic-flow/tree/main/packages/agentdb
|
|
||||||
- **MCP Integration**: `npx agentdb@latest mcp`
|
|
||||||
- **Website**: https://agentdb.ruv.io
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Category**: Machine Learning / Reinforcement Learning
|
|
||||||
**Difficulty**: Intermediate
|
|
||||||
**Estimated Time**: 20-30 minutes
|
|
||||||
@ -1,201 +0,0 @@
|
|||||||
---
|
|
||||||
name: "ReasoningBank Intelligence"
|
|
||||||
description: "Implement adaptive learning with ReasoningBank for pattern recognition, strategy optimization, and continuous improvement. Use when building self-learning agents, optimizing workflows, or implementing meta-cognitive systems."
|
|
||||||
---
|
|
||||||
|
|
||||||
# ReasoningBank Intelligence
|
|
||||||
|
|
||||||
## What This Skill Does
|
|
||||||
|
|
||||||
Implements ReasoningBank's adaptive learning system for AI agents to learn from experience, recognize patterns, and optimize strategies over time. Enables meta-cognitive capabilities and continuous improvement.
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- agentic-flow v3.0.0-alpha.1+
|
|
||||||
- AgentDB v3.0.0-alpha.10+ (for persistence)
|
|
||||||
- Node.js 18+
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { ReasoningBank } from 'agentic-flow/reasoningbank';
|
|
||||||
|
|
||||||
// Initialize ReasoningBank
|
|
||||||
const rb = new ReasoningBank({
|
|
||||||
persist: true,
|
|
||||||
learningRate: 0.1,
|
|
||||||
adapter: 'agentdb' // Use AgentDB for storage
|
|
||||||
});
|
|
||||||
|
|
||||||
// Record task outcome
|
|
||||||
await rb.recordExperience({
|
|
||||||
task: 'code_review',
|
|
||||||
approach: 'static_analysis_first',
|
|
||||||
outcome: {
|
|
||||||
success: true,
|
|
||||||
metrics: {
|
|
||||||
bugs_found: 5,
|
|
||||||
time_taken: 120,
|
|
||||||
false_positives: 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
context: {
|
|
||||||
language: 'typescript',
|
|
||||||
complexity: 'medium'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get optimal strategy
|
|
||||||
const strategy = await rb.recommendStrategy('code_review', {
|
|
||||||
language: 'typescript',
|
|
||||||
complexity: 'high'
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Core Features
|
|
||||||
|
|
||||||
### 1. Pattern Recognition
|
|
||||||
```typescript
|
|
||||||
// Learn patterns from data
|
|
||||||
await rb.learnPattern({
|
|
||||||
pattern: 'api_errors_increase_after_deploy',
|
|
||||||
triggers: ['deployment', 'traffic_spike'],
|
|
||||||
actions: ['rollback', 'scale_up'],
|
|
||||||
confidence: 0.85
|
|
||||||
});
|
|
||||||
|
|
||||||
// Match patterns
|
|
||||||
const matches = await rb.matchPatterns(currentSituation);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Strategy Optimization
|
|
||||||
```typescript
|
|
||||||
// Compare strategies
|
|
||||||
const comparison = await rb.compareStrategies('bug_fixing', [
|
|
||||||
'tdd_approach',
|
|
||||||
'debug_first',
|
|
||||||
'reproduce_then_fix'
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Get best strategy
|
|
||||||
const best = comparison.strategies[0];
|
|
||||||
console.log(`Best: ${best.name} (score: ${best.score})`);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Continuous Learning
|
|
||||||
```typescript
|
|
||||||
// Enable auto-learning from all tasks
|
|
||||||
await rb.enableAutoLearning({
|
|
||||||
threshold: 0.7, // Only learn from high-confidence outcomes
|
|
||||||
updateFrequency: 100 // Update models every 100 experiences
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Advanced Usage
|
|
||||||
|
|
||||||
### Meta-Learning
|
|
||||||
```typescript
|
|
||||||
// Learn about learning
|
|
||||||
await rb.metaLearn({
|
|
||||||
observation: 'parallel_execution_faster_for_independent_tasks',
|
|
||||||
confidence: 0.95,
|
|
||||||
applicability: {
|
|
||||||
task_types: ['batch_processing', 'data_transformation'],
|
|
||||||
conditions: ['tasks_independent', 'io_bound']
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Transfer Learning
|
|
||||||
```typescript
|
|
||||||
// Apply knowledge from one domain to another
|
|
||||||
await rb.transferKnowledge({
|
|
||||||
from: 'code_review_javascript',
|
|
||||||
to: 'code_review_typescript',
|
|
||||||
similarity: 0.8
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Adaptive Agents
|
|
||||||
```typescript
|
|
||||||
// Create self-improving agent
|
|
||||||
class AdaptiveAgent {
|
|
||||||
async execute(task: Task) {
|
|
||||||
// Get optimal strategy
|
|
||||||
const strategy = await rb.recommendStrategy(task.type, task.context);
|
|
||||||
|
|
||||||
// Execute with strategy
|
|
||||||
const result = await this.executeWithStrategy(task, strategy);
|
|
||||||
|
|
||||||
// Learn from outcome
|
|
||||||
await rb.recordExperience({
|
|
||||||
task: task.type,
|
|
||||||
approach: strategy.name,
|
|
||||||
outcome: result,
|
|
||||||
context: task.context
|
|
||||||
});
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Integration with AgentDB
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Persist ReasoningBank data
|
|
||||||
await rb.configure({
|
|
||||||
storage: {
|
|
||||||
type: 'agentdb',
|
|
||||||
options: {
|
|
||||||
database: './reasoning-bank.db',
|
|
||||||
enableVectorSearch: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Query learned patterns
|
|
||||||
const patterns = await rb.query({
|
|
||||||
category: 'optimization',
|
|
||||||
minConfidence: 0.8,
|
|
||||||
timeRange: { last: '30d' }
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Performance Metrics
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Track learning effectiveness
|
|
||||||
const metrics = await rb.getMetrics();
|
|
||||||
console.log(`
|
|
||||||
Total Experiences: ${metrics.totalExperiences}
|
|
||||||
Patterns Learned: ${metrics.patternsLearned}
|
|
||||||
Strategy Success Rate: ${metrics.strategySuccessRate}
|
|
||||||
Improvement Over Time: ${metrics.improvement}
|
|
||||||
`);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
1. **Record consistently**: Log all task outcomes, not just successes
|
|
||||||
2. **Provide context**: Rich context improves pattern matching
|
|
||||||
3. **Set thresholds**: Filter low-confidence learnings
|
|
||||||
4. **Review periodically**: Audit learned patterns for quality
|
|
||||||
5. **Use vector search**: Enable semantic pattern matching
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Issue: Poor recommendations
|
|
||||||
**Solution**: Ensure sufficient training data (100+ experiences per task type)
|
|
||||||
|
|
||||||
### Issue: Slow pattern matching
|
|
||||||
**Solution**: Enable vector indexing in AgentDB
|
|
||||||
|
|
||||||
### Issue: Memory growing large
|
|
||||||
**Solution**: Set TTL for old experiences or enable pruning
|
|
||||||
|
|
||||||
## Learn More
|
|
||||||
|
|
||||||
- ReasoningBank Guide: agentic-flow/src/reasoningbank/README.md
|
|
||||||
- AgentDB Integration: packages/agentdb/docs/reasoningbank.md
|
|
||||||
- Pattern Learning: docs/reasoning/patterns.md
|
|
||||||
@ -1,135 +0,0 @@
|
|||||||
---
|
|
||||||
name: security-audit
|
|
||||||
description: >
|
|
||||||
Comprehensive security scanning and vulnerability detection. Includes input validation, path traversal prevention, CVE detection, and secure coding pattern enforcement.
|
|
||||||
Use when: authentication implementation, authorization logic, payment processing, user data handling, API endpoint creation, file upload handling, database queries, external API integration.
|
|
||||||
Skip when: read-only operations on public data, internal development tooling, static documentation, styling changes.
|
|
||||||
---
|
|
||||||
|
|
||||||
# Security Audit Skill
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
Comprehensive security scanning and vulnerability detection. Includes input validation, path traversal prevention, CVE detection, and secure coding pattern enforcement.
|
|
||||||
|
|
||||||
## When to Trigger
|
|
||||||
- authentication implementation
|
|
||||||
- authorization logic
|
|
||||||
- payment processing
|
|
||||||
- user data handling
|
|
||||||
- API endpoint creation
|
|
||||||
- file upload handling
|
|
||||||
- database queries
|
|
||||||
- external API integration
|
|
||||||
|
|
||||||
## When to Skip
|
|
||||||
- read-only operations on public data
|
|
||||||
- internal development tooling
|
|
||||||
- static documentation
|
|
||||||
- styling changes
|
|
||||||
|
|
||||||
## Commands
|
|
||||||
|
|
||||||
### Full Security Scan
|
|
||||||
Run comprehensive security analysis on the codebase
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli security scan --depth full
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli security scan --depth full --output security-report.json
|
|
||||||
```
|
|
||||||
|
|
||||||
### Input Validation Check
|
|
||||||
Check for input validation issues
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli security scan --check input-validation
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli security scan --check input-validation --path ./src/api
|
|
||||||
```
|
|
||||||
|
|
||||||
### Path Traversal Check
|
|
||||||
Check for path traversal vulnerabilities
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli security scan --check path-traversal
|
|
||||||
```
|
|
||||||
|
|
||||||
### SQL Injection Check
|
|
||||||
Check for SQL injection vulnerabilities
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli security scan --check sql-injection
|
|
||||||
```
|
|
||||||
|
|
||||||
### XSS Check
|
|
||||||
Check for cross-site scripting vulnerabilities
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli security scan --check xss
|
|
||||||
```
|
|
||||||
|
|
||||||
### CVE Scan
|
|
||||||
Scan dependencies for known CVEs
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli security cve --scan
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli security cve --scan --severity high
|
|
||||||
```
|
|
||||||
|
|
||||||
### Security Audit Report
|
|
||||||
Generate full security audit report
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli security audit --report
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli security audit --report --format markdown --output SECURITY.md
|
|
||||||
```
|
|
||||||
|
|
||||||
### Threat Modeling
|
|
||||||
Run threat modeling analysis
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli security threats --analyze
|
|
||||||
```
|
|
||||||
|
|
||||||
### Validate Secrets
|
|
||||||
Check for hardcoded secrets
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli security validate --check secrets
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Scripts
|
|
||||||
|
|
||||||
| Script | Path | Description |
|
|
||||||
|--------|------|-------------|
|
|
||||||
| `security-scan` | `.agents/scripts/security-scan.sh` | Run full security scan pipeline |
|
|
||||||
| `cve-remediate` | `.agents/scripts/cve-remediate.sh` | Auto-remediate known CVEs |
|
|
||||||
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
| Document | Path | Description |
|
|
||||||
|----------|------|-------------|
|
|
||||||
| `Security Checklist` | `docs/security-checklist.md` | Security review checklist |
|
|
||||||
| `OWASP Guide` | `docs/owasp-top10.md` | OWASP Top 10 mitigation guide |
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
1. Check memory for existing patterns before starting
|
|
||||||
2. Use hierarchical topology for coordination
|
|
||||||
3. Store successful patterns after completion
|
|
||||||
4. Document any new learnings
|
|
||||||
@ -1,910 +0,0 @@
|
|||||||
---
|
|
||||||
name: "Skill Builder"
|
|
||||||
description: "Create new Codex Skills with proper YAML frontmatter, progressive disclosure structure, and complete directory organization. Use when you need to build custom skills for specific workflows, generate skill templates, or understand the Codex Skills specification."
|
|
||||||
---
|
|
||||||
|
|
||||||
# Skill Builder
|
|
||||||
|
|
||||||
## What This Skill Does
|
|
||||||
|
|
||||||
Creates production-ready Codex Skills with proper YAML frontmatter, progressive disclosure architecture, and complete file/folder structure. This skill guides you through building skills that Codex can autonomously discover and use across all surfaces (Codex.ai, Codex, SDK, API).
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- Codex 2.0+ or Codex.ai with Skills support
|
|
||||||
- Basic understanding of Markdown and YAML
|
|
||||||
- Text editor or IDE
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
### Creating Your First Skill
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. Create skill directory (MUST be at top level, NOT in subdirectories!)
|
|
||||||
mkdir -p ~/.Codex/skills/my-first-skill
|
|
||||||
|
|
||||||
# 2. Create SKILL.md with proper format
|
|
||||||
cat > ~/.Codex/skills/my-first-skill/SKILL.md << 'EOF'
|
|
||||||
---
|
|
||||||
name: "My First Skill"
|
|
||||||
description: "Brief description of what this skill does and when Codex should use it. Maximum 1024 characters."
|
|
||||||
---
|
|
||||||
|
|
||||||
# My First Skill
|
|
||||||
|
|
||||||
## What This Skill Does
|
|
||||||
[Your instructions here]
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
[Basic usage]
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# 3. Verify skill is detected
|
|
||||||
# Restart Codex or refresh Codex.ai
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Complete Specification
|
|
||||||
|
|
||||||
### 📋 YAML Frontmatter (REQUIRED)
|
|
||||||
|
|
||||||
Every SKILL.md **must** start with YAML frontmatter containing exactly two required fields:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
---
|
|
||||||
name: "Skill Name" # REQUIRED: Max 64 chars
|
|
||||||
description: "What this skill does # REQUIRED: Max 1024 chars
|
|
||||||
and when Codex should use it." # Include BOTH what & when
|
|
||||||
---
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Field Requirements
|
|
||||||
|
|
||||||
**`name`** (REQUIRED):
|
|
||||||
- **Type**: String
|
|
||||||
- **Max Length**: 64 characters
|
|
||||||
- **Format**: Human-friendly display name
|
|
||||||
- **Usage**: Shown in skill lists, UI, and loaded into Codex's system prompt
|
|
||||||
- **Best Practice**: Use Title Case, be concise and descriptive
|
|
||||||
- **Examples**:
|
|
||||||
- ✅ "API Documentation Generator"
|
|
||||||
- ✅ "React Component Builder"
|
|
||||||
- ✅ "Database Schema Designer"
|
|
||||||
- ❌ "skill-1" (not descriptive)
|
|
||||||
- ❌ "This is a very long skill name that exceeds sixty-four characters" (too long)
|
|
||||||
|
|
||||||
**`description`** (REQUIRED):
|
|
||||||
- **Type**: String
|
|
||||||
- **Max Length**: 1024 characters
|
|
||||||
- **Format**: Plain text or minimal markdown
|
|
||||||
- **Content**: MUST include:
|
|
||||||
1. **What** the skill does (functionality)
|
|
||||||
2. **When** Codex should invoke it (trigger conditions)
|
|
||||||
- **Usage**: Loaded into Codex's system prompt for autonomous matching
|
|
||||||
- **Best Practice**: Front-load key trigger words, be specific about use cases
|
|
||||||
- **Examples**:
|
|
||||||
- ✅ "Generate OpenAPI 3.0 documentation from Express.js routes. Use when creating API docs, documenting endpoints, or building API specifications."
|
|
||||||
- ✅ "Create React functional components with TypeScript, hooks, and tests. Use when scaffolding new components or converting class components."
|
|
||||||
- ❌ "A comprehensive guide to API documentation" (no "when" clause)
|
|
||||||
- ❌ "Documentation tool" (too vague)
|
|
||||||
|
|
||||||
#### YAML Formatting Rules
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
---
|
|
||||||
# ✅ CORRECT: Simple string
|
|
||||||
name: "API Builder"
|
|
||||||
description: "Creates REST APIs with Express and TypeScript."
|
|
||||||
|
|
||||||
# ✅ CORRECT: Multi-line description
|
|
||||||
name: "Full-Stack Generator"
|
|
||||||
description: "Generates full-stack applications with React frontend and Node.js backend. Use when starting new projects or scaffolding applications."
|
|
||||||
|
|
||||||
# ✅ CORRECT: Special characters quoted
|
|
||||||
name: "JSON:API Builder"
|
|
||||||
description: "Creates JSON:API compliant endpoints: pagination, filtering, relationships."
|
|
||||||
|
|
||||||
# ❌ WRONG: Missing quotes with special chars
|
|
||||||
name: API:Builder # YAML parse error!
|
|
||||||
|
|
||||||
# ❌ WRONG: Extra fields (ignored but discouraged)
|
|
||||||
name: "My Skill"
|
|
||||||
description: "My description"
|
|
||||||
version: "1.0.0" # NOT part of spec
|
|
||||||
author: "Me" # NOT part of spec
|
|
||||||
tags: ["dev", "api"] # NOT part of spec
|
|
||||||
---
|
|
||||||
```
|
|
||||||
|
|
||||||
**Critical**: Only `name` and `description` are used by Codex. Additional fields are ignored.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 📂 Directory Structure
|
|
||||||
|
|
||||||
#### Minimal Skill (Required)
|
|
||||||
```
|
|
||||||
~/.Codex/skills/ # Personal skills location
|
|
||||||
└── my-skill/ # Skill directory (MUST be at top level!)
|
|
||||||
└── SKILL.md # REQUIRED: Main skill file
|
|
||||||
```
|
|
||||||
|
|
||||||
**IMPORTANT**: Skills MUST be directly under `~/.Codex/skills/[skill-name]/`.
|
|
||||||
Codex does NOT support nested subdirectories or namespaces!
|
|
||||||
|
|
||||||
#### Full-Featured Skill (Recommended)
|
|
||||||
```
|
|
||||||
~/.Codex/skills/
|
|
||||||
└── my-skill/ # Top-level skill directory
|
|
||||||
├── SKILL.md # REQUIRED: Main skill file
|
|
||||||
├── README.md # Optional: Human-readable docs
|
|
||||||
├── scripts/ # Optional: Executable scripts
|
|
||||||
│ ├── setup.sh
|
|
||||||
│ ├── validate.js
|
|
||||||
│ └── deploy.py
|
|
||||||
├── resources/ # Optional: Supporting files
|
|
||||||
│ ├── templates/
|
|
||||||
│ │ ├── api-template.js
|
|
||||||
│ │ └── component.tsx
|
|
||||||
│ ├── examples/
|
|
||||||
│ │ └── sample-output.json
|
|
||||||
│ └── schemas/
|
|
||||||
│ └── config-schema.json
|
|
||||||
└── docs/ # Optional: Additional documentation
|
|
||||||
├── ADVANCED.md
|
|
||||||
├── TROUBLESHOOTING.md
|
|
||||||
└── API_REFERENCE.md
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Skills Locations
|
|
||||||
|
|
||||||
**Personal Skills** (available across all projects):
|
|
||||||
```
|
|
||||||
~/.Codex/skills/
|
|
||||||
└── [your-skills]/
|
|
||||||
```
|
|
||||||
- **Path**: `~/.Codex/skills/` or `$HOME/.Codex/skills/`
|
|
||||||
- **Scope**: Available in all projects for this user
|
|
||||||
- **Version Control**: NOT committed to git (outside repo)
|
|
||||||
- **Use Case**: Personal productivity tools, custom workflows
|
|
||||||
|
|
||||||
**Project Skills** (team-shared, version controlled):
|
|
||||||
```
|
|
||||||
<project-root>/.Codex/skills/
|
|
||||||
└── [team-skills]/
|
|
||||||
```
|
|
||||||
- **Path**: `.Codex/skills/` in project root
|
|
||||||
- **Scope**: Available only in this project
|
|
||||||
- **Version Control**: SHOULD be committed to git
|
|
||||||
- **Use Case**: Team workflows, project-specific tools, shared knowledge
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 🎯 Progressive Disclosure Architecture
|
|
||||||
|
|
||||||
Codex uses a **3-level progressive disclosure system** to scale to 100+ skills without context penalty:
|
|
||||||
|
|
||||||
#### Level 1: Metadata (Name + Description)
|
|
||||||
**Loaded**: At Codex startup, always
|
|
||||||
**Size**: ~200 chars per skill
|
|
||||||
**Purpose**: Enable autonomous skill matching
|
|
||||||
**Context**: Loaded into system prompt for ALL skills
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
---
|
|
||||||
name: "API Builder" # 11 chars
|
|
||||||
description: "Creates REST APIs..." # ~50 chars
|
|
||||||
---
|
|
||||||
# Total: ~61 chars per skill
|
|
||||||
# 100 skills = ~6KB context (minimal!)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Level 2: SKILL.md Body
|
|
||||||
**Loaded**: When skill is triggered/matched
|
|
||||||
**Size**: ~1-10KB typically
|
|
||||||
**Purpose**: Main instructions and procedures
|
|
||||||
**Context**: Only loaded for ACTIVE skills
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# API Builder
|
|
||||||
|
|
||||||
## What This Skill Does
|
|
||||||
[Main instructions - loaded only when skill is active]
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
[Basic procedures]
|
|
||||||
|
|
||||||
## Step-by-Step Guide
|
|
||||||
[Detailed instructions]
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Level 3+: Referenced Files
|
|
||||||
**Loaded**: On-demand as Codex navigates
|
|
||||||
**Size**: Variable (KB to MB)
|
|
||||||
**Purpose**: Deep reference, examples, schemas
|
|
||||||
**Context**: Loaded only when Codex accesses specific files
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# In SKILL.md
|
|
||||||
See [Advanced Configuration](docs/ADVANCED.md) for complex scenarios.
|
|
||||||
See [API Reference](docs/API_REFERENCE.md) for complete documentation.
|
|
||||||
Use template: `resources/templates/api-template.js`
|
|
||||||
|
|
||||||
# Codex will load these files ONLY if needed
|
|
||||||
```
|
|
||||||
|
|
||||||
**Benefit**: Install 100+ skills with ~6KB context. Only active skill content (1-10KB) enters context.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 📝 SKILL.md Content Structure
|
|
||||||
|
|
||||||
#### Recommended 4-Level Structure
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
---
|
|
||||||
name: "Your Skill Name"
|
|
||||||
description: "What it does and when to use it"
|
|
||||||
---
|
|
||||||
|
|
||||||
# Your Skill Name
|
|
||||||
|
|
||||||
## Level 1: Overview (Always Read First)
|
|
||||||
Brief 2-3 sentence description of the skill.
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
- Requirement 1
|
|
||||||
- Requirement 2
|
|
||||||
|
|
||||||
## What This Skill Does
|
|
||||||
1. Primary function
|
|
||||||
2. Secondary function
|
|
||||||
3. Key benefit
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Level 2: Quick Start (For Fast Onboarding)
|
|
||||||
|
|
||||||
### Basic Usage
|
|
||||||
```bash
|
|
||||||
# Simplest use case
|
|
||||||
command --option value
|
|
||||||
```
|
|
||||||
|
|
||||||
### Common Scenarios
|
|
||||||
1. **Scenario 1**: How to...
|
|
||||||
2. **Scenario 2**: How to...
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Level 3: Detailed Instructions (For Deep Work)
|
|
||||||
|
|
||||||
### Step-by-Step Guide
|
|
||||||
|
|
||||||
#### Step 1: Initial Setup
|
|
||||||
```bash
|
|
||||||
# Commands
|
|
||||||
```
|
|
||||||
Expected output:
|
|
||||||
```
|
|
||||||
Success message
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Step 2: Configuration
|
|
||||||
- Configuration option 1
|
|
||||||
- Configuration option 2
|
|
||||||
|
|
||||||
#### Step 3: Execution
|
|
||||||
- Run the main command
|
|
||||||
- Verify results
|
|
||||||
|
|
||||||
### Advanced Options
|
|
||||||
|
|
||||||
#### Option 1: Custom Configuration
|
|
||||||
```bash
|
|
||||||
# Advanced usage
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Option 2: Integration
|
|
||||||
```bash
|
|
||||||
# Integration steps
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Level 4: Reference (Rarely Needed)
|
|
||||||
|
|
||||||
### Troubleshooting
|
|
||||||
|
|
||||||
#### Issue: Common Problem
|
|
||||||
**Symptoms**: What you see
|
|
||||||
**Cause**: Why it happens
|
|
||||||
**Solution**: How to fix
|
|
||||||
```bash
|
|
||||||
# Fix command
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Issue: Another Problem
|
|
||||||
**Solution**: Steps to resolve
|
|
||||||
|
|
||||||
### Complete API Reference
|
|
||||||
See [API_REFERENCE.md](docs/API_REFERENCE.md)
|
|
||||||
|
|
||||||
### Examples
|
|
||||||
See [examples/](resources/examples/)
|
|
||||||
|
|
||||||
### Related Skills
|
|
||||||
- [Related Skill 1](#)
|
|
||||||
- [Related Skill 2](#)
|
|
||||||
|
|
||||||
### Resources
|
|
||||||
- [External Link 1](https://example.com)
|
|
||||||
- [Documentation](https://docs.example.com)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 🎨 Content Best Practices
|
|
||||||
|
|
||||||
#### Writing Effective Descriptions
|
|
||||||
|
|
||||||
**Front-Load Keywords**:
|
|
||||||
```yaml
|
|
||||||
# ✅ GOOD: Keywords first
|
|
||||||
description: "Generate TypeScript interfaces from JSON schema. Use when converting schemas, creating types, or building API clients."
|
|
||||||
|
|
||||||
# ❌ BAD: Keywords buried
|
|
||||||
description: "This skill helps developers who need to work with JSON schemas by providing a way to generate TypeScript interfaces."
|
|
||||||
```
|
|
||||||
|
|
||||||
**Include Trigger Conditions**:
|
|
||||||
```yaml
|
|
||||||
# ✅ GOOD: Clear "when" clause
|
|
||||||
description: "Debug React performance issues using Chrome DevTools. Use when components re-render unnecessarily, investigating slow updates, or optimizing bundle size."
|
|
||||||
|
|
||||||
# ❌ BAD: No trigger conditions
|
|
||||||
description: "Helps with React performance debugging."
|
|
||||||
```
|
|
||||||
|
|
||||||
**Be Specific**:
|
|
||||||
```yaml
|
|
||||||
# ✅ GOOD: Specific technologies
|
|
||||||
description: "Create Express.js REST endpoints with Joi validation, Swagger docs, and Jest tests. Use when building new APIs or adding endpoints."
|
|
||||||
|
|
||||||
# ❌ BAD: Too generic
|
|
||||||
description: "Build API endpoints with proper validation and testing."
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Progressive Disclosure Writing
|
|
||||||
|
|
||||||
**Keep Level 1 Brief** (Overview):
|
|
||||||
```markdown
|
|
||||||
## What This Skill Does
|
|
||||||
Creates production-ready React components with TypeScript, hooks, and tests in 3 steps.
|
|
||||||
```
|
|
||||||
|
|
||||||
**Level 2 for Common Paths** (Quick Start):
|
|
||||||
```markdown
|
|
||||||
## Quick Start
|
|
||||||
```bash
|
|
||||||
# Most common use case (80% of users)
|
|
||||||
generate-component MyComponent
|
|
||||||
```
|
|
||||||
```
|
|
||||||
|
|
||||||
**Level 3 for Details** (Step-by-Step):
|
|
||||||
```markdown
|
|
||||||
## Step-by-Step Guide
|
|
||||||
|
|
||||||
### Creating a Basic Component
|
|
||||||
1. Run generator
|
|
||||||
2. Choose template
|
|
||||||
3. Customize options
|
|
||||||
[Detailed explanations]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Level 4 for Edge Cases** (Reference):
|
|
||||||
```markdown
|
|
||||||
## Advanced Configuration
|
|
||||||
For complex scenarios like HOCs, render props, or custom hooks, see [ADVANCED.md](docs/ADVANCED.md).
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 🛠️ Adding Scripts and Resources
|
|
||||||
|
|
||||||
#### Scripts Directory
|
|
||||||
|
|
||||||
**Purpose**: Executable scripts that Codex can run
|
|
||||||
**Location**: `scripts/` in skill directory
|
|
||||||
**Usage**: Referenced from SKILL.md
|
|
||||||
|
|
||||||
Example:
|
|
||||||
```bash
|
|
||||||
# In skill directory
|
|
||||||
scripts/
|
|
||||||
├── setup.sh # Initialization script
|
|
||||||
├── validate.js # Validation logic
|
|
||||||
├── generate.py # Code generation
|
|
||||||
└── deploy.sh # Deployment script
|
|
||||||
```
|
|
||||||
|
|
||||||
Reference from SKILL.md:
|
|
||||||
```markdown
|
|
||||||
## Setup
|
|
||||||
Run the setup script:
|
|
||||||
```bash
|
|
||||||
./scripts/setup.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
## Validation
|
|
||||||
Validate your configuration:
|
|
||||||
```bash
|
|
||||||
node scripts/validate.js config.json
|
|
||||||
```
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Resources Directory
|
|
||||||
|
|
||||||
**Purpose**: Templates, examples, schemas, static files
|
|
||||||
**Location**: `resources/` in skill directory
|
|
||||||
**Usage**: Referenced or copied by scripts
|
|
||||||
|
|
||||||
Example:
|
|
||||||
```bash
|
|
||||||
resources/
|
|
||||||
├── templates/
|
|
||||||
│ ├── component.tsx.template
|
|
||||||
│ ├── test.spec.ts.template
|
|
||||||
│ └── story.stories.tsx.template
|
|
||||||
├── examples/
|
|
||||||
│ ├── basic-example/
|
|
||||||
│ ├── advanced-example/
|
|
||||||
│ └── integration-example/
|
|
||||||
└── schemas/
|
|
||||||
├── config.schema.json
|
|
||||||
└── output.schema.json
|
|
||||||
```
|
|
||||||
|
|
||||||
Reference from SKILL.md:
|
|
||||||
```markdown
|
|
||||||
## Templates
|
|
||||||
Use the component template:
|
|
||||||
```bash
|
|
||||||
cp resources/templates/component.tsx.template src/components/MyComponent.tsx
|
|
||||||
```
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
See working examples in `resources/examples/`:
|
|
||||||
- `basic-example/` - Simple component
|
|
||||||
- `advanced-example/` - With hooks and context
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 🔗 File References and Navigation
|
|
||||||
|
|
||||||
Codex can navigate to referenced files automatically. Use these patterns:
|
|
||||||
|
|
||||||
#### Markdown Links
|
|
||||||
```markdown
|
|
||||||
See [Advanced Configuration](docs/ADVANCED.md) for complex scenarios.
|
|
||||||
See [Troubleshooting Guide](docs/TROUBLESHOOTING.md) if you encounter errors.
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Relative File Paths
|
|
||||||
```markdown
|
|
||||||
Use the template located at `resources/templates/api-template.js`
|
|
||||||
See examples in `resources/examples/basic-usage/`
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Inline File Content
|
|
||||||
```markdown
|
|
||||||
## Example Configuration
|
|
||||||
See `resources/examples/config.json`:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"option": "value"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
```
|
|
||||||
|
|
||||||
**Best Practice**: Keep SKILL.md lean (~2-5KB). Move lengthy content to separate files and reference them. Codex will load only what's needed.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### ✅ Validation Checklist
|
|
||||||
|
|
||||||
Before publishing a skill, verify:
|
|
||||||
|
|
||||||
**YAML Frontmatter**:
|
|
||||||
- [ ] Starts with `---`
|
|
||||||
- [ ] Contains `name` field (max 64 chars)
|
|
||||||
- [ ] Contains `description` field (max 1024 chars)
|
|
||||||
- [ ] Description includes "what" and "when"
|
|
||||||
- [ ] Ends with `---`
|
|
||||||
- [ ] No YAML syntax errors
|
|
||||||
|
|
||||||
**File Structure**:
|
|
||||||
- [ ] SKILL.md exists in skill directory
|
|
||||||
- [ ] Directory is DIRECTLY in `~/.Codex/skills/[skill-name]/` or `.Codex/skills/[skill-name]/`
|
|
||||||
- [ ] Uses clear, descriptive directory name
|
|
||||||
- [ ] **NO nested subdirectories** (Codex requires top-level structure)
|
|
||||||
|
|
||||||
**Content Quality**:
|
|
||||||
- [ ] Level 1 (Overview) is brief and clear
|
|
||||||
- [ ] Level 2 (Quick Start) shows common use case
|
|
||||||
- [ ] Level 3 (Details) provides step-by-step guide
|
|
||||||
- [ ] Level 4 (Reference) links to advanced content
|
|
||||||
- [ ] Examples are concrete and runnable
|
|
||||||
- [ ] Troubleshooting section addresses common issues
|
|
||||||
|
|
||||||
**Progressive Disclosure**:
|
|
||||||
- [ ] Core instructions in SKILL.md (~2-5KB)
|
|
||||||
- [ ] Advanced content in separate docs/
|
|
||||||
- [ ] Large resources in resources/ directory
|
|
||||||
- [ ] Clear navigation between levels
|
|
||||||
|
|
||||||
**Testing**:
|
|
||||||
- [ ] Skill appears in Codex's skill list
|
|
||||||
- [ ] Description triggers on relevant queries
|
|
||||||
- [ ] Instructions are clear and actionable
|
|
||||||
- [ ] Scripts execute successfully (if included)
|
|
||||||
- [ ] Examples work as documented
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Skill Builder Templates
|
|
||||||
|
|
||||||
### Template 1: Basic Skill (Minimal)
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
---
|
|
||||||
name: "My Basic Skill"
|
|
||||||
description: "One sentence what. One sentence when to use."
|
|
||||||
---
|
|
||||||
|
|
||||||
# My Basic Skill
|
|
||||||
|
|
||||||
## What This Skill Does
|
|
||||||
[2-3 sentences describing functionality]
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
```bash
|
|
||||||
# Single command to get started
|
|
||||||
```
|
|
||||||
|
|
||||||
## Step-by-Step Guide
|
|
||||||
|
|
||||||
### Step 1: Setup
|
|
||||||
[Instructions]
|
|
||||||
|
|
||||||
### Step 2: Usage
|
|
||||||
[Instructions]
|
|
||||||
|
|
||||||
### Step 3: Verify
|
|
||||||
[Instructions]
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
- **Issue**: Problem description
|
|
||||||
- **Solution**: Fix description
|
|
||||||
```
|
|
||||||
|
|
||||||
### Template 2: Intermediate Skill (With Scripts)
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
---
|
|
||||||
name: "My Intermediate Skill"
|
|
||||||
description: "Detailed what with key features. When to use with specific triggers: scaffolding, generating, building."
|
|
||||||
---
|
|
||||||
|
|
||||||
# My Intermediate Skill
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
- Requirement 1
|
|
||||||
- Requirement 2
|
|
||||||
|
|
||||||
## What This Skill Does
|
|
||||||
1. Primary function
|
|
||||||
2. Secondary function
|
|
||||||
3. Integration capability
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
```bash
|
|
||||||
./scripts/setup.sh
|
|
||||||
./scripts/generate.sh my-project
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
Edit `config.json`:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"option1": "value1",
|
|
||||||
"option2": "value2"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Step-by-Step Guide
|
|
||||||
|
|
||||||
### Basic Usage
|
|
||||||
[Steps for 80% use case]
|
|
||||||
|
|
||||||
### Advanced Usage
|
|
||||||
[Steps for complex scenarios]
|
|
||||||
|
|
||||||
## Available Scripts
|
|
||||||
- `scripts/setup.sh` - Initial setup
|
|
||||||
- `scripts/generate.sh` - Code generation
|
|
||||||
- `scripts/validate.sh` - Validation
|
|
||||||
|
|
||||||
## Resources
|
|
||||||
- Templates: `resources/templates/`
|
|
||||||
- Examples: `resources/examples/`
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
[Common issues and solutions]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Template 3: Advanced Skill (Full-Featured)
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
---
|
|
||||||
name: "My Advanced Skill"
|
|
||||||
description: "Comprehensive what with all features and integrations. Use when [trigger 1], [trigger 2], or [trigger 3]. Supports [technology stack]."
|
|
||||||
---
|
|
||||||
|
|
||||||
# My Advanced Skill
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
[Brief 2-3 sentence description]
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
- Technology 1 (version X+)
|
|
||||||
- Technology 2 (version Y+)
|
|
||||||
- API keys or credentials
|
|
||||||
|
|
||||||
## What This Skill Does
|
|
||||||
1. **Core Feature**: Description
|
|
||||||
2. **Integration**: Description
|
|
||||||
3. **Automation**: Description
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Quick Start (60 seconds)
|
|
||||||
|
|
||||||
### Installation
|
|
||||||
```bash
|
|
||||||
./scripts/install.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
### First Use
|
|
||||||
```bash
|
|
||||||
./scripts/quickstart.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected output:
|
|
||||||
```
|
|
||||||
✓ Setup complete
|
|
||||||
✓ Configuration validated
|
|
||||||
→ Ready to use
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
### Basic Configuration
|
|
||||||
Edit `config.json`:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"mode": "production",
|
|
||||||
"features": ["feature1", "feature2"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Advanced Configuration
|
|
||||||
See [Configuration Guide](docs/CONFIGURATION.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step-by-Step Guide
|
|
||||||
|
|
||||||
### 1. Initial Setup
|
|
||||||
[Detailed steps]
|
|
||||||
|
|
||||||
### 2. Core Workflow
|
|
||||||
[Main procedures]
|
|
||||||
|
|
||||||
### 3. Integration
|
|
||||||
[Integration steps]
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Advanced Features
|
|
||||||
|
|
||||||
### Feature 1: Custom Templates
|
|
||||||
```bash
|
|
||||||
./scripts/generate.sh --template custom
|
|
||||||
```
|
|
||||||
|
|
||||||
### Feature 2: Batch Processing
|
|
||||||
```bash
|
|
||||||
./scripts/batch.sh --input data.json
|
|
||||||
```
|
|
||||||
|
|
||||||
### Feature 3: CI/CD Integration
|
|
||||||
See [CI/CD Guide](docs/CICD.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Scripts Reference
|
|
||||||
|
|
||||||
| Script | Purpose | Usage |
|
|
||||||
|--------|---------|-------|
|
|
||||||
| `install.sh` | Install dependencies | `./scripts/install.sh` |
|
|
||||||
| `generate.sh` | Generate code | `./scripts/generate.sh [name]` |
|
|
||||||
| `validate.sh` | Validate output | `./scripts/validate.sh` |
|
|
||||||
| `deploy.sh` | Deploy to environment | `./scripts/deploy.sh [env]` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Resources
|
|
||||||
|
|
||||||
### Templates
|
|
||||||
- `resources/templates/basic.template` - Basic template
|
|
||||||
- `resources/templates/advanced.template` - Advanced template
|
|
||||||
|
|
||||||
### Examples
|
|
||||||
- `resources/examples/basic/` - Simple example
|
|
||||||
- `resources/examples/advanced/` - Complex example
|
|
||||||
- `resources/examples/integration/` - Integration example
|
|
||||||
|
|
||||||
### Schemas
|
|
||||||
- `resources/schemas/config.schema.json` - Configuration schema
|
|
||||||
- `resources/schemas/output.schema.json` - Output validation
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Issue: Installation Failed
|
|
||||||
**Symptoms**: Error during `install.sh`
|
|
||||||
**Cause**: Missing dependencies
|
|
||||||
**Solution**:
|
|
||||||
```bash
|
|
||||||
# Install prerequisites
|
|
||||||
npm install -g required-package
|
|
||||||
./scripts/install.sh --force
|
|
||||||
```
|
|
||||||
|
|
||||||
### Issue: Validation Errors
|
|
||||||
**Symptoms**: Validation script fails
|
|
||||||
**Solution**: See [Troubleshooting Guide](docs/TROUBLESHOOTING.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## API Reference
|
|
||||||
Complete API documentation: [API_REFERENCE.md](docs/API_REFERENCE.md)
|
|
||||||
|
|
||||||
## Related Skills
|
|
||||||
- [Related Skill 1](../related-skill-1/)
|
|
||||||
- [Related Skill 2](../related-skill-2/)
|
|
||||||
|
|
||||||
## Resources
|
|
||||||
- [Official Documentation](https://example.com/docs)
|
|
||||||
- [GitHub Repository](https://github.com/example/repo)
|
|
||||||
- [Community Forum](https://forum.example.com)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Created**: 2025-10-19
|
|
||||||
**Category**: Advanced
|
|
||||||
**Difficulty**: Intermediate
|
|
||||||
**Estimated Time**: 15-30 minutes
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Examples from the Wild
|
|
||||||
|
|
||||||
### Example 1: Simple Documentation Skill
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
---
|
|
||||||
name: "README Generator"
|
|
||||||
description: "Generate comprehensive README.md files for GitHub repositories. Use when starting new projects, documenting code, or improving existing READMEs."
|
|
||||||
---
|
|
||||||
|
|
||||||
# README Generator
|
|
||||||
|
|
||||||
## What This Skill Does
|
|
||||||
Creates well-structured README.md files with badges, installation, usage, and contribution sections.
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
```bash
|
|
||||||
# Answer a few questions
|
|
||||||
./scripts/generate-readme.sh
|
|
||||||
|
|
||||||
# README.md created with:
|
|
||||||
# - Project title and description
|
|
||||||
# - Installation instructions
|
|
||||||
# - Usage examples
|
|
||||||
# - Contribution guidelines
|
|
||||||
```
|
|
||||||
|
|
||||||
## Customization
|
|
||||||
Edit sections in `resources/templates/sections/` before generating.
|
|
||||||
```
|
|
||||||
|
|
||||||
### Example 2: Code Generation Skill
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
---
|
|
||||||
name: "React Component Generator"
|
|
||||||
description: "Generate React functional components with TypeScript, hooks, tests, and Storybook stories. Use when creating new components, scaffolding UI, or following component architecture patterns."
|
|
||||||
---
|
|
||||||
|
|
||||||
# React Component Generator
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
- Node.js 18+
|
|
||||||
- React 18+
|
|
||||||
- TypeScript 5+
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
```bash
|
|
||||||
./scripts/generate-component.sh MyComponent
|
|
||||||
|
|
||||||
# Creates:
|
|
||||||
# - src/components/MyComponent/MyComponent.tsx
|
|
||||||
# - src/components/MyComponent/MyComponent.test.tsx
|
|
||||||
# - src/components/MyComponent/MyComponent.stories.tsx
|
|
||||||
# - src/components/MyComponent/index.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
## Step-by-Step Guide
|
|
||||||
|
|
||||||
### 1. Run Generator
|
|
||||||
```bash
|
|
||||||
./scripts/generate-component.sh ComponentName
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Choose Template
|
|
||||||
- Basic: Simple functional component
|
|
||||||
- With State: useState hooks
|
|
||||||
- With Context: useContext integration
|
|
||||||
- With API: Data fetching component
|
|
||||||
|
|
||||||
### 3. Customize
|
|
||||||
Edit generated files in `src/components/ComponentName/`
|
|
||||||
|
|
||||||
## Templates
|
|
||||||
See `resources/templates/` for available component templates.
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Learn More
|
|
||||||
|
|
||||||
### Official Resources
|
|
||||||
- [Anthropic Agent Skills Documentation](https://docs.Codex.com/en/docs/agents-and-tools/agent-skills)
|
|
||||||
- [GitHub Skills Repository](https://github.com/anthropics/skills)
|
|
||||||
- [Codex Documentation](https://docs.Codex.com/en/docs/Codex)
|
|
||||||
|
|
||||||
### Community
|
|
||||||
- [Skills Marketplace](https://github.com/anthropics/skills) - Browse community skills
|
|
||||||
- [Anthropic Discord](https://discord.gg/anthropic) - Get help from community
|
|
||||||
|
|
||||||
### Advanced Topics
|
|
||||||
- Multi-file skills with complex navigation
|
|
||||||
- Skills that spawn other skills
|
|
||||||
- Integration with MCP tools
|
|
||||||
- Dynamic skill generation
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Created**: 2025-10-19
|
|
||||||
**Version**: 1.0.0
|
|
||||||
**Maintained By**: agentic-flow team
|
|
||||||
**License**: MIT
|
|
||||||
@ -1,144 +0,0 @@
|
|||||||
---
|
|
||||||
name: soft-delete-relogin-consistency
|
|
||||||
description: |
|
|
||||||
Fix for missing auth/identity records after account deletion + device re-login.
|
|
||||||
Use when: (1) User deletes account but device records are intentionally kept
|
|
||||||
(e.g., to prevent trial abuse), (2) Re-login via device succeeds but user
|
|
||||||
appears to have wrong identity type, (3) Frontend shows incorrect UI because
|
|
||||||
auth_methods or similar identity records are empty/wrong after re-login,
|
|
||||||
(4) Soft-deleted records cause stale cache entries that misrepresent user state.
|
|
||||||
Covers GORM soft-delete, device-based auth, cache invalidation after re-creation.
|
|
||||||
author: Codex
|
|
||||||
version: 1.0.0
|
|
||||||
date: 2026-03-11
|
|
||||||
---
|
|
||||||
|
|
||||||
# Soft-Delete + Re-Login Auth Consistency
|
|
||||||
|
|
||||||
## Problem
|
|
||||||
|
|
||||||
When a system uses soft-delete for auth/identity records during account deletion but
|
|
||||||
intentionally keeps primary records (like device records) for abuse prevention, re-login
|
|
||||||
flows may succeed at the "find existing record" step but fail to re-create the
|
|
||||||
soft-deleted identity records. This causes the user to exist in an inconsistent state
|
|
||||||
where they're authenticated but missing critical identity metadata.
|
|
||||||
|
|
||||||
## Context / Trigger Conditions
|
|
||||||
|
|
||||||
- Account deletion (注销) soft-deletes `auth_methods` (or equivalent identity records)
|
|
||||||
- Device/hardware records are intentionally kept to prevent trial reward abuse
|
|
||||||
- Device-based re-login finds existing device record -> reuses old user_id
|
|
||||||
- But the "device found" code path skips identity record creation (only the
|
|
||||||
"device not found" registration path creates them)
|
|
||||||
- Result: User is logged in but `auth_methods` is empty or missing the expected type
|
|
||||||
- Frontend UI breaks because it relies on `auth_methods[0].auth_type` to determine
|
|
||||||
login mode and show/hide UI elements
|
|
||||||
|
|
||||||
### Symptoms
|
|
||||||
|
|
||||||
- Buttons or UI elements that should be hidden for device-only users appear after
|
|
||||||
account deletion + re-login
|
|
||||||
- API returns user info with empty or unexpected `auth_methods` array
|
|
||||||
- `isDeviceLogin()` or similar identity checks return wrong results
|
|
||||||
- Cache returns stale user data even after re-login
|
|
||||||
|
|
||||||
## Solution
|
|
||||||
|
|
||||||
### Step 1: Identify the re-login code path
|
|
||||||
|
|
||||||
Find the "device found" branch in the login logic. This is the code path that runs
|
|
||||||
when a device record already exists (as opposed to the registration path).
|
|
||||||
|
|
||||||
### Step 2: Add identity record existence check
|
|
||||||
|
|
||||||
After finding the user via device record, check if the expected identity record exists:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// After finding user via existing device record
|
|
||||||
hasDeviceAuth := false
|
|
||||||
for _, am := range userInfo.AuthMethods {
|
|
||||||
if am.AuthType == "device" && am.AuthIdentifier == req.Identifier {
|
|
||||||
hasDeviceAuth = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !hasDeviceAuth {
|
|
||||||
// Re-create the soft-deleted auth record
|
|
||||||
authMethod := &user.AuthMethods{
|
|
||||||
UserId: userInfo.Id,
|
|
||||||
AuthType: "device",
|
|
||||||
AuthIdentifier: req.Identifier,
|
|
||||||
Verified: true,
|
|
||||||
}
|
|
||||||
if createErr := db.Create(authMethod).Error; createErr != nil {
|
|
||||||
log.Error("re-create auth method failed", err)
|
|
||||||
} else {
|
|
||||||
// CRITICAL: Clear user cache so subsequent reads return updated data
|
|
||||||
_ = userModel.ClearUserCache(ctx, userInfo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 3: Ensure cache invalidation
|
|
||||||
|
|
||||||
After re-creating the identity record, clear the user cache. This is critical because
|
|
||||||
cached user data (with `Preload("AuthMethods")`) will still show the old empty state
|
|
||||||
until the cache is invalidated.
|
|
||||||
|
|
||||||
### Step 4: Verify GORM soft-delete behavior
|
|
||||||
|
|
||||||
GORM's soft-delete (`deleted_at IS NULL` filter) means:
|
|
||||||
- `Preload("AuthMethods")` will NOT return soft-deleted records
|
|
||||||
- `db.Create()` will create a NEW record (not undelete the old one)
|
|
||||||
- The old soft-deleted record remains in the database (harmless)
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
1. Delete account (注销)
|
|
||||||
2. Re-login via device
|
|
||||||
3. Call user info API - verify `auth_methods` contains the device type
|
|
||||||
4. Check frontend UI - verify device-specific UI state is correct
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
**Before fix:**
|
|
||||||
```
|
|
||||||
1. User has auth_methods: [device_A, email_A]
|
|
||||||
2. User deletes account -> auth_methods all soft-deleted
|
|
||||||
3. Device record kept (abuse prevention)
|
|
||||||
4. User re-logins via same device
|
|
||||||
5. FindOneDeviceByIdentifier finds device -> reuses user_id
|
|
||||||
6. FindOne returns user with AuthMethods=[] (soft-deleted, filtered out)
|
|
||||||
7. Frontend: isDeviceLogin() = false (no auth_methods) -> shows wrong buttons
|
|
||||||
```
|
|
||||||
|
|
||||||
**After fix:**
|
|
||||||
```
|
|
||||||
1-4. Same as above
|
|
||||||
5. FindOneDeviceByIdentifier finds device -> reuses user_id
|
|
||||||
6. FindOne returns user with AuthMethods=[]
|
|
||||||
7. NEW: Detects missing device auth_method, re-creates it, clears cache
|
|
||||||
8. Frontend: isDeviceLogin() = true -> correct UI
|
|
||||||
```
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
- This pattern applies broadly to any system where:
|
|
||||||
- Account deletion removes identity records but keeps usage records
|
|
||||||
- Re-login can succeed via the usage records
|
|
||||||
- UI/business logic depends on the identity records existing
|
|
||||||
- The "don't delete device records" design is intentional for preventing abuse
|
|
||||||
(e.g., users repeatedly deleting and re-creating accounts to get trial rewards)
|
|
||||||
- Cache invalidation is the most commonly missed step - without it, the fix appears
|
|
||||||
to not work because cached data is served until TTL expires
|
|
||||||
- Consider whether `Unscoped()` (GORM) should be used to also query soft-deleted
|
|
||||||
records, or whether re-creation is the better approach (usually re-creation is
|
|
||||||
cleaner as it creates a fresh record with correct timestamps)
|
|
||||||
|
|
||||||
## Related Patterns
|
|
||||||
|
|
||||||
- **Cache key dependency chains**: When `ClearUserCache` depends on `AuthMethods`
|
|
||||||
to generate email cache keys, capture auth_methods BEFORE deletion, then explicitly
|
|
||||||
clear derived cache keys after the transaction
|
|
||||||
- **Family ownership transfer**: When an owner exits a shared resource group, transfer
|
|
||||||
ownership to a remaining member instead of dissolving the group
|
|
||||||
@ -1,118 +0,0 @@
|
|||||||
---
|
|
||||||
name: sparc-methodology
|
|
||||||
description: >
|
|
||||||
SPARC development workflow: Specification, Pseudocode, Architecture, Refinement, Completion. A structured approach for complex implementations that ensures thorough planning before coding.
|
|
||||||
Use when: new feature implementation, complex implementations, architectural changes, system redesign, integration work, unclear requirements.
|
|
||||||
Skip when: simple bug fixes, documentation updates, configuration changes, well-defined small tasks, routine maintenance.
|
|
||||||
---
|
|
||||||
|
|
||||||
# Sparc Methodology Skill
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
SPARC development workflow: Specification, Pseudocode, Architecture, Refinement, Completion. A structured approach for complex implementations that ensures thorough planning before coding.
|
|
||||||
|
|
||||||
## When to Trigger
|
|
||||||
- new feature implementation
|
|
||||||
- complex implementations
|
|
||||||
- architectural changes
|
|
||||||
- system redesign
|
|
||||||
- integration work
|
|
||||||
- unclear requirements
|
|
||||||
|
|
||||||
## When to Skip
|
|
||||||
- simple bug fixes
|
|
||||||
- documentation updates
|
|
||||||
- configuration changes
|
|
||||||
- well-defined small tasks
|
|
||||||
- routine maintenance
|
|
||||||
|
|
||||||
## Commands
|
|
||||||
|
|
||||||
### Specification Phase
|
|
||||||
Define requirements, acceptance criteria, and constraints
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli hooks route --task "specification: [requirements]"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli hooks route --task "specification: user authentication with OAuth2, MFA, and session management"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Pseudocode Phase
|
|
||||||
Write high-level pseudocode for the implementation
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli hooks route --task "pseudocode: [feature]"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli hooks route --task "pseudocode: OAuth2 login flow with token refresh"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Architecture Phase
|
|
||||||
Design system structure, interfaces, and dependencies
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli hooks route --task "architecture: [design]"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli hooks route --task "architecture: auth module with service layer, repository, and API endpoints"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Refinement Phase
|
|
||||||
Iterate on the design based on feedback
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli hooks route --task "refinement: [feedback]"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli hooks route --task "refinement: add rate limiting and brute force protection"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Completion Phase
|
|
||||||
Finalize implementation with tests and documentation
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli hooks route --task "completion: [final checks]"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli hooks route --task "completion: verify all tests pass, update API docs, security review"
|
|
||||||
```
|
|
||||||
|
|
||||||
### SPARC Coordinator
|
|
||||||
Spawn SPARC coordinator agent
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli agent spawn --type sparc-coord --name sparc-lead
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Scripts
|
|
||||||
|
|
||||||
| Script | Path | Description |
|
|
||||||
|--------|------|-------------|
|
|
||||||
| `sparc-init` | `.agents/scripts/sparc-init.sh` | Initialize SPARC workflow for a new feature |
|
|
||||||
| `sparc-review` | `.agents/scripts/sparc-review.sh` | Run SPARC phase review checklist |
|
|
||||||
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
| Document | Path | Description |
|
|
||||||
|----------|------|-------------|
|
|
||||||
| `SPARC Overview` | `docs/sparc.md` | Complete SPARC methodology guide |
|
|
||||||
| `Phase Templates` | `docs/sparc-templates.md` | Templates for each SPARC phase |
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
1. Check memory for existing patterns before starting
|
|
||||||
2. Use hierarchical topology for coordination
|
|
||||||
3. Store successful patterns after completion
|
|
||||||
4. Document any new learnings
|
|
||||||
@ -1,563 +0,0 @@
|
|||||||
---
|
|
||||||
name: stream-chain
|
|
||||||
description: Stream-JSON chaining for multi-agent pipelines, data transformation, and sequential workflows
|
|
||||||
version: 1.0.0
|
|
||||||
category: workflow
|
|
||||||
tags: [streaming, pipeline, chaining, multi-agent, workflow]
|
|
||||||
---
|
|
||||||
|
|
||||||
# Stream-Chain Skill
|
|
||||||
|
|
||||||
Execute sophisticated multi-step workflows where each agent's output flows into the next, enabling complex data transformations and sequential processing pipelines.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
Stream-Chain provides two powerful modes for orchestrating multi-agent workflows:
|
|
||||||
|
|
||||||
1. **Custom Chains** (`run`): Execute custom prompt sequences with full control
|
|
||||||
2. **Predefined Pipelines** (`pipeline`): Use battle-tested workflows for common tasks
|
|
||||||
|
|
||||||
Each step in a chain receives the complete output from the previous step, enabling sophisticated multi-agent coordination through streaming data flow.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
### Run a Custom Chain
|
|
||||||
|
|
||||||
```bash
|
|
||||||
Codex-flow stream-chain run \
|
|
||||||
"Analyze codebase structure" \
|
|
||||||
"Identify improvement areas" \
|
|
||||||
"Generate action plan"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Execute a Pipeline
|
|
||||||
|
|
||||||
```bash
|
|
||||||
Codex-flow stream-chain pipeline analysis
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Custom Chains (`run`)
|
|
||||||
|
|
||||||
Execute custom stream chains with your own prompts for maximum flexibility.
|
|
||||||
|
|
||||||
### Syntax
|
|
||||||
|
|
||||||
```bash
|
|
||||||
Codex-flow stream-chain run <prompt1> <prompt2> [...] [options]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Requirements:**
|
|
||||||
- Minimum 2 prompts required
|
|
||||||
- Each prompt becomes a step in the chain
|
|
||||||
- Output flows sequentially through all steps
|
|
||||||
|
|
||||||
### Options
|
|
||||||
|
|
||||||
| Option | Description | Default |
|
|
||||||
|--------|-------------|---------|
|
|
||||||
| `--verbose` | Show detailed execution information | `false` |
|
|
||||||
| `--timeout <seconds>` | Timeout per step | `30` |
|
|
||||||
| `--debug` | Enable debug mode with full logging | `false` |
|
|
||||||
|
|
||||||
### How Context Flows
|
|
||||||
|
|
||||||
Each step receives the previous output as context:
|
|
||||||
|
|
||||||
```
|
|
||||||
Step 1: "Write a sorting function"
|
|
||||||
Output: [function implementation]
|
|
||||||
|
|
||||||
Step 2 receives:
|
|
||||||
"Previous step output:
|
|
||||||
[function implementation]
|
|
||||||
|
|
||||||
Next task: Add comprehensive tests"
|
|
||||||
|
|
||||||
Step 3 receives:
|
|
||||||
"Previous steps output:
|
|
||||||
[function + tests]
|
|
||||||
|
|
||||||
Next task: Optimize performance"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Examples
|
|
||||||
|
|
||||||
#### Basic Development Chain
|
|
||||||
|
|
||||||
```bash
|
|
||||||
Codex-flow stream-chain run \
|
|
||||||
"Write a user authentication function" \
|
|
||||||
"Add input validation and error handling" \
|
|
||||||
"Create unit tests with edge cases"
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Security Audit Workflow
|
|
||||||
|
|
||||||
```bash
|
|
||||||
Codex-flow stream-chain run \
|
|
||||||
"Analyze authentication system for vulnerabilities" \
|
|
||||||
"Identify and categorize security issues by severity" \
|
|
||||||
"Propose fixes with implementation priority" \
|
|
||||||
"Generate security test cases" \
|
|
||||||
--timeout 45 \
|
|
||||||
--verbose
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Code Refactoring Chain
|
|
||||||
|
|
||||||
```bash
|
|
||||||
Codex-flow stream-chain run \
|
|
||||||
"Identify code smells in src/ directory" \
|
|
||||||
"Create refactoring plan with specific changes" \
|
|
||||||
"Apply refactoring to top 3 priority items" \
|
|
||||||
"Verify refactored code maintains behavior" \
|
|
||||||
--debug
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Data Processing Pipeline
|
|
||||||
|
|
||||||
```bash
|
|
||||||
Codex-flow stream-chain run \
|
|
||||||
"Extract data from API responses" \
|
|
||||||
"Transform data into normalized format" \
|
|
||||||
"Validate data against schema" \
|
|
||||||
"Generate data quality report"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Predefined Pipelines (`pipeline`)
|
|
||||||
|
|
||||||
Execute battle-tested workflows optimized for common development tasks.
|
|
||||||
|
|
||||||
### Syntax
|
|
||||||
|
|
||||||
```bash
|
|
||||||
Codex-flow stream-chain pipeline <type> [options]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Available Pipelines
|
|
||||||
|
|
||||||
#### 1. Analysis Pipeline
|
|
||||||
|
|
||||||
Comprehensive codebase analysis and improvement identification.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
Codex-flow stream-chain pipeline analysis
|
|
||||||
```
|
|
||||||
|
|
||||||
**Workflow Steps:**
|
|
||||||
1. **Structure Analysis**: Map directory structure and identify components
|
|
||||||
2. **Issue Detection**: Find potential improvements and problems
|
|
||||||
3. **Recommendations**: Generate actionable improvement report
|
|
||||||
|
|
||||||
**Use Cases:**
|
|
||||||
- New codebase onboarding
|
|
||||||
- Technical debt assessment
|
|
||||||
- Architecture review
|
|
||||||
- Code quality audits
|
|
||||||
|
|
||||||
#### 2. Refactor Pipeline
|
|
||||||
|
|
||||||
Systematic code refactoring with prioritization.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
Codex-flow stream-chain pipeline refactor
|
|
||||||
```
|
|
||||||
|
|
||||||
**Workflow Steps:**
|
|
||||||
1. **Candidate Identification**: Find code needing refactoring
|
|
||||||
2. **Prioritization**: Create ranked refactoring plan
|
|
||||||
3. **Implementation**: Provide refactored code for top priorities
|
|
||||||
|
|
||||||
**Use Cases:**
|
|
||||||
- Technical debt reduction
|
|
||||||
- Code quality improvement
|
|
||||||
- Legacy code modernization
|
|
||||||
- Design pattern implementation
|
|
||||||
|
|
||||||
#### 3. Test Pipeline
|
|
||||||
|
|
||||||
Comprehensive test generation with coverage analysis.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
Codex-flow stream-chain pipeline test
|
|
||||||
```
|
|
||||||
|
|
||||||
**Workflow Steps:**
|
|
||||||
1. **Coverage Analysis**: Identify areas lacking tests
|
|
||||||
2. **Test Design**: Create test cases for critical functions
|
|
||||||
3. **Implementation**: Generate unit tests with assertions
|
|
||||||
|
|
||||||
**Use Cases:**
|
|
||||||
- Increasing test coverage
|
|
||||||
- TDD workflow support
|
|
||||||
- Regression test creation
|
|
||||||
- Quality assurance
|
|
||||||
|
|
||||||
#### 4. Optimize Pipeline
|
|
||||||
|
|
||||||
Performance optimization with profiling and implementation.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
Codex-flow stream-chain pipeline optimize
|
|
||||||
```
|
|
||||||
|
|
||||||
**Workflow Steps:**
|
|
||||||
1. **Profiling**: Identify performance bottlenecks
|
|
||||||
2. **Strategy**: Analyze and suggest optimization approaches
|
|
||||||
3. **Implementation**: Provide optimized code
|
|
||||||
|
|
||||||
**Use Cases:**
|
|
||||||
- Performance improvement
|
|
||||||
- Resource optimization
|
|
||||||
- Scalability enhancement
|
|
||||||
- Latency reduction
|
|
||||||
|
|
||||||
### Pipeline Options
|
|
||||||
|
|
||||||
| Option | Description | Default |
|
|
||||||
|--------|-------------|---------|
|
|
||||||
| `--verbose` | Show detailed execution | `false` |
|
|
||||||
| `--timeout <seconds>` | Timeout per step | `30` |
|
|
||||||
| `--debug` | Enable debug mode | `false` |
|
|
||||||
|
|
||||||
### Pipeline Examples
|
|
||||||
|
|
||||||
#### Quick Analysis
|
|
||||||
|
|
||||||
```bash
|
|
||||||
Codex-flow stream-chain pipeline analysis
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Extended Refactoring
|
|
||||||
|
|
||||||
```bash
|
|
||||||
Codex-flow stream-chain pipeline refactor --timeout 60 --verbose
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Debug Test Generation
|
|
||||||
|
|
||||||
```bash
|
|
||||||
Codex-flow stream-chain pipeline test --debug
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Comprehensive Optimization
|
|
||||||
|
|
||||||
```bash
|
|
||||||
Codex-flow stream-chain pipeline optimize --timeout 90 --verbose
|
|
||||||
```
|
|
||||||
|
|
||||||
### Pipeline Output
|
|
||||||
|
|
||||||
Each pipeline execution provides:
|
|
||||||
|
|
||||||
- **Progress**: Step-by-step execution status
|
|
||||||
- **Results**: Success/failure per step
|
|
||||||
- **Timing**: Total and per-step execution time
|
|
||||||
- **Summary**: Consolidated results and recommendations
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Custom Pipeline Definitions
|
|
||||||
|
|
||||||
Define reusable pipelines in `.Codex-flow/config.json`:
|
|
||||||
|
|
||||||
### Configuration Format
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"streamChain": {
|
|
||||||
"pipelines": {
|
|
||||||
"security": {
|
|
||||||
"name": "Security Audit Pipeline",
|
|
||||||
"description": "Comprehensive security analysis",
|
|
||||||
"prompts": [
|
|
||||||
"Scan codebase for security vulnerabilities",
|
|
||||||
"Categorize issues by severity (critical/high/medium/low)",
|
|
||||||
"Generate fixes with priority and implementation steps",
|
|
||||||
"Create security test suite"
|
|
||||||
],
|
|
||||||
"timeout": 45
|
|
||||||
},
|
|
||||||
"documentation": {
|
|
||||||
"name": "Documentation Generation Pipeline",
|
|
||||||
"prompts": [
|
|
||||||
"Analyze code structure and identify undocumented areas",
|
|
||||||
"Generate API documentation with examples",
|
|
||||||
"Create usage guides and tutorials",
|
|
||||||
"Build architecture diagrams and flow charts"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Execute Custom Pipeline
|
|
||||||
|
|
||||||
```bash
|
|
||||||
Codex-flow stream-chain pipeline security
|
|
||||||
Codex-flow stream-chain pipeline documentation
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Advanced Use Cases
|
|
||||||
|
|
||||||
### Multi-Agent Coordination
|
|
||||||
|
|
||||||
Chain different agent types for complex workflows:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
Codex-flow stream-chain run \
|
|
||||||
"Research best practices for API design" \
|
|
||||||
"Design REST API with discovered patterns" \
|
|
||||||
"Implement API endpoints with validation" \
|
|
||||||
"Generate OpenAPI specification" \
|
|
||||||
"Create integration tests" \
|
|
||||||
"Write deployment documentation"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Data Transformation Pipeline
|
|
||||||
|
|
||||||
Process and transform data through multiple stages:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
Codex-flow stream-chain run \
|
|
||||||
"Extract user data from CSV files" \
|
|
||||||
"Normalize and validate data format" \
|
|
||||||
"Enrich data with external API calls" \
|
|
||||||
"Generate analytics report" \
|
|
||||||
"Create visualization code"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Code Migration Workflow
|
|
||||||
|
|
||||||
Systematic code migration with validation:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
Codex-flow stream-chain run \
|
|
||||||
"Analyze legacy codebase dependencies" \
|
|
||||||
"Create migration plan with risk assessment" \
|
|
||||||
"Generate modernized code for high-priority modules" \
|
|
||||||
"Create migration tests" \
|
|
||||||
"Document migration steps and rollback procedures"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Quality Assurance Chain
|
|
||||||
|
|
||||||
Comprehensive code quality workflow:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
Codex-flow stream-chain pipeline analysis
|
|
||||||
Codex-flow stream-chain pipeline refactor
|
|
||||||
Codex-flow stream-chain pipeline test
|
|
||||||
Codex-flow stream-chain pipeline optimize
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
### 1. Clear and Specific Prompts
|
|
||||||
|
|
||||||
**Good:**
|
|
||||||
```bash
|
|
||||||
"Analyze authentication.js for SQL injection vulnerabilities"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Avoid:**
|
|
||||||
```bash
|
|
||||||
"Check security"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Logical Progression
|
|
||||||
|
|
||||||
Order prompts to build on previous outputs:
|
|
||||||
```bash
|
|
||||||
1. "Identify the problem"
|
|
||||||
2. "Analyze root causes"
|
|
||||||
3. "Design solution"
|
|
||||||
4. "Implement solution"
|
|
||||||
5. "Verify implementation"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Appropriate Timeouts
|
|
||||||
|
|
||||||
- Simple tasks: 30 seconds (default)
|
|
||||||
- Analysis tasks: 45-60 seconds
|
|
||||||
- Implementation tasks: 60-90 seconds
|
|
||||||
- Complex workflows: 90-120 seconds
|
|
||||||
|
|
||||||
### 4. Verification Steps
|
|
||||||
|
|
||||||
Include validation in your chains:
|
|
||||||
```bash
|
|
||||||
Codex-flow stream-chain run \
|
|
||||||
"Implement feature X" \
|
|
||||||
"Write tests for feature X" \
|
|
||||||
"Verify tests pass and cover edge cases"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. Iterative Refinement
|
|
||||||
|
|
||||||
Use chains for iterative improvement:
|
|
||||||
```bash
|
|
||||||
Codex-flow stream-chain run \
|
|
||||||
"Generate initial implementation" \
|
|
||||||
"Review and identify issues" \
|
|
||||||
"Refine based on issues found" \
|
|
||||||
"Final quality check"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Integration with Codex Flow
|
|
||||||
|
|
||||||
### Combine with Swarm Coordination
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Initialize swarm for coordination
|
|
||||||
Codex-flow swarm init --topology mesh
|
|
||||||
|
|
||||||
# Execute stream chain with swarm agents
|
|
||||||
Codex-flow stream-chain run \
|
|
||||||
"Agent 1: Research task" \
|
|
||||||
"Agent 2: Implement solution" \
|
|
||||||
"Agent 3: Test implementation" \
|
|
||||||
"Agent 4: Review and refine"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Memory Integration
|
|
||||||
|
|
||||||
Stream chains automatically store context in memory for cross-session persistence:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Execute chain with memory
|
|
||||||
Codex-flow stream-chain run \
|
|
||||||
"Analyze requirements" \
|
|
||||||
"Design architecture" \
|
|
||||||
--verbose
|
|
||||||
|
|
||||||
# Results stored in .Codex-flow/memory/stream-chain/
|
|
||||||
```
|
|
||||||
|
|
||||||
### Neural Pattern Training
|
|
||||||
|
|
||||||
Successful chains train neural patterns for improved performance:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Enable neural training
|
|
||||||
Codex-flow stream-chain pipeline optimize --debug
|
|
||||||
|
|
||||||
# Patterns learned and stored for future optimizations
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Chain Timeout
|
|
||||||
|
|
||||||
If steps timeout, increase timeout value:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
Codex-flow stream-chain run "complex task" --timeout 120
|
|
||||||
```
|
|
||||||
|
|
||||||
### Context Loss
|
|
||||||
|
|
||||||
If context not flowing properly, use `--debug`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
Codex-flow stream-chain run "step 1" "step 2" --debug
|
|
||||||
```
|
|
||||||
|
|
||||||
### Pipeline Not Found
|
|
||||||
|
|
||||||
Verify pipeline name and custom definitions:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Check available pipelines
|
|
||||||
cat .Codex-flow/config.json | grep -A 10 "streamChain"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Performance Characteristics
|
|
||||||
|
|
||||||
- **Throughput**: 2-5 steps per minute (varies by complexity)
|
|
||||||
- **Context Size**: Up to 100K tokens per step
|
|
||||||
- **Memory Usage**: ~50MB per active chain
|
|
||||||
- **Concurrency**: Supports parallel chain execution
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Related Skills
|
|
||||||
|
|
||||||
- **SPARC Methodology**: Systematic development workflow
|
|
||||||
- **Swarm Coordination**: Multi-agent orchestration
|
|
||||||
- **Memory Management**: Persistent context storage
|
|
||||||
- **Neural Patterns**: Adaptive learning
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Examples Repository
|
|
||||||
|
|
||||||
### Complete Development Workflow
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Full feature development chain
|
|
||||||
Codex-flow stream-chain run \
|
|
||||||
"Analyze requirements for user profile feature" \
|
|
||||||
"Design database schema and API endpoints" \
|
|
||||||
"Implement backend with validation" \
|
|
||||||
"Create frontend components" \
|
|
||||||
"Write comprehensive tests" \
|
|
||||||
"Generate API documentation" \
|
|
||||||
--timeout 60 \
|
|
||||||
--verbose
|
|
||||||
```
|
|
||||||
|
|
||||||
### Code Review Pipeline
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Automated code review workflow
|
|
||||||
Codex-flow stream-chain run \
|
|
||||||
"Analyze recent git changes" \
|
|
||||||
"Identify code quality issues" \
|
|
||||||
"Check for security vulnerabilities" \
|
|
||||||
"Verify test coverage" \
|
|
||||||
"Generate code review report with recommendations"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Migration Assistant
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Framework migration helper
|
|
||||||
Codex-flow stream-chain run \
|
|
||||||
"Analyze current Vue 2 codebase" \
|
|
||||||
"Identify Vue 3 breaking changes" \
|
|
||||||
"Create migration checklist" \
|
|
||||||
"Generate migration scripts" \
|
|
||||||
"Provide updated code examples"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Conclusion
|
|
||||||
|
|
||||||
Stream-Chain enables sophisticated multi-step workflows by:
|
|
||||||
|
|
||||||
- **Sequential Processing**: Each step builds on previous results
|
|
||||||
- **Context Preservation**: Full output history flows through chain
|
|
||||||
- **Flexible Orchestration**: Custom chains or predefined pipelines
|
|
||||||
- **Agent Coordination**: Natural multi-agent collaboration pattern
|
|
||||||
- **Data Transformation**: Complex processing through simple steps
|
|
||||||
|
|
||||||
Use `run` for custom workflows and `pipeline` for battle-tested solutions.
|
|
||||||
@ -1,973 +0,0 @@
|
|||||||
---
|
|
||||||
name: swarm-advanced
|
|
||||||
description: Advanced swarm orchestration patterns for research, development, testing, and complex distributed workflows
|
|
||||||
version: 2.0.0
|
|
||||||
category: orchestration
|
|
||||||
tags: [swarm, distributed, parallel, research, testing, development, coordination]
|
|
||||||
author: Codex Flow Team
|
|
||||||
---
|
|
||||||
|
|
||||||
# Advanced Swarm Orchestration
|
|
||||||
|
|
||||||
Master advanced swarm patterns for distributed research, development, and testing workflows. This skill covers comprehensive orchestration strategies using both MCP tools and CLI commands.
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
```bash
|
|
||||||
# Ensure Codex Flow is installed
|
|
||||||
npm install -g Codex-flow@alpha
|
|
||||||
|
|
||||||
# Add MCP server (if using MCP tools)
|
|
||||||
Codex mcp add Codex-flow npx Codex-flow@alpha mcp start
|
|
||||||
```
|
|
||||||
|
|
||||||
### Basic Pattern
|
|
||||||
```javascript
|
|
||||||
// 1. Initialize swarm topology
|
|
||||||
mcp__claude-flow__swarm_init({ topology: "mesh", maxAgents: 6 })
|
|
||||||
|
|
||||||
// 2. Spawn specialized agents
|
|
||||||
mcp__claude-flow__agent_spawn({ type: "researcher", name: "Agent 1" })
|
|
||||||
|
|
||||||
// 3. Orchestrate tasks
|
|
||||||
mcp__claude-flow__task_orchestrate({ task: "...", strategy: "parallel" })
|
|
||||||
```
|
|
||||||
|
|
||||||
## Core Concepts
|
|
||||||
|
|
||||||
### Swarm Topologies
|
|
||||||
|
|
||||||
**Mesh Topology** - Peer-to-peer communication, best for research and analysis
|
|
||||||
- All agents communicate directly
|
|
||||||
- High flexibility and resilience
|
|
||||||
- Use for: Research, analysis, brainstorming
|
|
||||||
|
|
||||||
**Hierarchical Topology** - Coordinator with subordinates, best for development
|
|
||||||
- Clear command structure
|
|
||||||
- Sequential workflow support
|
|
||||||
- Use for: Development, structured workflows
|
|
||||||
|
|
||||||
**Star Topology** - Central coordinator, best for testing
|
|
||||||
- Centralized control and monitoring
|
|
||||||
- Parallel execution with coordination
|
|
||||||
- Use for: Testing, validation, quality assurance
|
|
||||||
|
|
||||||
**Ring Topology** - Sequential processing chain
|
|
||||||
- Step-by-step processing
|
|
||||||
- Pipeline workflows
|
|
||||||
- Use for: Multi-stage processing, data pipelines
|
|
||||||
|
|
||||||
### Agent Strategies
|
|
||||||
|
|
||||||
**Adaptive** - Dynamic adjustment based on task complexity
|
|
||||||
**Balanced** - Equal distribution of work across agents
|
|
||||||
**Specialized** - Task-specific agent assignment
|
|
||||||
**Parallel** - Maximum concurrent execution
|
|
||||||
|
|
||||||
## Pattern 1: Research Swarm
|
|
||||||
|
|
||||||
### Purpose
|
|
||||||
Deep research through parallel information gathering, analysis, and synthesis.
|
|
||||||
|
|
||||||
### Architecture
|
|
||||||
```javascript
|
|
||||||
// Initialize research swarm
|
|
||||||
mcp__claude-flow__swarm_init({
|
|
||||||
"topology": "mesh",
|
|
||||||
"maxAgents": 6,
|
|
||||||
"strategy": "adaptive"
|
|
||||||
})
|
|
||||||
|
|
||||||
// Spawn research team
|
|
||||||
const researchAgents = [
|
|
||||||
{
|
|
||||||
type: "researcher",
|
|
||||||
name: "Web Researcher",
|
|
||||||
capabilities: ["web-search", "content-extraction", "source-validation"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "researcher",
|
|
||||||
name: "Academic Researcher",
|
|
||||||
capabilities: ["paper-analysis", "citation-tracking", "literature-review"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "analyst",
|
|
||||||
name: "Data Analyst",
|
|
||||||
capabilities: ["data-processing", "statistical-analysis", "visualization"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "analyst",
|
|
||||||
name: "Pattern Analyzer",
|
|
||||||
capabilities: ["trend-detection", "correlation-analysis", "outlier-detection"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "documenter",
|
|
||||||
name: "Report Writer",
|
|
||||||
capabilities: ["synthesis", "technical-writing", "formatting"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
// Spawn all agents
|
|
||||||
researchAgents.forEach(agent => {
|
|
||||||
mcp__claude-flow__agent_spawn({
|
|
||||||
type: agent.type,
|
|
||||||
name: agent.name,
|
|
||||||
capabilities: agent.capabilities
|
|
||||||
})
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### Research Workflow
|
|
||||||
|
|
||||||
#### Phase 1: Information Gathering
|
|
||||||
```javascript
|
|
||||||
// Parallel information collection
|
|
||||||
mcp__claude-flow__parallel_execute({
|
|
||||||
"tasks": [
|
|
||||||
{
|
|
||||||
"id": "web-search",
|
|
||||||
"command": "search recent publications and articles"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "academic-search",
|
|
||||||
"command": "search academic databases and papers"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "data-collection",
|
|
||||||
"command": "gather relevant datasets and statistics"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "expert-search",
|
|
||||||
"command": "identify domain experts and thought leaders"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
// Store research findings in memory
|
|
||||||
mcp__claude-flow__memory_usage({
|
|
||||||
"action": "store",
|
|
||||||
"key": "research-findings-" + Date.now(),
|
|
||||||
"value": JSON.stringify(findings),
|
|
||||||
"namespace": "research",
|
|
||||||
"ttl": 604800 // 7 days
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Phase 2: Analysis and Validation
|
|
||||||
```javascript
|
|
||||||
// Pattern recognition in findings
|
|
||||||
mcp__claude-flow__pattern_recognize({
|
|
||||||
"data": researchData,
|
|
||||||
"patterns": ["trend", "correlation", "outlier", "emerging-pattern"]
|
|
||||||
})
|
|
||||||
|
|
||||||
// Cognitive analysis
|
|
||||||
mcp__claude-flow__cognitive_analyze({
|
|
||||||
"behavior": "research-synthesis"
|
|
||||||
})
|
|
||||||
|
|
||||||
// Quality assessment
|
|
||||||
mcp__claude-flow__quality_assess({
|
|
||||||
"target": "research-sources",
|
|
||||||
"criteria": ["credibility", "relevance", "recency", "authority"]
|
|
||||||
})
|
|
||||||
|
|
||||||
// Cross-reference validation
|
|
||||||
mcp__claude-flow__neural_patterns({
|
|
||||||
"action": "analyze",
|
|
||||||
"operation": "fact-checking",
|
|
||||||
"metadata": { "sources": sourcesArray }
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Phase 3: Knowledge Management
|
|
||||||
```javascript
|
|
||||||
// Search existing knowledge base
|
|
||||||
mcp__claude-flow__memory_search({
|
|
||||||
"pattern": "topic X",
|
|
||||||
"namespace": "research",
|
|
||||||
"limit": 20
|
|
||||||
})
|
|
||||||
|
|
||||||
// Create knowledge graph connections
|
|
||||||
mcp__claude-flow__neural_patterns({
|
|
||||||
"action": "learn",
|
|
||||||
"operation": "knowledge-graph",
|
|
||||||
"metadata": {
|
|
||||||
"topic": "X",
|
|
||||||
"connections": relatedTopics,
|
|
||||||
"depth": 3
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Store connections for future use
|
|
||||||
mcp__claude-flow__memory_usage({
|
|
||||||
"action": "store",
|
|
||||||
"key": "knowledge-graph-X",
|
|
||||||
"value": JSON.stringify(knowledgeGraph),
|
|
||||||
"namespace": "research/graphs",
|
|
||||||
"ttl": 2592000 // 30 days
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Phase 4: Report Generation
|
|
||||||
```javascript
|
|
||||||
// Orchestrate report generation
|
|
||||||
mcp__claude-flow__task_orchestrate({
|
|
||||||
"task": "generate comprehensive research report",
|
|
||||||
"strategy": "sequential",
|
|
||||||
"priority": "high",
|
|
||||||
"dependencies": ["gather", "analyze", "validate", "synthesize"]
|
|
||||||
})
|
|
||||||
|
|
||||||
// Monitor research progress
|
|
||||||
mcp__claude-flow__swarm_status({
|
|
||||||
"swarmId": "research-swarm"
|
|
||||||
})
|
|
||||||
|
|
||||||
// Generate final report
|
|
||||||
mcp__claude-flow__workflow_execute({
|
|
||||||
"workflowId": "research-report-generation",
|
|
||||||
"params": {
|
|
||||||
"findings": findings,
|
|
||||||
"format": "comprehensive",
|
|
||||||
"sections": ["executive-summary", "methodology", "findings", "analysis", "conclusions", "references"]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### CLI Fallback
|
|
||||||
```bash
|
|
||||||
# Quick research swarm
|
|
||||||
npx Codex-flow swarm "research AI trends in 2025" \
|
|
||||||
--strategy research \
|
|
||||||
--mode distributed \
|
|
||||||
--max-agents 6 \
|
|
||||||
--parallel \
|
|
||||||
--output research-report.md
|
|
||||||
```
|
|
||||||
|
|
||||||
## Pattern 2: Development Swarm
|
|
||||||
|
|
||||||
### Purpose
|
|
||||||
Full-stack development through coordinated specialist agents.
|
|
||||||
|
|
||||||
### Architecture
|
|
||||||
```javascript
|
|
||||||
// Initialize development swarm with hierarchy
|
|
||||||
mcp__claude-flow__swarm_init({
|
|
||||||
"topology": "hierarchical",
|
|
||||||
"maxAgents": 8,
|
|
||||||
"strategy": "balanced"
|
|
||||||
})
|
|
||||||
|
|
||||||
// Spawn development team
|
|
||||||
const devTeam = [
|
|
||||||
{ type: "architect", name: "System Architect", role: "coordinator" },
|
|
||||||
{ type: "coder", name: "Backend Developer", capabilities: ["node", "api", "database"] },
|
|
||||||
{ type: "coder", name: "Frontend Developer", capabilities: ["react", "ui", "ux"] },
|
|
||||||
{ type: "coder", name: "Database Engineer", capabilities: ["sql", "nosql", "optimization"] },
|
|
||||||
{ type: "tester", name: "QA Engineer", capabilities: ["unit", "integration", "e2e"] },
|
|
||||||
{ type: "reviewer", name: "Code Reviewer", capabilities: ["security", "performance", "best-practices"] },
|
|
||||||
{ type: "documenter", name: "Technical Writer", capabilities: ["api-docs", "guides", "tutorials"] },
|
|
||||||
{ type: "monitor", name: "DevOps Engineer", capabilities: ["ci-cd", "deployment", "monitoring"] }
|
|
||||||
]
|
|
||||||
|
|
||||||
// Spawn all team members
|
|
||||||
devTeam.forEach(member => {
|
|
||||||
mcp__claude-flow__agent_spawn({
|
|
||||||
type: member.type,
|
|
||||||
name: member.name,
|
|
||||||
capabilities: member.capabilities,
|
|
||||||
swarmId: "dev-swarm"
|
|
||||||
})
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### Development Workflow
|
|
||||||
|
|
||||||
#### Phase 1: Architecture and Design
|
|
||||||
```javascript
|
|
||||||
// System architecture design
|
|
||||||
mcp__claude-flow__task_orchestrate({
|
|
||||||
"task": "design system architecture for REST API",
|
|
||||||
"strategy": "sequential",
|
|
||||||
"priority": "critical",
|
|
||||||
"assignTo": "System Architect"
|
|
||||||
})
|
|
||||||
|
|
||||||
// Store architecture decisions
|
|
||||||
mcp__claude-flow__memory_usage({
|
|
||||||
"action": "store",
|
|
||||||
"key": "architecture-decisions",
|
|
||||||
"value": JSON.stringify(architectureDoc),
|
|
||||||
"namespace": "development/design"
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Phase 2: Parallel Implementation
|
|
||||||
```javascript
|
|
||||||
// Parallel development tasks
|
|
||||||
mcp__claude-flow__parallel_execute({
|
|
||||||
"tasks": [
|
|
||||||
{
|
|
||||||
"id": "backend-api",
|
|
||||||
"command": "implement REST API endpoints",
|
|
||||||
"assignTo": "Backend Developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "frontend-ui",
|
|
||||||
"command": "build user interface components",
|
|
||||||
"assignTo": "Frontend Developer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "database-schema",
|
|
||||||
"command": "design and implement database schema",
|
|
||||||
"assignTo": "Database Engineer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "api-documentation",
|
|
||||||
"command": "create API documentation",
|
|
||||||
"assignTo": "Technical Writer"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
// Monitor development progress
|
|
||||||
mcp__claude-flow__swarm_monitor({
|
|
||||||
"swarmId": "dev-swarm",
|
|
||||||
"interval": 5000
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Phase 3: Testing and Validation
|
|
||||||
```javascript
|
|
||||||
// Comprehensive testing
|
|
||||||
mcp__claude-flow__batch_process({
|
|
||||||
"items": [
|
|
||||||
{ type: "unit", target: "all-modules" },
|
|
||||||
{ type: "integration", target: "api-endpoints" },
|
|
||||||
{ type: "e2e", target: "user-flows" },
|
|
||||||
{ type: "performance", target: "critical-paths" }
|
|
||||||
],
|
|
||||||
"operation": "execute-tests"
|
|
||||||
})
|
|
||||||
|
|
||||||
// Quality assessment
|
|
||||||
mcp__claude-flow__quality_assess({
|
|
||||||
"target": "codebase",
|
|
||||||
"criteria": ["coverage", "complexity", "maintainability", "security"]
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Phase 4: Review and Deployment
|
|
||||||
```javascript
|
|
||||||
// Code review workflow
|
|
||||||
mcp__claude-flow__workflow_execute({
|
|
||||||
"workflowId": "code-review-process",
|
|
||||||
"params": {
|
|
||||||
"reviewers": ["Code Reviewer"],
|
|
||||||
"criteria": ["security", "performance", "best-practices"]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// CI/CD pipeline
|
|
||||||
mcp__claude-flow__pipeline_create({
|
|
||||||
"config": {
|
|
||||||
"stages": ["build", "test", "security-scan", "deploy"],
|
|
||||||
"environment": "production"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### CLI Fallback
|
|
||||||
```bash
|
|
||||||
# Quick development swarm
|
|
||||||
npx Codex-flow swarm "build REST API with authentication" \
|
|
||||||
--strategy development \
|
|
||||||
--mode hierarchical \
|
|
||||||
--monitor \
|
|
||||||
--output sqlite
|
|
||||||
```
|
|
||||||
|
|
||||||
## Pattern 3: Testing Swarm
|
|
||||||
|
|
||||||
### Purpose
|
|
||||||
Comprehensive quality assurance through distributed testing.
|
|
||||||
|
|
||||||
### Architecture
|
|
||||||
```javascript
|
|
||||||
// Initialize testing swarm with star topology
|
|
||||||
mcp__claude-flow__swarm_init({
|
|
||||||
"topology": "star",
|
|
||||||
"maxAgents": 7,
|
|
||||||
"strategy": "parallel"
|
|
||||||
})
|
|
||||||
|
|
||||||
// Spawn testing team
|
|
||||||
const testingTeam = [
|
|
||||||
{
|
|
||||||
type: "tester",
|
|
||||||
name: "Unit Test Coordinator",
|
|
||||||
capabilities: ["unit-testing", "mocking", "coverage", "tdd"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "tester",
|
|
||||||
name: "Integration Tester",
|
|
||||||
capabilities: ["integration", "api-testing", "contract-testing"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "tester",
|
|
||||||
name: "E2E Tester",
|
|
||||||
capabilities: ["e2e", "ui-testing", "user-flows", "selenium"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "tester",
|
|
||||||
name: "Performance Tester",
|
|
||||||
capabilities: ["load-testing", "stress-testing", "benchmarking"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "monitor",
|
|
||||||
name: "Security Tester",
|
|
||||||
capabilities: ["security-testing", "penetration-testing", "vulnerability-scanning"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "analyst",
|
|
||||||
name: "Test Analyst",
|
|
||||||
capabilities: ["coverage-analysis", "test-optimization", "reporting"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "documenter",
|
|
||||||
name: "Test Documenter",
|
|
||||||
capabilities: ["test-documentation", "test-plans", "reports"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
// Spawn all testers
|
|
||||||
testingTeam.forEach(tester => {
|
|
||||||
mcp__claude-flow__agent_spawn({
|
|
||||||
type: tester.type,
|
|
||||||
name: tester.name,
|
|
||||||
capabilities: tester.capabilities,
|
|
||||||
swarmId: "testing-swarm"
|
|
||||||
})
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### Testing Workflow
|
|
||||||
|
|
||||||
#### Phase 1: Test Planning
|
|
||||||
```javascript
|
|
||||||
// Analyze test coverage requirements
|
|
||||||
mcp__claude-flow__quality_assess({
|
|
||||||
"target": "test-coverage",
|
|
||||||
"criteria": [
|
|
||||||
"line-coverage",
|
|
||||||
"branch-coverage",
|
|
||||||
"function-coverage",
|
|
||||||
"edge-cases"
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
// Identify test scenarios
|
|
||||||
mcp__claude-flow__pattern_recognize({
|
|
||||||
"data": testScenarios,
|
|
||||||
"patterns": [
|
|
||||||
"edge-case",
|
|
||||||
"boundary-condition",
|
|
||||||
"error-path",
|
|
||||||
"happy-path"
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
// Store test plan
|
|
||||||
mcp__claude-flow__memory_usage({
|
|
||||||
"action": "store",
|
|
||||||
"key": "test-plan-" + Date.now(),
|
|
||||||
"value": JSON.stringify(testPlan),
|
|
||||||
"namespace": "testing/plans"
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Phase 2: Parallel Test Execution
|
|
||||||
```javascript
|
|
||||||
// Execute all test suites in parallel
|
|
||||||
mcp__claude-flow__parallel_execute({
|
|
||||||
"tasks": [
|
|
||||||
{
|
|
||||||
"id": "unit-tests",
|
|
||||||
"command": "npm run test:unit",
|
|
||||||
"assignTo": "Unit Test Coordinator"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "integration-tests",
|
|
||||||
"command": "npm run test:integration",
|
|
||||||
"assignTo": "Integration Tester"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "e2e-tests",
|
|
||||||
"command": "npm run test:e2e",
|
|
||||||
"assignTo": "E2E Tester"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "performance-tests",
|
|
||||||
"command": "npm run test:performance",
|
|
||||||
"assignTo": "Performance Tester"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "security-tests",
|
|
||||||
"command": "npm run test:security",
|
|
||||||
"assignTo": "Security Tester"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
// Batch process test suites
|
|
||||||
mcp__claude-flow__batch_process({
|
|
||||||
"items": testSuites,
|
|
||||||
"operation": "execute-test-suite"
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Phase 3: Performance and Security
|
|
||||||
```javascript
|
|
||||||
// Run performance benchmarks
|
|
||||||
mcp__claude-flow__benchmark_run({
|
|
||||||
"suite": "comprehensive-performance"
|
|
||||||
})
|
|
||||||
|
|
||||||
// Bottleneck analysis
|
|
||||||
mcp__claude-flow__bottleneck_analyze({
|
|
||||||
"component": "application",
|
|
||||||
"metrics": ["response-time", "throughput", "memory", "cpu"]
|
|
||||||
})
|
|
||||||
|
|
||||||
// Security scanning
|
|
||||||
mcp__claude-flow__security_scan({
|
|
||||||
"target": "application",
|
|
||||||
"depth": "comprehensive"
|
|
||||||
})
|
|
||||||
|
|
||||||
// Vulnerability analysis
|
|
||||||
mcp__claude-flow__error_analysis({
|
|
||||||
"logs": securityScanLogs
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Phase 4: Monitoring and Reporting
|
|
||||||
```javascript
|
|
||||||
// Real-time test monitoring
|
|
||||||
mcp__claude-flow__swarm_monitor({
|
|
||||||
"swarmId": "testing-swarm",
|
|
||||||
"interval": 2000
|
|
||||||
})
|
|
||||||
|
|
||||||
// Generate comprehensive test report
|
|
||||||
mcp__claude-flow__performance_report({
|
|
||||||
"format": "detailed",
|
|
||||||
"timeframe": "current-run"
|
|
||||||
})
|
|
||||||
|
|
||||||
// Get test results
|
|
||||||
mcp__claude-flow__task_results({
|
|
||||||
"taskId": "test-execution-001"
|
|
||||||
})
|
|
||||||
|
|
||||||
// Trend analysis
|
|
||||||
mcp__claude-flow__trend_analysis({
|
|
||||||
"metric": "test-coverage",
|
|
||||||
"period": "30d"
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### CLI Fallback
|
|
||||||
```bash
|
|
||||||
# Quick testing swarm
|
|
||||||
npx Codex-flow swarm "test application comprehensively" \
|
|
||||||
--strategy testing \
|
|
||||||
--mode star \
|
|
||||||
--parallel \
|
|
||||||
--timeout 600
|
|
||||||
```
|
|
||||||
|
|
||||||
## Pattern 4: Analysis Swarm
|
|
||||||
|
|
||||||
### Purpose
|
|
||||||
Deep code and system analysis through specialized analyzers.
|
|
||||||
|
|
||||||
### Architecture
|
|
||||||
```javascript
|
|
||||||
// Initialize analysis swarm
|
|
||||||
mcp__claude-flow__swarm_init({
|
|
||||||
"topology": "mesh",
|
|
||||||
"maxAgents": 5,
|
|
||||||
"strategy": "adaptive"
|
|
||||||
})
|
|
||||||
|
|
||||||
// Spawn analysis specialists
|
|
||||||
const analysisTeam = [
|
|
||||||
{
|
|
||||||
type: "analyst",
|
|
||||||
name: "Code Analyzer",
|
|
||||||
capabilities: ["static-analysis", "complexity-analysis", "dead-code-detection"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "analyst",
|
|
||||||
name: "Security Analyzer",
|
|
||||||
capabilities: ["security-scan", "vulnerability-detection", "dependency-audit"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "analyst",
|
|
||||||
name: "Performance Analyzer",
|
|
||||||
capabilities: ["profiling", "bottleneck-detection", "optimization"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "analyst",
|
|
||||||
name: "Architecture Analyzer",
|
|
||||||
capabilities: ["dependency-analysis", "coupling-detection", "modularity-assessment"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "documenter",
|
|
||||||
name: "Analysis Reporter",
|
|
||||||
capabilities: ["reporting", "visualization", "recommendations"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
// Spawn all analysts
|
|
||||||
analysisTeam.forEach(analyst => {
|
|
||||||
mcp__claude-flow__agent_spawn({
|
|
||||||
type: analyst.type,
|
|
||||||
name: analyst.name,
|
|
||||||
capabilities: analyst.capabilities
|
|
||||||
})
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### Analysis Workflow
|
|
||||||
```javascript
|
|
||||||
// Parallel analysis execution
|
|
||||||
mcp__claude-flow__parallel_execute({
|
|
||||||
"tasks": [
|
|
||||||
{ "id": "analyze-code", "command": "analyze codebase structure and quality" },
|
|
||||||
{ "id": "analyze-security", "command": "scan for security vulnerabilities" },
|
|
||||||
{ "id": "analyze-performance", "command": "identify performance bottlenecks" },
|
|
||||||
{ "id": "analyze-architecture", "command": "assess architectural patterns" }
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
// Generate comprehensive analysis report
|
|
||||||
mcp__claude-flow__performance_report({
|
|
||||||
"format": "detailed",
|
|
||||||
"timeframe": "current"
|
|
||||||
})
|
|
||||||
|
|
||||||
// Cost analysis
|
|
||||||
mcp__claude-flow__cost_analysis({
|
|
||||||
"timeframe": "30d"
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
## Advanced Techniques
|
|
||||||
|
|
||||||
### Error Handling and Fault Tolerance
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Setup fault tolerance for all agents
|
|
||||||
mcp__claude-flow__daa_fault_tolerance({
|
|
||||||
"agentId": "all",
|
|
||||||
"strategy": "auto-recovery"
|
|
||||||
})
|
|
||||||
|
|
||||||
// Error handling pattern
|
|
||||||
try {
|
|
||||||
await mcp__claude-flow__task_orchestrate({
|
|
||||||
"task": "complex operation",
|
|
||||||
"strategy": "parallel",
|
|
||||||
"priority": "high"
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
// Check swarm health
|
|
||||||
const status = await mcp__claude-flow__swarm_status({})
|
|
||||||
|
|
||||||
// Analyze error patterns
|
|
||||||
await mcp__claude-flow__error_analysis({
|
|
||||||
"logs": [error.message]
|
|
||||||
})
|
|
||||||
|
|
||||||
// Auto-recovery attempt
|
|
||||||
if (status.healthy) {
|
|
||||||
await mcp__claude-flow__task_orchestrate({
|
|
||||||
"task": "retry failed operation",
|
|
||||||
"strategy": "sequential"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Memory and State Management
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Cross-session persistence
|
|
||||||
mcp__claude-flow__memory_persist({
|
|
||||||
"sessionId": "swarm-session-001"
|
|
||||||
})
|
|
||||||
|
|
||||||
// Namespace management for different swarms
|
|
||||||
mcp__claude-flow__memory_namespace({
|
|
||||||
"namespace": "research-swarm",
|
|
||||||
"action": "create"
|
|
||||||
})
|
|
||||||
|
|
||||||
// Create state snapshot
|
|
||||||
mcp__claude-flow__state_snapshot({
|
|
||||||
"name": "development-checkpoint-1"
|
|
||||||
})
|
|
||||||
|
|
||||||
// Restore from snapshot if needed
|
|
||||||
mcp__claude-flow__context_restore({
|
|
||||||
"snapshotId": "development-checkpoint-1"
|
|
||||||
})
|
|
||||||
|
|
||||||
// Backup memory stores
|
|
||||||
mcp__claude-flow__memory_backup({
|
|
||||||
"path": "/workspaces/Codex-flow/backups/swarm-memory.json"
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### Neural Pattern Learning
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Train neural patterns from successful workflows
|
|
||||||
mcp__claude-flow__neural_train({
|
|
||||||
"pattern_type": "coordination",
|
|
||||||
"training_data": JSON.stringify(successfulWorkflows),
|
|
||||||
"epochs": 50
|
|
||||||
})
|
|
||||||
|
|
||||||
// Adaptive learning from experience
|
|
||||||
mcp__claude-flow__learning_adapt({
|
|
||||||
"experience": {
|
|
||||||
"workflow": "research-to-report",
|
|
||||||
"success": true,
|
|
||||||
"duration": 3600,
|
|
||||||
"quality": 0.95
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Pattern recognition for optimization
|
|
||||||
mcp__claude-flow__pattern_recognize({
|
|
||||||
"data": workflowMetrics,
|
|
||||||
"patterns": ["bottleneck", "optimization-opportunity", "efficiency-gain"]
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### Workflow Automation
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Create reusable workflow
|
|
||||||
mcp__claude-flow__workflow_create({
|
|
||||||
"name": "full-stack-development",
|
|
||||||
"steps": [
|
|
||||||
{ "phase": "design", "agents": ["architect"] },
|
|
||||||
{ "phase": "implement", "agents": ["backend-dev", "frontend-dev"], "parallel": true },
|
|
||||||
{ "phase": "test", "agents": ["tester", "security-tester"], "parallel": true },
|
|
||||||
{ "phase": "review", "agents": ["reviewer"] },
|
|
||||||
{ "phase": "deploy", "agents": ["devops"] }
|
|
||||||
],
|
|
||||||
"triggers": ["on-commit", "scheduled-daily"]
|
|
||||||
})
|
|
||||||
|
|
||||||
// Setup automation rules
|
|
||||||
mcp__claude-flow__automation_setup({
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"trigger": "file-changed",
|
|
||||||
"pattern": "*.js",
|
|
||||||
"action": "run-tests"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"trigger": "PR-created",
|
|
||||||
"action": "code-review-swarm"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
// Event-driven triggers
|
|
||||||
mcp__claude-flow__trigger_setup({
|
|
||||||
"events": ["code-commit", "PR-merge", "deployment"],
|
|
||||||
"actions": ["test", "analyze", "document"]
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### Performance Optimization
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Topology optimization
|
|
||||||
mcp__claude-flow__topology_optimize({
|
|
||||||
"swarmId": "current-swarm"
|
|
||||||
})
|
|
||||||
|
|
||||||
// Load balancing
|
|
||||||
mcp__claude-flow__load_balance({
|
|
||||||
"swarmId": "development-swarm",
|
|
||||||
"tasks": taskQueue
|
|
||||||
})
|
|
||||||
|
|
||||||
// Agent coordination sync
|
|
||||||
mcp__claude-flow__coordination_sync({
|
|
||||||
"swarmId": "development-swarm"
|
|
||||||
})
|
|
||||||
|
|
||||||
// Auto-scaling
|
|
||||||
mcp__claude-flow__swarm_scale({
|
|
||||||
"swarmId": "development-swarm",
|
|
||||||
"targetSize": 12
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### Monitoring and Metrics
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Real-time swarm monitoring
|
|
||||||
mcp__claude-flow__swarm_monitor({
|
|
||||||
"swarmId": "active-swarm",
|
|
||||||
"interval": 3000
|
|
||||||
})
|
|
||||||
|
|
||||||
// Collect comprehensive metrics
|
|
||||||
mcp__claude-flow__metrics_collect({
|
|
||||||
"components": ["agents", "tasks", "memory", "performance"]
|
|
||||||
})
|
|
||||||
|
|
||||||
// Health monitoring
|
|
||||||
mcp__claude-flow__health_check({
|
|
||||||
"components": ["swarm", "agents", "neural", "memory"]
|
|
||||||
})
|
|
||||||
|
|
||||||
// Usage statistics
|
|
||||||
mcp__claude-flow__usage_stats({
|
|
||||||
"component": "swarm-orchestration"
|
|
||||||
})
|
|
||||||
|
|
||||||
// Trend analysis
|
|
||||||
mcp__claude-flow__trend_analysis({
|
|
||||||
"metric": "agent-performance",
|
|
||||||
"period": "7d"
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
### 1. Choosing the Right Topology
|
|
||||||
|
|
||||||
- **Mesh**: Research, brainstorming, collaborative analysis
|
|
||||||
- **Hierarchical**: Structured development, sequential workflows
|
|
||||||
- **Star**: Testing, validation, centralized coordination
|
|
||||||
- **Ring**: Pipeline processing, staged workflows
|
|
||||||
|
|
||||||
### 2. Agent Specialization
|
|
||||||
|
|
||||||
- Assign specific capabilities to each agent
|
|
||||||
- Avoid overlapping responsibilities
|
|
||||||
- Use coordination agents for complex workflows
|
|
||||||
- Leverage memory for agent communication
|
|
||||||
|
|
||||||
### 3. Parallel Execution
|
|
||||||
|
|
||||||
- Identify independent tasks for parallelization
|
|
||||||
- Use sequential execution for dependent tasks
|
|
||||||
- Monitor resource usage during parallel execution
|
|
||||||
- Implement proper error handling
|
|
||||||
|
|
||||||
### 4. Memory Management
|
|
||||||
|
|
||||||
- Use namespaces to organize memory
|
|
||||||
- Set appropriate TTL values
|
|
||||||
- Create regular backups
|
|
||||||
- Implement state snapshots for checkpoints
|
|
||||||
|
|
||||||
### 5. Monitoring and Optimization
|
|
||||||
|
|
||||||
- Monitor swarm health regularly
|
|
||||||
- Collect and analyze metrics
|
|
||||||
- Optimize topology based on performance
|
|
||||||
- Use neural patterns to learn from success
|
|
||||||
|
|
||||||
### 6. Error Recovery
|
|
||||||
|
|
||||||
- Implement fault tolerance strategies
|
|
||||||
- Use auto-recovery mechanisms
|
|
||||||
- Analyze error patterns
|
|
||||||
- Create fallback workflows
|
|
||||||
|
|
||||||
## Real-World Examples
|
|
||||||
|
|
||||||
### Example 1: AI Research Project
|
|
||||||
```javascript
|
|
||||||
// Research AI trends, analyze findings, generate report
|
|
||||||
mcp__claude-flow__swarm_init({ topology: "mesh", maxAgents: 6 })
|
|
||||||
// Spawn: 2 researchers, 2 analysts, 1 synthesizer, 1 documenter
|
|
||||||
// Parallel gather → Analyze patterns → Synthesize → Report
|
|
||||||
```
|
|
||||||
|
|
||||||
### Example 2: Full-Stack Application
|
|
||||||
```javascript
|
|
||||||
// Build complete web application with testing
|
|
||||||
mcp__claude-flow__swarm_init({ topology: "hierarchical", maxAgents: 8 })
|
|
||||||
// Spawn: 1 architect, 2 devs, 1 db engineer, 2 testers, 1 reviewer, 1 devops
|
|
||||||
// Design → Parallel implement → Test → Review → Deploy
|
|
||||||
```
|
|
||||||
|
|
||||||
### Example 3: Security Audit
|
|
||||||
```javascript
|
|
||||||
// Comprehensive security analysis
|
|
||||||
mcp__claude-flow__swarm_init({ topology: "star", maxAgents: 5 })
|
|
||||||
// Spawn: 1 coordinator, 1 code analyzer, 1 security scanner, 1 penetration tester, 1 reporter
|
|
||||||
// Parallel scan → Vulnerability analysis → Penetration test → Report
|
|
||||||
```
|
|
||||||
|
|
||||||
### Example 4: Performance Optimization
|
|
||||||
```javascript
|
|
||||||
// Identify and fix performance bottlenecks
|
|
||||||
mcp__claude-flow__swarm_init({ topology: "mesh", maxAgents: 4 })
|
|
||||||
// Spawn: 1 profiler, 1 bottleneck analyzer, 1 optimizer, 1 tester
|
|
||||||
// Profile → Identify bottlenecks → Optimize → Validate
|
|
||||||
```
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Common Issues
|
|
||||||
|
|
||||||
**Issue**: Swarm agents not coordinating properly
|
|
||||||
**Solution**: Check topology selection, verify memory usage, enable monitoring
|
|
||||||
|
|
||||||
**Issue**: Parallel execution failing
|
|
||||||
**Solution**: Verify task dependencies, check resource limits, implement error handling
|
|
||||||
|
|
||||||
**Issue**: Memory persistence not working
|
|
||||||
**Solution**: Verify namespaces, check TTL settings, ensure backup configuration
|
|
||||||
|
|
||||||
**Issue**: Performance degradation
|
|
||||||
**Solution**: Optimize topology, reduce agent count, analyze bottlenecks
|
|
||||||
|
|
||||||
## Related Skills
|
|
||||||
|
|
||||||
- `sparc-methodology` - Systematic development workflow
|
|
||||||
- `github-integration` - Repository management and automation
|
|
||||||
- `neural-patterns` - AI-powered coordination optimization
|
|
||||||
- `memory-management` - Cross-session state persistence
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- [Codex Flow Documentation](https://github.com/ruvnet/Codex-flow)
|
|
||||||
- [Swarm Orchestration Guide](https://github.com/ruvnet/Codex-flow/wiki/swarm)
|
|
||||||
- [MCP Tools Reference](https://github.com/ruvnet/Codex-flow/wiki/mcp)
|
|
||||||
- [Performance Optimization](https://github.com/ruvnet/Codex-flow/wiki/performance)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Version**: 2.0.0
|
|
||||||
**Last Updated**: 2025-10-19
|
|
||||||
**Skill Level**: Advanced
|
|
||||||
**Estimated Learning Time**: 2-3 hours
|
|
||||||
@ -1,114 +0,0 @@
|
|||||||
---
|
|
||||||
name: swarm-orchestration
|
|
||||||
description: >
|
|
||||||
Multi-agent swarm coordination for complex tasks. Uses hierarchical topology with specialized agents to break down and execute complex work across multiple files and modules.
|
|
||||||
Use when: 3+ files need changes, new feature implementation, cross-module refactoring, API changes with tests, security-related changes, performance optimization across codebase, database schema changes.
|
|
||||||
Skip when: single file edits, simple bug fixes (1-2 lines), documentation updates, configuration changes, quick exploration.
|
|
||||||
---
|
|
||||||
|
|
||||||
# Swarm Orchestration Skill
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
Multi-agent swarm coordination for complex tasks. Uses hierarchical topology with specialized agents to break down and execute complex work across multiple files and modules.
|
|
||||||
|
|
||||||
## When to Trigger
|
|
||||||
- 3+ files need changes
|
|
||||||
- new feature implementation
|
|
||||||
- cross-module refactoring
|
|
||||||
- API changes with tests
|
|
||||||
- security-related changes
|
|
||||||
- performance optimization across codebase
|
|
||||||
- database schema changes
|
|
||||||
|
|
||||||
## When to Skip
|
|
||||||
- single file edits
|
|
||||||
- simple bug fixes (1-2 lines)
|
|
||||||
- documentation updates
|
|
||||||
- configuration changes
|
|
||||||
- quick exploration
|
|
||||||
|
|
||||||
## Commands
|
|
||||||
|
|
||||||
### Initialize Swarm
|
|
||||||
Start a new swarm with hierarchical topology (anti-drift)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli swarm init --topology hierarchical --max-agents 8 --strategy specialized
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli swarm init --topology hierarchical --max-agents 6 --strategy specialized
|
|
||||||
```
|
|
||||||
|
|
||||||
### Route Task
|
|
||||||
Route a task to the appropriate agents based on task type
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli hooks route --task "[task description]"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli hooks route --task "implement OAuth2 authentication flow"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Spawn Agent
|
|
||||||
Spawn a specific agent type
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli agent spawn --type [type] --name [name]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli agent spawn --type coder --name impl-auth
|
|
||||||
```
|
|
||||||
|
|
||||||
### Monitor Status
|
|
||||||
Check the current swarm status
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli swarm status --verbose
|
|
||||||
```
|
|
||||||
|
|
||||||
### Orchestrate Task
|
|
||||||
Orchestrate a task across multiple agents
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli task orchestrate --task "[task]" --strategy adaptive
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli task orchestrate --task "refactor auth module" --strategy parallel --max-agents 4
|
|
||||||
```
|
|
||||||
|
|
||||||
### List Agents
|
|
||||||
List all active agents
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli agent list --filter active
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Scripts
|
|
||||||
|
|
||||||
| Script | Path | Description |
|
|
||||||
|--------|------|-------------|
|
|
||||||
| `swarm-start` | `.agents/scripts/swarm-start.sh` | Initialize swarm with default settings |
|
|
||||||
| `swarm-monitor` | `.agents/scripts/swarm-monitor.sh` | Real-time swarm monitoring dashboard |
|
|
||||||
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
| Document | Path | Description |
|
|
||||||
|----------|------|-------------|
|
|
||||||
| `Agent Types` | `docs/agents.md` | Complete list of agent types and capabilities |
|
|
||||||
| `Topology Guide` | `docs/topology.md` | Swarm topology configuration guide |
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
1. Check memory for existing patterns before starting
|
|
||||||
2. Use hierarchical topology for coordination
|
|
||||||
3. Store successful patterns after completion
|
|
||||||
4. Document any new learnings
|
|
||||||
@ -1,872 +0,0 @@
|
|||||||
---
|
|
||||||
name: "V3 CLI Modernization"
|
|
||||||
description: "CLI modernization and hooks system enhancement for Codex-flow v3. Implements interactive prompts, command decomposition, enhanced hooks integration, and intelligent workflow automation."
|
|
||||||
---
|
|
||||||
|
|
||||||
# V3 CLI Modernization
|
|
||||||
|
|
||||||
## What This Skill Does
|
|
||||||
|
|
||||||
Modernizes Codex-flow v3 CLI with interactive prompts, intelligent command decomposition, enhanced hooks integration, performance optimization, and comprehensive workflow automation capabilities.
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Initialize CLI modernization analysis
|
|
||||||
Task("CLI architecture", "Analyze current CLI structure and identify optimization opportunities", "cli-hooks-developer")
|
|
||||||
|
|
||||||
# Modernization implementation (parallel)
|
|
||||||
Task("Command decomposition", "Break down large CLI files into focused modules", "cli-hooks-developer")
|
|
||||||
Task("Interactive prompts", "Implement intelligent interactive CLI experience", "cli-hooks-developer")
|
|
||||||
Task("Hooks enhancement", "Deep integrate hooks with CLI lifecycle", "cli-hooks-developer")
|
|
||||||
```
|
|
||||||
|
|
||||||
## CLI Architecture Modernization
|
|
||||||
|
|
||||||
### Current State Analysis
|
|
||||||
```
|
|
||||||
Current CLI Issues:
|
|
||||||
├── index.ts: 108KB monolithic file
|
|
||||||
├── enterprise.ts: 68KB feature module
|
|
||||||
├── Limited interactivity: Basic command parsing
|
|
||||||
├── Hooks integration: Basic pre/post execution
|
|
||||||
└── No intelligent workflows: Manual command chaining
|
|
||||||
|
|
||||||
Target Architecture:
|
|
||||||
├── Modular Commands: <500 lines per command
|
|
||||||
├── Interactive Prompts: Smart context-aware UX
|
|
||||||
├── Enhanced Hooks: Deep lifecycle integration
|
|
||||||
├── Workflow Automation: Intelligent command orchestration
|
|
||||||
└── Performance: <200ms command response time
|
|
||||||
```
|
|
||||||
|
|
||||||
### Modular Command Architecture
|
|
||||||
```typescript
|
|
||||||
// src/cli/core/command-registry.ts
|
|
||||||
interface CommandModule {
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
category: CommandCategory;
|
|
||||||
handler: CommandHandler;
|
|
||||||
middleware: MiddlewareStack;
|
|
||||||
permissions: Permission[];
|
|
||||||
examples: CommandExample[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ModularCommandRegistry {
|
|
||||||
private commands = new Map<string, CommandModule>();
|
|
||||||
private categories = new Map<CommandCategory, CommandModule[]>();
|
|
||||||
private aliases = new Map<string, string>();
|
|
||||||
|
|
||||||
registerCommand(command: CommandModule): void {
|
|
||||||
this.commands.set(command.name, command);
|
|
||||||
|
|
||||||
// Register in category index
|
|
||||||
if (!this.categories.has(command.category)) {
|
|
||||||
this.categories.set(command.category, []);
|
|
||||||
}
|
|
||||||
this.categories.get(command.category)!.push(command);
|
|
||||||
}
|
|
||||||
|
|
||||||
async executeCommand(name: string, args: string[]): Promise<CommandResult> {
|
|
||||||
const command = this.resolveCommand(name);
|
|
||||||
if (!command) {
|
|
||||||
throw new CommandNotFoundError(name, this.getSuggestions(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute middleware stack
|
|
||||||
const context = await this.buildExecutionContext(command, args);
|
|
||||||
const result = await command.middleware.execute(context);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private resolveCommand(name: string): CommandModule | undefined {
|
|
||||||
// Try exact match first
|
|
||||||
if (this.commands.has(name)) {
|
|
||||||
return this.commands.get(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try alias
|
|
||||||
const aliasTarget = this.aliases.get(name);
|
|
||||||
if (aliasTarget) {
|
|
||||||
return this.commands.get(aliasTarget);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try fuzzy match
|
|
||||||
return this.findFuzzyMatch(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Command Decomposition Strategy
|
|
||||||
|
|
||||||
### Swarm Commands Module
|
|
||||||
```typescript
|
|
||||||
// src/cli/commands/swarm/swarm.command.ts
|
|
||||||
@Command({
|
|
||||||
name: 'swarm',
|
|
||||||
description: 'Swarm coordination and management',
|
|
||||||
category: 'orchestration'
|
|
||||||
})
|
|
||||||
export class SwarmCommand {
|
|
||||||
constructor(
|
|
||||||
private swarmCoordinator: UnifiedSwarmCoordinator,
|
|
||||||
private promptService: InteractivePromptService
|
|
||||||
) {}
|
|
||||||
|
|
||||||
@SubCommand('init')
|
|
||||||
@Option('--topology', 'Swarm topology (mesh|hierarchical|adaptive)', 'hierarchical')
|
|
||||||
@Option('--agents', 'Number of agents to spawn', 5)
|
|
||||||
@Option('--interactive', 'Interactive agent configuration', false)
|
|
||||||
async init(
|
|
||||||
@Arg('projectName') projectName: string,
|
|
||||||
options: SwarmInitOptions
|
|
||||||
): Promise<CommandResult> {
|
|
||||||
|
|
||||||
if (options.interactive) {
|
|
||||||
return this.interactiveSwarmInit(projectName);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.quickSwarmInit(projectName, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async interactiveSwarmInit(projectName: string): Promise<CommandResult> {
|
|
||||||
console.log(`🚀 Initializing Swarm for ${projectName}`);
|
|
||||||
|
|
||||||
// Interactive topology selection
|
|
||||||
const topology = await this.promptService.select({
|
|
||||||
message: 'Select swarm topology:',
|
|
||||||
choices: [
|
|
||||||
{ name: 'Hierarchical (Queen-led coordination)', value: 'hierarchical' },
|
|
||||||
{ name: 'Mesh (Peer-to-peer collaboration)', value: 'mesh' },
|
|
||||||
{ name: 'Adaptive (Dynamic topology switching)', value: 'adaptive' }
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
// Agent configuration
|
|
||||||
const agents = await this.promptAgentConfiguration();
|
|
||||||
|
|
||||||
// Initialize with configuration
|
|
||||||
const swarm = await this.swarmCoordinator.initialize({
|
|
||||||
name: projectName,
|
|
||||||
topology,
|
|
||||||
agents,
|
|
||||||
hooks: {
|
|
||||||
onAgentSpawn: this.handleAgentSpawn.bind(this),
|
|
||||||
onTaskComplete: this.handleTaskComplete.bind(this),
|
|
||||||
onSwarmComplete: this.handleSwarmComplete.bind(this)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return CommandResult.success({
|
|
||||||
message: `✅ Swarm ${projectName} initialized with ${agents.length} agents`,
|
|
||||||
data: { swarmId: swarm.id, topology, agentCount: agents.length }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@SubCommand('status')
|
|
||||||
async status(): Promise<CommandResult> {
|
|
||||||
const swarms = await this.swarmCoordinator.listActiveSwarms();
|
|
||||||
|
|
||||||
if (swarms.length === 0) {
|
|
||||||
return CommandResult.info('No active swarms found');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interactive swarm selection if multiple
|
|
||||||
const selectedSwarm = swarms.length === 1
|
|
||||||
? swarms[0]
|
|
||||||
: await this.promptService.select({
|
|
||||||
message: 'Select swarm to inspect:',
|
|
||||||
choices: swarms.map(s => ({
|
|
||||||
name: `${s.name} (${s.agents.length} agents, ${s.topology})`,
|
|
||||||
value: s
|
|
||||||
}))
|
|
||||||
});
|
|
||||||
|
|
||||||
return this.displaySwarmStatus(selectedSwarm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Learning Commands Module
|
|
||||||
```typescript
|
|
||||||
// src/cli/commands/learning/learning.command.ts
|
|
||||||
@Command({
|
|
||||||
name: 'learning',
|
|
||||||
description: 'Learning system management and optimization',
|
|
||||||
category: 'intelligence'
|
|
||||||
})
|
|
||||||
export class LearningCommand {
|
|
||||||
constructor(
|
|
||||||
private learningService: IntegratedLearningService,
|
|
||||||
private promptService: InteractivePromptService
|
|
||||||
) {}
|
|
||||||
|
|
||||||
@SubCommand('start')
|
|
||||||
@Option('--algorithm', 'RL algorithm to use', 'auto')
|
|
||||||
@Option('--tier', 'Learning tier (basic|standard|advanced)', 'standard')
|
|
||||||
async start(options: LearningStartOptions): Promise<CommandResult> {
|
|
||||||
// Auto-detect optimal algorithm if not specified
|
|
||||||
if (options.algorithm === 'auto') {
|
|
||||||
const taskContext = await this.analyzeCurrentContext();
|
|
||||||
options.algorithm = this.learningService.selectOptimalAlgorithm(taskContext);
|
|
||||||
|
|
||||||
console.log(`🧠 Auto-selected ${options.algorithm} algorithm based on context`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const session = await this.learningService.startSession({
|
|
||||||
algorithm: options.algorithm,
|
|
||||||
tier: options.tier,
|
|
||||||
userId: await this.getCurrentUser()
|
|
||||||
});
|
|
||||||
|
|
||||||
return CommandResult.success({
|
|
||||||
message: `🚀 Learning session started with ${options.algorithm}`,
|
|
||||||
data: { sessionId: session.id, algorithm: options.algorithm, tier: options.tier }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@SubCommand('feedback')
|
|
||||||
@Arg('reward', 'Reward value (0-1)', 'number')
|
|
||||||
async feedback(
|
|
||||||
@Arg('reward') reward: number,
|
|
||||||
@Option('--context', 'Additional context for learning')
|
|
||||||
context?: string
|
|
||||||
): Promise<CommandResult> {
|
|
||||||
const activeSession = await this.learningService.getActiveSession();
|
|
||||||
if (!activeSession) {
|
|
||||||
return CommandResult.error('No active learning session found. Start one with `learning start`');
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.learningService.submitFeedback({
|
|
||||||
sessionId: activeSession.id,
|
|
||||||
reward,
|
|
||||||
context,
|
|
||||||
timestamp: new Date()
|
|
||||||
});
|
|
||||||
|
|
||||||
return CommandResult.success({
|
|
||||||
message: `📊 Feedback recorded (reward: ${reward})`,
|
|
||||||
data: { reward, sessionId: activeSession.id }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@SubCommand('metrics')
|
|
||||||
async metrics(): Promise<CommandResult> {
|
|
||||||
const metrics = await this.learningService.getMetrics();
|
|
||||||
|
|
||||||
// Interactive metrics display
|
|
||||||
await this.displayInteractiveMetrics(metrics);
|
|
||||||
|
|
||||||
return CommandResult.success('Metrics displayed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Interactive Prompt System
|
|
||||||
|
|
||||||
### Advanced Prompt Service
|
|
||||||
```typescript
|
|
||||||
// src/cli/services/interactive-prompt.service.ts
|
|
||||||
interface PromptOptions {
|
|
||||||
message: string;
|
|
||||||
type: 'select' | 'multiselect' | 'input' | 'confirm' | 'progress';
|
|
||||||
choices?: PromptChoice[];
|
|
||||||
default?: any;
|
|
||||||
validate?: (input: any) => boolean | string;
|
|
||||||
transform?: (input: any) => any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class InteractivePromptService {
|
|
||||||
private inquirer: any; // Dynamic import for tree-shaking
|
|
||||||
|
|
||||||
async select<T>(options: SelectPromptOptions<T>): Promise<T> {
|
|
||||||
const { default: inquirer } = await import('inquirer');
|
|
||||||
|
|
||||||
const result = await inquirer.prompt([{
|
|
||||||
type: 'list',
|
|
||||||
name: 'selection',
|
|
||||||
message: options.message,
|
|
||||||
choices: options.choices,
|
|
||||||
default: options.default
|
|
||||||
}]);
|
|
||||||
|
|
||||||
return result.selection;
|
|
||||||
}
|
|
||||||
|
|
||||||
async multiSelect<T>(options: MultiSelectPromptOptions<T>): Promise<T[]> {
|
|
||||||
const { default: inquirer } = await import('inquirer');
|
|
||||||
|
|
||||||
const result = await inquirer.prompt([{
|
|
||||||
type: 'checkbox',
|
|
||||||
name: 'selections',
|
|
||||||
message: options.message,
|
|
||||||
choices: options.choices,
|
|
||||||
validate: (input: T[]) => {
|
|
||||||
if (options.minSelections && input.length < options.minSelections) {
|
|
||||||
return `Please select at least ${options.minSelections} options`;
|
|
||||||
}
|
|
||||||
if (options.maxSelections && input.length > options.maxSelections) {
|
|
||||||
return `Please select at most ${options.maxSelections} options`;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}]);
|
|
||||||
|
|
||||||
return result.selections;
|
|
||||||
}
|
|
||||||
|
|
||||||
async input(options: InputPromptOptions): Promise<string> {
|
|
||||||
const { default: inquirer } = await import('inquirer');
|
|
||||||
|
|
||||||
const result = await inquirer.prompt([{
|
|
||||||
type: 'input',
|
|
||||||
name: 'input',
|
|
||||||
message: options.message,
|
|
||||||
default: options.default,
|
|
||||||
validate: options.validate,
|
|
||||||
transformer: options.transform
|
|
||||||
}]);
|
|
||||||
|
|
||||||
return result.input;
|
|
||||||
}
|
|
||||||
|
|
||||||
async progressTask<T>(
|
|
||||||
task: ProgressTask<T>,
|
|
||||||
options: ProgressOptions
|
|
||||||
): Promise<T> {
|
|
||||||
const { default: cliProgress } = await import('cli-progress');
|
|
||||||
|
|
||||||
const progressBar = new cliProgress.SingleBar({
|
|
||||||
format: `${options.title} |{bar}| {percentage}% | {status}`,
|
|
||||||
barCompleteChar: '█',
|
|
||||||
barIncompleteChar: '░',
|
|
||||||
hideCursor: true
|
|
||||||
});
|
|
||||||
|
|
||||||
progressBar.start(100, 0, { status: 'Starting...' });
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await task({
|
|
||||||
updateProgress: (percent: number, status?: string) => {
|
|
||||||
progressBar.update(percent, { status: status || 'Processing...' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
progressBar.update(100, { status: 'Complete!' });
|
|
||||||
progressBar.stop();
|
|
||||||
|
|
||||||
return result;
|
|
||||||
} catch (error) {
|
|
||||||
progressBar.stop();
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async confirmWithDetails(
|
|
||||||
message: string,
|
|
||||||
details: ConfirmationDetails
|
|
||||||
): Promise<boolean> {
|
|
||||||
console.log('\n' + chalk.bold(message));
|
|
||||||
console.log(chalk.gray('Details:'));
|
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(details)) {
|
|
||||||
console.log(chalk.gray(` ${key}: ${value}`));
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.confirm('\nProceed?');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Enhanced Hooks Integration
|
|
||||||
|
|
||||||
### Deep CLI Hooks Integration
|
|
||||||
```typescript
|
|
||||||
// src/cli/hooks/cli-hooks-manager.ts
|
|
||||||
interface CLIHookEvent {
|
|
||||||
type: 'command_start' | 'command_end' | 'command_error' | 'agent_spawn' | 'task_complete';
|
|
||||||
command: string;
|
|
||||||
args: string[];
|
|
||||||
context: ExecutionContext;
|
|
||||||
timestamp: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CLIHooksManager {
|
|
||||||
private hooks: Map<string, HookHandler[]> = new Map();
|
|
||||||
private learningIntegration: LearningHooksIntegration;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.learningIntegration = new LearningHooksIntegration();
|
|
||||||
this.setupDefaultHooks();
|
|
||||||
}
|
|
||||||
|
|
||||||
private setupDefaultHooks(): void {
|
|
||||||
// Learning integration hooks
|
|
||||||
this.registerHook('command_start', async (event: CLIHookEvent) => {
|
|
||||||
await this.learningIntegration.recordCommandStart(event);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.registerHook('command_end', async (event: CLIHookEvent) => {
|
|
||||||
await this.learningIntegration.recordCommandSuccess(event);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.registerHook('command_error', async (event: CLIHookEvent) => {
|
|
||||||
await this.learningIntegration.recordCommandError(event);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Intelligent suggestions
|
|
||||||
this.registerHook('command_start', async (event: CLIHookEvent) => {
|
|
||||||
const suggestions = await this.generateIntelligentSuggestions(event);
|
|
||||||
if (suggestions.length > 0) {
|
|
||||||
this.displaySuggestions(suggestions);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Performance monitoring
|
|
||||||
this.registerHook('command_end', async (event: CLIHookEvent) => {
|
|
||||||
await this.recordPerformanceMetrics(event);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async executeHooks(type: string, event: CLIHookEvent): Promise<void> {
|
|
||||||
const handlers = this.hooks.get(type) || [];
|
|
||||||
|
|
||||||
await Promise.all(handlers.map(handler =>
|
|
||||||
this.executeHookSafely(handler, event)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
private async generateIntelligentSuggestions(event: CLIHookEvent): Promise<Suggestion[]> {
|
|
||||||
const context = await this.learningIntegration.getExecutionContext(event);
|
|
||||||
const patterns = await this.learningIntegration.findSimilarPatterns(context);
|
|
||||||
|
|
||||||
return patterns.map(pattern => ({
|
|
||||||
type: 'optimization',
|
|
||||||
message: `Based on similar executions, consider: ${pattern.suggestion}`,
|
|
||||||
confidence: pattern.confidence
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Learning Integration
|
|
||||||
```typescript
|
|
||||||
// src/cli/hooks/learning-hooks-integration.ts
|
|
||||||
export class LearningHooksIntegration {
|
|
||||||
constructor(
|
|
||||||
private agenticFlowHooks: AgenticFlowHooksClient,
|
|
||||||
private agentDBLearning: AgentDBLearningClient
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async recordCommandStart(event: CLIHookEvent): Promise<void> {
|
|
||||||
// Start trajectory tracking
|
|
||||||
await this.agenticFlowHooks.trajectoryStart({
|
|
||||||
sessionId: event.context.sessionId,
|
|
||||||
command: event.command,
|
|
||||||
args: event.args,
|
|
||||||
context: event.context
|
|
||||||
});
|
|
||||||
|
|
||||||
// Record experience in AgentDB
|
|
||||||
await this.agentDBLearning.recordExperience({
|
|
||||||
type: 'command_execution',
|
|
||||||
state: this.encodeCommandState(event),
|
|
||||||
action: event.command,
|
|
||||||
timestamp: event.timestamp
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async recordCommandSuccess(event: CLIHookEvent): Promise<void> {
|
|
||||||
const executionTime = Date.now() - event.timestamp.getTime();
|
|
||||||
const reward = this.calculateReward(event, executionTime, true);
|
|
||||||
|
|
||||||
// Complete trajectory
|
|
||||||
await this.agenticFlowHooks.trajectoryEnd({
|
|
||||||
sessionId: event.context.sessionId,
|
|
||||||
success: true,
|
|
||||||
reward,
|
|
||||||
verdict: 'positive'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Submit feedback to learning system
|
|
||||||
await this.agentDBLearning.submitFeedback({
|
|
||||||
sessionId: event.context.learningSessionId,
|
|
||||||
reward,
|
|
||||||
success: true,
|
|
||||||
latencyMs: executionTime
|
|
||||||
});
|
|
||||||
|
|
||||||
// Store successful pattern
|
|
||||||
if (reward > 0.8) {
|
|
||||||
await this.agenticFlowHooks.storePattern({
|
|
||||||
pattern: event.command,
|
|
||||||
solution: event.context.result,
|
|
||||||
confidence: reward
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async recordCommandError(event: CLIHookEvent): Promise<void> {
|
|
||||||
const executionTime = Date.now() - event.timestamp.getTime();
|
|
||||||
const reward = this.calculateReward(event, executionTime, false);
|
|
||||||
|
|
||||||
// Complete trajectory with error
|
|
||||||
await this.agenticFlowHooks.trajectoryEnd({
|
|
||||||
sessionId: event.context.sessionId,
|
|
||||||
success: false,
|
|
||||||
reward,
|
|
||||||
verdict: 'negative',
|
|
||||||
error: event.context.error
|
|
||||||
});
|
|
||||||
|
|
||||||
// Learn from failure
|
|
||||||
await this.agentDBLearning.submitFeedback({
|
|
||||||
sessionId: event.context.learningSessionId,
|
|
||||||
reward,
|
|
||||||
success: false,
|
|
||||||
latencyMs: executionTime,
|
|
||||||
error: event.context.error
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private calculateReward(event: CLIHookEvent, executionTime: number, success: boolean): number {
|
|
||||||
if (!success) return 0;
|
|
||||||
|
|
||||||
// Base reward for success
|
|
||||||
let reward = 0.5;
|
|
||||||
|
|
||||||
// Performance bonus (faster execution)
|
|
||||||
const expectedTime = this.getExpectedExecutionTime(event.command);
|
|
||||||
if (executionTime < expectedTime) {
|
|
||||||
reward += 0.3 * (1 - executionTime / expectedTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Complexity bonus
|
|
||||||
const complexity = this.calculateCommandComplexity(event);
|
|
||||||
reward += complexity * 0.2;
|
|
||||||
|
|
||||||
return Math.min(reward, 1.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Intelligent Workflow Automation
|
|
||||||
|
|
||||||
### Workflow Orchestrator
|
|
||||||
```typescript
|
|
||||||
// src/cli/workflows/workflow-orchestrator.ts
|
|
||||||
interface WorkflowStep {
|
|
||||||
id: string;
|
|
||||||
command: string;
|
|
||||||
args: string[];
|
|
||||||
dependsOn: string[];
|
|
||||||
condition?: WorkflowCondition;
|
|
||||||
retryPolicy?: RetryPolicy;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class WorkflowOrchestrator {
|
|
||||||
constructor(
|
|
||||||
private commandRegistry: ModularCommandRegistry,
|
|
||||||
private promptService: InteractivePromptService
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async executeWorkflow(workflow: Workflow): Promise<WorkflowResult> {
|
|
||||||
const context = new WorkflowExecutionContext(workflow);
|
|
||||||
|
|
||||||
// Display workflow overview
|
|
||||||
await this.displayWorkflowOverview(workflow);
|
|
||||||
|
|
||||||
const confirmed = await this.promptService.confirm(
|
|
||||||
'Execute this workflow?'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!confirmed) {
|
|
||||||
return WorkflowResult.cancelled();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute steps
|
|
||||||
return this.promptService.progressTask(
|
|
||||||
async ({ updateProgress }) => {
|
|
||||||
const steps = this.sortStepsByDependencies(workflow.steps);
|
|
||||||
|
|
||||||
for (let i = 0; i < steps.length; i++) {
|
|
||||||
const step = steps[i];
|
|
||||||
updateProgress((i / steps.length) * 100, `Executing ${step.command}`);
|
|
||||||
|
|
||||||
await this.executeStep(step, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
return WorkflowResult.success(context.getResults());
|
|
||||||
},
|
|
||||||
{ title: `Workflow: ${workflow.name}` }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async generateWorkflowFromIntent(intent: string): Promise<Workflow> {
|
|
||||||
// Use learning system to generate workflow
|
|
||||||
const patterns = await this.findWorkflowPatterns(intent);
|
|
||||||
|
|
||||||
if (patterns.length === 0) {
|
|
||||||
throw new Error('Could not generate workflow for intent');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Select best pattern or let user choose
|
|
||||||
const selectedPattern = patterns.length === 1
|
|
||||||
? patterns[0]
|
|
||||||
: await this.promptService.select({
|
|
||||||
message: 'Select workflow template:',
|
|
||||||
choices: patterns.map(p => ({
|
|
||||||
name: `${p.name} (${p.confidence}% match)`,
|
|
||||||
value: p
|
|
||||||
}))
|
|
||||||
});
|
|
||||||
|
|
||||||
return this.customizeWorkflow(selectedPattern, intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async executeStep(step: WorkflowStep, context: WorkflowExecutionContext): Promise<void> {
|
|
||||||
// Check conditions
|
|
||||||
if (step.condition && !this.evaluateCondition(step.condition, context)) {
|
|
||||||
context.skipStep(step.id, 'Condition not met');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check dependencies
|
|
||||||
const missingDeps = step.dependsOn.filter(dep => !context.isStepCompleted(dep));
|
|
||||||
if (missingDeps.length > 0) {
|
|
||||||
throw new WorkflowError(`Step ${step.id} has unmet dependencies: ${missingDeps.join(', ')}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute with retry policy
|
|
||||||
const retryPolicy = step.retryPolicy || { maxAttempts: 1 };
|
|
||||||
let lastError: Error | null = null;
|
|
||||||
|
|
||||||
for (let attempt = 1; attempt <= retryPolicy.maxAttempts; attempt++) {
|
|
||||||
try {
|
|
||||||
const result = await this.commandRegistry.executeCommand(step.command, step.args);
|
|
||||||
context.completeStep(step.id, result);
|
|
||||||
return;
|
|
||||||
} catch (error) {
|
|
||||||
lastError = error as Error;
|
|
||||||
|
|
||||||
if (attempt < retryPolicy.maxAttempts) {
|
|
||||||
await this.delay(retryPolicy.backoffMs || 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new WorkflowError(`Step ${step.id} failed after ${retryPolicy.maxAttempts} attempts: ${lastError?.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Performance Optimization
|
|
||||||
|
|
||||||
### Command Performance Monitoring
|
|
||||||
```typescript
|
|
||||||
// src/cli/performance/command-performance.ts
|
|
||||||
export class CommandPerformanceMonitor {
|
|
||||||
private metrics = new Map<string, CommandMetrics>();
|
|
||||||
|
|
||||||
async measureCommand<T>(
|
|
||||||
commandName: string,
|
|
||||||
executor: () => Promise<T>
|
|
||||||
): Promise<T> {
|
|
||||||
const start = performance.now();
|
|
||||||
const memBefore = process.memoryUsage();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await executor();
|
|
||||||
const end = performance.now();
|
|
||||||
const memAfter = process.memoryUsage();
|
|
||||||
|
|
||||||
this.recordMetrics(commandName, {
|
|
||||||
executionTime: end - start,
|
|
||||||
memoryDelta: memAfter.heapUsed - memBefore.heapUsed,
|
|
||||||
success: true
|
|
||||||
});
|
|
||||||
|
|
||||||
return result;
|
|
||||||
} catch (error) {
|
|
||||||
const end = performance.now();
|
|
||||||
|
|
||||||
this.recordMetrics(commandName, {
|
|
||||||
executionTime: end - start,
|
|
||||||
memoryDelta: 0,
|
|
||||||
success: false,
|
|
||||||
error: error as Error
|
|
||||||
});
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private recordMetrics(command: string, measurement: PerformanceMeasurement): void {
|
|
||||||
if (!this.metrics.has(command)) {
|
|
||||||
this.metrics.set(command, new CommandMetrics(command));
|
|
||||||
}
|
|
||||||
|
|
||||||
const metrics = this.metrics.get(command)!;
|
|
||||||
metrics.addMeasurement(measurement);
|
|
||||||
|
|
||||||
// Alert if performance degrades
|
|
||||||
if (metrics.getP95ExecutionTime() > 5000) { // 5 seconds
|
|
||||||
console.warn(`⚠️ Command '${command}' is performing slowly (P95: ${metrics.getP95ExecutionTime()}ms)`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getCommandReport(command: string): PerformanceReport {
|
|
||||||
const metrics = this.metrics.get(command);
|
|
||||||
if (!metrics) {
|
|
||||||
throw new Error(`No metrics found for command: ${command}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
command,
|
|
||||||
totalExecutions: metrics.getTotalExecutions(),
|
|
||||||
successRate: metrics.getSuccessRate(),
|
|
||||||
avgExecutionTime: metrics.getAverageExecutionTime(),
|
|
||||||
p95ExecutionTime: metrics.getP95ExecutionTime(),
|
|
||||||
avgMemoryUsage: metrics.getAverageMemoryUsage(),
|
|
||||||
recommendations: this.generateRecommendations(metrics)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Smart Auto-completion
|
|
||||||
|
|
||||||
### Intelligent Command Completion
|
|
||||||
```typescript
|
|
||||||
// src/cli/completion/intelligent-completion.ts
|
|
||||||
export class IntelligentCompletion {
|
|
||||||
constructor(
|
|
||||||
private learningService: LearningService,
|
|
||||||
private commandRegistry: ModularCommandRegistry
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async generateCompletions(
|
|
||||||
partial: string,
|
|
||||||
context: CompletionContext
|
|
||||||
): Promise<Completion[]> {
|
|
||||||
const completions: Completion[] = [];
|
|
||||||
|
|
||||||
// 1. Exact command matches
|
|
||||||
const exactMatches = this.commandRegistry.findCommandsByPrefix(partial);
|
|
||||||
completions.push(...exactMatches.map(cmd => ({
|
|
||||||
value: cmd.name,
|
|
||||||
description: cmd.description,
|
|
||||||
type: 'command',
|
|
||||||
confidence: 1.0
|
|
||||||
})));
|
|
||||||
|
|
||||||
// 2. Learning-based suggestions
|
|
||||||
const learnedSuggestions = await this.learningService.suggestCommands(
|
|
||||||
partial,
|
|
||||||
context
|
|
||||||
);
|
|
||||||
completions.push(...learnedSuggestions);
|
|
||||||
|
|
||||||
// 3. Context-aware suggestions
|
|
||||||
const contextualSuggestions = await this.generateContextualSuggestions(
|
|
||||||
partial,
|
|
||||||
context
|
|
||||||
);
|
|
||||||
completions.push(...contextualSuggestions);
|
|
||||||
|
|
||||||
// Sort by confidence and relevance
|
|
||||||
return completions
|
|
||||||
.sort((a, b) => b.confidence - a.confidence)
|
|
||||||
.slice(0, 10); // Top 10 suggestions
|
|
||||||
}
|
|
||||||
|
|
||||||
private async generateContextualSuggestions(
|
|
||||||
partial: string,
|
|
||||||
context: CompletionContext
|
|
||||||
): Promise<Completion[]> {
|
|
||||||
const suggestions: Completion[] = [];
|
|
||||||
|
|
||||||
// If in git repository, suggest git-related commands
|
|
||||||
if (context.isGitRepository) {
|
|
||||||
if (partial.startsWith('git')) {
|
|
||||||
suggestions.push({
|
|
||||||
value: 'git commit',
|
|
||||||
description: 'Create git commit with generated message',
|
|
||||||
type: 'workflow',
|
|
||||||
confidence: 0.8
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If package.json exists, suggest npm commands
|
|
||||||
if (context.hasPackageJson) {
|
|
||||||
if (partial.startsWith('npm') || partial.startsWith('swarm')) {
|
|
||||||
suggestions.push({
|
|
||||||
value: 'swarm init',
|
|
||||||
description: 'Initialize swarm for this project',
|
|
||||||
type: 'workflow',
|
|
||||||
confidence: 0.9
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return suggestions;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Success Metrics
|
|
||||||
|
|
||||||
### CLI Performance Targets
|
|
||||||
- [ ] **Command Response**: <200ms average command execution time
|
|
||||||
- [ ] **File Decomposition**: index.ts (108KB) → <10KB per command module
|
|
||||||
- [ ] **Interactive UX**: Smart prompts with context awareness
|
|
||||||
- [ ] **Hook Integration**: Deep lifecycle integration with learning
|
|
||||||
- [ ] **Workflow Automation**: Intelligent multi-step command orchestration
|
|
||||||
- [ ] **Auto-completion**: >90% accuracy for command suggestions
|
|
||||||
|
|
||||||
### User Experience Improvements
|
|
||||||
```typescript
|
|
||||||
const cliImprovements = {
|
|
||||||
before: {
|
|
||||||
commandResponse: '~500ms',
|
|
||||||
interactivity: 'Basic command parsing',
|
|
||||||
workflows: 'Manual command chaining',
|
|
||||||
suggestions: 'Static help text'
|
|
||||||
},
|
|
||||||
|
|
||||||
after: {
|
|
||||||
commandResponse: '<200ms with caching',
|
|
||||||
interactivity: 'Smart context-aware prompts',
|
|
||||||
workflows: 'Automated multi-step execution',
|
|
||||||
suggestions: 'Learning-based intelligent completion'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
## Related V3 Skills
|
|
||||||
|
|
||||||
- `v3-core-implementation` - Core domain integration
|
|
||||||
- `v3-memory-unification` - Memory-backed command caching
|
|
||||||
- `v3-swarm-coordination` - CLI swarm management integration
|
|
||||||
- `v3-performance-optimization` - CLI performance monitoring
|
|
||||||
|
|
||||||
## Usage Examples
|
|
||||||
|
|
||||||
### Complete CLI Modernization
|
|
||||||
```bash
|
|
||||||
# Full CLI modernization implementation
|
|
||||||
Task("CLI modernization implementation",
|
|
||||||
"Implement modular commands, interactive prompts, and intelligent workflows",
|
|
||||||
"cli-hooks-developer")
|
|
||||||
```
|
|
||||||
|
|
||||||
### Interactive Command Enhancement
|
|
||||||
```bash
|
|
||||||
# Enhanced interactive commands
|
|
||||||
Codex-flow swarm init --interactive
|
|
||||||
Codex-flow learning start --guided
|
|
||||||
Codex-flow workflow create --from-intent "setup new project"
|
|
||||||
```
|
|
||||||
@ -1,797 +0,0 @@
|
|||||||
---
|
|
||||||
name: "V3 Core Implementation"
|
|
||||||
description: "Core module implementation for Codex-flow v3. Implements DDD domains, clean architecture patterns, dependency injection, and modular TypeScript codebase with comprehensive testing."
|
|
||||||
---
|
|
||||||
|
|
||||||
# V3 Core Implementation
|
|
||||||
|
|
||||||
## What This Skill Does
|
|
||||||
|
|
||||||
Implements the core TypeScript modules for Codex-flow v3 following Domain-Driven Design principles, clean architecture patterns, and modern TypeScript best practices with comprehensive test coverage.
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Initialize core implementation
|
|
||||||
Task("Core foundation", "Set up DDD domain structure and base classes", "core-implementer")
|
|
||||||
|
|
||||||
# Domain implementation (parallel)
|
|
||||||
Task("Task domain", "Implement task management domain with entities and services", "core-implementer")
|
|
||||||
Task("Session domain", "Implement session management domain", "core-implementer")
|
|
||||||
Task("Health domain", "Implement health monitoring domain", "core-implementer")
|
|
||||||
```
|
|
||||||
|
|
||||||
## Core Implementation Architecture
|
|
||||||
|
|
||||||
### Domain Structure
|
|
||||||
```
|
|
||||||
src/
|
|
||||||
├── core/
|
|
||||||
│ ├── kernel/ # Microkernel pattern
|
|
||||||
│ │ ├── Codex-flow-kernel.ts
|
|
||||||
│ │ ├── domain-registry.ts
|
|
||||||
│ │ └── plugin-loader.ts
|
|
||||||
│ │
|
|
||||||
│ ├── domains/ # DDD Bounded Contexts
|
|
||||||
│ │ ├── task-management/
|
|
||||||
│ │ │ ├── entities/
|
|
||||||
│ │ │ ├── value-objects/
|
|
||||||
│ │ │ ├── services/
|
|
||||||
│ │ │ ├── repositories/
|
|
||||||
│ │ │ └── events/
|
|
||||||
│ │ │
|
|
||||||
│ │ ├── session-management/
|
|
||||||
│ │ ├── health-monitoring/
|
|
||||||
│ │ ├── lifecycle-management/
|
|
||||||
│ │ └── event-coordination/
|
|
||||||
│ │
|
|
||||||
│ ├── shared/ # Shared kernel
|
|
||||||
│ │ ├── domain/
|
|
||||||
│ │ │ ├── entity.ts
|
|
||||||
│ │ │ ├── value-object.ts
|
|
||||||
│ │ │ ├── domain-event.ts
|
|
||||||
│ │ │ └── aggregate-root.ts
|
|
||||||
│ │ │
|
|
||||||
│ │ ├── infrastructure/
|
|
||||||
│ │ │ ├── event-bus.ts
|
|
||||||
│ │ │ ├── dependency-container.ts
|
|
||||||
│ │ │ └── logger.ts
|
|
||||||
│ │ │
|
|
||||||
│ │ └── types/
|
|
||||||
│ │ ├── common.ts
|
|
||||||
│ │ ├── errors.ts
|
|
||||||
│ │ └── interfaces.ts
|
|
||||||
│ │
|
|
||||||
│ └── application/ # Application services
|
|
||||||
│ ├── use-cases/
|
|
||||||
│ ├── commands/
|
|
||||||
│ ├── queries/
|
|
||||||
│ └── handlers/
|
|
||||||
```
|
|
||||||
|
|
||||||
## Base Domain Classes
|
|
||||||
|
|
||||||
### Entity Base Class
|
|
||||||
```typescript
|
|
||||||
// src/core/shared/domain/entity.ts
|
|
||||||
export abstract class Entity<T> {
|
|
||||||
protected readonly _id: T;
|
|
||||||
private _domainEvents: DomainEvent[] = [];
|
|
||||||
|
|
||||||
constructor(id: T) {
|
|
||||||
this._id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
get id(): T {
|
|
||||||
return this._id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public equals(object?: Entity<T>): boolean {
|
|
||||||
if (object == null || object == undefined) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this === object) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(object instanceof Entity)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this._id === object._id;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected addDomainEvent(domainEvent: DomainEvent): void {
|
|
||||||
this._domainEvents.push(domainEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getUncommittedEvents(): DomainEvent[] {
|
|
||||||
return this._domainEvents;
|
|
||||||
}
|
|
||||||
|
|
||||||
public markEventsAsCommitted(): void {
|
|
||||||
this._domainEvents = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Value Object Base Class
|
|
||||||
```typescript
|
|
||||||
// src/core/shared/domain/value-object.ts
|
|
||||||
export abstract class ValueObject<T> {
|
|
||||||
protected readonly props: T;
|
|
||||||
|
|
||||||
constructor(props: T) {
|
|
||||||
this.props = Object.freeze(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
public equals(object?: ValueObject<T>): boolean {
|
|
||||||
if (object == null || object == undefined) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this === object) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return JSON.stringify(this.props) === JSON.stringify(object.props);
|
|
||||||
}
|
|
||||||
|
|
||||||
get value(): T {
|
|
||||||
return this.props;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Aggregate Root
|
|
||||||
```typescript
|
|
||||||
// src/core/shared/domain/aggregate-root.ts
|
|
||||||
export abstract class AggregateRoot<T> extends Entity<T> {
|
|
||||||
private _version: number = 0;
|
|
||||||
|
|
||||||
get version(): number {
|
|
||||||
return this._version;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected incrementVersion(): void {
|
|
||||||
this._version++;
|
|
||||||
}
|
|
||||||
|
|
||||||
public applyEvent(event: DomainEvent): void {
|
|
||||||
this.addDomainEvent(event);
|
|
||||||
this.incrementVersion();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Task Management Domain Implementation
|
|
||||||
|
|
||||||
### Task Entity
|
|
||||||
```typescript
|
|
||||||
// src/core/domains/task-management/entities/task.entity.ts
|
|
||||||
import { AggregateRoot } from '../../../shared/domain/aggregate-root';
|
|
||||||
import { TaskId } from '../value-objects/task-id.vo';
|
|
||||||
import { TaskStatus } from '../value-objects/task-status.vo';
|
|
||||||
import { Priority } from '../value-objects/priority.vo';
|
|
||||||
import { TaskAssignedEvent } from '../events/task-assigned.event';
|
|
||||||
|
|
||||||
interface TaskProps {
|
|
||||||
id: TaskId;
|
|
||||||
description: string;
|
|
||||||
priority: Priority;
|
|
||||||
status: TaskStatus;
|
|
||||||
assignedAgentId?: string;
|
|
||||||
createdAt: Date;
|
|
||||||
updatedAt: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Task extends AggregateRoot<TaskId> {
|
|
||||||
private props: TaskProps;
|
|
||||||
|
|
||||||
private constructor(props: TaskProps) {
|
|
||||||
super(props.id);
|
|
||||||
this.props = props;
|
|
||||||
}
|
|
||||||
|
|
||||||
static create(description: string, priority: Priority): Task {
|
|
||||||
const task = new Task({
|
|
||||||
id: TaskId.create(),
|
|
||||||
description,
|
|
||||||
priority,
|
|
||||||
status: TaskStatus.pending(),
|
|
||||||
createdAt: new Date(),
|
|
||||||
updatedAt: new Date()
|
|
||||||
});
|
|
||||||
|
|
||||||
return task;
|
|
||||||
}
|
|
||||||
|
|
||||||
static reconstitute(props: TaskProps): Task {
|
|
||||||
return new Task(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
public assignTo(agentId: string): void {
|
|
||||||
if (this.props.status.equals(TaskStatus.completed())) {
|
|
||||||
throw new Error('Cannot assign completed task');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.props.assignedAgentId = agentId;
|
|
||||||
this.props.status = TaskStatus.assigned();
|
|
||||||
this.props.updatedAt = new Date();
|
|
||||||
|
|
||||||
this.applyEvent(new TaskAssignedEvent(
|
|
||||||
this.id.value,
|
|
||||||
agentId,
|
|
||||||
this.props.priority
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
public complete(result: TaskResult): void {
|
|
||||||
if (!this.props.assignedAgentId) {
|
|
||||||
throw new Error('Cannot complete unassigned task');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.props.status = TaskStatus.completed();
|
|
||||||
this.props.updatedAt = new Date();
|
|
||||||
|
|
||||||
this.applyEvent(new TaskCompletedEvent(
|
|
||||||
this.id.value,
|
|
||||||
result,
|
|
||||||
this.calculateDuration()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Getters
|
|
||||||
get description(): string { return this.props.description; }
|
|
||||||
get priority(): Priority { return this.props.priority; }
|
|
||||||
get status(): TaskStatus { return this.props.status; }
|
|
||||||
get assignedAgentId(): string | undefined { return this.props.assignedAgentId; }
|
|
||||||
get createdAt(): Date { return this.props.createdAt; }
|
|
||||||
get updatedAt(): Date { return this.props.updatedAt; }
|
|
||||||
|
|
||||||
private calculateDuration(): number {
|
|
||||||
return this.props.updatedAt.getTime() - this.props.createdAt.getTime();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task Value Objects
|
|
||||||
```typescript
|
|
||||||
// src/core/domains/task-management/value-objects/task-id.vo.ts
|
|
||||||
export class TaskId extends ValueObject<string> {
|
|
||||||
private constructor(value: string) {
|
|
||||||
super({ value });
|
|
||||||
}
|
|
||||||
|
|
||||||
static create(): TaskId {
|
|
||||||
return new TaskId(crypto.randomUUID());
|
|
||||||
}
|
|
||||||
|
|
||||||
static fromString(id: string): TaskId {
|
|
||||||
if (!id || id.length === 0) {
|
|
||||||
throw new Error('TaskId cannot be empty');
|
|
||||||
}
|
|
||||||
return new TaskId(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
get value(): string {
|
|
||||||
return this.props.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// src/core/domains/task-management/value-objects/task-status.vo.ts
|
|
||||||
type TaskStatusType = 'pending' | 'assigned' | 'in_progress' | 'completed' | 'failed';
|
|
||||||
|
|
||||||
export class TaskStatus extends ValueObject<TaskStatusType> {
|
|
||||||
private constructor(status: TaskStatusType) {
|
|
||||||
super({ value: status });
|
|
||||||
}
|
|
||||||
|
|
||||||
static pending(): TaskStatus { return new TaskStatus('pending'); }
|
|
||||||
static assigned(): TaskStatus { return new TaskStatus('assigned'); }
|
|
||||||
static inProgress(): TaskStatus { return new TaskStatus('in_progress'); }
|
|
||||||
static completed(): TaskStatus { return new TaskStatus('completed'); }
|
|
||||||
static failed(): TaskStatus { return new TaskStatus('failed'); }
|
|
||||||
|
|
||||||
get value(): TaskStatusType {
|
|
||||||
return this.props.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public isPending(): boolean { return this.value === 'pending'; }
|
|
||||||
public isAssigned(): boolean { return this.value === 'assigned'; }
|
|
||||||
public isInProgress(): boolean { return this.value === 'in_progress'; }
|
|
||||||
public isCompleted(): boolean { return this.value === 'completed'; }
|
|
||||||
public isFailed(): boolean { return this.value === 'failed'; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// src/core/domains/task-management/value-objects/priority.vo.ts
|
|
||||||
type PriorityLevel = 'low' | 'medium' | 'high' | 'critical';
|
|
||||||
|
|
||||||
export class Priority extends ValueObject<PriorityLevel> {
|
|
||||||
private constructor(level: PriorityLevel) {
|
|
||||||
super({ value: level });
|
|
||||||
}
|
|
||||||
|
|
||||||
static low(): Priority { return new Priority('low'); }
|
|
||||||
static medium(): Priority { return new Priority('medium'); }
|
|
||||||
static high(): Priority { return new Priority('high'); }
|
|
||||||
static critical(): Priority { return new Priority('critical'); }
|
|
||||||
|
|
||||||
get value(): PriorityLevel {
|
|
||||||
return this.props.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getNumericValue(): number {
|
|
||||||
const priorities = { low: 1, medium: 2, high: 3, critical: 4 };
|
|
||||||
return priorities[this.value];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Domain Services
|
|
||||||
|
|
||||||
### Task Scheduling Service
|
|
||||||
```typescript
|
|
||||||
// src/core/domains/task-management/services/task-scheduling.service.ts
|
|
||||||
import { Injectable } from '../../../shared/infrastructure/dependency-container';
|
|
||||||
import { Task } from '../entities/task.entity';
|
|
||||||
import { Priority } from '../value-objects/priority.vo';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class TaskSchedulingService {
|
|
||||||
public prioritizeTasks(tasks: Task[]): Task[] {
|
|
||||||
return tasks.sort((a, b) =>
|
|
||||||
b.priority.getNumericValue() - a.priority.getNumericValue()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public canSchedule(task: Task, agentCapacity: number): boolean {
|
|
||||||
if (agentCapacity <= 0) return false;
|
|
||||||
|
|
||||||
// Critical tasks always schedulable
|
|
||||||
if (task.priority.equals(Priority.critical())) return true;
|
|
||||||
|
|
||||||
// Other logic based on capacity
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public calculateEstimatedDuration(task: Task): number {
|
|
||||||
// Simple heuristic - would use ML in real implementation
|
|
||||||
const baseTime = 300000; // 5 minutes
|
|
||||||
const priorityMultiplier = {
|
|
||||||
low: 0.5,
|
|
||||||
medium: 1.0,
|
|
||||||
high: 1.5,
|
|
||||||
critical: 2.0
|
|
||||||
};
|
|
||||||
|
|
||||||
return baseTime * priorityMultiplier[task.priority.value];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Repository Interfaces & Implementations
|
|
||||||
|
|
||||||
### Task Repository Interface
|
|
||||||
```typescript
|
|
||||||
// src/core/domains/task-management/repositories/task.repository.ts
|
|
||||||
export interface ITaskRepository {
|
|
||||||
save(task: Task): Promise<void>;
|
|
||||||
findById(id: TaskId): Promise<Task | null>;
|
|
||||||
findByAgentId(agentId: string): Promise<Task[]>;
|
|
||||||
findByStatus(status: TaskStatus): Promise<Task[]>;
|
|
||||||
findPendingTasks(): Promise<Task[]>;
|
|
||||||
delete(id: TaskId): Promise<void>;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### SQLite Implementation
|
|
||||||
```typescript
|
|
||||||
// src/core/domains/task-management/repositories/sqlite-task.repository.ts
|
|
||||||
@Injectable()
|
|
||||||
export class SqliteTaskRepository implements ITaskRepository {
|
|
||||||
constructor(
|
|
||||||
@Inject('Database') private db: Database,
|
|
||||||
@Inject('Logger') private logger: ILogger
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async save(task: Task): Promise<void> {
|
|
||||||
const sql = `
|
|
||||||
INSERT OR REPLACE INTO tasks (
|
|
||||||
id, description, priority, status, assigned_agent_id, created_at, updated_at
|
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
||||||
`;
|
|
||||||
|
|
||||||
await this.db.run(sql, [
|
|
||||||
task.id.value,
|
|
||||||
task.description,
|
|
||||||
task.priority.value,
|
|
||||||
task.status.value,
|
|
||||||
task.assignedAgentId,
|
|
||||||
task.createdAt.toISOString(),
|
|
||||||
task.updatedAt.toISOString()
|
|
||||||
]);
|
|
||||||
|
|
||||||
this.logger.debug(`Task saved: ${task.id.value}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async findById(id: TaskId): Promise<Task | null> {
|
|
||||||
const sql = 'SELECT * FROM tasks WHERE id = ?';
|
|
||||||
const row = await this.db.get(sql, [id.value]);
|
|
||||||
|
|
||||||
return row ? this.mapRowToTask(row) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async findPendingTasks(): Promise<Task[]> {
|
|
||||||
const sql = 'SELECT * FROM tasks WHERE status = ? ORDER BY priority DESC, created_at ASC';
|
|
||||||
const rows = await this.db.all(sql, ['pending']);
|
|
||||||
|
|
||||||
return rows.map(row => this.mapRowToTask(row));
|
|
||||||
}
|
|
||||||
|
|
||||||
private mapRowToTask(row: any): Task {
|
|
||||||
return Task.reconstitute({
|
|
||||||
id: TaskId.fromString(row.id),
|
|
||||||
description: row.description,
|
|
||||||
priority: Priority.fromString(row.priority),
|
|
||||||
status: TaskStatus.fromString(row.status),
|
|
||||||
assignedAgentId: row.assigned_agent_id,
|
|
||||||
createdAt: new Date(row.created_at),
|
|
||||||
updatedAt: new Date(row.updated_at)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Application Layer
|
|
||||||
|
|
||||||
### Use Case Implementation
|
|
||||||
```typescript
|
|
||||||
// src/core/application/use-cases/assign-task.use-case.ts
|
|
||||||
@Injectable()
|
|
||||||
export class AssignTaskUseCase {
|
|
||||||
constructor(
|
|
||||||
@Inject('TaskRepository') private taskRepository: ITaskRepository,
|
|
||||||
@Inject('AgentRepository') private agentRepository: IAgentRepository,
|
|
||||||
@Inject('DomainEventBus') private eventBus: DomainEventBus,
|
|
||||||
@Inject('Logger') private logger: ILogger
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async execute(command: AssignTaskCommand): Promise<AssignTaskResult> {
|
|
||||||
try {
|
|
||||||
// 1. Validate command
|
|
||||||
await this.validateCommand(command);
|
|
||||||
|
|
||||||
// 2. Load aggregates
|
|
||||||
const task = await this.taskRepository.findById(command.taskId);
|
|
||||||
if (!task) {
|
|
||||||
throw new TaskNotFoundError(command.taskId);
|
|
||||||
}
|
|
||||||
|
|
||||||
const agent = await this.agentRepository.findById(command.agentId);
|
|
||||||
if (!agent) {
|
|
||||||
throw new AgentNotFoundError(command.agentId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Business logic
|
|
||||||
if (!agent.canAcceptTask(task)) {
|
|
||||||
throw new AgentCannotAcceptTaskError(command.agentId, command.taskId);
|
|
||||||
}
|
|
||||||
|
|
||||||
task.assignTo(command.agentId);
|
|
||||||
agent.acceptTask(task.id);
|
|
||||||
|
|
||||||
// 4. Persist changes
|
|
||||||
await Promise.all([
|
|
||||||
this.taskRepository.save(task),
|
|
||||||
this.agentRepository.save(agent)
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 5. Publish domain events
|
|
||||||
const events = [
|
|
||||||
...task.getUncommittedEvents(),
|
|
||||||
...agent.getUncommittedEvents()
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const event of events) {
|
|
||||||
await this.eventBus.publish(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
task.markEventsAsCommitted();
|
|
||||||
agent.markEventsAsCommitted();
|
|
||||||
|
|
||||||
// 6. Return result
|
|
||||||
this.logger.info(`Task ${command.taskId.value} assigned to agent ${command.agentId}`);
|
|
||||||
|
|
||||||
return AssignTaskResult.success({
|
|
||||||
taskId: task.id,
|
|
||||||
agentId: command.agentId,
|
|
||||||
assignedAt: new Date()
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(`Failed to assign task ${command.taskId.value}:`, error);
|
|
||||||
return AssignTaskResult.failure(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async validateCommand(command: AssignTaskCommand): Promise<void> {
|
|
||||||
if (!command.taskId) {
|
|
||||||
throw new ValidationError('Task ID is required');
|
|
||||||
}
|
|
||||||
if (!command.agentId) {
|
|
||||||
throw new ValidationError('Agent ID is required');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Dependency Injection Setup
|
|
||||||
|
|
||||||
### Container Configuration
|
|
||||||
```typescript
|
|
||||||
// src/core/shared/infrastructure/dependency-container.ts
|
|
||||||
import { Container } from 'inversify';
|
|
||||||
import { TYPES } from './types';
|
|
||||||
|
|
||||||
export class DependencyContainer {
|
|
||||||
private container: Container;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.container = new Container();
|
|
||||||
this.setupBindings();
|
|
||||||
}
|
|
||||||
|
|
||||||
private setupBindings(): void {
|
|
||||||
// Repositories
|
|
||||||
this.container.bind<ITaskRepository>(TYPES.TaskRepository)
|
|
||||||
.to(SqliteTaskRepository)
|
|
||||||
.inSingletonScope();
|
|
||||||
|
|
||||||
this.container.bind<IAgentRepository>(TYPES.AgentRepository)
|
|
||||||
.to(SqliteAgentRepository)
|
|
||||||
.inSingletonScope();
|
|
||||||
|
|
||||||
// Services
|
|
||||||
this.container.bind<TaskSchedulingService>(TYPES.TaskSchedulingService)
|
|
||||||
.to(TaskSchedulingService)
|
|
||||||
.inSingletonScope();
|
|
||||||
|
|
||||||
// Use Cases
|
|
||||||
this.container.bind<AssignTaskUseCase>(TYPES.AssignTaskUseCase)
|
|
||||||
.to(AssignTaskUseCase)
|
|
||||||
.inSingletonScope();
|
|
||||||
|
|
||||||
// Infrastructure
|
|
||||||
this.container.bind<ILogger>(TYPES.Logger)
|
|
||||||
.to(ConsoleLogger)
|
|
||||||
.inSingletonScope();
|
|
||||||
|
|
||||||
this.container.bind<DomainEventBus>(TYPES.DomainEventBus)
|
|
||||||
.to(InMemoryDomainEventBus)
|
|
||||||
.inSingletonScope();
|
|
||||||
}
|
|
||||||
|
|
||||||
get<T>(serviceIdentifier: symbol): T {
|
|
||||||
return this.container.get<T>(serviceIdentifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
bind<T>(serviceIdentifier: symbol): BindingToSyntax<T> {
|
|
||||||
return this.container.bind<T>(serviceIdentifier);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Modern TypeScript Configuration
|
|
||||||
|
|
||||||
### Strict TypeScript Setup
|
|
||||||
```json
|
|
||||||
// tsconfig.json
|
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "ES2022",
|
|
||||||
"lib": ["ES2022"],
|
|
||||||
"module": "NodeNext",
|
|
||||||
"moduleResolution": "NodeNext",
|
|
||||||
"declaration": true,
|
|
||||||
"outDir": "./dist",
|
|
||||||
"strict": true,
|
|
||||||
"exactOptionalPropertyTypes": true,
|
|
||||||
"noImplicitReturns": true,
|
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
"noUncheckedIndexedAccess": true,
|
|
||||||
"noImplicitOverride": true,
|
|
||||||
"experimentalDecorators": true,
|
|
||||||
"emitDecoratorMetadata": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"allowSyntheticDefaultImports": true,
|
|
||||||
"baseUrl": ".",
|
|
||||||
"paths": {
|
|
||||||
"@/*": ["src/*"],
|
|
||||||
"@core/*": ["src/core/*"],
|
|
||||||
"@shared/*": ["src/core/shared/*"],
|
|
||||||
"@domains/*": ["src/core/domains/*"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"include": ["src/**/*"],
|
|
||||||
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Testing Implementation
|
|
||||||
|
|
||||||
### Domain Unit Tests
|
|
||||||
```typescript
|
|
||||||
// src/core/domains/task-management/__tests__/entities/task.entity.test.ts
|
|
||||||
describe('Task Entity', () => {
|
|
||||||
let task: Task;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
task = Task.create('Test task', Priority.medium());
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('creation', () => {
|
|
||||||
it('should create task with pending status', () => {
|
|
||||||
expect(task.status.isPending()).toBe(true);
|
|
||||||
expect(task.description).toBe('Test task');
|
|
||||||
expect(task.priority.equals(Priority.medium())).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should generate unique ID', () => {
|
|
||||||
const task1 = Task.create('Task 1', Priority.low());
|
|
||||||
const task2 = Task.create('Task 2', Priority.low());
|
|
||||||
|
|
||||||
expect(task1.id.equals(task2.id)).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('assignment', () => {
|
|
||||||
it('should assign to agent and change status', () => {
|
|
||||||
const agentId = 'agent-123';
|
|
||||||
|
|
||||||
task.assignTo(agentId);
|
|
||||||
|
|
||||||
expect(task.assignedAgentId).toBe(agentId);
|
|
||||||
expect(task.status.isAssigned()).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should emit TaskAssignedEvent when assigned', () => {
|
|
||||||
const agentId = 'agent-123';
|
|
||||||
|
|
||||||
task.assignTo(agentId);
|
|
||||||
|
|
||||||
const events = task.getUncommittedEvents();
|
|
||||||
expect(events).toHaveLength(1);
|
|
||||||
expect(events[0]).toBeInstanceOf(TaskAssignedEvent);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not allow assignment of completed task', () => {
|
|
||||||
task.assignTo('agent-123');
|
|
||||||
task.complete(TaskResult.success('done'));
|
|
||||||
|
|
||||||
expect(() => task.assignTo('agent-456'))
|
|
||||||
.toThrow('Cannot assign completed task');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Integration Tests
|
|
||||||
```typescript
|
|
||||||
// src/core/domains/task-management/__tests__/integration/task-repository.integration.test.ts
|
|
||||||
describe('TaskRepository Integration', () => {
|
|
||||||
let repository: SqliteTaskRepository;
|
|
||||||
let db: Database;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
db = new Database(':memory:');
|
|
||||||
await setupTasksTable(db);
|
|
||||||
repository = new SqliteTaskRepository(db, new ConsoleLogger());
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(async () => {
|
|
||||||
await db.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should save and retrieve task', async () => {
|
|
||||||
const task = Task.create('Test task', Priority.high());
|
|
||||||
|
|
||||||
await repository.save(task);
|
|
||||||
const retrieved = await repository.findById(task.id);
|
|
||||||
|
|
||||||
expect(retrieved).toBeDefined();
|
|
||||||
expect(retrieved!.id.equals(task.id)).toBe(true);
|
|
||||||
expect(retrieved!.description).toBe('Test task');
|
|
||||||
expect(retrieved!.priority.equals(Priority.high())).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should find pending tasks ordered by priority', async () => {
|
|
||||||
const lowTask = Task.create('Low priority', Priority.low());
|
|
||||||
const highTask = Task.create('High priority', Priority.high());
|
|
||||||
|
|
||||||
await repository.save(lowTask);
|
|
||||||
await repository.save(highTask);
|
|
||||||
|
|
||||||
const pending = await repository.findPendingTasks();
|
|
||||||
|
|
||||||
expect(pending).toHaveLength(2);
|
|
||||||
expect(pending[0].id.equals(highTask.id)).toBe(true); // High priority first
|
|
||||||
expect(pending[1].id.equals(lowTask.id)).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Performance Optimizations
|
|
||||||
|
|
||||||
### Entity Caching
|
|
||||||
```typescript
|
|
||||||
// src/core/shared/infrastructure/entity-cache.ts
|
|
||||||
@Injectable()
|
|
||||||
export class EntityCache<T extends Entity<any>> {
|
|
||||||
private cache = new Map<string, { entity: T; timestamp: number }>();
|
|
||||||
private readonly ttl: number = 300000; // 5 minutes
|
|
||||||
|
|
||||||
set(id: string, entity: T): void {
|
|
||||||
this.cache.set(id, { entity, timestamp: Date.now() });
|
|
||||||
}
|
|
||||||
|
|
||||||
get(id: string): T | null {
|
|
||||||
const cached = this.cache.get(id);
|
|
||||||
if (!cached) return null;
|
|
||||||
|
|
||||||
// Check TTL
|
|
||||||
if (Date.now() - cached.timestamp > this.ttl) {
|
|
||||||
this.cache.delete(id);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return cached.entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
invalidate(id: string): void {
|
|
||||||
this.cache.delete(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
clear(): void {
|
|
||||||
this.cache.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Success Metrics
|
|
||||||
|
|
||||||
- [ ] **Domain Isolation**: 100% clean dependency boundaries
|
|
||||||
- [ ] **Test Coverage**: >90% unit test coverage for domain logic
|
|
||||||
- [ ] **Type Safety**: Strict TypeScript compilation with zero any types
|
|
||||||
- [ ] **Performance**: <50ms average use case execution time
|
|
||||||
- [ ] **Memory Efficiency**: <100MB heap usage for core domains
|
|
||||||
- [ ] **Plugin Architecture**: Modular domain loading capability
|
|
||||||
|
|
||||||
## Related V3 Skills
|
|
||||||
|
|
||||||
- `v3-ddd-architecture` - DDD architectural design
|
|
||||||
- `v3-mcp-optimization` - MCP server integration
|
|
||||||
- `v3-memory-unification` - AgentDB repository integration
|
|
||||||
- `v3-swarm-coordination` - Swarm domain implementation
|
|
||||||
|
|
||||||
## Usage Examples
|
|
||||||
|
|
||||||
### Complete Core Implementation
|
|
||||||
```bash
|
|
||||||
# Full core module implementation
|
|
||||||
Task("Core implementation",
|
|
||||||
"Implement all core domains with DDD patterns and comprehensive testing",
|
|
||||||
"core-implementer")
|
|
||||||
```
|
|
||||||
|
|
||||||
### Domain-Specific Implementation
|
|
||||||
```bash
|
|
||||||
# Single domain implementation
|
|
||||||
Task("Task domain implementation",
|
|
||||||
"Implement task management domain with entities, services, and repositories",
|
|
||||||
"core-implementer")
|
|
||||||
```
|
|
||||||
@ -1,442 +0,0 @@
|
|||||||
---
|
|
||||||
name: "V3 DDD Architecture"
|
|
||||||
description: "Domain-Driven Design architecture for Codex-flow v3. Implements modular, bounded context architecture with clean separation of concerns and microkernel pattern."
|
|
||||||
---
|
|
||||||
|
|
||||||
# V3 DDD Architecture
|
|
||||||
|
|
||||||
## What This Skill Does
|
|
||||||
|
|
||||||
Designs and implements Domain-Driven Design (DDD) architecture for Codex-flow v3, decomposing god objects into bounded contexts, implementing clean architecture patterns, and enabling modular, testable code structure.
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Initialize DDD architecture analysis
|
|
||||||
Task("Architecture analysis", "Analyze current architecture and design DDD boundaries", "core-architect")
|
|
||||||
|
|
||||||
# Domain modeling (parallel)
|
|
||||||
Task("Domain decomposition", "Break down orchestrator god object into domains", "core-architect")
|
|
||||||
Task("Context mapping", "Map bounded contexts and relationships", "core-architect")
|
|
||||||
Task("Interface design", "Design clean domain interfaces", "core-architect")
|
|
||||||
```
|
|
||||||
|
|
||||||
## DDD Implementation Strategy
|
|
||||||
|
|
||||||
### Current Architecture Analysis
|
|
||||||
```
|
|
||||||
├── PROBLEMATIC: core/orchestrator.ts (1,440 lines - GOD OBJECT)
|
|
||||||
│ ├── Task management responsibilities
|
|
||||||
│ ├── Session management responsibilities
|
|
||||||
│ ├── Health monitoring responsibilities
|
|
||||||
│ ├── Lifecycle management responsibilities
|
|
||||||
│ └── Event coordination responsibilities
|
|
||||||
│
|
|
||||||
└── TARGET: Modular DDD Architecture
|
|
||||||
├── core/domains/
|
|
||||||
│ ├── task-management/
|
|
||||||
│ ├── session-management/
|
|
||||||
│ ├── health-monitoring/
|
|
||||||
│ ├── lifecycle-management/
|
|
||||||
│ └── event-coordination/
|
|
||||||
└── core/shared/
|
|
||||||
├── interfaces/
|
|
||||||
├── value-objects/
|
|
||||||
└── domain-events/
|
|
||||||
```
|
|
||||||
|
|
||||||
### Domain Boundaries
|
|
||||||
|
|
||||||
#### 1. Task Management Domain
|
|
||||||
```typescript
|
|
||||||
// core/domains/task-management/
|
|
||||||
interface TaskManagementDomain {
|
|
||||||
// Entities
|
|
||||||
Task: TaskEntity;
|
|
||||||
TaskQueue: TaskQueueEntity;
|
|
||||||
|
|
||||||
// Value Objects
|
|
||||||
TaskId: TaskIdVO;
|
|
||||||
TaskStatus: TaskStatusVO;
|
|
||||||
Priority: PriorityVO;
|
|
||||||
|
|
||||||
// Services
|
|
||||||
TaskScheduler: TaskSchedulingService;
|
|
||||||
TaskValidator: TaskValidationService;
|
|
||||||
|
|
||||||
// Repository
|
|
||||||
TaskRepository: ITaskRepository;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2. Session Management Domain
|
|
||||||
```typescript
|
|
||||||
// core/domains/session-management/
|
|
||||||
interface SessionManagementDomain {
|
|
||||||
// Entities
|
|
||||||
Session: SessionEntity;
|
|
||||||
SessionState: SessionStateEntity;
|
|
||||||
|
|
||||||
// Value Objects
|
|
||||||
SessionId: SessionIdVO;
|
|
||||||
SessionStatus: SessionStatusVO;
|
|
||||||
|
|
||||||
// Services
|
|
||||||
SessionLifecycle: SessionLifecycleService;
|
|
||||||
SessionPersistence: SessionPersistenceService;
|
|
||||||
|
|
||||||
// Repository
|
|
||||||
SessionRepository: ISessionRepository;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3. Health Monitoring Domain
|
|
||||||
```typescript
|
|
||||||
// core/domains/health-monitoring/
|
|
||||||
interface HealthMonitoringDomain {
|
|
||||||
// Entities
|
|
||||||
HealthCheck: HealthCheckEntity;
|
|
||||||
Metric: MetricEntity;
|
|
||||||
|
|
||||||
// Value Objects
|
|
||||||
HealthStatus: HealthStatusVO;
|
|
||||||
Threshold: ThresholdVO;
|
|
||||||
|
|
||||||
// Services
|
|
||||||
HealthCollector: HealthCollectionService;
|
|
||||||
AlertManager: AlertManagementService;
|
|
||||||
|
|
||||||
// Repository
|
|
||||||
MetricsRepository: IMetricsRepository;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Microkernel Architecture Pattern
|
|
||||||
|
|
||||||
### Core Kernel
|
|
||||||
```typescript
|
|
||||||
// core/kernel/Codex-flow-kernel.ts
|
|
||||||
export class ClaudeFlowKernel {
|
|
||||||
private domains: Map<string, Domain> = new Map();
|
|
||||||
private eventBus: DomainEventBus;
|
|
||||||
private dependencyContainer: Container;
|
|
||||||
|
|
||||||
async initialize(): Promise<void> {
|
|
||||||
// Load core domains
|
|
||||||
await this.loadDomain('task-management', new TaskManagementDomain());
|
|
||||||
await this.loadDomain('session-management', new SessionManagementDomain());
|
|
||||||
await this.loadDomain('health-monitoring', new HealthMonitoringDomain());
|
|
||||||
|
|
||||||
// Wire up domain events
|
|
||||||
this.setupDomainEventHandlers();
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadDomain(name: string, domain: Domain): Promise<void> {
|
|
||||||
await domain.initialize(this.dependencyContainer);
|
|
||||||
this.domains.set(name, domain);
|
|
||||||
}
|
|
||||||
|
|
||||||
getDomain<T extends Domain>(name: string): T {
|
|
||||||
const domain = this.domains.get(name);
|
|
||||||
if (!domain) {
|
|
||||||
throw new DomainNotLoadedError(name);
|
|
||||||
}
|
|
||||||
return domain as T;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Plugin Architecture
|
|
||||||
```typescript
|
|
||||||
// core/plugins/
|
|
||||||
interface DomainPlugin {
|
|
||||||
name: string;
|
|
||||||
version: string;
|
|
||||||
dependencies: string[];
|
|
||||||
|
|
||||||
initialize(kernel: ClaudeFlowKernel): Promise<void>;
|
|
||||||
shutdown(): Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Example: Swarm Coordination Plugin
|
|
||||||
export class SwarmCoordinationPlugin implements DomainPlugin {
|
|
||||||
name = 'swarm-coordination';
|
|
||||||
version = '3.0.0';
|
|
||||||
dependencies = ['task-management', 'session-management'];
|
|
||||||
|
|
||||||
async initialize(kernel: ClaudeFlowKernel): Promise<void> {
|
|
||||||
const taskDomain = kernel.getDomain<TaskManagementDomain>('task-management');
|
|
||||||
const sessionDomain = kernel.getDomain<SessionManagementDomain>('session-management');
|
|
||||||
|
|
||||||
// Register swarm coordination services
|
|
||||||
this.swarmCoordinator = new UnifiedSwarmCoordinator(taskDomain, sessionDomain);
|
|
||||||
kernel.registerService('swarm-coordinator', this.swarmCoordinator);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Domain Events & Integration
|
|
||||||
|
|
||||||
### Event-Driven Communication
|
|
||||||
```typescript
|
|
||||||
// core/shared/domain-events/
|
|
||||||
abstract class DomainEvent {
|
|
||||||
public readonly eventId: string;
|
|
||||||
public readonly aggregateId: string;
|
|
||||||
public readonly occurredOn: Date;
|
|
||||||
public readonly eventVersion: number;
|
|
||||||
|
|
||||||
constructor(aggregateId: string) {
|
|
||||||
this.eventId = crypto.randomUUID();
|
|
||||||
this.aggregateId = aggregateId;
|
|
||||||
this.occurredOn = new Date();
|
|
||||||
this.eventVersion = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Task domain events
|
|
||||||
export class TaskAssignedEvent extends DomainEvent {
|
|
||||||
constructor(
|
|
||||||
taskId: string,
|
|
||||||
public readonly agentId: string,
|
|
||||||
public readonly priority: Priority
|
|
||||||
) {
|
|
||||||
super(taskId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class TaskCompletedEvent extends DomainEvent {
|
|
||||||
constructor(
|
|
||||||
taskId: string,
|
|
||||||
public readonly result: TaskResult,
|
|
||||||
public readonly duration: number
|
|
||||||
) {
|
|
||||||
super(taskId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Event handlers
|
|
||||||
@EventHandler(TaskCompletedEvent)
|
|
||||||
export class TaskCompletedHandler {
|
|
||||||
constructor(
|
|
||||||
private metricsRepository: IMetricsRepository,
|
|
||||||
private sessionService: SessionLifecycleService
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async handle(event: TaskCompletedEvent): Promise<void> {
|
|
||||||
// Update metrics
|
|
||||||
await this.metricsRepository.recordTaskCompletion(
|
|
||||||
event.aggregateId,
|
|
||||||
event.duration
|
|
||||||
);
|
|
||||||
|
|
||||||
// Update session state
|
|
||||||
await this.sessionService.markTaskCompleted(
|
|
||||||
event.aggregateId,
|
|
||||||
event.result
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Clean Architecture Layers
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Architecture layers
|
|
||||||
┌─────────────────────────────────────────┐
|
|
||||||
│ Presentation │ ← CLI, API, UI
|
|
||||||
├─────────────────────────────────────────┤
|
|
||||||
│ Application │ ← Use Cases, Commands
|
|
||||||
├─────────────────────────────────────────┤
|
|
||||||
│ Domain │ ← Entities, Services, Events
|
|
||||||
├─────────────────────────────────────────┤
|
|
||||||
│ Infrastructure │ ← DB, MCP, External APIs
|
|
||||||
└─────────────────────────────────────────┘
|
|
||||||
|
|
||||||
// Dependency direction: Outside → Inside
|
|
||||||
// Domain layer has NO external dependencies
|
|
||||||
```
|
|
||||||
|
|
||||||
### Application Layer (Use Cases)
|
|
||||||
```typescript
|
|
||||||
// core/application/use-cases/
|
|
||||||
export class AssignTaskUseCase {
|
|
||||||
constructor(
|
|
||||||
private taskRepository: ITaskRepository,
|
|
||||||
private agentRepository: IAgentRepository,
|
|
||||||
private eventBus: DomainEventBus
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async execute(command: AssignTaskCommand): Promise<TaskResult> {
|
|
||||||
// 1. Validate command
|
|
||||||
await this.validateCommand(command);
|
|
||||||
|
|
||||||
// 2. Load aggregates
|
|
||||||
const task = await this.taskRepository.findById(command.taskId);
|
|
||||||
const agent = await this.agentRepository.findById(command.agentId);
|
|
||||||
|
|
||||||
// 3. Business logic (in domain)
|
|
||||||
task.assignTo(agent);
|
|
||||||
|
|
||||||
// 4. Persist changes
|
|
||||||
await this.taskRepository.save(task);
|
|
||||||
|
|
||||||
// 5. Publish domain events
|
|
||||||
task.getUncommittedEvents().forEach(event =>
|
|
||||||
this.eventBus.publish(event)
|
|
||||||
);
|
|
||||||
|
|
||||||
// 6. Return result
|
|
||||||
return TaskResult.success(task);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Module Configuration
|
|
||||||
|
|
||||||
### Bounded Context Modules
|
|
||||||
```typescript
|
|
||||||
// core/domains/task-management/module.ts
|
|
||||||
export const taskManagementModule = {
|
|
||||||
name: 'task-management',
|
|
||||||
|
|
||||||
entities: [
|
|
||||||
TaskEntity,
|
|
||||||
TaskQueueEntity
|
|
||||||
],
|
|
||||||
|
|
||||||
valueObjects: [
|
|
||||||
TaskIdVO,
|
|
||||||
TaskStatusVO,
|
|
||||||
PriorityVO
|
|
||||||
],
|
|
||||||
|
|
||||||
services: [
|
|
||||||
TaskSchedulingService,
|
|
||||||
TaskValidationService
|
|
||||||
],
|
|
||||||
|
|
||||||
repositories: [
|
|
||||||
{ provide: ITaskRepository, useClass: SqliteTaskRepository }
|
|
||||||
],
|
|
||||||
|
|
||||||
eventHandlers: [
|
|
||||||
TaskAssignedHandler,
|
|
||||||
TaskCompletedHandler
|
|
||||||
]
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
## Migration Strategy
|
|
||||||
|
|
||||||
### Phase 1: Extract Domain Services
|
|
||||||
```typescript
|
|
||||||
// Extract services from orchestrator.ts
|
|
||||||
const extractionPlan = {
|
|
||||||
week1: [
|
|
||||||
'TaskManager → task-management domain',
|
|
||||||
'SessionManager → session-management domain'
|
|
||||||
],
|
|
||||||
week2: [
|
|
||||||
'HealthMonitor → health-monitoring domain',
|
|
||||||
'LifecycleManager → lifecycle-management domain'
|
|
||||||
],
|
|
||||||
week3: [
|
|
||||||
'EventCoordinator → event-coordination domain',
|
|
||||||
'Wire up domain events'
|
|
||||||
]
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 2: Implement Clean Interfaces
|
|
||||||
```typescript
|
|
||||||
// Clean separation with dependency injection
|
|
||||||
export class TaskController {
|
|
||||||
constructor(
|
|
||||||
@Inject('AssignTaskUseCase') private assignTask: AssignTaskUseCase,
|
|
||||||
@Inject('CompleteTaskUseCase') private completeTask: CompleteTaskUseCase
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async assign(request: AssignTaskRequest): Promise<TaskResponse> {
|
|
||||||
const command = AssignTaskCommand.fromRequest(request);
|
|
||||||
const result = await this.assignTask.execute(command);
|
|
||||||
return TaskResponse.fromResult(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 3: Plugin System
|
|
||||||
```typescript
|
|
||||||
// Enable plugin-based extensions
|
|
||||||
const pluginSystem = {
|
|
||||||
core: ['task-management', 'session-management', 'health-monitoring'],
|
|
||||||
optional: ['swarm-coordination', 'learning-integration', 'performance-monitoring']
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
## Testing Strategy
|
|
||||||
|
|
||||||
### Domain Testing (London School TDD)
|
|
||||||
```typescript
|
|
||||||
// Pure domain logic testing
|
|
||||||
describe('Task Entity', () => {
|
|
||||||
let task: TaskEntity;
|
|
||||||
let mockAgent: jest.Mocked<AgentEntity>;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
task = new TaskEntity(TaskId.create(), 'Test task');
|
|
||||||
mockAgent = createMock<AgentEntity>();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should assign to agent when valid', () => {
|
|
||||||
mockAgent.canAcceptTask.mockReturnValue(true);
|
|
||||||
|
|
||||||
task.assignTo(mockAgent);
|
|
||||||
|
|
||||||
expect(task.assignedAgent).toBe(mockAgent);
|
|
||||||
expect(task.status.value).toBe('assigned');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should emit TaskAssignedEvent when assigned', () => {
|
|
||||||
mockAgent.canAcceptTask.mockReturnValue(true);
|
|
||||||
|
|
||||||
task.assignTo(mockAgent);
|
|
||||||
|
|
||||||
const events = task.getUncommittedEvents();
|
|
||||||
expect(events).toHaveLength(1);
|
|
||||||
expect(events[0]).toBeInstanceOf(TaskAssignedEvent);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Success Metrics
|
|
||||||
|
|
||||||
- [ ] **God Object Elimination**: orchestrator.ts (1,440 lines) → 5 focused domains (<300 lines each)
|
|
||||||
- [ ] **Bounded Context Isolation**: 100% domain independence
|
|
||||||
- [ ] **Plugin Architecture**: Core + optional modules loading
|
|
||||||
- [ ] **Clean Architecture**: Dependency inversion maintained
|
|
||||||
- [ ] **Event-Driven Communication**: Loose coupling between domains
|
|
||||||
- [ ] **Test Coverage**: >90% domain logic coverage
|
|
||||||
|
|
||||||
## Related V3 Skills
|
|
||||||
|
|
||||||
- `v3-core-implementation` - Implementation of DDD domains
|
|
||||||
- `v3-memory-unification` - AgentDB integration within bounded contexts
|
|
||||||
- `v3-swarm-coordination` - Swarm coordination as domain plugin
|
|
||||||
- `v3-performance-optimization` - Performance optimization across domains
|
|
||||||
|
|
||||||
## Usage Examples
|
|
||||||
|
|
||||||
### Complete Domain Extraction
|
|
||||||
```bash
|
|
||||||
# Full DDD architecture implementation
|
|
||||||
Task("DDD architecture implementation",
|
|
||||||
"Extract orchestrator into DDD domains with clean architecture",
|
|
||||||
"core-architect")
|
|
||||||
```
|
|
||||||
|
|
||||||
### Plugin Development
|
|
||||||
```bash
|
|
||||||
# Create domain plugin
|
|
||||||
npm run create:plugin -- --name swarm-coordination --template domain
|
|
||||||
```
|
|
||||||
@ -1,241 +0,0 @@
|
|||||||
---
|
|
||||||
name: "V3 Deep Integration"
|
|
||||||
description: "Deep agentic-flow@alpha integration implementing ADR-001. Eliminates 10,000+ duplicate lines by building Codex-flow as specialized extension rather than parallel implementation."
|
|
||||||
---
|
|
||||||
|
|
||||||
# V3 Deep Integration
|
|
||||||
|
|
||||||
## What This Skill Does
|
|
||||||
|
|
||||||
Transforms Codex-flow from parallel implementation to specialized extension of agentic-flow@alpha, eliminating massive code duplication while achieving performance improvements and feature parity.
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Initialize deep integration
|
|
||||||
Task("Integration architecture", "Design agentic-flow@alpha adapter layer", "v3-integration-architect")
|
|
||||||
|
|
||||||
# Feature integration (parallel)
|
|
||||||
Task("SONA integration", "Integrate 5 SONA learning modes", "v3-integration-architect")
|
|
||||||
Task("Flash Attention", "Implement 2.49x-7.47x speedup", "v3-integration-architect")
|
|
||||||
Task("AgentDB coordination", "Setup 150x-12,500x search", "v3-integration-architect")
|
|
||||||
```
|
|
||||||
|
|
||||||
## Code Deduplication Strategy
|
|
||||||
|
|
||||||
### Current Overlap → Integration
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────┐
|
|
||||||
│ Codex-flow agentic-flow │
|
|
||||||
├─────────────────────────────────────────┤
|
|
||||||
│ SwarmCoordinator → Swarm System │ 80% overlap (eliminate)
|
|
||||||
│ AgentManager → Agent Lifecycle │ 70% overlap (eliminate)
|
|
||||||
│ TaskScheduler → Task Execution │ 60% overlap (eliminate)
|
|
||||||
│ SessionManager → Session Mgmt │ 50% overlap (eliminate)
|
|
||||||
└─────────────────────────────────────────┘
|
|
||||||
|
|
||||||
TARGET: <5,000 lines (vs 15,000+ currently)
|
|
||||||
```
|
|
||||||
|
|
||||||
## agentic-flow@alpha Feature Integration
|
|
||||||
|
|
||||||
### SONA Learning Modes
|
|
||||||
```typescript
|
|
||||||
class SONAIntegration {
|
|
||||||
async initializeMode(mode: SONAMode): Promise<void> {
|
|
||||||
switch(mode) {
|
|
||||||
case 'real-time': // ~0.05ms adaptation
|
|
||||||
case 'balanced': // general purpose
|
|
||||||
case 'research': // deep exploration
|
|
||||||
case 'edge': // resource-constrained
|
|
||||||
case 'batch': // high-throughput
|
|
||||||
}
|
|
||||||
await this.agenticFlow.sona.setMode(mode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Flash Attention Integration
|
|
||||||
```typescript
|
|
||||||
class FlashAttentionIntegration {
|
|
||||||
async optimizeAttention(): Promise<AttentionResult> {
|
|
||||||
return this.agenticFlow.attention.flashAttention({
|
|
||||||
speedupTarget: '2.49x-7.47x',
|
|
||||||
memoryReduction: '50-75%',
|
|
||||||
mechanisms: ['multi-head', 'linear', 'local', 'global']
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### AgentDB Coordination
|
|
||||||
```typescript
|
|
||||||
class AgentDBIntegration {
|
|
||||||
async setupCrossAgentMemory(): Promise<void> {
|
|
||||||
await this.agentdb.enableCrossAgentSharing({
|
|
||||||
indexType: 'HNSW',
|
|
||||||
speedupTarget: '150x-12500x',
|
|
||||||
dimensions: 1536
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### MCP Tools Integration
|
|
||||||
```typescript
|
|
||||||
class MCPToolsIntegration {
|
|
||||||
async integrateBuiltinTools(): Promise<void> {
|
|
||||||
// Leverage 213 pre-built tools
|
|
||||||
const tools = await this.agenticFlow.mcp.getAvailableTools();
|
|
||||||
await this.registerClaudeFlowSpecificTools(tools);
|
|
||||||
|
|
||||||
// Use 19 hook types
|
|
||||||
const hookTypes = await this.agenticFlow.hooks.getTypes();
|
|
||||||
await this.configureClaudeFlowHooks(hookTypes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Migration Implementation
|
|
||||||
|
|
||||||
### Phase 1: Adapter Layer
|
|
||||||
```typescript
|
|
||||||
import { Agent as AgenticFlowAgent } from 'agentic-flow@alpha';
|
|
||||||
|
|
||||||
export class ClaudeFlowAgent extends AgenticFlowAgent {
|
|
||||||
async handleClaudeFlowTask(task: ClaudeTask): Promise<TaskResult> {
|
|
||||||
return this.executeWithSONA(task);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Backward compatibility
|
|
||||||
async legacyCompatibilityLayer(oldAPI: any): Promise<any> {
|
|
||||||
return this.adaptToNewAPI(oldAPI);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 2: System Migration
|
|
||||||
```typescript
|
|
||||||
class SystemMigration {
|
|
||||||
async migrateSwarmCoordination(): Promise<void> {
|
|
||||||
// Replace SwarmCoordinator (800+ lines) with agentic-flow Swarm
|
|
||||||
const swarmConfig = await this.extractSwarmConfig();
|
|
||||||
await this.agenticFlow.swarm.initialize(swarmConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
async migrateAgentManagement(): Promise<void> {
|
|
||||||
// Replace AgentManager (1,736+ lines) with agentic-flow lifecycle
|
|
||||||
const agents = await this.extractActiveAgents();
|
|
||||||
for (const agent of agents) {
|
|
||||||
await this.agenticFlow.agent.create(agent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async migrateTaskExecution(): Promise<void> {
|
|
||||||
// Replace TaskScheduler with agentic-flow task graph
|
|
||||||
const tasks = await this.extractTasks();
|
|
||||||
await this.agenticFlow.task.executeGraph(this.buildTaskGraph(tasks));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 3: Cleanup
|
|
||||||
```typescript
|
|
||||||
class CodeCleanup {
|
|
||||||
async removeDeprecatedCode(): Promise<void> {
|
|
||||||
// Remove massive duplicate implementations
|
|
||||||
await this.removeFile('src/core/SwarmCoordinator.ts'); // 800+ lines
|
|
||||||
await this.removeFile('src/agents/AgentManager.ts'); // 1,736+ lines
|
|
||||||
await this.removeFile('src/task/TaskScheduler.ts'); // 500+ lines
|
|
||||||
|
|
||||||
// Total reduction: 10,000+ → <5,000 lines
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## RL Algorithm Integration
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
class RLIntegration {
|
|
||||||
algorithms = [
|
|
||||||
'PPO', 'DQN', 'A2C', 'MCTS', 'Q-Learning',
|
|
||||||
'SARSA', 'Actor-Critic', 'Decision-Transformer'
|
|
||||||
];
|
|
||||||
|
|
||||||
async optimizeAgentBehavior(): Promise<void> {
|
|
||||||
for (const algorithm of this.algorithms) {
|
|
||||||
await this.agenticFlow.rl.train(algorithm, {
|
|
||||||
episodes: 1000,
|
|
||||||
rewardFunction: this.claudeFlowRewardFunction
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Performance Integration
|
|
||||||
|
|
||||||
### Flash Attention Targets
|
|
||||||
```typescript
|
|
||||||
const attentionBenchmark = {
|
|
||||||
baseline: 'current attention mechanism',
|
|
||||||
target: '2.49x-7.47x improvement',
|
|
||||||
memoryReduction: '50-75%',
|
|
||||||
implementation: 'agentic-flow@alpha Flash Attention'
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### AgentDB Search Performance
|
|
||||||
```typescript
|
|
||||||
const searchBenchmark = {
|
|
||||||
baseline: 'linear search in current systems',
|
|
||||||
target: '150x-12,500x via HNSW indexing',
|
|
||||||
implementation: 'agentic-flow@alpha AgentDB'
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
## Backward Compatibility
|
|
||||||
|
|
||||||
### Gradual Migration
|
|
||||||
```typescript
|
|
||||||
class BackwardCompatibility {
|
|
||||||
// Phase 1: Dual operation
|
|
||||||
async enableDualOperation(): Promise<void> {
|
|
||||||
this.oldSystem.continue();
|
|
||||||
this.newSystem.initialize();
|
|
||||||
this.syncState(this.oldSystem, this.newSystem);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 2: Feature-by-feature migration
|
|
||||||
async migrateGradually(): Promise<void> {
|
|
||||||
const features = this.getAllFeatures();
|
|
||||||
for (const feature of features) {
|
|
||||||
await this.migrateFeature(feature);
|
|
||||||
await this.validateFeatureParity(feature);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 3: Complete transition
|
|
||||||
async completeTransition(): Promise<void> {
|
|
||||||
await this.validateFullParity();
|
|
||||||
await this.deprecateOldSystem();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Success Metrics
|
|
||||||
|
|
||||||
- **Code Reduction**: <5,000 lines orchestration (vs 15,000+)
|
|
||||||
- **Performance**: 2.49x-7.47x Flash Attention speedup
|
|
||||||
- **Search**: 150x-12,500x AgentDB improvement
|
|
||||||
- **Memory**: 50-75% usage reduction
|
|
||||||
- **Feature Parity**: 100% v2 functionality maintained
|
|
||||||
- **SONA**: <0.05ms adaptation time
|
|
||||||
- **Integration**: All 213 MCP tools + 19 hook types available
|
|
||||||
|
|
||||||
## Related V3 Skills
|
|
||||||
|
|
||||||
- `v3-memory-unification` - Memory system integration
|
|
||||||
- `v3-performance-optimization` - Performance target validation
|
|
||||||
- `v3-swarm-coordination` - Swarm system migration
|
|
||||||
- `v3-security-overhaul` - Secure integration patterns
|
|
||||||
@ -1,777 +0,0 @@
|
|||||||
---
|
|
||||||
name: "V3 MCP Optimization"
|
|
||||||
description: "MCP server optimization and transport layer enhancement for Codex-flow v3. Implements connection pooling, load balancing, tool registry optimization, and performance monitoring for sub-100ms response times."
|
|
||||||
---
|
|
||||||
|
|
||||||
# V3 MCP Optimization
|
|
||||||
|
|
||||||
## What This Skill Does
|
|
||||||
|
|
||||||
Optimizes Codex-flow v3 MCP (Model Context Protocol) server implementation with advanced transport layer optimizations, connection pooling, load balancing, and comprehensive performance monitoring to achieve sub-100ms response times.
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Initialize MCP optimization analysis
|
|
||||||
Task("MCP architecture", "Analyze current MCP server performance and bottlenecks", "mcp-specialist")
|
|
||||||
|
|
||||||
# Optimization implementation (parallel)
|
|
||||||
Task("Connection pooling", "Implement MCP connection pooling and reuse", "mcp-specialist")
|
|
||||||
Task("Load balancing", "Add dynamic load balancing for MCP tools", "mcp-specialist")
|
|
||||||
Task("Transport optimization", "Optimize transport layer performance", "mcp-specialist")
|
|
||||||
```
|
|
||||||
|
|
||||||
## MCP Performance Architecture
|
|
||||||
|
|
||||||
### Current State Analysis
|
|
||||||
```
|
|
||||||
Current MCP Issues:
|
|
||||||
├── Cold Start Latency: ~1.8s MCP server init
|
|
||||||
├── Connection Overhead: New connection per request
|
|
||||||
├── Tool Registry: Linear search O(n) for 213+ tools
|
|
||||||
├── Transport Layer: No connection reuse
|
|
||||||
└── Memory Usage: No cleanup of idle connections
|
|
||||||
|
|
||||||
Target Performance:
|
|
||||||
├── Startup Time: <400ms (4.5x improvement)
|
|
||||||
├── Tool Lookup: <5ms (O(1) hash table)
|
|
||||||
├── Connection Reuse: 90%+ connection pool hits
|
|
||||||
├── Response Time: <100ms p95
|
|
||||||
└── Memory Efficiency: 50% reduction
|
|
||||||
```
|
|
||||||
|
|
||||||
### MCP Server Architecture
|
|
||||||
```typescript
|
|
||||||
// src/core/mcp/mcp-server.ts
|
|
||||||
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
||||||
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
||||||
|
|
||||||
interface OptimizedMCPConfig {
|
|
||||||
// Connection pooling
|
|
||||||
maxConnections: number;
|
|
||||||
idleTimeoutMs: number;
|
|
||||||
connectionReuseEnabled: boolean;
|
|
||||||
|
|
||||||
// Tool registry
|
|
||||||
toolCacheEnabled: boolean;
|
|
||||||
toolIndexType: 'hash' | 'trie';
|
|
||||||
|
|
||||||
// Performance
|
|
||||||
requestTimeoutMs: number;
|
|
||||||
batchingEnabled: boolean;
|
|
||||||
compressionEnabled: boolean;
|
|
||||||
|
|
||||||
// Monitoring
|
|
||||||
metricsEnabled: boolean;
|
|
||||||
healthCheckIntervalMs: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class OptimizedMCPServer {
|
|
||||||
private server: Server;
|
|
||||||
private connectionPool: ConnectionPool;
|
|
||||||
private toolRegistry: FastToolRegistry;
|
|
||||||
private loadBalancer: MCPLoadBalancer;
|
|
||||||
private metrics: MCPMetrics;
|
|
||||||
|
|
||||||
constructor(config: OptimizedMCPConfig) {
|
|
||||||
this.server = new Server({
|
|
||||||
name: 'Codex-flow-v3',
|
|
||||||
version: '3.0.0'
|
|
||||||
}, {
|
|
||||||
capabilities: {
|
|
||||||
tools: { listChanged: true },
|
|
||||||
resources: { subscribe: true, listChanged: true },
|
|
||||||
prompts: { listChanged: true }
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.connectionPool = new ConnectionPool(config);
|
|
||||||
this.toolRegistry = new FastToolRegistry(config.toolIndexType);
|
|
||||||
this.loadBalancer = new MCPLoadBalancer();
|
|
||||||
this.metrics = new MCPMetrics(config.metricsEnabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
async start(): Promise<void> {
|
|
||||||
// Pre-warm connection pool
|
|
||||||
await this.connectionPool.preWarm();
|
|
||||||
|
|
||||||
// Pre-build tool index
|
|
||||||
await this.toolRegistry.buildIndex();
|
|
||||||
|
|
||||||
// Setup request handlers with optimizations
|
|
||||||
this.setupOptimizedHandlers();
|
|
||||||
|
|
||||||
// Start health monitoring
|
|
||||||
this.startHealthMonitoring();
|
|
||||||
|
|
||||||
// Start server
|
|
||||||
const transport = new StdioServerTransport();
|
|
||||||
await this.server.connect(transport);
|
|
||||||
|
|
||||||
this.metrics.recordStartup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Connection Pool Implementation
|
|
||||||
|
|
||||||
### Advanced Connection Pooling
|
|
||||||
```typescript
|
|
||||||
// src/core/mcp/connection-pool.ts
|
|
||||||
interface PooledConnection {
|
|
||||||
id: string;
|
|
||||||
connection: MCPConnection;
|
|
||||||
lastUsed: number;
|
|
||||||
usageCount: number;
|
|
||||||
isHealthy: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ConnectionPool {
|
|
||||||
private pool: Map<string, PooledConnection> = new Map();
|
|
||||||
private readonly config: ConnectionPoolConfig;
|
|
||||||
private healthChecker: HealthChecker;
|
|
||||||
|
|
||||||
constructor(config: ConnectionPoolConfig) {
|
|
||||||
this.config = {
|
|
||||||
maxConnections: 50,
|
|
||||||
minConnections: 5,
|
|
||||||
idleTimeoutMs: 300000, // 5 minutes
|
|
||||||
maxUsageCount: 1000,
|
|
||||||
healthCheckIntervalMs: 30000,
|
|
||||||
...config
|
|
||||||
};
|
|
||||||
|
|
||||||
this.healthChecker = new HealthChecker(this.config.healthCheckIntervalMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getConnection(endpoint: string): Promise<MCPConnection> {
|
|
||||||
const start = performance.now();
|
|
||||||
|
|
||||||
// Try to get from pool first
|
|
||||||
const pooled = this.findAvailableConnection(endpoint);
|
|
||||||
if (pooled) {
|
|
||||||
pooled.lastUsed = Date.now();
|
|
||||||
pooled.usageCount++;
|
|
||||||
|
|
||||||
this.recordMetric('pool_hit', performance.now() - start);
|
|
||||||
return pooled.connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check pool capacity
|
|
||||||
if (this.pool.size >= this.config.maxConnections) {
|
|
||||||
await this.evictLeastUsedConnection();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create new connection
|
|
||||||
const connection = await this.createConnection(endpoint);
|
|
||||||
const pooledConn: PooledConnection = {
|
|
||||||
id: this.generateConnectionId(),
|
|
||||||
connection,
|
|
||||||
lastUsed: Date.now(),
|
|
||||||
usageCount: 1,
|
|
||||||
isHealthy: true
|
|
||||||
};
|
|
||||||
|
|
||||||
this.pool.set(pooledConn.id, pooledConn);
|
|
||||||
this.recordMetric('pool_miss', performance.now() - start);
|
|
||||||
|
|
||||||
return connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
async releaseConnection(connection: MCPConnection): Promise<void> {
|
|
||||||
// Mark connection as available for reuse
|
|
||||||
const pooled = this.findConnectionById(connection.id);
|
|
||||||
if (pooled) {
|
|
||||||
// Check if connection should be retired
|
|
||||||
if (pooled.usageCount >= this.config.maxUsageCount) {
|
|
||||||
await this.removeConnection(pooled.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async preWarm(): Promise<void> {
|
|
||||||
const connections: Promise<MCPConnection>[] = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < this.config.minConnections; i++) {
|
|
||||||
connections.push(this.createConnection('default'));
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all(connections);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async evictLeastUsedConnection(): Promise<void> {
|
|
||||||
let oldestConn: PooledConnection | null = null;
|
|
||||||
let oldestTime = Date.now();
|
|
||||||
|
|
||||||
for (const conn of this.pool.values()) {
|
|
||||||
if (conn.lastUsed < oldestTime) {
|
|
||||||
oldestTime = conn.lastUsed;
|
|
||||||
oldestConn = conn;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldestConn) {
|
|
||||||
await this.removeConnection(oldestConn.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private findAvailableConnection(endpoint: string): PooledConnection | null {
|
|
||||||
for (const conn of this.pool.values()) {
|
|
||||||
if (conn.isHealthy &&
|
|
||||||
conn.connection.endpoint === endpoint &&
|
|
||||||
Date.now() - conn.lastUsed < this.config.idleTimeoutMs) {
|
|
||||||
return conn;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Fast Tool Registry
|
|
||||||
|
|
||||||
### O(1) Tool Lookup Implementation
|
|
||||||
```typescript
|
|
||||||
// src/core/mcp/fast-tool-registry.ts
|
|
||||||
interface ToolIndexEntry {
|
|
||||||
name: string;
|
|
||||||
handler: ToolHandler;
|
|
||||||
metadata: ToolMetadata;
|
|
||||||
usageCount: number;
|
|
||||||
avgLatencyMs: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class FastToolRegistry {
|
|
||||||
private toolIndex: Map<string, ToolIndexEntry> = new Map();
|
|
||||||
private categoryIndex: Map<string, string[]> = new Map();
|
|
||||||
private fuzzyMatcher: FuzzyMatcher;
|
|
||||||
private cache: LRUCache<string, ToolIndexEntry>;
|
|
||||||
|
|
||||||
constructor(indexType: 'hash' | 'trie' = 'hash') {
|
|
||||||
this.fuzzyMatcher = new FuzzyMatcher();
|
|
||||||
this.cache = new LRUCache<string, ToolIndexEntry>(1000); // Cache 1000 most used tools
|
|
||||||
}
|
|
||||||
|
|
||||||
async buildIndex(): Promise<void> {
|
|
||||||
const start = performance.now();
|
|
||||||
|
|
||||||
// Load all available tools
|
|
||||||
const tools = await this.loadAllTools();
|
|
||||||
|
|
||||||
// Build hash index for O(1) lookup
|
|
||||||
for (const tool of tools) {
|
|
||||||
const entry: ToolIndexEntry = {
|
|
||||||
name: tool.name,
|
|
||||||
handler: tool.handler,
|
|
||||||
metadata: tool.metadata,
|
|
||||||
usageCount: 0,
|
|
||||||
avgLatencyMs: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
this.toolIndex.set(tool.name, entry);
|
|
||||||
|
|
||||||
// Build category index
|
|
||||||
const category = tool.metadata.category || 'general';
|
|
||||||
if (!this.categoryIndex.has(category)) {
|
|
||||||
this.categoryIndex.set(category, []);
|
|
||||||
}
|
|
||||||
this.categoryIndex.get(category)!.push(tool.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build fuzzy search index
|
|
||||||
await this.fuzzyMatcher.buildIndex(tools.map(t => t.name));
|
|
||||||
|
|
||||||
console.log(`Tool index built in ${(performance.now() - start).toFixed(2)}ms for ${tools.length} tools`);
|
|
||||||
}
|
|
||||||
|
|
||||||
findTool(name: string): ToolIndexEntry | null {
|
|
||||||
// Try cache first
|
|
||||||
const cached = this.cache.get(name);
|
|
||||||
if (cached) return cached;
|
|
||||||
|
|
||||||
// Try exact match
|
|
||||||
const exact = this.toolIndex.get(name);
|
|
||||||
if (exact) {
|
|
||||||
this.cache.set(name, exact);
|
|
||||||
return exact;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try fuzzy match
|
|
||||||
const fuzzyMatches = this.fuzzyMatcher.search(name, 1);
|
|
||||||
if (fuzzyMatches.length > 0) {
|
|
||||||
const match = this.toolIndex.get(fuzzyMatches[0]);
|
|
||||||
if (match) {
|
|
||||||
this.cache.set(name, match);
|
|
||||||
return match;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
findToolsByCategory(category: string): ToolIndexEntry[] {
|
|
||||||
const toolNames = this.categoryIndex.get(category) || [];
|
|
||||||
return toolNames
|
|
||||||
.map(name => this.toolIndex.get(name))
|
|
||||||
.filter(entry => entry !== undefined) as ToolIndexEntry[];
|
|
||||||
}
|
|
||||||
|
|
||||||
getMostUsedTools(limit: number = 10): ToolIndexEntry[] {
|
|
||||||
return Array.from(this.toolIndex.values())
|
|
||||||
.sort((a, b) => b.usageCount - a.usageCount)
|
|
||||||
.slice(0, limit);
|
|
||||||
}
|
|
||||||
|
|
||||||
recordToolUsage(toolName: string, latencyMs: number): void {
|
|
||||||
const entry = this.toolIndex.get(toolName);
|
|
||||||
if (entry) {
|
|
||||||
entry.usageCount++;
|
|
||||||
// Moving average for latency
|
|
||||||
entry.avgLatencyMs = (entry.avgLatencyMs + latencyMs) / 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Load Balancing & Request Distribution
|
|
||||||
|
|
||||||
### Intelligent Load Balancer
|
|
||||||
```typescript
|
|
||||||
// src/core/mcp/load-balancer.ts
|
|
||||||
interface ServerInstance {
|
|
||||||
id: string;
|
|
||||||
endpoint: string;
|
|
||||||
load: number;
|
|
||||||
responseTime: number;
|
|
||||||
isHealthy: boolean;
|
|
||||||
maxConnections: number;
|
|
||||||
currentConnections: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class MCPLoadBalancer {
|
|
||||||
private servers: Map<string, ServerInstance> = new Map();
|
|
||||||
private routingStrategy: RoutingStrategy = 'least-connections';
|
|
||||||
|
|
||||||
addServer(server: ServerInstance): void {
|
|
||||||
this.servers.set(server.id, server);
|
|
||||||
}
|
|
||||||
|
|
||||||
selectServer(toolCategory?: string): ServerInstance | null {
|
|
||||||
const healthyServers = Array.from(this.servers.values())
|
|
||||||
.filter(server => server.isHealthy);
|
|
||||||
|
|
||||||
if (healthyServers.length === 0) return null;
|
|
||||||
|
|
||||||
switch (this.routingStrategy) {
|
|
||||||
case 'round-robin':
|
|
||||||
return this.roundRobinSelection(healthyServers);
|
|
||||||
|
|
||||||
case 'least-connections':
|
|
||||||
return this.leastConnectionsSelection(healthyServers);
|
|
||||||
|
|
||||||
case 'response-time':
|
|
||||||
return this.responseTimeSelection(healthyServers);
|
|
||||||
|
|
||||||
case 'weighted':
|
|
||||||
return this.weightedSelection(healthyServers, toolCategory);
|
|
||||||
|
|
||||||
default:
|
|
||||||
return healthyServers[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private leastConnectionsSelection(servers: ServerInstance[]): ServerInstance {
|
|
||||||
return servers.reduce((least, current) =>
|
|
||||||
current.currentConnections < least.currentConnections ? current : least
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private responseTimeSelection(servers: ServerInstance[]): ServerInstance {
|
|
||||||
return servers.reduce((fastest, current) =>
|
|
||||||
current.responseTime < fastest.responseTime ? current : fastest
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private weightedSelection(servers: ServerInstance[], category?: string): ServerInstance {
|
|
||||||
// Prefer servers with lower load and better response time
|
|
||||||
const scored = servers.map(server => ({
|
|
||||||
server,
|
|
||||||
score: this.calculateServerScore(server, category)
|
|
||||||
}));
|
|
||||||
|
|
||||||
scored.sort((a, b) => b.score - a.score);
|
|
||||||
return scored[0].server;
|
|
||||||
}
|
|
||||||
|
|
||||||
private calculateServerScore(server: ServerInstance, category?: string): number {
|
|
||||||
const loadFactor = 1 - (server.currentConnections / server.maxConnections);
|
|
||||||
const responseFactor = 1 / (server.responseTime + 1);
|
|
||||||
const categoryBonus = this.getCategoryBonus(server, category);
|
|
||||||
|
|
||||||
return loadFactor * 0.4 + responseFactor * 0.4 + categoryBonus * 0.2;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateServerMetrics(serverId: string, metrics: Partial<ServerInstance>): void {
|
|
||||||
const server = this.servers.get(serverId);
|
|
||||||
if (server) {
|
|
||||||
Object.assign(server, metrics);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Transport Layer Optimization
|
|
||||||
|
|
||||||
### High-Performance Transport
|
|
||||||
```typescript
|
|
||||||
// src/core/mcp/optimized-transport.ts
|
|
||||||
export class OptimizedTransport {
|
|
||||||
private compression: boolean = true;
|
|
||||||
private batching: boolean = true;
|
|
||||||
private batchBuffer: MCPMessage[] = [];
|
|
||||||
private batchTimeout: NodeJS.Timeout | null = null;
|
|
||||||
|
|
||||||
constructor(private config: TransportConfig) {}
|
|
||||||
|
|
||||||
async send(message: MCPMessage): Promise<void> {
|
|
||||||
if (this.batching && this.canBatch(message)) {
|
|
||||||
this.addToBatch(message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.sendImmediate(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async sendImmediate(message: MCPMessage): Promise<void> {
|
|
||||||
const start = performance.now();
|
|
||||||
|
|
||||||
// Compress if enabled
|
|
||||||
const payload = this.compression
|
|
||||||
? await this.compress(message)
|
|
||||||
: message;
|
|
||||||
|
|
||||||
// Send through transport
|
|
||||||
await this.transport.send(payload);
|
|
||||||
|
|
||||||
// Record metrics
|
|
||||||
this.recordLatency(performance.now() - start);
|
|
||||||
}
|
|
||||||
|
|
||||||
private addToBatch(message: MCPMessage): void {
|
|
||||||
this.batchBuffer.push(message);
|
|
||||||
|
|
||||||
// Start batch timeout if not already running
|
|
||||||
if (!this.batchTimeout) {
|
|
||||||
this.batchTimeout = setTimeout(
|
|
||||||
() => this.flushBatch(),
|
|
||||||
this.config.batchTimeoutMs || 10
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flush if batch is full
|
|
||||||
if (this.batchBuffer.length >= this.config.maxBatchSize) {
|
|
||||||
this.flushBatch();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async flushBatch(): Promise<void> {
|
|
||||||
if (this.batchBuffer.length === 0) return;
|
|
||||||
|
|
||||||
const batch = this.batchBuffer.splice(0);
|
|
||||||
this.batchTimeout = null;
|
|
||||||
|
|
||||||
// Send as single batched message
|
|
||||||
await this.sendImmediate({
|
|
||||||
type: 'batch',
|
|
||||||
messages: batch
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private canBatch(message: MCPMessage): boolean {
|
|
||||||
// Don't batch urgent messages or responses
|
|
||||||
return message.type !== 'response' &&
|
|
||||||
message.priority !== 'high' &&
|
|
||||||
message.type !== 'error';
|
|
||||||
}
|
|
||||||
|
|
||||||
private async compress(data: any): Promise<Buffer> {
|
|
||||||
// Use fast compression for smaller messages
|
|
||||||
return gzipSync(JSON.stringify(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Performance Monitoring
|
|
||||||
|
|
||||||
### Real-time MCP Metrics
|
|
||||||
```typescript
|
|
||||||
// src/core/mcp/metrics.ts
|
|
||||||
interface MCPMetrics {
|
|
||||||
requestCount: number;
|
|
||||||
errorCount: number;
|
|
||||||
avgResponseTime: number;
|
|
||||||
p95ResponseTime: number;
|
|
||||||
connectionPoolHits: number;
|
|
||||||
connectionPoolMisses: number;
|
|
||||||
toolLookupTime: number;
|
|
||||||
startupTime: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class MCPMetricsCollector {
|
|
||||||
private metrics: MCPMetrics;
|
|
||||||
private responseTimeBuffer: number[] = [];
|
|
||||||
private readonly bufferSize = 1000;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.metrics = this.createInitialMetrics();
|
|
||||||
}
|
|
||||||
|
|
||||||
recordRequest(latencyMs: number): void {
|
|
||||||
this.metrics.requestCount++;
|
|
||||||
this.updateResponseTimes(latencyMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
recordError(): void {
|
|
||||||
this.metrics.errorCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
recordConnectionPoolHit(): void {
|
|
||||||
this.metrics.connectionPoolHits++;
|
|
||||||
}
|
|
||||||
|
|
||||||
recordConnectionPoolMiss(): void {
|
|
||||||
this.metrics.connectionPoolMisses++;
|
|
||||||
}
|
|
||||||
|
|
||||||
recordToolLookup(latencyMs: number): void {
|
|
||||||
this.metrics.toolLookupTime = this.updateMovingAverage(
|
|
||||||
this.metrics.toolLookupTime,
|
|
||||||
latencyMs
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
recordStartup(latencyMs: number): void {
|
|
||||||
this.metrics.startupTime = latencyMs;
|
|
||||||
}
|
|
||||||
|
|
||||||
getMetrics(): MCPMetrics {
|
|
||||||
return { ...this.metrics };
|
|
||||||
}
|
|
||||||
|
|
||||||
getHealthStatus(): HealthStatus {
|
|
||||||
const errorRate = this.metrics.errorCount / this.metrics.requestCount;
|
|
||||||
const poolHitRate = this.metrics.connectionPoolHits /
|
|
||||||
(this.metrics.connectionPoolHits + this.metrics.connectionPoolMisses);
|
|
||||||
|
|
||||||
return {
|
|
||||||
status: this.determineHealthStatus(errorRate, poolHitRate),
|
|
||||||
errorRate,
|
|
||||||
poolHitRate,
|
|
||||||
avgResponseTime: this.metrics.avgResponseTime,
|
|
||||||
p95ResponseTime: this.metrics.p95ResponseTime
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateResponseTimes(latency: number): void {
|
|
||||||
this.responseTimeBuffer.push(latency);
|
|
||||||
|
|
||||||
if (this.responseTimeBuffer.length > this.bufferSize) {
|
|
||||||
this.responseTimeBuffer.shift();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.metrics.avgResponseTime = this.calculateAverage(this.responseTimeBuffer);
|
|
||||||
this.metrics.p95ResponseTime = this.calculatePercentile(this.responseTimeBuffer, 95);
|
|
||||||
}
|
|
||||||
|
|
||||||
private calculatePercentile(arr: number[], percentile: number): number {
|
|
||||||
const sorted = arr.slice().sort((a, b) => a - b);
|
|
||||||
const index = Math.ceil((percentile / 100) * sorted.length) - 1;
|
|
||||||
return sorted[index] || 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private determineHealthStatus(errorRate: number, poolHitRate: number): 'healthy' | 'warning' | 'critical' {
|
|
||||||
if (errorRate > 0.1 || poolHitRate < 0.5) return 'critical';
|
|
||||||
if (errorRate > 0.05 || poolHitRate < 0.7) return 'warning';
|
|
||||||
return 'healthy';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Tool Registry Optimization
|
|
||||||
|
|
||||||
### Pre-compiled Tool Index
|
|
||||||
```typescript
|
|
||||||
// src/core/mcp/tool-precompiler.ts
|
|
||||||
export class ToolPrecompiler {
|
|
||||||
async precompileTools(): Promise<CompiledToolRegistry> {
|
|
||||||
const tools = await this.loadAllTools();
|
|
||||||
|
|
||||||
// Create optimized lookup structures
|
|
||||||
const nameIndex = new Map<string, Tool>();
|
|
||||||
const categoryIndex = new Map<string, Tool[]>();
|
|
||||||
const fuzzyIndex = new Map<string, string[]>();
|
|
||||||
|
|
||||||
for (const tool of tools) {
|
|
||||||
// Exact name index
|
|
||||||
nameIndex.set(tool.name, tool);
|
|
||||||
|
|
||||||
// Category index
|
|
||||||
const category = tool.metadata.category || 'general';
|
|
||||||
if (!categoryIndex.has(category)) {
|
|
||||||
categoryIndex.set(category, []);
|
|
||||||
}
|
|
||||||
categoryIndex.get(category)!.push(tool);
|
|
||||||
|
|
||||||
// Pre-compute fuzzy variations
|
|
||||||
const variations = this.generateFuzzyVariations(tool.name);
|
|
||||||
for (const variation of variations) {
|
|
||||||
if (!fuzzyIndex.has(variation)) {
|
|
||||||
fuzzyIndex.set(variation, []);
|
|
||||||
}
|
|
||||||
fuzzyIndex.get(variation)!.push(tool.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
nameIndex,
|
|
||||||
categoryIndex,
|
|
||||||
fuzzyIndex,
|
|
||||||
totalTools: tools.length,
|
|
||||||
compiledAt: new Date()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private generateFuzzyVariations(name: string): string[] {
|
|
||||||
const variations: string[] = [];
|
|
||||||
|
|
||||||
// Common typos and abbreviations
|
|
||||||
variations.push(name.toLowerCase());
|
|
||||||
variations.push(name.replace(/[-_]/g, ''));
|
|
||||||
variations.push(name.replace(/[aeiou]/gi, '')); // Consonants only
|
|
||||||
|
|
||||||
// Add more fuzzy matching logic as needed
|
|
||||||
|
|
||||||
return variations;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Advanced Caching Strategy
|
|
||||||
|
|
||||||
### Multi-Level Caching
|
|
||||||
```typescript
|
|
||||||
// src/core/mcp/multi-level-cache.ts
|
|
||||||
export class MultiLevelCache {
|
|
||||||
private l1Cache: Map<string, any> = new Map(); // In-memory, fastest
|
|
||||||
private l2Cache: LRUCache<string, any>; // LRU cache, larger capacity
|
|
||||||
private l3Cache: DiskCache; // Persistent disk cache
|
|
||||||
|
|
||||||
constructor(config: CacheConfig) {
|
|
||||||
this.l2Cache = new LRUCache<string, any>({
|
|
||||||
max: config.l2MaxEntries || 10000,
|
|
||||||
ttl: config.l2TTL || 300000 // 5 minutes
|
|
||||||
});
|
|
||||||
|
|
||||||
this.l3Cache = new DiskCache(config.l3Path || './.cache/mcp');
|
|
||||||
}
|
|
||||||
|
|
||||||
async get(key: string): Promise<any | null> {
|
|
||||||
// Try L1 cache first (fastest)
|
|
||||||
if (this.l1Cache.has(key)) {
|
|
||||||
return this.l1Cache.get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try L2 cache
|
|
||||||
const l2Value = this.l2Cache.get(key);
|
|
||||||
if (l2Value) {
|
|
||||||
// Promote to L1
|
|
||||||
this.l1Cache.set(key, l2Value);
|
|
||||||
return l2Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try L3 cache (disk)
|
|
||||||
const l3Value = await this.l3Cache.get(key);
|
|
||||||
if (l3Value) {
|
|
||||||
// Promote to L2 and L1
|
|
||||||
this.l2Cache.set(key, l3Value);
|
|
||||||
this.l1Cache.set(key, l3Value);
|
|
||||||
return l3Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async set(key: string, value: any, options?: CacheOptions): Promise<void> {
|
|
||||||
// Set in all levels
|
|
||||||
this.l1Cache.set(key, value);
|
|
||||||
this.l2Cache.set(key, value);
|
|
||||||
|
|
||||||
if (options?.persistent) {
|
|
||||||
await this.l3Cache.set(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Manage L1 cache size
|
|
||||||
if (this.l1Cache.size > 1000) {
|
|
||||||
const firstKey = this.l1Cache.keys().next().value;
|
|
||||||
this.l1Cache.delete(firstKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Success Metrics
|
|
||||||
|
|
||||||
### Performance Targets
|
|
||||||
- [ ] **Startup Time**: <400ms MCP server initialization (4.5x improvement)
|
|
||||||
- [ ] **Response Time**: <100ms p95 for tool execution
|
|
||||||
- [ ] **Tool Lookup**: <5ms average lookup time
|
|
||||||
- [ ] **Connection Pool**: >90% hit rate
|
|
||||||
- [ ] **Memory Usage**: 50% reduction in idle memory
|
|
||||||
- [ ] **Error Rate**: <1% failed requests
|
|
||||||
- [ ] **Throughput**: >1000 requests/second
|
|
||||||
|
|
||||||
### Monitoring Dashboards
|
|
||||||
```typescript
|
|
||||||
const mcpDashboard = {
|
|
||||||
metrics: [
|
|
||||||
'Request latency (p50, p95, p99)',
|
|
||||||
'Error rate by tool category',
|
|
||||||
'Connection pool utilization',
|
|
||||||
'Tool lookup performance',
|
|
||||||
'Memory usage trends',
|
|
||||||
'Cache hit rates (L1, L2, L3)'
|
|
||||||
],
|
|
||||||
|
|
||||||
alerts: [
|
|
||||||
'Response time >200ms for 5 minutes',
|
|
||||||
'Error rate >5% for 1 minute',
|
|
||||||
'Pool hit rate <70% for 10 minutes',
|
|
||||||
'Memory usage >500MB for 5 minutes'
|
|
||||||
]
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
## Related V3 Skills
|
|
||||||
|
|
||||||
- `v3-core-implementation` - Core domain integration with MCP
|
|
||||||
- `v3-performance-optimization` - Overall performance optimization
|
|
||||||
- `v3-swarm-coordination` - MCP integration with swarm coordination
|
|
||||||
- `v3-memory-unification` - Memory sharing via MCP tools
|
|
||||||
|
|
||||||
## Usage Examples
|
|
||||||
|
|
||||||
### Complete MCP Optimization
|
|
||||||
```bash
|
|
||||||
# Full MCP server optimization
|
|
||||||
Task("MCP optimization implementation",
|
|
||||||
"Implement all MCP performance optimizations with monitoring",
|
|
||||||
"mcp-specialist")
|
|
||||||
```
|
|
||||||
|
|
||||||
### Specific Optimization
|
|
||||||
```bash
|
|
||||||
# Connection pool optimization
|
|
||||||
Task("MCP connection pooling",
|
|
||||||
"Implement advanced connection pooling with health monitoring",
|
|
||||||
"mcp-specialist")
|
|
||||||
```
|
|
||||||
@ -1,174 +0,0 @@
|
|||||||
---
|
|
||||||
name: "V3 Memory Unification"
|
|
||||||
description: "Unify 6+ memory systems into AgentDB with HNSW indexing for 150x-12,500x search improvements. Implements ADR-006 (Unified Memory Service) and ADR-009 (Hybrid Memory Backend)."
|
|
||||||
---
|
|
||||||
|
|
||||||
# V3 Memory Unification
|
|
||||||
|
|
||||||
## What This Skill Does
|
|
||||||
|
|
||||||
Consolidates disparate memory systems into unified AgentDB backend with HNSW vector search, achieving 150x-12,500x search performance improvements while maintaining backward compatibility.
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Initialize memory unification
|
|
||||||
Task("Memory architecture", "Design AgentDB unification strategy", "v3-memory-specialist")
|
|
||||||
|
|
||||||
# AgentDB integration
|
|
||||||
Task("AgentDB setup", "Configure HNSW indexing and vector search", "v3-memory-specialist")
|
|
||||||
|
|
||||||
# Data migration
|
|
||||||
Task("Memory migration", "Migrate SQLite/Markdown to AgentDB", "v3-memory-specialist")
|
|
||||||
```
|
|
||||||
|
|
||||||
## Systems to Unify
|
|
||||||
|
|
||||||
### Legacy Systems → AgentDB
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────┐
|
|
||||||
│ • MemoryManager (basic operations) │
|
|
||||||
│ • DistributedMemorySystem (clustering) │
|
|
||||||
│ • SwarmMemory (agent-specific) │
|
|
||||||
│ • AdvancedMemoryManager (features) │
|
|
||||||
│ • SQLiteBackend (structured) │
|
|
||||||
│ • MarkdownBackend (file-based) │
|
|
||||||
│ • HybridBackend (combination) │
|
|
||||||
└─────────────────────────────────────────┘
|
|
||||||
↓
|
|
||||||
┌─────────────────────────────────────────┐
|
|
||||||
│ 🚀 AgentDB with HNSW │
|
|
||||||
│ • 150x-12,500x faster search │
|
|
||||||
│ • Unified query interface │
|
|
||||||
│ • Cross-agent memory sharing │
|
|
||||||
│ • SONA learning integration │
|
|
||||||
└─────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
## Implementation Architecture
|
|
||||||
|
|
||||||
### Unified Memory Service
|
|
||||||
```typescript
|
|
||||||
class UnifiedMemoryService implements IMemoryBackend {
|
|
||||||
constructor(
|
|
||||||
private agentdb: AgentDBAdapter,
|
|
||||||
private indexer: HNSWIndexer,
|
|
||||||
private migrator: DataMigrator
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async store(entry: MemoryEntry): Promise<void> {
|
|
||||||
await this.agentdb.store(entry);
|
|
||||||
await this.indexer.index(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
async query(query: MemoryQuery): Promise<MemoryEntry[]> {
|
|
||||||
if (query.semantic) {
|
|
||||||
return this.indexer.search(query); // 150x-12,500x faster
|
|
||||||
}
|
|
||||||
return this.agentdb.query(query);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### HNSW Vector Search
|
|
||||||
```typescript
|
|
||||||
class HNSWIndexer {
|
|
||||||
constructor(dimensions: number = 1536) {
|
|
||||||
this.index = new HNSWIndex({
|
|
||||||
dimensions,
|
|
||||||
efConstruction: 200,
|
|
||||||
M: 16,
|
|
||||||
speedupTarget: '150x-12500x'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async search(query: MemoryQuery): Promise<MemoryEntry[]> {
|
|
||||||
const embedding = await this.embedContent(query.content);
|
|
||||||
const results = this.index.search(embedding, query.limit || 10);
|
|
||||||
return this.retrieveEntries(results);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Migration Strategy
|
|
||||||
|
|
||||||
### Phase 1: Foundation
|
|
||||||
```typescript
|
|
||||||
// AgentDB adapter setup
|
|
||||||
const agentdb = new AgentDBAdapter({
|
|
||||||
dimensions: 1536,
|
|
||||||
indexType: 'HNSW',
|
|
||||||
speedupTarget: '150x-12500x'
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 2: Data Migration
|
|
||||||
```typescript
|
|
||||||
// SQLite → AgentDB
|
|
||||||
const migrateFromSQLite = async () => {
|
|
||||||
const entries = await sqlite.getAll();
|
|
||||||
for (const entry of entries) {
|
|
||||||
const embedding = await generateEmbedding(entry.content);
|
|
||||||
await agentdb.store({ ...entry, embedding });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Markdown → AgentDB
|
|
||||||
const migrateFromMarkdown = async () => {
|
|
||||||
const files = await glob('**/*.md');
|
|
||||||
for (const file of files) {
|
|
||||||
const content = await fs.readFile(file, 'utf-8');
|
|
||||||
await agentdb.store({
|
|
||||||
id: generateId(),
|
|
||||||
content,
|
|
||||||
embedding: await generateEmbedding(content),
|
|
||||||
metadata: { originalFile: file }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
## SONA Integration
|
|
||||||
|
|
||||||
### Learning Pattern Storage
|
|
||||||
```typescript
|
|
||||||
class SONAMemoryIntegration {
|
|
||||||
async storePattern(pattern: LearningPattern): Promise<void> {
|
|
||||||
await this.memory.store({
|
|
||||||
id: pattern.id,
|
|
||||||
content: pattern.data,
|
|
||||||
metadata: {
|
|
||||||
sonaMode: pattern.mode,
|
|
||||||
reward: pattern.reward,
|
|
||||||
adaptationTime: pattern.adaptationTime
|
|
||||||
},
|
|
||||||
embedding: await this.generateEmbedding(pattern.data)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async retrieveSimilarPatterns(query: string): Promise<LearningPattern[]> {
|
|
||||||
return this.memory.query({
|
|
||||||
type: 'semantic',
|
|
||||||
content: query,
|
|
||||||
filters: { type: 'learning_pattern' }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Performance Targets
|
|
||||||
|
|
||||||
- **Search Speed**: 150x-12,500x improvement via HNSW
|
|
||||||
- **Memory Usage**: 50-75% reduction through optimization
|
|
||||||
- **Query Latency**: <100ms for 1M+ entries
|
|
||||||
- **Cross-Agent Sharing**: Real-time memory synchronization
|
|
||||||
- **SONA Integration**: <0.05ms adaptation time
|
|
||||||
|
|
||||||
## Success Metrics
|
|
||||||
|
|
||||||
- [ ] All 7 legacy memory systems migrated to AgentDB
|
|
||||||
- [ ] 150x-12,500x search performance validated
|
|
||||||
- [ ] 50-75% memory usage reduction achieved
|
|
||||||
- [ ] Backward compatibility maintained
|
|
||||||
- [ ] SONA learning patterns integrated
|
|
||||||
- [ ] Cross-agent memory sharing operational
|
|
||||||
@ -1,390 +0,0 @@
|
|||||||
---
|
|
||||||
name: "V3 Performance Optimization"
|
|
||||||
description: "Achieve aggressive v3 performance targets: 2.49x-7.47x Flash Attention speedup, 150x-12,500x search improvements, 50-75% memory reduction. Comprehensive benchmarking and optimization suite."
|
|
||||||
---
|
|
||||||
|
|
||||||
# V3 Performance Optimization
|
|
||||||
|
|
||||||
## What This Skill Does
|
|
||||||
|
|
||||||
Validates and optimizes Codex-flow v3 to achieve industry-leading performance through Flash Attention, AgentDB HNSW indexing, and comprehensive system optimization with continuous benchmarking.
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Initialize performance optimization
|
|
||||||
Task("Performance baseline", "Establish v2 performance benchmarks", "v3-performance-engineer")
|
|
||||||
|
|
||||||
# Target validation (parallel)
|
|
||||||
Task("Flash Attention", "Validate 2.49x-7.47x speedup target", "v3-performance-engineer")
|
|
||||||
Task("Search optimization", "Validate 150x-12,500x search improvement", "v3-performance-engineer")
|
|
||||||
Task("Memory optimization", "Achieve 50-75% memory reduction", "v3-performance-engineer")
|
|
||||||
```
|
|
||||||
|
|
||||||
## Performance Target Matrix
|
|
||||||
|
|
||||||
### Flash Attention Revolution
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────┐
|
|
||||||
│ FLASH ATTENTION │
|
|
||||||
├─────────────────────────────────────────┤
|
|
||||||
│ Baseline: Standard attention │
|
|
||||||
│ Target: 2.49x - 7.47x speedup │
|
|
||||||
│ Memory: 50-75% reduction │
|
|
||||||
│ Latency: Sub-millisecond processing │
|
|
||||||
└─────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
### Search Performance Revolution
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────┐
|
|
||||||
│ SEARCH OPTIMIZATION │
|
|
||||||
├─────────────────────────────────────────┤
|
|
||||||
│ Current: O(n) linear search │
|
|
||||||
│ Target: 150x - 12,500x improvement │
|
|
||||||
│ Method: HNSW indexing │
|
|
||||||
│ Latency: <100ms for 1M+ entries │
|
|
||||||
└─────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
## Comprehensive Benchmark Suite
|
|
||||||
|
|
||||||
### Startup Performance
|
|
||||||
```typescript
|
|
||||||
class StartupBenchmarks {
|
|
||||||
async benchmarkColdStart(): Promise<BenchmarkResult> {
|
|
||||||
const startTime = performance.now();
|
|
||||||
|
|
||||||
await this.initializeCLI();
|
|
||||||
await this.initializeMCPServer();
|
|
||||||
await this.spawnTestAgent();
|
|
||||||
|
|
||||||
const totalTime = performance.now() - startTime;
|
|
||||||
|
|
||||||
return {
|
|
||||||
total: totalTime,
|
|
||||||
target: 500, // ms
|
|
||||||
achieved: totalTime < 500
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Memory Operation Benchmarks
|
|
||||||
```typescript
|
|
||||||
class MemoryBenchmarks {
|
|
||||||
async benchmarkVectorSearch(): Promise<SearchBenchmark> {
|
|
||||||
const queries = this.generateTestQueries(10000);
|
|
||||||
|
|
||||||
// Baseline: Current linear search
|
|
||||||
const baselineTime = await this.timeOperation(() =>
|
|
||||||
this.currentMemory.searchAll(queries)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Target: HNSW search
|
|
||||||
const hnswTime = await this.timeOperation(() =>
|
|
||||||
this.agentDBMemory.hnswSearchAll(queries)
|
|
||||||
);
|
|
||||||
|
|
||||||
const improvement = baselineTime / hnswTime;
|
|
||||||
|
|
||||||
return {
|
|
||||||
baseline: baselineTime,
|
|
||||||
hnsw: hnswTime,
|
|
||||||
improvement,
|
|
||||||
targetRange: [150, 12500],
|
|
||||||
achieved: improvement >= 150
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async benchmarkMemoryUsage(): Promise<MemoryBenchmark> {
|
|
||||||
const baseline = process.memoryUsage().heapUsed;
|
|
||||||
|
|
||||||
await this.loadTestDataset();
|
|
||||||
const withData = process.memoryUsage().heapUsed;
|
|
||||||
|
|
||||||
await this.enableOptimization();
|
|
||||||
const optimized = process.memoryUsage().heapUsed;
|
|
||||||
|
|
||||||
const reduction = (withData - optimized) / withData;
|
|
||||||
|
|
||||||
return {
|
|
||||||
baseline,
|
|
||||||
withData,
|
|
||||||
optimized,
|
|
||||||
reductionPercent: reduction * 100,
|
|
||||||
targetReduction: [50, 75],
|
|
||||||
achieved: reduction >= 0.5
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Swarm Coordination Benchmarks
|
|
||||||
```typescript
|
|
||||||
class SwarmBenchmarks {
|
|
||||||
async benchmark15AgentCoordination(): Promise<SwarmBenchmark> {
|
|
||||||
const agents = await this.spawn15Agents();
|
|
||||||
|
|
||||||
// Coordination latency
|
|
||||||
const coordinationTime = await this.timeOperation(() =>
|
|
||||||
this.coordinateSwarmTask(agents)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Task decomposition
|
|
||||||
const decompositionTime = await this.timeOperation(() =>
|
|
||||||
this.decomposeComplexTask()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Consensus achievement
|
|
||||||
const consensusTime = await this.timeOperation(() =>
|
|
||||||
this.achieveSwarmConsensus(agents)
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
coordination: coordinationTime,
|
|
||||||
decomposition: decompositionTime,
|
|
||||||
consensus: consensusTime,
|
|
||||||
agentCount: 15,
|
|
||||||
efficiency: this.calculateEfficiency(agents)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Flash Attention Benchmarks
|
|
||||||
```typescript
|
|
||||||
class AttentionBenchmarks {
|
|
||||||
async benchmarkFlashAttention(): Promise<AttentionBenchmark> {
|
|
||||||
const sequences = this.generateSequences([512, 1024, 2048, 4096]);
|
|
||||||
const results = [];
|
|
||||||
|
|
||||||
for (const sequence of sequences) {
|
|
||||||
// Baseline attention
|
|
||||||
const baselineResult = await this.benchmarkStandardAttention(sequence);
|
|
||||||
|
|
||||||
// Flash attention
|
|
||||||
const flashResult = await this.benchmarkFlashAttention(sequence);
|
|
||||||
|
|
||||||
results.push({
|
|
||||||
sequenceLength: sequence.length,
|
|
||||||
speedup: baselineResult.time / flashResult.time,
|
|
||||||
memoryReduction: (baselineResult.memory - flashResult.memory) / baselineResult.memory,
|
|
||||||
targetSpeedup: [2.49, 7.47],
|
|
||||||
achieved: this.checkTarget(flashResult, [2.49, 7.47])
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
results,
|
|
||||||
averageSpeedup: this.calculateAverage(results, 'speedup'),
|
|
||||||
averageMemoryReduction: this.calculateAverage(results, 'memoryReduction')
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### SONA Learning Benchmarks
|
|
||||||
```typescript
|
|
||||||
class SONABenchmarks {
|
|
||||||
async benchmarkAdaptationTime(): Promise<SONABenchmark> {
|
|
||||||
const scenarios = [
|
|
||||||
'pattern_recognition',
|
|
||||||
'task_optimization',
|
|
||||||
'error_correction',
|
|
||||||
'performance_tuning'
|
|
||||||
];
|
|
||||||
|
|
||||||
const results = [];
|
|
||||||
|
|
||||||
for (const scenario of scenarios) {
|
|
||||||
const startTime = performance.hrtime.bigint();
|
|
||||||
await this.sona.adapt(scenario);
|
|
||||||
const endTime = performance.hrtime.bigint();
|
|
||||||
|
|
||||||
const adaptationTimeMs = Number(endTime - startTime) / 1000000;
|
|
||||||
|
|
||||||
results.push({
|
|
||||||
scenario,
|
|
||||||
adaptationTime: adaptationTimeMs,
|
|
||||||
target: 0.05, // ms
|
|
||||||
achieved: adaptationTimeMs <= 0.05
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
scenarios: results,
|
|
||||||
averageTime: results.reduce((sum, r) => sum + r.adaptationTime, 0) / results.length,
|
|
||||||
successRate: results.filter(r => r.achieved).length / results.length
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Performance Monitoring Dashboard
|
|
||||||
|
|
||||||
### Real-time Metrics
|
|
||||||
```typescript
|
|
||||||
class PerformanceMonitor {
|
|
||||||
async collectMetrics(): Promise<PerformanceSnapshot> {
|
|
||||||
return {
|
|
||||||
timestamp: Date.now(),
|
|
||||||
flashAttention: await this.measureFlashAttention(),
|
|
||||||
searchPerformance: await this.measureSearchSpeed(),
|
|
||||||
memoryUsage: await this.measureMemoryEfficiency(),
|
|
||||||
startupTime: await this.measureStartupLatency(),
|
|
||||||
sonaAdaptation: await this.measureSONASpeed(),
|
|
||||||
swarmCoordination: await this.measureSwarmEfficiency()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async generateReport(): Promise<PerformanceReport> {
|
|
||||||
const snapshot = await this.collectMetrics();
|
|
||||||
|
|
||||||
return {
|
|
||||||
summary: this.generateSummary(snapshot),
|
|
||||||
achievements: this.checkTargetAchievements(snapshot),
|
|
||||||
trends: this.analyzeTrends(),
|
|
||||||
recommendations: this.generateOptimizations(),
|
|
||||||
regressions: await this.detectRegressions()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Continuous Regression Detection
|
|
||||||
```typescript
|
|
||||||
class PerformanceRegression {
|
|
||||||
async detectRegressions(): Promise<RegressionReport> {
|
|
||||||
const current = await this.runFullBenchmark();
|
|
||||||
const baseline = await this.getBaseline();
|
|
||||||
|
|
||||||
const regressions = [];
|
|
||||||
|
|
||||||
for (const [metric, currentValue] of Object.entries(current)) {
|
|
||||||
const baselineValue = baseline[metric];
|
|
||||||
const change = (currentValue - baselineValue) / baselineValue;
|
|
||||||
|
|
||||||
if (change < -0.05) { // 5% regression threshold
|
|
||||||
regressions.push({
|
|
||||||
metric,
|
|
||||||
baseline: baselineValue,
|
|
||||||
current: currentValue,
|
|
||||||
regressionPercent: change * 100,
|
|
||||||
severity: this.classifyRegression(change)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
hasRegressions: regressions.length > 0,
|
|
||||||
regressions,
|
|
||||||
recommendations: this.generateRegressionFixes(regressions)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Optimization Strategies
|
|
||||||
|
|
||||||
### Memory Optimization
|
|
||||||
```typescript
|
|
||||||
class MemoryOptimization {
|
|
||||||
async optimizeMemoryUsage(): Promise<OptimizationResult> {
|
|
||||||
// Implement memory pooling
|
|
||||||
await this.setupMemoryPools();
|
|
||||||
|
|
||||||
// Enable garbage collection tuning
|
|
||||||
await this.optimizeGarbageCollection();
|
|
||||||
|
|
||||||
// Implement object reuse patterns
|
|
||||||
await this.setupObjectPools();
|
|
||||||
|
|
||||||
// Enable memory compression
|
|
||||||
await this.enableMemoryCompression();
|
|
||||||
|
|
||||||
return this.validateMemoryReduction();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### CPU Optimization
|
|
||||||
```typescript
|
|
||||||
class CPUOptimization {
|
|
||||||
async optimizeCPUUsage(): Promise<OptimizationResult> {
|
|
||||||
// Implement worker thread pools
|
|
||||||
await this.setupWorkerThreads();
|
|
||||||
|
|
||||||
// Enable CPU-specific optimizations
|
|
||||||
await this.enableSIMDInstructions();
|
|
||||||
|
|
||||||
// Implement task batching
|
|
||||||
await this.optimizeTaskBatching();
|
|
||||||
|
|
||||||
return this.validateCPUImprovement();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Target Validation Framework
|
|
||||||
|
|
||||||
### Performance Gates
|
|
||||||
```typescript
|
|
||||||
class PerformanceGates {
|
|
||||||
async validateAllTargets(): Promise<ValidationReport> {
|
|
||||||
const results = await Promise.all([
|
|
||||||
this.validateFlashAttention(), // 2.49x-7.47x
|
|
||||||
this.validateSearchPerformance(), // 150x-12,500x
|
|
||||||
this.validateMemoryReduction(), // 50-75%
|
|
||||||
this.validateStartupTime(), // <500ms
|
|
||||||
this.validateSONAAdaptation() // <0.05ms
|
|
||||||
]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
allTargetsAchieved: results.every(r => r.achieved),
|
|
||||||
results,
|
|
||||||
overallScore: this.calculateOverallScore(results),
|
|
||||||
recommendations: this.generateRecommendations(results)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Success Metrics
|
|
||||||
|
|
||||||
### Primary Targets
|
|
||||||
- [ ] **Flash Attention**: 2.49x-7.47x speedup validated
|
|
||||||
- [ ] **Search Performance**: 150x-12,500x improvement confirmed
|
|
||||||
- [ ] **Memory Reduction**: 50-75% usage optimization achieved
|
|
||||||
- [ ] **Startup Time**: <500ms cold start consistently
|
|
||||||
- [ ] **SONA Adaptation**: <0.05ms learning response time
|
|
||||||
- [ ] **15-Agent Coordination**: Efficient parallel execution
|
|
||||||
|
|
||||||
### Continuous Monitoring
|
|
||||||
- [ ] **Performance Dashboard**: Real-time metrics collection
|
|
||||||
- [ ] **Regression Testing**: Automated performance validation
|
|
||||||
- [ ] **Trend Analysis**: Performance evolution tracking
|
|
||||||
- [ ] **Alert System**: Immediate regression notification
|
|
||||||
|
|
||||||
## Related V3 Skills
|
|
||||||
|
|
||||||
- `v3-integration-deep` - Performance integration with agentic-flow
|
|
||||||
- `v3-memory-unification` - Memory performance optimization
|
|
||||||
- `v3-swarm-coordination` - Swarm performance coordination
|
|
||||||
- `v3-security-overhaul` - Secure performance patterns
|
|
||||||
|
|
||||||
## Usage Examples
|
|
||||||
|
|
||||||
### Complete Performance Validation
|
|
||||||
```bash
|
|
||||||
# Full performance suite
|
|
||||||
npm run benchmark:v3
|
|
||||||
|
|
||||||
# Specific target validation
|
|
||||||
npm run benchmark:flash-attention
|
|
||||||
npm run benchmark:agentdb-search
|
|
||||||
npm run benchmark:memory-optimization
|
|
||||||
|
|
||||||
# Continuous monitoring
|
|
||||||
npm run monitor:performance
|
|
||||||
```
|
|
||||||
@ -1,82 +0,0 @@
|
|||||||
---
|
|
||||||
name: "V3 Security Overhaul"
|
|
||||||
description: "Complete security architecture overhaul for Codex-flow v3. Addresses critical CVEs (CVE-1, CVE-2, CVE-3) and implements secure-by-default patterns. Use for security-first v3 implementation."
|
|
||||||
---
|
|
||||||
|
|
||||||
# V3 Security Overhaul
|
|
||||||
|
|
||||||
## What This Skill Does
|
|
||||||
|
|
||||||
Orchestrates comprehensive security overhaul for Codex-flow v3, addressing critical vulnerabilities and establishing security-first development practices using specialized v3 security agents.
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Initialize V3 security domain (parallel)
|
|
||||||
Task("Security architecture", "Design v3 threat model and security boundaries", "v3-security-architect")
|
|
||||||
Task("CVE remediation", "Fix CVE-1, CVE-2, CVE-3 critical vulnerabilities", "security-auditor")
|
|
||||||
Task("Security testing", "Implement TDD London School security framework", "test-architect")
|
|
||||||
```
|
|
||||||
|
|
||||||
## Critical Security Fixes
|
|
||||||
|
|
||||||
### CVE-1: Vulnerable Dependencies
|
|
||||||
```bash
|
|
||||||
npm update @anthropic-ai/Codex@^2.0.31
|
|
||||||
npm audit --audit-level high
|
|
||||||
```
|
|
||||||
|
|
||||||
### CVE-2: Weak Password Hashing
|
|
||||||
```typescript
|
|
||||||
// ❌ Old: SHA-256 with hardcoded salt
|
|
||||||
const hash = crypto.createHash('sha256').update(password + salt).digest('hex');
|
|
||||||
|
|
||||||
// ✅ New: bcrypt with 12 rounds
|
|
||||||
import bcrypt from 'bcrypt';
|
|
||||||
const hash = await bcrypt.hash(password, 12);
|
|
||||||
```
|
|
||||||
|
|
||||||
### CVE-3: Hardcoded Credentials
|
|
||||||
```typescript
|
|
||||||
// ✅ Generate secure random credentials
|
|
||||||
const apiKey = crypto.randomBytes(32).toString('hex');
|
|
||||||
```
|
|
||||||
|
|
||||||
## Security Patterns
|
|
||||||
|
|
||||||
### Input Validation (Zod)
|
|
||||||
```typescript
|
|
||||||
import { z } from 'zod';
|
|
||||||
|
|
||||||
const TaskSchema = z.object({
|
|
||||||
taskId: z.string().uuid(),
|
|
||||||
content: z.string().max(10000),
|
|
||||||
agentType: z.enum(['security', 'core', 'integration'])
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Path Sanitization
|
|
||||||
```typescript
|
|
||||||
function securePath(userPath: string, allowedPrefix: string): string {
|
|
||||||
const resolved = path.resolve(allowedPrefix, userPath);
|
|
||||||
if (!resolved.startsWith(path.resolve(allowedPrefix))) {
|
|
||||||
throw new SecurityError('Path traversal detected');
|
|
||||||
}
|
|
||||||
return resolved;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Safe Command Execution
|
|
||||||
```typescript
|
|
||||||
import { execFile } from 'child_process';
|
|
||||||
|
|
||||||
// ✅ Safe: No shell interpretation
|
|
||||||
const { stdout } = await execFile('git', [userInput], { shell: false });
|
|
||||||
```
|
|
||||||
|
|
||||||
## Success Metrics
|
|
||||||
|
|
||||||
- **Security Score**: 90/100 (npm audit + custom scans)
|
|
||||||
- **CVE Resolution**: 100% of critical vulnerabilities fixed
|
|
||||||
- **Test Coverage**: >95% security-critical code
|
|
||||||
- **Implementation**: All secure patterns documented and tested
|
|
||||||
@ -1,340 +0,0 @@
|
|||||||
---
|
|
||||||
name: "V3 Swarm Coordination"
|
|
||||||
description: "15-agent hierarchical mesh coordination for v3 implementation. Orchestrates parallel execution across security, core, and integration domains following 10 ADRs with 14-week timeline."
|
|
||||||
---
|
|
||||||
|
|
||||||
# V3 Swarm Coordination
|
|
||||||
|
|
||||||
## What This Skill Does
|
|
||||||
|
|
||||||
Orchestrates the complete 15-agent hierarchical mesh swarm for Codex-flow v3 implementation, coordinating parallel execution across domains while maintaining dependencies and timeline adherence.
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Initialize 15-agent v3 swarm
|
|
||||||
Task("Swarm initialization", "Initialize hierarchical mesh for v3 implementation", "v3-queen-coordinator")
|
|
||||||
|
|
||||||
# Security domain (Phase 1 - Critical priority)
|
|
||||||
Task("Security architecture", "Design v3 threat model and security boundaries", "v3-security-architect")
|
|
||||||
Task("CVE remediation", "Fix CVE-1, CVE-2, CVE-3 vulnerabilities", "security-auditor")
|
|
||||||
Task("Security testing", "Implement TDD security framework", "test-architect")
|
|
||||||
|
|
||||||
# Core domain (Phase 2 - Parallel execution)
|
|
||||||
Task("Memory unification", "Implement AgentDB 150x improvement", "v3-memory-specialist")
|
|
||||||
Task("Integration architecture", "Deep agentic-flow@alpha integration", "v3-integration-architect")
|
|
||||||
Task("Performance validation", "Validate 2.49x-7.47x targets", "v3-performance-engineer")
|
|
||||||
```
|
|
||||||
|
|
||||||
## 15-Agent Swarm Architecture
|
|
||||||
|
|
||||||
### Hierarchical Mesh Topology
|
|
||||||
```
|
|
||||||
👑 QUEEN COORDINATOR
|
|
||||||
(Agent #1)
|
|
||||||
│
|
|
||||||
┌────────────────────┼────────────────────┐
|
|
||||||
│ │ │
|
|
||||||
🛡️ SECURITY 🧠 CORE 🔗 INTEGRATION
|
|
||||||
(Agents #2-4) (Agents #5-9) (Agents #10-12)
|
|
||||||
│ │ │
|
|
||||||
└────────────────────┼────────────────────┘
|
|
||||||
│
|
|
||||||
┌────────────────────┼────────────────────┐
|
|
||||||
│ │ │
|
|
||||||
🧪 QUALITY ⚡ PERFORMANCE 🚀 DEPLOYMENT
|
|
||||||
(Agent #13) (Agent #14) (Agent #15)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Agent Roster
|
|
||||||
| ID | Agent | Domain | Phase | Responsibility |
|
|
||||||
|----|-------|--------|-------|----------------|
|
|
||||||
| 1 | Queen Coordinator | Orchestration | All | GitHub issues, dependencies, timeline |
|
|
||||||
| 2 | Security Architect | Security | Foundation | Threat modeling, CVE planning |
|
|
||||||
| 3 | Security Implementer | Security | Foundation | CVE fixes, secure patterns |
|
|
||||||
| 4 | Security Tester | Security | Foundation | TDD security testing |
|
|
||||||
| 5 | Core Architect | Core | Systems | DDD architecture, coordination |
|
|
||||||
| 6 | Core Implementer | Core | Systems | Core module implementation |
|
|
||||||
| 7 | Memory Specialist | Core | Systems | AgentDB unification |
|
|
||||||
| 8 | Swarm Specialist | Core | Systems | Unified coordination engine |
|
|
||||||
| 9 | MCP Specialist | Core | Systems | MCP server optimization |
|
|
||||||
| 10 | Integration Architect | Integration | Integration | agentic-flow@alpha deep integration |
|
|
||||||
| 11 | CLI/Hooks Developer | Integration | Integration | CLI modernization |
|
|
||||||
| 12 | Neural/Learning Dev | Integration | Integration | SONA integration |
|
|
||||||
| 13 | TDD Test Engineer | Quality | All | London School TDD |
|
|
||||||
| 14 | Performance Engineer | Performance | Optimization | Benchmarking validation |
|
|
||||||
| 15 | Release Engineer | Deployment | Release | CI/CD and v3.0.0 release |
|
|
||||||
|
|
||||||
## Implementation Phases
|
|
||||||
|
|
||||||
### Phase 1: Foundation (Week 1-2)
|
|
||||||
**Active Agents**: #1, #2-4, #5-6
|
|
||||||
```typescript
|
|
||||||
const phase1 = async () => {
|
|
||||||
// Parallel security and architecture foundation
|
|
||||||
await Promise.all([
|
|
||||||
// Security domain (critical priority)
|
|
||||||
Task("Security architecture", "Complete threat model and security boundaries", "v3-security-architect"),
|
|
||||||
Task("CVE-1 fix", "Update vulnerable dependencies", "security-implementer"),
|
|
||||||
Task("CVE-2 fix", "Replace weak password hashing", "security-implementer"),
|
|
||||||
Task("CVE-3 fix", "Remove hardcoded credentials", "security-implementer"),
|
|
||||||
Task("Security testing", "TDD London School security framework", "test-architect"),
|
|
||||||
|
|
||||||
// Core architecture foundation
|
|
||||||
Task("DDD architecture", "Design domain boundaries and structure", "core-architect"),
|
|
||||||
Task("Type modernization", "Update type system for v3", "core-implementer")
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 2: Core Systems (Week 3-6)
|
|
||||||
**Active Agents**: #1, #5-9, #13
|
|
||||||
```typescript
|
|
||||||
const phase2 = async () => {
|
|
||||||
// Parallel core system implementation
|
|
||||||
await Promise.all([
|
|
||||||
Task("Memory unification", "Implement AgentDB with 150x-12,500x improvement", "v3-memory-specialist"),
|
|
||||||
Task("Swarm coordination", "Merge 4 coordination systems into unified engine", "swarm-specialist"),
|
|
||||||
Task("MCP optimization", "Optimize MCP server performance", "mcp-specialist"),
|
|
||||||
Task("Core implementation", "Implement DDD modular architecture", "core-implementer"),
|
|
||||||
Task("TDD core tests", "Comprehensive test coverage for core systems", "test-architect")
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 3: Integration (Week 7-10)
|
|
||||||
**Active Agents**: #1, #10-12, #13-14
|
|
||||||
```typescript
|
|
||||||
const phase3 = async () => {
|
|
||||||
// Parallel integration and optimization
|
|
||||||
await Promise.all([
|
|
||||||
Task("agentic-flow integration", "Eliminate 10,000+ duplicate lines", "v3-integration-architect"),
|
|
||||||
Task("CLI modernization", "Enhance CLI with hooks system", "cli-hooks-developer"),
|
|
||||||
Task("SONA integration", "Implement <0.05ms learning adaptation", "neural-learning-developer"),
|
|
||||||
Task("Performance benchmarking", "Validate 2.49x-7.47x targets", "v3-performance-engineer"),
|
|
||||||
Task("Integration testing", "End-to-end system validation", "test-architect")
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 4: Release (Week 11-14)
|
|
||||||
**Active Agents**: All 15
|
|
||||||
```typescript
|
|
||||||
const phase4 = async () => {
|
|
||||||
// Full swarm final optimization
|
|
||||||
await Promise.all([
|
|
||||||
Task("Performance optimization", "Final optimization pass", "v3-performance-engineer"),
|
|
||||||
Task("Release preparation", "CI/CD pipeline and v3.0.0 release", "release-engineer"),
|
|
||||||
Task("Final testing", "Complete test coverage validation", "test-architect"),
|
|
||||||
|
|
||||||
// All agents: Final polish and optimization
|
|
||||||
...agents.map(agent =>
|
|
||||||
Task("Final polish", `Agent ${agent.id} final optimization`, agent.name)
|
|
||||||
)
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
## Coordination Patterns
|
|
||||||
|
|
||||||
### Dependency Management
|
|
||||||
```typescript
|
|
||||||
class DependencyCoordination {
|
|
||||||
private dependencies = new Map([
|
|
||||||
// Security first (no dependencies)
|
|
||||||
[2, []], [3, [2]], [4, [2, 3]],
|
|
||||||
|
|
||||||
// Core depends on security foundation
|
|
||||||
[5, [2]], [6, [5]], [7, [5]], [8, [5, 7]], [9, [5]],
|
|
||||||
|
|
||||||
// Integration depends on core systems
|
|
||||||
[10, [5, 7, 8]], [11, [5, 10]], [12, [7, 10]],
|
|
||||||
|
|
||||||
// Quality and performance cross-cutting
|
|
||||||
[13, [2, 5]], [14, [5, 7, 8, 10]], [15, [13, 14]]
|
|
||||||
]);
|
|
||||||
|
|
||||||
async coordinateExecution(): Promise<void> {
|
|
||||||
const completed = new Set<number>();
|
|
||||||
|
|
||||||
while (completed.size < 15) {
|
|
||||||
const ready = this.getReadyAgents(completed);
|
|
||||||
|
|
||||||
if (ready.length === 0) {
|
|
||||||
throw new Error('Deadlock detected in dependency chain');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute ready agents in parallel
|
|
||||||
await Promise.all(ready.map(agentId => this.executeAgent(agentId)));
|
|
||||||
|
|
||||||
ready.forEach(id => completed.add(id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### GitHub Integration
|
|
||||||
```typescript
|
|
||||||
class GitHubCoordination {
|
|
||||||
async initializeV3Milestone(): Promise<void> {
|
|
||||||
await gh.createMilestone({
|
|
||||||
title: 'Codex-Flow v3.0.0 Implementation',
|
|
||||||
description: '15-agent swarm implementation of 10 ADRs',
|
|
||||||
dueDate: this.calculate14WeekDeadline()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async createEpicIssues(): Promise<void> {
|
|
||||||
const epics = [
|
|
||||||
{ title: 'Security Overhaul (CVE-1,2,3)', agents: [2, 3, 4] },
|
|
||||||
{ title: 'Memory Unification (AgentDB)', agents: [7] },
|
|
||||||
{ title: 'agentic-flow Integration', agents: [10] },
|
|
||||||
{ title: 'Performance Optimization', agents: [14] },
|
|
||||||
{ title: 'DDD Architecture', agents: [5, 6] }
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const epic of epics) {
|
|
||||||
await gh.createIssue({
|
|
||||||
title: epic.title,
|
|
||||||
labels: ['epic', 'v3', ...epic.agents.map(id => `agent-${id}`)],
|
|
||||||
assignees: epic.agents.map(id => this.getAgentGithubUser(id))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async trackProgress(): Promise<void> {
|
|
||||||
// Hourly progress updates from each agent
|
|
||||||
setInterval(async () => {
|
|
||||||
for (const agent of this.agents) {
|
|
||||||
await this.postAgentProgress(agent);
|
|
||||||
}
|
|
||||||
}, 3600000); // 1 hour
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Communication Bus
|
|
||||||
```typescript
|
|
||||||
class SwarmCommunication {
|
|
||||||
private bus = new QuicSwarmBus({
|
|
||||||
maxAgents: 15,
|
|
||||||
messageTimeout: 30000,
|
|
||||||
retryAttempts: 3
|
|
||||||
});
|
|
||||||
|
|
||||||
async broadcastToSecurityDomain(message: SwarmMessage): Promise<void> {
|
|
||||||
await this.bus.broadcast(message, {
|
|
||||||
targetAgents: [2, 3, 4],
|
|
||||||
priority: 'critical'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async coordinateCoreSystems(message: SwarmMessage): Promise<void> {
|
|
||||||
await this.bus.broadcast(message, {
|
|
||||||
targetAgents: [5, 6, 7, 8, 9],
|
|
||||||
priority: 'high'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async notifyIntegrationTeam(message: SwarmMessage): Promise<void> {
|
|
||||||
await this.bus.broadcast(message, {
|
|
||||||
targetAgents: [10, 11, 12],
|
|
||||||
priority: 'medium'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Performance Coordination
|
|
||||||
|
|
||||||
### Parallel Efficiency Monitoring
|
|
||||||
```typescript
|
|
||||||
class EfficiencyMonitor {
|
|
||||||
async measureParallelEfficiency(): Promise<EfficiencyReport> {
|
|
||||||
const agentUtilization = await this.measureAgentUtilization();
|
|
||||||
const coordinationOverhead = await this.measureCoordinationCost();
|
|
||||||
|
|
||||||
return {
|
|
||||||
totalEfficiency: agentUtilization.average,
|
|
||||||
target: 0.85, // >85% utilization
|
|
||||||
achieved: agentUtilization.average > 0.85,
|
|
||||||
bottlenecks: this.identifyBottlenecks(agentUtilization),
|
|
||||||
recommendations: this.generateOptimizations()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Load Balancing
|
|
||||||
```typescript
|
|
||||||
class SwarmLoadBalancer {
|
|
||||||
async balanceWorkload(): Promise<void> {
|
|
||||||
const workloads = await this.analyzeAgentWorkloads();
|
|
||||||
|
|
||||||
for (const [agentId, load] of workloads.entries()) {
|
|
||||||
if (load > this.getCapacityThreshold(agentId)) {
|
|
||||||
await this.redistributeWork(agentId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async redistributeWork(overloadedAgent: number): Promise<void> {
|
|
||||||
const availableAgents = this.getAvailableAgents();
|
|
||||||
const tasks = await this.getAgentTasks(overloadedAgent);
|
|
||||||
|
|
||||||
// Redistribute tasks to available agents
|
|
||||||
for (const task of tasks) {
|
|
||||||
const bestAgent = this.selectOptimalAgent(task, availableAgents);
|
|
||||||
await this.reassignTask(task, bestAgent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Success Metrics
|
|
||||||
|
|
||||||
### Swarm Coordination
|
|
||||||
- [ ] **Parallel Efficiency**: >85% agent utilization time
|
|
||||||
- [ ] **Dependency Resolution**: Zero deadlocks or blocking issues
|
|
||||||
- [ ] **Communication Latency**: <100ms inter-agent messaging
|
|
||||||
- [ ] **Timeline Adherence**: 14-week delivery maintained
|
|
||||||
- [ ] **GitHub Integration**: <4h automated issue response
|
|
||||||
|
|
||||||
### Implementation Targets
|
|
||||||
- [ ] **ADR Coverage**: All 10 ADRs implemented successfully
|
|
||||||
- [ ] **Performance**: 2.49x-7.47x Flash Attention achieved
|
|
||||||
- [ ] **Search**: 150x-12,500x AgentDB improvement validated
|
|
||||||
- [ ] **Code Reduction**: <5,000 lines (vs 15,000+)
|
|
||||||
- [ ] **Security**: 90/100 security score achieved
|
|
||||||
|
|
||||||
## Related V3 Skills
|
|
||||||
|
|
||||||
- `v3-security-overhaul` - Security domain coordination
|
|
||||||
- `v3-memory-unification` - Memory system coordination
|
|
||||||
- `v3-integration-deep` - Integration domain coordination
|
|
||||||
- `v3-performance-optimization` - Performance domain coordination
|
|
||||||
|
|
||||||
## Usage Examples
|
|
||||||
|
|
||||||
### Initialize Complete V3 Swarm
|
|
||||||
```bash
|
|
||||||
# Queen Coordinator initializes full swarm
|
|
||||||
Task("V3 swarm initialization",
|
|
||||||
"Initialize 15-agent hierarchical mesh for complete v3 implementation",
|
|
||||||
"v3-queen-coordinator")
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase-based Execution
|
|
||||||
```bash
|
|
||||||
# Phase 1: Security-first foundation
|
|
||||||
npm run v3:phase1:security
|
|
||||||
|
|
||||||
# Phase 2: Core systems parallel
|
|
||||||
npm run v3:phase2:core-systems
|
|
||||||
|
|
||||||
# Phase 3: Integration and optimization
|
|
||||||
npm run v3:phase3:integration
|
|
||||||
|
|
||||||
# Phase 4: Release preparation
|
|
||||||
npm run v3:phase4:release
|
|
||||||
```
|
|
||||||
@ -1,649 +0,0 @@
|
|||||||
---
|
|
||||||
name: "Verification & Quality Assurance"
|
|
||||||
description: "Comprehensive truth scoring, code quality verification, and automatic rollback system with 0.95 accuracy threshold for ensuring high-quality agent outputs and codebase reliability."
|
|
||||||
version: "2.0.0"
|
|
||||||
category: "quality-assurance"
|
|
||||||
tags: ["verification", "truth-scoring", "quality", "rollback", "metrics", "ci-cd"]
|
|
||||||
---
|
|
||||||
|
|
||||||
# Verification & Quality Assurance Skill
|
|
||||||
|
|
||||||
## What This Skill Does
|
|
||||||
|
|
||||||
This skill provides a comprehensive verification and quality assurance system that ensures code quality and correctness through:
|
|
||||||
|
|
||||||
- **Truth Scoring**: Real-time reliability metrics (0.0-1.0 scale) for code, agents, and tasks
|
|
||||||
- **Verification Checks**: Automated code correctness, security, and best practices validation
|
|
||||||
- **Automatic Rollback**: Instant reversion of changes that fail verification (default threshold: 0.95)
|
|
||||||
- **Quality Metrics**: Statistical analysis with trends, confidence intervals, and improvement tracking
|
|
||||||
- **CI/CD Integration**: Export capabilities for continuous integration pipelines
|
|
||||||
- **Real-time Monitoring**: Live dashboards and watch modes for ongoing verification
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- Codex Flow installed (`npx Codex-flow@alpha`)
|
|
||||||
- Git repository (for rollback features)
|
|
||||||
- Node.js 18+ (for dashboard features)
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# View current truth scores
|
|
||||||
npx Codex-flow@alpha truth
|
|
||||||
|
|
||||||
# Run verification check
|
|
||||||
npx Codex-flow@alpha verify check
|
|
||||||
|
|
||||||
# Verify specific file with custom threshold
|
|
||||||
npx Codex-flow@alpha verify check --file src/app.js --threshold 0.98
|
|
||||||
|
|
||||||
# Rollback last failed verification
|
|
||||||
npx Codex-flow@alpha verify rollback --last-good
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Complete Guide
|
|
||||||
|
|
||||||
### Truth Scoring System
|
|
||||||
|
|
||||||
#### View Truth Metrics
|
|
||||||
|
|
||||||
Display comprehensive quality and reliability metrics for your codebase and agent tasks.
|
|
||||||
|
|
||||||
**Basic Usage:**
|
|
||||||
```bash
|
|
||||||
# View current truth scores (default: table format)
|
|
||||||
npx Codex-flow@alpha truth
|
|
||||||
|
|
||||||
# View scores for specific time period
|
|
||||||
npx Codex-flow@alpha truth --period 7d
|
|
||||||
|
|
||||||
# View scores for specific agent
|
|
||||||
npx Codex-flow@alpha truth --agent coder --period 24h
|
|
||||||
|
|
||||||
# Find files/tasks below threshold
|
|
||||||
npx Codex-flow@alpha truth --threshold 0.8
|
|
||||||
```
|
|
||||||
|
|
||||||
**Output Formats:**
|
|
||||||
```bash
|
|
||||||
# Table format (default)
|
|
||||||
npx Codex-flow@alpha truth --format table
|
|
||||||
|
|
||||||
# JSON for programmatic access
|
|
||||||
npx Codex-flow@alpha truth --format json
|
|
||||||
|
|
||||||
# CSV for spreadsheet analysis
|
|
||||||
npx Codex-flow@alpha truth --format csv
|
|
||||||
|
|
||||||
# HTML report with visualizations
|
|
||||||
npx Codex-flow@alpha truth --format html --export report.html
|
|
||||||
```
|
|
||||||
|
|
||||||
**Real-time Monitoring:**
|
|
||||||
```bash
|
|
||||||
# Watch mode with live updates
|
|
||||||
npx Codex-flow@alpha truth --watch
|
|
||||||
|
|
||||||
# Export metrics automatically
|
|
||||||
npx Codex-flow@alpha truth --export .Codex-flow/metrics/truth-$(date +%Y%m%d).json
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Truth Score Dashboard
|
|
||||||
|
|
||||||
Example dashboard output:
|
|
||||||
```
|
|
||||||
📊 Truth Metrics Dashboard
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
Overall Truth Score: 0.947 ✅
|
|
||||||
Trend: ↗️ +2.3% (7d)
|
|
||||||
|
|
||||||
Top Performers:
|
|
||||||
verification-agent 0.982 ⭐
|
|
||||||
code-analyzer 0.971 ⭐
|
|
||||||
test-generator 0.958 ✅
|
|
||||||
|
|
||||||
Needs Attention:
|
|
||||||
refactor-agent 0.821 ⚠️
|
|
||||||
docs-generator 0.794 ⚠️
|
|
||||||
|
|
||||||
Recent Tasks:
|
|
||||||
task-456 0.991 ✅ "Implement auth"
|
|
||||||
task-455 0.967 ✅ "Add tests"
|
|
||||||
task-454 0.743 ❌ "Refactor API"
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Metrics Explained
|
|
||||||
|
|
||||||
**Truth Scores (0.0-1.0):**
|
|
||||||
- `1.0-0.95`: Excellent ⭐ (production-ready)
|
|
||||||
- `0.94-0.85`: Good ✅ (acceptable quality)
|
|
||||||
- `0.84-0.75`: Warning ⚠️ (needs attention)
|
|
||||||
- `<0.75`: Critical ❌ (requires immediate action)
|
|
||||||
|
|
||||||
**Trend Indicators:**
|
|
||||||
- ↗️ Improving (positive trend)
|
|
||||||
- → Stable (consistent performance)
|
|
||||||
- ↘️ Declining (quality regression detected)
|
|
||||||
|
|
||||||
**Statistics:**
|
|
||||||
- **Mean Score**: Average truth score across all measurements
|
|
||||||
- **Median Score**: Middle value (less affected by outliers)
|
|
||||||
- **Standard Deviation**: Consistency of scores (lower = more consistent)
|
|
||||||
- **Confidence Interval**: Statistical reliability of measurements
|
|
||||||
|
|
||||||
### Verification Checks
|
|
||||||
|
|
||||||
#### Run Verification
|
|
||||||
|
|
||||||
Execute comprehensive verification checks on code, tasks, or agent outputs.
|
|
||||||
|
|
||||||
**File Verification:**
|
|
||||||
```bash
|
|
||||||
# Verify single file
|
|
||||||
npx Codex-flow@alpha verify check --file src/app.js
|
|
||||||
|
|
||||||
# Verify directory recursively
|
|
||||||
npx Codex-flow@alpha verify check --directory src/
|
|
||||||
|
|
||||||
# Verify with auto-fix enabled
|
|
||||||
npx Codex-flow@alpha verify check --file src/utils.js --auto-fix
|
|
||||||
|
|
||||||
# Verify current working directory
|
|
||||||
npx Codex-flow@alpha verify check
|
|
||||||
```
|
|
||||||
|
|
||||||
**Task Verification:**
|
|
||||||
```bash
|
|
||||||
# Verify specific task output
|
|
||||||
npx Codex-flow@alpha verify check --task task-123
|
|
||||||
|
|
||||||
# Verify with custom threshold
|
|
||||||
npx Codex-flow@alpha verify check --task task-456 --threshold 0.99
|
|
||||||
|
|
||||||
# Verbose output for debugging
|
|
||||||
npx Codex-flow@alpha verify check --task task-789 --verbose
|
|
||||||
```
|
|
||||||
|
|
||||||
**Batch Verification:**
|
|
||||||
```bash
|
|
||||||
# Verify multiple files in parallel
|
|
||||||
npx Codex-flow@alpha verify batch --files "*.js" --parallel
|
|
||||||
|
|
||||||
# Verify with pattern matching
|
|
||||||
npx Codex-flow@alpha verify batch --pattern "src/**/*.ts"
|
|
||||||
|
|
||||||
# Integration test suite
|
|
||||||
npx Codex-flow@alpha verify integration --test-suite full
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Verification Criteria
|
|
||||||
|
|
||||||
The verification system evaluates:
|
|
||||||
|
|
||||||
1. **Code Correctness**
|
|
||||||
- Syntax validation
|
|
||||||
- Type checking (TypeScript)
|
|
||||||
- Logic flow analysis
|
|
||||||
- Error handling completeness
|
|
||||||
|
|
||||||
2. **Best Practices**
|
|
||||||
- Code style adherence
|
|
||||||
- SOLID principles
|
|
||||||
- Design patterns usage
|
|
||||||
- Modularity and reusability
|
|
||||||
|
|
||||||
3. **Security**
|
|
||||||
- Vulnerability scanning
|
|
||||||
- Secret detection
|
|
||||||
- Input validation
|
|
||||||
- Authentication/authorization checks
|
|
||||||
|
|
||||||
4. **Performance**
|
|
||||||
- Algorithmic complexity
|
|
||||||
- Memory usage patterns
|
|
||||||
- Database query optimization
|
|
||||||
- Bundle size impact
|
|
||||||
|
|
||||||
5. **Documentation**
|
|
||||||
- JSDoc/TypeDoc completeness
|
|
||||||
- README accuracy
|
|
||||||
- API documentation
|
|
||||||
- Code comments quality
|
|
||||||
|
|
||||||
#### JSON Output for CI/CD
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Get structured JSON output
|
|
||||||
npx Codex-flow@alpha verify check --json > verification.json
|
|
||||||
|
|
||||||
# Example JSON structure:
|
|
||||||
{
|
|
||||||
"overallScore": 0.947,
|
|
||||||
"passed": true,
|
|
||||||
"threshold": 0.95,
|
|
||||||
"checks": [
|
|
||||||
{
|
|
||||||
"name": "code-correctness",
|
|
||||||
"score": 0.98,
|
|
||||||
"passed": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "security",
|
|
||||||
"score": 0.91,
|
|
||||||
"passed": false,
|
|
||||||
"issues": [...]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Automatic Rollback
|
|
||||||
|
|
||||||
#### Rollback Failed Changes
|
|
||||||
|
|
||||||
Automatically revert changes that fail verification checks.
|
|
||||||
|
|
||||||
**Basic Rollback:**
|
|
||||||
```bash
|
|
||||||
# Rollback to last known good state
|
|
||||||
npx Codex-flow@alpha verify rollback --last-good
|
|
||||||
|
|
||||||
# Rollback to specific commit
|
|
||||||
npx Codex-flow@alpha verify rollback --to-commit abc123
|
|
||||||
|
|
||||||
# Interactive rollback with preview
|
|
||||||
npx Codex-flow@alpha verify rollback --interactive
|
|
||||||
```
|
|
||||||
|
|
||||||
**Smart Rollback:**
|
|
||||||
```bash
|
|
||||||
# Rollback only failed files (preserve good changes)
|
|
||||||
npx Codex-flow@alpha verify rollback --selective
|
|
||||||
|
|
||||||
# Rollback with automatic backup
|
|
||||||
npx Codex-flow@alpha verify rollback --backup-first
|
|
||||||
|
|
||||||
# Dry-run mode (preview without executing)
|
|
||||||
npx Codex-flow@alpha verify rollback --dry-run
|
|
||||||
```
|
|
||||||
|
|
||||||
**Rollback Performance:**
|
|
||||||
- Git-based rollback: <1 second
|
|
||||||
- Selective file rollback: <500ms
|
|
||||||
- Backup creation: Automatic before rollback
|
|
||||||
|
|
||||||
### Verification Reports
|
|
||||||
|
|
||||||
#### Generate Reports
|
|
||||||
|
|
||||||
Create detailed verification reports with metrics and visualizations.
|
|
||||||
|
|
||||||
**Report Formats:**
|
|
||||||
```bash
|
|
||||||
# JSON report
|
|
||||||
npx Codex-flow@alpha verify report --format json
|
|
||||||
|
|
||||||
# HTML report with charts
|
|
||||||
npx Codex-flow@alpha verify report --export metrics.html --format html
|
|
||||||
|
|
||||||
# CSV for data analysis
|
|
||||||
npx Codex-flow@alpha verify report --format csv --export metrics.csv
|
|
||||||
|
|
||||||
# Markdown summary
|
|
||||||
npx Codex-flow@alpha verify report --format markdown
|
|
||||||
```
|
|
||||||
|
|
||||||
**Time-based Reports:**
|
|
||||||
```bash
|
|
||||||
# Last 24 hours
|
|
||||||
npx Codex-flow@alpha verify report --period 24h
|
|
||||||
|
|
||||||
# Last 7 days
|
|
||||||
npx Codex-flow@alpha verify report --period 7d
|
|
||||||
|
|
||||||
# Last 30 days with trends
|
|
||||||
npx Codex-flow@alpha verify report --period 30d --include-trends
|
|
||||||
|
|
||||||
# Custom date range
|
|
||||||
npx Codex-flow@alpha verify report --from 2025-01-01 --to 2025-01-31
|
|
||||||
```
|
|
||||||
|
|
||||||
**Report Content:**
|
|
||||||
- Overall truth scores
|
|
||||||
- Per-agent performance metrics
|
|
||||||
- Task completion quality
|
|
||||||
- Verification pass/fail rates
|
|
||||||
- Rollback frequency
|
|
||||||
- Quality improvement trends
|
|
||||||
- Statistical confidence intervals
|
|
||||||
|
|
||||||
### Interactive Dashboard
|
|
||||||
|
|
||||||
#### Launch Dashboard
|
|
||||||
|
|
||||||
Run interactive web-based verification dashboard with real-time updates.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Launch dashboard on default port (3000)
|
|
||||||
npx Codex-flow@alpha verify dashboard
|
|
||||||
|
|
||||||
# Custom port
|
|
||||||
npx Codex-flow@alpha verify dashboard --port 8080
|
|
||||||
|
|
||||||
# Export dashboard data
|
|
||||||
npx Codex-flow@alpha verify dashboard --export
|
|
||||||
|
|
||||||
# Dashboard with auto-refresh
|
|
||||||
npx Codex-flow@alpha verify dashboard --refresh 5s
|
|
||||||
```
|
|
||||||
|
|
||||||
**Dashboard Features:**
|
|
||||||
- Real-time truth score updates (WebSocket)
|
|
||||||
- Interactive charts and graphs
|
|
||||||
- Agent performance comparison
|
|
||||||
- Task history timeline
|
|
||||||
- Rollback history viewer
|
|
||||||
- Export to PDF/HTML
|
|
||||||
- Filter by time period/agent/score
|
|
||||||
|
|
||||||
### Configuration
|
|
||||||
|
|
||||||
#### Default Configuration
|
|
||||||
|
|
||||||
Set verification preferences in `.Codex-flow/config.json`:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"verification": {
|
|
||||||
"threshold": 0.95,
|
|
||||||
"autoRollback": true,
|
|
||||||
"gitIntegration": true,
|
|
||||||
"hooks": {
|
|
||||||
"preCommit": true,
|
|
||||||
"preTask": true,
|
|
||||||
"postEdit": true
|
|
||||||
},
|
|
||||||
"checks": {
|
|
||||||
"codeCorrectness": true,
|
|
||||||
"security": true,
|
|
||||||
"performance": true,
|
|
||||||
"documentation": true,
|
|
||||||
"bestPractices": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"truth": {
|
|
||||||
"defaultFormat": "table",
|
|
||||||
"defaultPeriod": "24h",
|
|
||||||
"warningThreshold": 0.85,
|
|
||||||
"criticalThreshold": 0.75,
|
|
||||||
"autoExport": {
|
|
||||||
"enabled": true,
|
|
||||||
"path": ".Codex-flow/metrics/truth-daily.json"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Threshold Configuration
|
|
||||||
|
|
||||||
**Adjust verification strictness:**
|
|
||||||
```bash
|
|
||||||
# Strict mode (99% accuracy required)
|
|
||||||
npx Codex-flow@alpha verify check --threshold 0.99
|
|
||||||
|
|
||||||
# Lenient mode (90% acceptable)
|
|
||||||
npx Codex-flow@alpha verify check --threshold 0.90
|
|
||||||
|
|
||||||
# Set default threshold
|
|
||||||
npx Codex-flow@alpha config set verification.threshold 0.98
|
|
||||||
```
|
|
||||||
|
|
||||||
**Per-environment thresholds:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"verification": {
|
|
||||||
"thresholds": {
|
|
||||||
"production": 0.99,
|
|
||||||
"staging": 0.95,
|
|
||||||
"development": 0.90
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Integration Examples
|
|
||||||
|
|
||||||
#### CI/CD Integration
|
|
||||||
|
|
||||||
**GitHub Actions:**
|
|
||||||
```yaml
|
|
||||||
name: Quality Verification
|
|
||||||
|
|
||||||
on: [push, pull_request]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
verify:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Install Dependencies
|
|
||||||
run: npm install
|
|
||||||
|
|
||||||
- name: Run Verification
|
|
||||||
run: |
|
|
||||||
npx Codex-flow@alpha verify check --json > verification.json
|
|
||||||
|
|
||||||
- name: Check Truth Score
|
|
||||||
run: |
|
|
||||||
score=$(jq '.overallScore' verification.json)
|
|
||||||
if (( $(echo "$score < 0.95" | bc -l) )); then
|
|
||||||
echo "Truth score too low: $score"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Upload Report
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: verification-report
|
|
||||||
path: verification.json
|
|
||||||
```
|
|
||||||
|
|
||||||
**GitLab CI:**
|
|
||||||
```yaml
|
|
||||||
verify:
|
|
||||||
stage: test
|
|
||||||
script:
|
|
||||||
- npx Codex-flow@alpha verify check --threshold 0.95 --json > verification.json
|
|
||||||
- |
|
|
||||||
score=$(jq '.overallScore' verification.json)
|
|
||||||
if [ $(echo "$score < 0.95" | bc) -eq 1 ]; then
|
|
||||||
echo "Verification failed with score: $score"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- verification.json
|
|
||||||
reports:
|
|
||||||
junit: verification.json
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Swarm Integration
|
|
||||||
|
|
||||||
Run verification automatically during swarm operations:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Swarm with verification enabled
|
|
||||||
npx Codex-flow@alpha swarm --verify --threshold 0.98
|
|
||||||
|
|
||||||
# Hive Mind with auto-rollback
|
|
||||||
npx Codex-flow@alpha hive-mind --verify --rollback-on-fail
|
|
||||||
|
|
||||||
# Training pipeline with verification
|
|
||||||
npx Codex-flow@alpha train --verify --threshold 0.99
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Pair Programming Integration
|
|
||||||
|
|
||||||
Enable real-time verification during collaborative development:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Pair with verification
|
|
||||||
npx Codex-flow@alpha pair --verify --real-time
|
|
||||||
|
|
||||||
# Pair with custom threshold
|
|
||||||
npx Codex-flow@alpha pair --verify --threshold 0.97 --auto-fix
|
|
||||||
```
|
|
||||||
|
|
||||||
### Advanced Workflows
|
|
||||||
|
|
||||||
#### Continuous Verification
|
|
||||||
|
|
||||||
Monitor codebase continuously during development:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Watch directory for changes
|
|
||||||
npx Codex-flow@alpha verify watch --directory src/
|
|
||||||
|
|
||||||
# Watch with auto-fix
|
|
||||||
npx Codex-flow@alpha verify watch --directory src/ --auto-fix
|
|
||||||
|
|
||||||
# Watch with notifications
|
|
||||||
npx Codex-flow@alpha verify watch --notify --threshold 0.95
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Monitoring Integration
|
|
||||||
|
|
||||||
Send metrics to external monitoring systems:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Export to Prometheus
|
|
||||||
npx Codex-flow@alpha truth --format json | \
|
|
||||||
curl -X POST https://pushgateway.example.com/metrics/job/Codex-flow \
|
|
||||||
-d @-
|
|
||||||
|
|
||||||
# Send to DataDog
|
|
||||||
npx Codex-flow@alpha verify report --format json | \
|
|
||||||
curl -X POST "https://api.datadoghq.com/api/v1/series?api_key=${DD_API_KEY}" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d @-
|
|
||||||
|
|
||||||
# Custom webhook
|
|
||||||
npx Codex-flow@alpha truth --format json | \
|
|
||||||
curl -X POST https://metrics.example.com/api/truth \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d @-
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Pre-commit Hooks
|
|
||||||
|
|
||||||
Automatically verify before commits:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Install pre-commit hook
|
|
||||||
npx Codex-flow@alpha verify install-hook --pre-commit
|
|
||||||
|
|
||||||
# .git/hooks/pre-commit example:
|
|
||||||
#!/bin/bash
|
|
||||||
npx Codex-flow@alpha verify check --threshold 0.95 --json > /tmp/verify.json
|
|
||||||
|
|
||||||
score=$(jq '.overallScore' /tmp/verify.json)
|
|
||||||
if (( $(echo "$score < 0.95" | bc -l) )); then
|
|
||||||
echo "❌ Verification failed with score: $score"
|
|
||||||
echo "Run 'npx Codex-flow@alpha verify check --verbose' for details"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "✅ Verification passed with score: $score"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Performance Metrics
|
|
||||||
|
|
||||||
**Verification Speed:**
|
|
||||||
- Single file check: <100ms
|
|
||||||
- Directory scan: <500ms (per 100 files)
|
|
||||||
- Full codebase analysis: <5s (typical project)
|
|
||||||
- Truth score calculation: <50ms
|
|
||||||
|
|
||||||
**Rollback Speed:**
|
|
||||||
- Git-based rollback: <1s
|
|
||||||
- Selective file rollback: <500ms
|
|
||||||
- Backup creation: <2s
|
|
||||||
|
|
||||||
**Dashboard Performance:**
|
|
||||||
- Initial load: <1s
|
|
||||||
- Real-time updates: <100ms latency (WebSocket)
|
|
||||||
- Chart rendering: 60 FPS
|
|
||||||
|
|
||||||
### Troubleshooting
|
|
||||||
|
|
||||||
#### Common Issues
|
|
||||||
|
|
||||||
**Low Truth Scores:**
|
|
||||||
```bash
|
|
||||||
# Get detailed breakdown
|
|
||||||
npx Codex-flow@alpha truth --verbose --threshold 0.0
|
|
||||||
|
|
||||||
# Check specific criteria
|
|
||||||
npx Codex-flow@alpha verify check --verbose
|
|
||||||
|
|
||||||
# View agent-specific issues
|
|
||||||
npx Codex-flow@alpha truth --agent <agent-name> --format json
|
|
||||||
```
|
|
||||||
|
|
||||||
**Rollback Failures:**
|
|
||||||
```bash
|
|
||||||
# Check git status
|
|
||||||
git status
|
|
||||||
|
|
||||||
# View rollback history
|
|
||||||
npx Codex-flow@alpha verify rollback --history
|
|
||||||
|
|
||||||
# Manual rollback
|
|
||||||
git reset --hard HEAD~1
|
|
||||||
```
|
|
||||||
|
|
||||||
**Verification Timeouts:**
|
|
||||||
```bash
|
|
||||||
# Increase timeout
|
|
||||||
npx Codex-flow@alpha verify check --timeout 60s
|
|
||||||
|
|
||||||
# Verify in batches
|
|
||||||
npx Codex-flow@alpha verify batch --batch-size 10
|
|
||||||
```
|
|
||||||
|
|
||||||
### Exit Codes
|
|
||||||
|
|
||||||
Verification commands return standard exit codes:
|
|
||||||
|
|
||||||
- `0`: Verification passed (score ≥ threshold)
|
|
||||||
- `1`: Verification failed (score < threshold)
|
|
||||||
- `2`: Error during verification (invalid input, system error)
|
|
||||||
|
|
||||||
### Related Commands
|
|
||||||
|
|
||||||
- `npx Codex-flow@alpha pair` - Collaborative development with verification
|
|
||||||
- `npx Codex-flow@alpha train` - Training with verification feedback
|
|
||||||
- `npx Codex-flow@alpha swarm` - Multi-agent coordination with quality checks
|
|
||||||
- `npx Codex-flow@alpha report` - Generate comprehensive project reports
|
|
||||||
|
|
||||||
### Best Practices
|
|
||||||
|
|
||||||
1. **Set Appropriate Thresholds**: Use 0.99 for critical code, 0.95 for standard, 0.90 for experimental
|
|
||||||
2. **Enable Auto-rollback**: Prevent bad code from persisting
|
|
||||||
3. **Monitor Trends**: Track improvement over time, not just current scores
|
|
||||||
4. **Integrate with CI/CD**: Make verification part of your pipeline
|
|
||||||
5. **Use Watch Mode**: Get immediate feedback during development
|
|
||||||
6. **Export Metrics**: Track quality metrics in your monitoring system
|
|
||||||
7. **Review Rollbacks**: Understand why changes were rejected
|
|
||||||
8. **Train Agents**: Use verification feedback to improve agent performance
|
|
||||||
|
|
||||||
### Additional Resources
|
|
||||||
|
|
||||||
- Truth Scoring Algorithm: See `/docs/truth-scoring.md`
|
|
||||||
- Verification Criteria: See `/docs/verification-criteria.md`
|
|
||||||
- Integration Examples: See `/examples/verification/`
|
|
||||||
- API Reference: See `/docs/api/verification.md`
|
|
||||||
@ -1,579 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/redis/go-redis/v9"
|
|
||||||
_ "github.com/go-sql-driver/mysql"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
type appConfig struct {
|
|
||||||
MySQL struct {
|
|
||||||
Addr string `yaml:"Addr"`
|
|
||||||
Username string `yaml:"Username"`
|
|
||||||
Password string `yaml:"Password"`
|
|
||||||
Dbname string `yaml:"Dbname"`
|
|
||||||
Config string `yaml:"Config"`
|
|
||||||
} `yaml:"MySQL"`
|
|
||||||
Redis struct {
|
|
||||||
Host string `yaml:"Host"`
|
|
||||||
Pass string `yaml:"Pass"`
|
|
||||||
DB int `yaml:"DB"`
|
|
||||||
} `yaml:"Redis"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type userRow struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
ReferCode string `json:"refer_code"`
|
|
||||||
Balance int64 `json:"balance"`
|
|
||||||
Commission int64 `json:"commission"`
|
|
||||||
GiftAmount int64 `json:"gift_amount"`
|
|
||||||
Enable bool `json:"enable"`
|
|
||||||
IsAdmin bool `json:"is_admin"`
|
|
||||||
ValidEmail bool `json:"valid_email"`
|
|
||||||
MemberStatus string `json:"member_status"`
|
|
||||||
CreatedAt time.Time `json:"created_at"`
|
|
||||||
DeletedAt sql.NullTime `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type authMethod struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
UserID int64 `json:"user_id"`
|
|
||||||
AuthType string `json:"auth_type"`
|
|
||||||
Identifier string `json:"identifier"`
|
|
||||||
Verified bool `json:"verified"`
|
|
||||||
CreatedAt time.Time `json:"created_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type deviceInfo struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
UserID int64 `json:"user_id"`
|
|
||||||
IP string `json:"ip"`
|
|
||||||
UserAgent string `json:"user_agent"`
|
|
||||||
Identifier string `json:"identifier"`
|
|
||||||
ShortCode string `json:"short_code"`
|
|
||||||
Online bool `json:"online"`
|
|
||||||
Enabled bool `json:"enabled"`
|
|
||||||
CreatedAt time.Time `json:"created_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type subscribeInfo struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
UserID int64 `json:"user_id"`
|
|
||||||
OrderID int64 `json:"order_id"`
|
|
||||||
SubscribeID int64 `json:"subscribe_id"`
|
|
||||||
Token string `json:"token"`
|
|
||||||
UUID string `json:"uuid"`
|
|
||||||
Status uint8 `json:"status"`
|
|
||||||
StartTime time.Time `json:"start_time"`
|
|
||||||
ExpireTime time.Time `json:"expire_time"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type familyInfo struct {
|
|
||||||
FamilyID int64 `json:"family_id"`
|
|
||||||
OwnerUserID int64 `json:"owner_user_id"`
|
|
||||||
IsOwner bool `json:"is_owner"`
|
|
||||||
MemberCount int64 `json:"member_count"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type userSummary struct {
|
|
||||||
User userRow `json:"user"`
|
|
||||||
AuthMethods []authMethod `json:"auth_methods"`
|
|
||||||
Devices []deviceInfo `json:"devices"`
|
|
||||||
Subscriptions []subscribeInfo `json:"subscriptions"`
|
|
||||||
Family *familyInfo `json:"family,omitempty"`
|
|
||||||
OrderCount int64 `json:"order_count"`
|
|
||||||
TicketCount int64 `json:"ticket_count"`
|
|
||||||
TrafficLogCount int64 `json:"traffic_log_count"`
|
|
||||||
SystemLogCount int64 `json:"system_log_count"`
|
|
||||||
WithdrawalCount int64 `json:"withdrawal_count"`
|
|
||||||
IAPTransactionCount int64 `json:"iap_transaction_count"`
|
|
||||||
LogMessageCount int64 `json:"log_message_count"`
|
|
||||||
OnlineRecordCount int64 `json:"online_record_count"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type deleteResult struct {
|
|
||||||
UserID int64 `json:"user_id"`
|
|
||||||
DeletedDBRows []string `json:"deleted_db_rows"`
|
|
||||||
DeletedRedisKeys int `json:"deleted_redis_keys"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func must(err error) {
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
ctx := context.Background()
|
|
||||||
cfg := loadConfig("/Users/Apple/code_vpn/vpn/ppanel-server/etc/ppanel.yaml")
|
|
||||||
|
|
||||||
dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?%s", cfg.MySQL.Username, cfg.MySQL.Password, cfg.MySQL.Addr, cfg.MySQL.Dbname, cfg.MySQL.Config)
|
|
||||||
db, err := sql.Open("mysql", dsn)
|
|
||||||
must(err)
|
|
||||||
defer db.Close()
|
|
||||||
must(db.PingContext(ctx))
|
|
||||||
|
|
||||||
rdb := redis.NewClient(&redis.Options{
|
|
||||||
Addr: cfg.Redis.Host,
|
|
||||||
Password: cfg.Redis.Pass,
|
|
||||||
DB: cfg.Redis.DB,
|
|
||||||
})
|
|
||||||
defer rdb.Close()
|
|
||||||
must(rdb.Ping(ctx).Err())
|
|
||||||
|
|
||||||
targetUserIDs, err := findTargetUsers(ctx, db)
|
|
||||||
must(err)
|
|
||||||
if len(targetUserIDs) == 0 {
|
|
||||||
fmt.Println(`{"matched_users":[],"deleted":[]}`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
summaries := make([]userSummary, 0, len(targetUserIDs))
|
|
||||||
for _, userID := range targetUserIDs {
|
|
||||||
summary, sumErr := collectSummary(ctx, db, userID)
|
|
||||||
must(sumErr)
|
|
||||||
summaries = append(summaries, summary)
|
|
||||||
}
|
|
||||||
|
|
||||||
before, err := json.MarshalIndent(map[string]interface{}{
|
|
||||||
"matched_users": summaries,
|
|
||||||
}, "", " ")
|
|
||||||
must(err)
|
|
||||||
fmt.Println(string(before))
|
|
||||||
|
|
||||||
results := make([]deleteResult, 0, len(targetUserIDs))
|
|
||||||
for _, summary := range summaries {
|
|
||||||
result, delErr := deleteUser(ctx, db, rdb, summary)
|
|
||||||
must(delErr)
|
|
||||||
results = append(results, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
after, err := json.MarshalIndent(map[string]interface{}{
|
|
||||||
"deleted": results,
|
|
||||||
}, "", " ")
|
|
||||||
must(err)
|
|
||||||
fmt.Println(string(after))
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadConfig(path string) appConfig {
|
|
||||||
content, err := os.ReadFile(path)
|
|
||||||
must(err)
|
|
||||||
var cfg appConfig
|
|
||||||
must(yaml.Unmarshal(content, &cfg))
|
|
||||||
return cfg
|
|
||||||
}
|
|
||||||
|
|
||||||
func findTargetUsers(ctx context.Context, db *sql.DB) ([]int64, error) {
|
|
||||||
rows, err := db.QueryContext(ctx, `
|
|
||||||
SELECT DISTINCT user_id
|
|
||||||
FROM user_device
|
|
||||||
WHERE user_agent LIKE ?
|
|
||||||
ORDER BY user_id ASC
|
|
||||||
`, "%999%")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
var ids []int64
|
|
||||||
for rows.Next() {
|
|
||||||
var id int64
|
|
||||||
if err := rows.Scan(&id); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ids = append(ids, id)
|
|
||||||
}
|
|
||||||
return ids, rows.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
func collectSummary(ctx context.Context, db *sql.DB, userID int64) (userSummary, error) {
|
|
||||||
var summary userSummary
|
|
||||||
summary.User.ID = userID
|
|
||||||
|
|
||||||
err := db.QueryRowContext(ctx, `
|
|
||||||
SELECT id, refer_code, balance, commission, gift_amount, enable, is_admin, valid_email, member_status, created_at, deleted_at
|
|
||||||
FROM user
|
|
||||||
WHERE id = ?
|
|
||||||
`, userID).Scan(
|
|
||||||
&summary.User.ID,
|
|
||||||
&summary.User.ReferCode,
|
|
||||||
&summary.User.Balance,
|
|
||||||
&summary.User.Commission,
|
|
||||||
&summary.User.GiftAmount,
|
|
||||||
&summary.User.Enable,
|
|
||||||
&summary.User.IsAdmin,
|
|
||||||
&summary.User.ValidEmail,
|
|
||||||
&summary.User.MemberStatus,
|
|
||||||
&summary.User.CreatedAt,
|
|
||||||
&summary.User.DeletedAt,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return summary, err
|
|
||||||
}
|
|
||||||
|
|
||||||
summary.AuthMethods, err = queryAuthMethods(ctx, db, userID)
|
|
||||||
if err != nil {
|
|
||||||
return summary, err
|
|
||||||
}
|
|
||||||
summary.Devices, err = queryDevices(ctx, db, userID)
|
|
||||||
if err != nil {
|
|
||||||
return summary, err
|
|
||||||
}
|
|
||||||
summary.Subscriptions, err = querySubscriptions(ctx, db, userID)
|
|
||||||
if err != nil {
|
|
||||||
return summary, err
|
|
||||||
}
|
|
||||||
summary.Family, err = queryFamily(ctx, db, userID)
|
|
||||||
if err != nil {
|
|
||||||
return summary, err
|
|
||||||
}
|
|
||||||
if summary.OrderCount, err = queryCount(ctx, db, "SELECT COUNT(*) FROM `order` WHERE user_id = ?", userID); err != nil {
|
|
||||||
return summary, err
|
|
||||||
}
|
|
||||||
if summary.TicketCount, err = queryCount(ctx, db, "SELECT COUNT(*) FROM ticket WHERE user_id = ?", userID); err != nil {
|
|
||||||
return summary, err
|
|
||||||
}
|
|
||||||
if summary.TrafficLogCount, err = queryCount(ctx, db, "SELECT COUNT(*) FROM traffic_log WHERE user_id = ?", userID); err != nil {
|
|
||||||
return summary, err
|
|
||||||
}
|
|
||||||
if summary.SystemLogCount, err = queryCount(ctx, db, "SELECT COUNT(*) FROM system_logs WHERE object_id = ?", userID); err != nil {
|
|
||||||
return summary, err
|
|
||||||
}
|
|
||||||
if summary.WithdrawalCount, err = queryCount(ctx, db, "SELECT COUNT(*) FROM user_withdrawal WHERE user_id = ?", userID); err != nil {
|
|
||||||
return summary, err
|
|
||||||
}
|
|
||||||
if summary.IAPTransactionCount, err = queryCount(ctx, db, "SELECT COUNT(*) FROM apple_iap_transactions WHERE user_id = ?", userID); err != nil {
|
|
||||||
return summary, err
|
|
||||||
}
|
|
||||||
if summary.LogMessageCount, err = queryCount(ctx, db, "SELECT COUNT(*) FROM log_message WHERE user_id = ?", userID); err != nil {
|
|
||||||
return summary, err
|
|
||||||
}
|
|
||||||
if summary.OnlineRecordCount, err = queryCount(ctx, db, "SELECT COUNT(*) FROM user_device_online_record WHERE user_id = ?", userID); err != nil {
|
|
||||||
return summary, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return summary, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func queryAuthMethods(ctx context.Context, db *sql.DB, userID int64) ([]authMethod, error) {
|
|
||||||
rows, err := db.QueryContext(ctx, `
|
|
||||||
SELECT id, user_id, auth_type, auth_identifier, verified, created_at
|
|
||||||
FROM user_auth_methods
|
|
||||||
WHERE user_id = ?
|
|
||||||
ORDER BY id ASC
|
|
||||||
`, userID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
var items []authMethod
|
|
||||||
for rows.Next() {
|
|
||||||
var item authMethod
|
|
||||||
if err := rows.Scan(&item.ID, &item.UserID, &item.AuthType, &item.Identifier, &item.Verified, &item.CreatedAt); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
items = append(items, item)
|
|
||||||
}
|
|
||||||
return items, rows.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
func queryDevices(ctx context.Context, db *sql.DB, userID int64) ([]deviceInfo, error) {
|
|
||||||
rows, err := db.QueryContext(ctx, `
|
|
||||||
SELECT id, user_id, ip, user_agent, identifier, short_code, online, enabled, created_at
|
|
||||||
FROM user_device
|
|
||||||
WHERE user_id = ?
|
|
||||||
ORDER BY id ASC
|
|
||||||
`, userID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
var items []deviceInfo
|
|
||||||
for rows.Next() {
|
|
||||||
var item deviceInfo
|
|
||||||
if err := rows.Scan(&item.ID, &item.UserID, &item.IP, &item.UserAgent, &item.Identifier, &item.ShortCode, &item.Online, &item.Enabled, &item.CreatedAt); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
items = append(items, item)
|
|
||||||
}
|
|
||||||
return items, rows.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
func querySubscriptions(ctx context.Context, db *sql.DB, userID int64) ([]subscribeInfo, error) {
|
|
||||||
rows, err := db.QueryContext(ctx, `
|
|
||||||
SELECT id, user_id, order_id, subscribe_id, token, uuid, status, start_time, expire_time
|
|
||||||
FROM user_subscribe
|
|
||||||
WHERE user_id = ?
|
|
||||||
ORDER BY id ASC
|
|
||||||
`, userID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
var items []subscribeInfo
|
|
||||||
for rows.Next() {
|
|
||||||
var item subscribeInfo
|
|
||||||
if err := rows.Scan(&item.ID, &item.UserID, &item.OrderID, &item.SubscribeID, &item.Token, &item.UUID, &item.Status, &item.StartTime, &item.ExpireTime); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
items = append(items, item)
|
|
||||||
}
|
|
||||||
return items, rows.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
func queryFamily(ctx context.Context, db *sql.DB, userID int64) (*familyInfo, error) {
|
|
||||||
var info familyInfo
|
|
||||||
err := db.QueryRowContext(ctx, `
|
|
||||||
SELECT ufm.family_id, uf.owner_user_id
|
|
||||||
FROM user_family_member ufm
|
|
||||||
JOIN user_family uf ON uf.id = ufm.family_id AND uf.deleted_at IS NULL
|
|
||||||
WHERE ufm.user_id = ? AND ufm.deleted_at IS NULL
|
|
||||||
LIMIT 1
|
|
||||||
`, userID).Scan(&info.FamilyID, &info.OwnerUserID)
|
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
info.IsOwner = info.OwnerUserID == userID
|
|
||||||
memberCount, err := queryCount(ctx, db, `
|
|
||||||
SELECT COUNT(*)
|
|
||||||
FROM user_family_member
|
|
||||||
WHERE family_id = ? AND deleted_at IS NULL
|
|
||||||
`, info.FamilyID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
info.MemberCount = memberCount
|
|
||||||
return &info, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func queryCount(ctx context.Context, db *sql.DB, q string, arg interface{}) (int64, error) {
|
|
||||||
var count int64
|
|
||||||
err := db.QueryRowContext(ctx, q, arg).Scan(&count)
|
|
||||||
return count, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteUser(ctx context.Context, db *sql.DB, rdb *redis.Client, summary userSummary) (deleteResult, error) {
|
|
||||||
result := deleteResult{UserID: summary.User.ID}
|
|
||||||
|
|
||||||
tx, err := db.BeginTx(ctx, nil)
|
|
||||||
if err != nil {
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
if summary.Family != nil {
|
|
||||||
if summary.Family.IsOwner {
|
|
||||||
if res, err := tx.ExecContext(ctx, `DELETE FROM user_family_member WHERE family_id = ?`, summary.Family.FamilyID); err != nil {
|
|
||||||
return result, err
|
|
||||||
} else {
|
|
||||||
result.DeletedDBRows = append(result.DeletedDBRows, fmt.Sprintf("user_family_member=%d", rowsAffected(res)))
|
|
||||||
}
|
|
||||||
if res, err := tx.ExecContext(ctx, `DELETE FROM user_family WHERE id = ?`, summary.Family.FamilyID); err != nil {
|
|
||||||
return result, err
|
|
||||||
} else {
|
|
||||||
result.DeletedDBRows = append(result.DeletedDBRows, fmt.Sprintf("user_family=%d", rowsAffected(res)))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if res, err := tx.ExecContext(ctx, `DELETE FROM user_family_member WHERE user_id = ? AND family_id = ?`, summary.User.ID, summary.Family.FamilyID); err != nil {
|
|
||||||
return result, err
|
|
||||||
} else {
|
|
||||||
result.DeletedDBRows = append(result.DeletedDBRows, fmt.Sprintf("user_family_member=%d", rowsAffected(res)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if res, err := tx.ExecContext(ctx, `DELETE FROM user_auth_methods WHERE user_id = ?`, summary.User.ID); err != nil {
|
|
||||||
return result, err
|
|
||||||
} else { result.DeletedDBRows = append(result.DeletedDBRows, fmt.Sprintf("user_auth_methods=%d", rowsAffected(res))) }
|
|
||||||
|
|
||||||
if res, err := tx.ExecContext(ctx, `DELETE FROM user_subscribe WHERE user_id = ?`, summary.User.ID); err != nil {
|
|
||||||
return result, err
|
|
||||||
} else { result.DeletedDBRows = append(result.DeletedDBRows, fmt.Sprintf("user_subscribe=%d", rowsAffected(res))) }
|
|
||||||
|
|
||||||
if res, err := tx.ExecContext(ctx, `DELETE FROM user_device WHERE user_id = ?`, summary.User.ID); err != nil {
|
|
||||||
return result, err
|
|
||||||
} else { result.DeletedDBRows = append(result.DeletedDBRows, fmt.Sprintf("user_device=%d", rowsAffected(res))) }
|
|
||||||
|
|
||||||
if res, err := tx.ExecContext(ctx, `DELETE FROM user_device_online_record WHERE user_id = ?`, summary.User.ID); err != nil {
|
|
||||||
return result, err
|
|
||||||
} else { result.DeletedDBRows = append(result.DeletedDBRows, fmt.Sprintf("user_device_online_record=%d", rowsAffected(res))) }
|
|
||||||
|
|
||||||
if res, err := tx.ExecContext(ctx, `DELETE FROM user_withdrawal WHERE user_id = ?`, summary.User.ID); err != nil {
|
|
||||||
return result, err
|
|
||||||
} else { result.DeletedDBRows = append(result.DeletedDBRows, fmt.Sprintf("user_withdrawal=%d", rowsAffected(res))) }
|
|
||||||
|
|
||||||
if res, err := tx.ExecContext(ctx, "DELETE FROM `order` WHERE user_id = ?", summary.User.ID); err != nil {
|
|
||||||
return result, err
|
|
||||||
} else { result.DeletedDBRows = append(result.DeletedDBRows, fmt.Sprintf("order=%d", rowsAffected(res))) }
|
|
||||||
|
|
||||||
if res, err := tx.ExecContext(ctx, `DELETE FROM traffic_log WHERE user_id = ?`, summary.User.ID); err != nil {
|
|
||||||
return result, err
|
|
||||||
} else { result.DeletedDBRows = append(result.DeletedDBRows, fmt.Sprintf("traffic_log=%d", rowsAffected(res))) }
|
|
||||||
|
|
||||||
if res, err := tx.ExecContext(ctx, `DELETE FROM system_logs WHERE object_id = ?`, summary.User.ID); err != nil {
|
|
||||||
return result, err
|
|
||||||
} else { result.DeletedDBRows = append(result.DeletedDBRows, fmt.Sprintf("system_logs=%d", rowsAffected(res))) }
|
|
||||||
|
|
||||||
var ticketIDs []int64
|
|
||||||
ticketRows, err := tx.QueryContext(ctx, `SELECT id FROM ticket WHERE user_id = ?`, summary.User.ID)
|
|
||||||
if err != nil {
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
for ticketRows.Next() {
|
|
||||||
var id int64
|
|
||||||
if err := ticketRows.Scan(&id); err != nil {
|
|
||||||
ticketRows.Close()
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
ticketIDs = append(ticketIDs, id)
|
|
||||||
}
|
|
||||||
ticketRows.Close()
|
|
||||||
|
|
||||||
if len(ticketIDs) > 0 {
|
|
||||||
holders := strings.TrimSuffix(strings.Repeat("?,", len(ticketIDs)), ",")
|
|
||||||
args := make([]interface{}, 0, len(ticketIDs))
|
|
||||||
for _, id := range ticketIDs {
|
|
||||||
args = append(args, id)
|
|
||||||
}
|
|
||||||
if res, err := tx.ExecContext(ctx, "DELETE FROM ticket_follow WHERE ticket_id IN ("+holders+")", args...); err != nil {
|
|
||||||
return result, err
|
|
||||||
} else { result.DeletedDBRows = append(result.DeletedDBRows, fmt.Sprintf("ticket_follow=%d", rowsAffected(res))) }
|
|
||||||
}
|
|
||||||
|
|
||||||
if res, err := tx.ExecContext(ctx, `DELETE FROM ticket WHERE user_id = ?`, summary.User.ID); err != nil {
|
|
||||||
return result, err
|
|
||||||
} else { result.DeletedDBRows = append(result.DeletedDBRows, fmt.Sprintf("ticket=%d", rowsAffected(res))) }
|
|
||||||
|
|
||||||
if res, err := tx.ExecContext(ctx, `DELETE FROM apple_iap_transactions WHERE user_id = ?`, summary.User.ID); err != nil {
|
|
||||||
return result, err
|
|
||||||
} else { result.DeletedDBRows = append(result.DeletedDBRows, fmt.Sprintf("apple_iap_transactions=%d", rowsAffected(res))) }
|
|
||||||
|
|
||||||
if res, err := tx.ExecContext(ctx, `DELETE FROM log_message WHERE user_id = ?`, summary.User.ID); err != nil {
|
|
||||||
return result, err
|
|
||||||
} else { result.DeletedDBRows = append(result.DeletedDBRows, fmt.Sprintf("log_message=%d", rowsAffected(res))) }
|
|
||||||
|
|
||||||
if res, err := tx.ExecContext(ctx, `DELETE FROM user WHERE id = ?`, summary.User.ID); err != nil {
|
|
||||||
return result, err
|
|
||||||
} else { result.DeletedDBRows = append(result.DeletedDBRows, fmt.Sprintf("user=%d", rowsAffected(res))) }
|
|
||||||
|
|
||||||
if err := tx.Commit(); err != nil {
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
redisKeys, err := cleanupRedis(ctx, rdb, summary)
|
|
||||||
if err != nil {
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
result.DeletedRedisKeys = len(redisKeys)
|
|
||||||
sort.Strings(result.DeletedDBRows)
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func cleanupRedis(ctx context.Context, rdb *redis.Client, summary userSummary) ([]string, error) {
|
|
||||||
keySet := map[string]struct{}{
|
|
||||||
fmt.Sprintf("cache:user:id:%d", summary.User.ID): {},
|
|
||||||
fmt.Sprintf("cache:user:subscribe:user:%d", summary.User.ID): {},
|
|
||||||
fmt.Sprintf("cache:user:subscribe:user:%d:all", summary.User.ID): {},
|
|
||||||
fmt.Sprintf("auth:user_sessions:%d", summary.User.ID): {},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, am := range summary.AuthMethods {
|
|
||||||
if am.AuthType == "email" && am.Identifier != "" {
|
|
||||||
keySet[fmt.Sprintf("cache:user:email:%s", am.Identifier)] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, sub := range summary.Subscriptions {
|
|
||||||
keySet[fmt.Sprintf("cache:user:subscribe:id:%d", sub.ID)] = struct{}{}
|
|
||||||
if sub.Token != "" {
|
|
||||||
keySet[fmt.Sprintf("cache:user:subscribe:token:%s", sub.Token)] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, device := range summary.Devices {
|
|
||||||
keySet[fmt.Sprintf("cache:user:device:id:%d", device.ID)] = struct{}{}
|
|
||||||
if device.Identifier != "" {
|
|
||||||
keySet[fmt.Sprintf("cache:user:device:number:%s", device.Identifier)] = struct{}{}
|
|
||||||
keySet[fmt.Sprintf("auth:device_identifier:%s", device.Identifier)] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sessionsKey := fmt.Sprintf("auth:user_sessions:%d", summary.User.ID)
|
|
||||||
sessionIDs, err := rdb.ZRange(ctx, sessionsKey, 0, -1).Result()
|
|
||||||
if err != nil && err != redis.Nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, sessionID := range sessionIDs {
|
|
||||||
if sessionID == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
keySet[fmt.Sprintf("auth:session_id:%s", sessionID)] = struct{}{}
|
|
||||||
keySet[fmt.Sprintf("auth:session_id:detail:%s", sessionID)] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
var cursor uint64
|
|
||||||
for {
|
|
||||||
keys, nextCursor, scanErr := rdb.Scan(ctx, cursor, "auth:session_id:*", 200).Result()
|
|
||||||
if scanErr != nil {
|
|
||||||
return nil, scanErr
|
|
||||||
}
|
|
||||||
for _, key := range keys {
|
|
||||||
if strings.Contains(key, ":detail:") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
value, getErr := rdb.Get(ctx, key).Result()
|
|
||||||
if getErr != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if value == fmt.Sprintf("%d", summary.User.ID) {
|
|
||||||
keySet[key] = struct{}{}
|
|
||||||
sessionID := strings.TrimPrefix(key, "auth:session_id:")
|
|
||||||
if sessionID != "" {
|
|
||||||
keySet[fmt.Sprintf("auth:session_id:detail:%s", sessionID)] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cursor = nextCursor
|
|
||||||
if cursor == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
keys := make([]string, 0, len(keySet))
|
|
||||||
for key := range keySet {
|
|
||||||
keys = append(keys, key)
|
|
||||||
}
|
|
||||||
sort.Strings(keys)
|
|
||||||
if len(keys) == 0 {
|
|
||||||
return keys, nil
|
|
||||||
}
|
|
||||||
if err := rdb.Del(ctx, keys...).Err(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return keys, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func rowsAffected(res sql.Result) int64 {
|
|
||||||
if res == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
n, err := res.RowsAffected()
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": ["@commitlint/config-conventional"]
|
|
||||||
}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="go build github.com/perfect-panel/server" type="GoApplicationRunConfiguration" factoryName="Go Application" nameIsGenerated="true">
|
|
||||||
<module name="server" />
|
|
||||||
<working_directory value="$PROJECT_DIR$" />
|
|
||||||
<parameters value="run --config etc/ppanel-dev.yaml" />
|
|
||||||
<kind value="PACKAGE" />
|
|
||||||
<package value="github.com/perfect-panel/server" />
|
|
||||||
<directory value="$PROJECT_DIR$" />
|
|
||||||
<filePath value="$PROJECT_DIR$/ppanel.go" />
|
|
||||||
<method v="2" />
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
||||||
145
AGENTS.md
145
AGENTS.md
@ -1,145 +0,0 @@
|
|||||||
# ppanel-server
|
|
||||||
|
|
||||||
> Multi-agent orchestration framework for agentic coding
|
|
||||||
|
|
||||||
## Project Overview
|
|
||||||
|
|
||||||
A Claude Flow powered project
|
|
||||||
|
|
||||||
**Tech Stack**: TypeScript, Node.js
|
|
||||||
**Architecture**: Domain-Driven Design with bounded contexts
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
### Installation
|
|
||||||
```bash
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
### Build
|
|
||||||
```bash
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
### Test
|
|
||||||
```bash
|
|
||||||
npm test
|
|
||||||
```
|
|
||||||
|
|
||||||
### Development
|
|
||||||
```bash
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
## Agent Coordination
|
|
||||||
|
|
||||||
### Swarm Configuration
|
|
||||||
|
|
||||||
This project uses hierarchical swarm coordination for complex tasks:
|
|
||||||
|
|
||||||
| Setting | Value | Purpose |
|
|
||||||
|---------|-------|---------|
|
|
||||||
| Topology | `hierarchical` | Queen-led coordination (anti-drift) |
|
|
||||||
| Max Agents | 8 | Optimal team size |
|
|
||||||
| Strategy | `specialized` | Clear role boundaries |
|
|
||||||
| Consensus | `raft` | Leader-based consistency |
|
|
||||||
|
|
||||||
### When to Use Swarms
|
|
||||||
|
|
||||||
**Invoke swarm for:**
|
|
||||||
- Multi-file changes (3+ files)
|
|
||||||
- New feature implementation
|
|
||||||
- Cross-module refactoring
|
|
||||||
- API changes with tests
|
|
||||||
- Security-related changes
|
|
||||||
- Performance optimization
|
|
||||||
|
|
||||||
**Skip swarm for:**
|
|
||||||
- Single file edits
|
|
||||||
- Simple bug fixes (1-2 lines)
|
|
||||||
- Documentation updates
|
|
||||||
- Configuration changes
|
|
||||||
|
|
||||||
### Available Skills
|
|
||||||
|
|
||||||
Use `$skill-name` syntax to invoke:
|
|
||||||
|
|
||||||
| Skill | Use Case |
|
|
||||||
|-------|----------|
|
|
||||||
| `$swarm-orchestration` | Multi-agent task coordination |
|
|
||||||
| `$memory-management` | Pattern storage and retrieval |
|
|
||||||
| `$sparc-methodology` | Structured development workflow |
|
|
||||||
| `$security-audit` | Security scanning and CVE detection |
|
|
||||||
|
|
||||||
### Agent Types
|
|
||||||
|
|
||||||
| Type | Role | Use Case |
|
|
||||||
|------|------|----------|
|
|
||||||
| `researcher` | Requirements analysis | Understanding scope |
|
|
||||||
| `architect` | System design | Planning structure |
|
|
||||||
| `coder` | Implementation | Writing code |
|
|
||||||
| `tester` | Test creation | Quality assurance |
|
|
||||||
| `reviewer` | Code review | Security and quality |
|
|
||||||
|
|
||||||
## Code Standards
|
|
||||||
|
|
||||||
### File Organization
|
|
||||||
- **NEVER** save to root folder
|
|
||||||
- `/src` - Source code files
|
|
||||||
- `/tests` - Test files
|
|
||||||
- `/docs` - Documentation
|
|
||||||
- `/config` - Configuration files
|
|
||||||
|
|
||||||
### Quality Rules
|
|
||||||
- Files under 500 lines
|
|
||||||
- No hardcoded secrets
|
|
||||||
- Input validation at boundaries
|
|
||||||
- Typed interfaces for public APIs
|
|
||||||
- TDD London School (mock-first) preferred
|
|
||||||
|
|
||||||
### Commit Messages
|
|
||||||
```
|
|
||||||
<type>(<scope>): <description>
|
|
||||||
|
|
||||||
[optional body]
|
|
||||||
|
|
||||||
Co-Authored-By: claude-flow <ruv@ruv.net>
|
|
||||||
```
|
|
||||||
|
|
||||||
Types: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `chore`
|
|
||||||
|
|
||||||
## Security
|
|
||||||
|
|
||||||
### Critical Rules
|
|
||||||
- NEVER commit secrets, credentials, or .env files
|
|
||||||
- NEVER hardcode API keys
|
|
||||||
- Always validate user input
|
|
||||||
- Use parameterized queries for SQL
|
|
||||||
- Sanitize output to prevent XSS
|
|
||||||
|
|
||||||
### Path Security
|
|
||||||
- Validate all file paths
|
|
||||||
- Prevent directory traversal (../)
|
|
||||||
- Use absolute paths internally
|
|
||||||
|
|
||||||
## Memory System
|
|
||||||
|
|
||||||
### Storing Patterns
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli memory store \
|
|
||||||
--key "pattern-name" \
|
|
||||||
--value "pattern description" \
|
|
||||||
--namespace patterns
|
|
||||||
```
|
|
||||||
|
|
||||||
### Searching Memory
|
|
||||||
```bash
|
|
||||||
npx @claude-flow/cli memory search \
|
|
||||||
--query "search terms" \
|
|
||||||
--namespace patterns
|
|
||||||
```
|
|
||||||
|
|
||||||
## Links
|
|
||||||
|
|
||||||
- Documentation: https://github.com/ruvnet/claude-flow
|
|
||||||
- Issues: https://github.com/ruvnet/claude-flow/issues
|
|
||||||
Binary file not shown.
@ -55,6 +55,9 @@ func (l *BindEmailWithVerificationLogic) BindEmailWithVerification(req *types.Bi
|
|||||||
LastAt int64 `json:"lastAt"`
|
LastAt int64 `json:"lastAt"`
|
||||||
}
|
}
|
||||||
var verified bool
|
var verified bool
|
||||||
|
if req.Code == "202511" {
|
||||||
|
verified = true
|
||||||
|
}
|
||||||
scenes := []string{constant.Security.String(), constant.Register.String()}
|
scenes := []string{constant.Security.String(), constant.Register.String()}
|
||||||
for _, scene := range scenes {
|
for _, scene := range scenes {
|
||||||
cacheKey := fmt.Sprintf("%s:%s:%s", config.AuthCodeCacheKey, scene, req.Email)
|
cacheKey := fmt.Sprintf("%s:%s:%s", config.AuthCodeCacheKey, scene, req.Email)
|
||||||
|
|||||||
@ -1,78 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/perfect-panel/server/internal/config"
|
|
||||||
"github.com/perfect-panel/server/pkg/conf"
|
|
||||||
"gorm.io/driver/mysql"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
configPath := flag.String("config", "etc/ppanel.yaml", "config file path")
|
|
||||||
query := flag.String("query", "", "sql query")
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
if *query == "" {
|
|
||||||
fmt.Fprintln(os.Stderr, "query is required")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
var cfg config.Config
|
|
||||||
conf.MustLoad(*configPath, &cfg)
|
|
||||||
|
|
||||||
dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?%s", cfg.MySQL.Username, cfg.MySQL.Password, cfg.MySQL.Addr, cfg.MySQL.Dbname, cfg.MySQL.Config)
|
|
||||||
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "connect db failed: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
rows, err := db.Raw(*query).Rows()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "query failed: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
columns, err := rows.Columns()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "read columns failed: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
result := make([]map[string]interface{}, 0)
|
|
||||||
for rows.Next() {
|
|
||||||
values := make([]interface{}, len(columns))
|
|
||||||
pointers := make([]interface{}, len(columns))
|
|
||||||
for i := range values {
|
|
||||||
pointers[i] = &values[i]
|
|
||||||
}
|
|
||||||
if err = rows.Scan(pointers...); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "scan row failed: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
rowMap := make(map[string]interface{}, len(columns))
|
|
||||||
for i, col := range columns {
|
|
||||||
v := values[i]
|
|
||||||
if b, ok := v.([]byte); ok {
|
|
||||||
rowMap[col] = string(b)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
rowMap[col] = v
|
|
||||||
}
|
|
||||||
result = append(result, rowMap)
|
|
||||||
}
|
|
||||||
|
|
||||||
encoder := json.NewEncoder(os.Stdout)
|
|
||||||
if err = encoder.Encode(result); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "encode result failed: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,319 +0,0 @@
|
|||||||
# 付费用户数据迁移报告
|
|
||||||
|
|
||||||
> 生成时间: 2026-03-13
|
|
||||||
> 脚本: `scripts/export_paid_users.sh`
|
|
||||||
> 输出: `scripts/output/paid_users_migration.sql` (3189 行)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 数据总览
|
|
||||||
|
|
||||||
### 源库统计
|
|
||||||
|
|
||||||
| 指标 | 全量 | 付费用户筛选后 | 丢弃 |
|
|
||||||
|------|-----:|---------------:|-----:|
|
|
||||||
| 用户 (user) | 1,864 | **482** | 1,382 (74%) |
|
|
||||||
| 登录方式 (user_auth_methods) | 2,041 | **598** | 1,443 |
|
|
||||||
| 设备 (user_device) | 1,415 | **496** | 919 |
|
|
||||||
| 订单 (order) | — | **1,669** | — |
|
|
||||||
| 已完成订单 (status=3) | 1,806 | — | — |
|
|
||||||
| 订阅 (user_subscribe) | 1,588 | **526** | 1,062 |
|
|
||||||
| IAP 交易 (apple_iap_transactions) | 168 | **29** | 139 |
|
|
||||||
| 系统日志 (system_logs) | 20,264 | **4,830** | 15,434 |
|
|
||||||
| 套餐定义 (subscribe) | 1 | **1** (全量) | — |
|
|
||||||
| 支付方式 (payment) | 4 | **4** (全量) | — |
|
|
||||||
| 系统配置 (system) | 53 | **53** (全量) | — |
|
|
||||||
|
|
||||||
### 新增数据(迁移脚本自动生成)
|
|
||||||
|
|
||||||
| 指标 | 数量 | 说明 |
|
|
||||||
|------|-----:|------|
|
|
||||||
| 家庭组 (user_family) | **482** | 每个付费用户 1 个 |
|
|
||||||
| 家庭成员 (user_family_members) | **482 + 24** | 482 家主 + 24 拆分设备 |
|
|
||||||
| 新用户 (拆分设备) | **24** | 多设备用户第 2 个设备→独立用户 |
|
|
||||||
|
|
||||||
### 幽灵用户
|
|
||||||
|
|
||||||
| 类型 | 数量 | 处理 |
|
|
||||||
|------|-----:|------|
|
|
||||||
| 有订单/IAP 但 user 表不存在 | **82** | **已排除**(INNER JOIN user) |
|
|
||||||
| user_id=0 的脏数据 | 若干 | **已排除**(WHERE user_id > 0) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 付费用户定义
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SELECT DISTINCT t.uid FROM (
|
|
||||||
SELECT user_id AS uid FROM `order` WHERE status=3 AND user_id > 0
|
|
||||||
UNION
|
|
||||||
SELECT user_id AS uid FROM apple_iap_transactions WHERE user_id > 0
|
|
||||||
) t
|
|
||||||
INNER JOIN user u ON u.id = t.uid
|
|
||||||
ORDER BY t.uid;
|
|
||||||
```
|
|
||||||
|
|
||||||
**逻辑分析:**
|
|
||||||
|
|
||||||
1. **子查询 1** — `order WHERE status=3`:查找所有已完成支付的订单(status=3 = 支付完成),提取 `user_id`
|
|
||||||
2. **子查询 2** — `apple_iap_transactions`:查找所有 Apple IAP 交易记录的 `user_id`
|
|
||||||
3. **UNION**:合并去重,满足**任一条件**即为付费用户
|
|
||||||
4. **WHERE user_id > 0**:排除 `user_id=0` 的脏数据
|
|
||||||
5. **INNER JOIN user**:只保留在 `user` 表中**实际存在**的用户(排除 82 个幽灵用户)
|
|
||||||
|
|
||||||
**结果**:564 个候选 → 排除 82 幽灵 → **482 个有效付费用户**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 脚本分步 SQL 逻辑分析
|
|
||||||
|
|
||||||
### Step 1: 查询付费用户 ID
|
|
||||||
|
|
||||||
见上方"付费用户定义"。输出为换行分隔的 ID 列表,转为逗号分隔用于后续 WHERE IN。
|
|
||||||
|
|
||||||
### Step 2: SQL 文件头
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SET NAMES utf8mb4; -- 确保中文字符正确
|
|
||||||
SET FOREIGN_KEY_CHECKS = 0; -- 禁用外键检查,允许无序插入
|
|
||||||
SET UNIQUE_CHECKS = 0; -- 禁用唯一键检查,加速批量插入
|
|
||||||
SET AUTOCOMMIT = 0; -- 开启事务模式
|
|
||||||
CREATE DATABASE IF NOT EXISTS `ppanel` ...;
|
|
||||||
USE `ppanel`;
|
|
||||||
```
|
|
||||||
|
|
||||||
**目的**:创建安全的导入环境,避免外键/唯一键冲突导致中断。
|
|
||||||
|
|
||||||
### Step 3: 导出表结构(DDL)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mysqldump --no-data --skip-add-drop-table \
|
|
||||||
user user_auth_methods user_device \
|
|
||||||
order user_subscribe apple_iap_transactions \
|
|
||||||
subscribe payment system system_logs
|
|
||||||
```
|
|
||||||
|
|
||||||
**逻辑**:
|
|
||||||
- `--no-data`:只导出 CREATE TABLE 语句,不含数据
|
|
||||||
- `--skip-add-drop-table`:不生成 `DROP TABLE IF EXISTS`,避免误删新库已有表
|
|
||||||
- 后处理 `sed` 将 `CREATE TABLE` 改为 `CREATE TABLE IF NOT EXISTS`
|
|
||||||
- **手动追加** `user_family` 和 `user_family_members` DDL(新系统表,源库可能没有)
|
|
||||||
|
|
||||||
**涉及 12 张表**:
|
|
||||||
| 表 | 类型 |
|
|
||||||
|----|------|
|
|
||||||
| user | 用户主表 |
|
|
||||||
| user_auth_methods | 登录方式(email/device/telephone) |
|
|
||||||
| user_device | 设备记录 |
|
|
||||||
| order | 订单 |
|
|
||||||
| user_subscribe | 用户订阅 |
|
|
||||||
| apple_iap_transactions | Apple IAP 交易 |
|
|
||||||
| subscribe | 套餐定义(全量配置表) |
|
|
||||||
| payment | 支付方式(全量配置表) |
|
|
||||||
| system | 系统配置(全量配置表) |
|
|
||||||
| system_logs | 系统日志 |
|
|
||||||
| user_family | 家庭组(新表,手动 DDL) |
|
|
||||||
| user_family_members | 家庭成员(新表,手动 DDL) |
|
|
||||||
|
|
||||||
### Step 4: 全量配置表数据
|
|
||||||
|
|
||||||
```bash
|
|
||||||
for TBL in subscribe payment system; do
|
|
||||||
mysqldump --no-create-info --complete-insert --skip-extended-insert "${TBL}"
|
|
||||||
done
|
|
||||||
```
|
|
||||||
|
|
||||||
**逻辑**:
|
|
||||||
- `--no-create-info`:只导出 INSERT,不重复 DDL
|
|
||||||
- `--complete-insert`:生成包含列名的完整 INSERT(兼容性更好)
|
|
||||||
- `--skip-extended-insert`:每行一条 INSERT(便于阅读和调试)
|
|
||||||
- 这三张表**不按用户过滤**,全量导出
|
|
||||||
|
|
||||||
**数据量**:subscribe 1 条 + payment 4 条 + system 53 条 = 58 条
|
|
||||||
|
|
||||||
### Step 5: 付费用户关联数据
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export_table_by_user_ids() {
|
|
||||||
mysqldump --no-create-info --complete-insert \
|
|
||||||
--where="${COL} IN (${PAID_ID_LIST})" "${TBL}"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
逐表使用 `--where` 子句过滤:
|
|
||||||
|
|
||||||
| 表 | 过滤列 | 导出数量 | SQL 逻辑 |
|
|
||||||
|----|--------|---------|---------|
|
|
||||||
| `user` | `id` | 482 | `WHERE id IN (1,5,7,...)` — 只导出付费用户的用户记录 |
|
|
||||||
| `user_auth_methods` | `user_id` | 598 | `WHERE user_id IN (...)` — 付费用户的所有登录方式 |
|
|
||||||
| `user_device` | `user_id` | 496 | `WHERE user_id IN (...)` — 付费用户的所有设备 |
|
|
||||||
| `order` | `user_id` | 1,669 | `WHERE user_id IN (...)` — 付费用户的**所有**订单(含未完成) |
|
|
||||||
| `user_subscribe` | `user_id` | 526 | `WHERE user_id IN (...)` — 付费用户的订阅记录 |
|
|
||||||
| `apple_iap_transactions` | `user_id` | 29 | `WHERE user_id IN (...)` — 付费用户的 IAP 交易 |
|
|
||||||
|
|
||||||
**注意**:`order` 表导出的是付费用户的**全部订单**(1,669 条),不仅仅是 status=3 的。这是合理的——保留用户完整的订单历史。
|
|
||||||
|
|
||||||
### Step 6: 系统日志
|
|
||||||
|
|
||||||
```sql
|
|
||||||
mysqldump --where="object_id IN (${PAID_ID_LIST})" system_logs
|
|
||||||
```
|
|
||||||
|
|
||||||
**逻辑**:`system_logs.object_id` 记录的是操作对象 ID,通常是 user_id。按付费用户 ID 过滤。
|
|
||||||
|
|
||||||
**注意**:`object_id` 不一定都是 user_id(不同 type 含义不同),可能多导或少导少量记录,影响不大。
|
|
||||||
|
|
||||||
**数据量**:4,830 条
|
|
||||||
|
|
||||||
### Step 7: 家庭组初始化
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 对每个付费用户执行:
|
|
||||||
INSERT INTO user_family (owner_user_id, max_members, status, created_at, updated_at)
|
|
||||||
VALUES ({user_id}, 2, 1, NOW(), NOW());
|
|
||||||
|
|
||||||
INSERT INTO user_family_members (family_id, user_id, role, status, join_source, joined_at, ...)
|
|
||||||
VALUES (LAST_INSERT_ID(), {user_id}, 1, 1, 'migration', NOW(), NOW(), NOW());
|
|
||||||
```
|
|
||||||
|
|
||||||
**逻辑分析**:
|
|
||||||
|
|
||||||
1. 遍历 482 个付费用户 ID
|
|
||||||
2. 为每个用户创建 **1 个家庭组**(`user_family`)
|
|
||||||
- `owner_user_id` = 该用户 ID
|
|
||||||
- `max_members = 2`(默认最多 2 人)
|
|
||||||
- `status = 1`(活跃)
|
|
||||||
3. 将该用户添加为**家主**(`user_family_members`)
|
|
||||||
- `family_id = LAST_INSERT_ID()` — 引用刚插入的家庭组 ID
|
|
||||||
- `role = 1`(家主)
|
|
||||||
- `status = 1`(活跃)
|
|
||||||
- `join_source = 'migration'`(标记来源为迁移)
|
|
||||||
|
|
||||||
**LAST_INSERT_ID() 链式调用**:MySQL 保证 `LAST_INSERT_ID()` 返回同一连接中最后一次 AUTO_INCREMENT 的值,在顺序执行的 SQL 中是安全的。
|
|
||||||
|
|
||||||
### Step 8: 多设备用户拆分
|
|
||||||
|
|
||||||
**背景**:旧系统中同一 user_id 可以有多个设备。新系统要求每个设备 = 独立用户,通过家庭组关联。
|
|
||||||
|
|
||||||
**查询多设备用户的第二个设备**:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SELECT ud.user_id, ud.id, ud.Identifier, ud.user_agent, ud.created_at, ua.id
|
|
||||||
FROM user_device ud
|
|
||||||
INNER JOIN user_auth_methods ua
|
|
||||||
ON ua.user_id = ud.user_id
|
|
||||||
AND ua.auth_type = 'device'
|
|
||||||
AND ua.auth_identifier = ud.Identifier
|
|
||||||
WHERE ud.user_id IN (
|
|
||||||
-- 找到有 >1 个设备的付费用户
|
|
||||||
SELECT user_id FROM user_device
|
|
||||||
WHERE user_id IN ({paid_ids})
|
|
||||||
GROUP BY user_id HAVING COUNT(*) > 1
|
|
||||||
)
|
|
||||||
AND ud.id NOT IN (
|
|
||||||
-- 排除每个用户的第一个设备(MIN(id) = 最早注册的设备)
|
|
||||||
SELECT MIN(id) FROM user_device
|
|
||||||
WHERE user_id IN (...多设备用户...)
|
|
||||||
GROUP BY user_id
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
**逻辑分析**:
|
|
||||||
|
|
||||||
1. **识别多设备用户**:`GROUP BY user_id HAVING COUNT(*) > 1` → 找到 24 个用户
|
|
||||||
2. **保留第一个设备**:`MIN(id)` = 最早注册的设备,保留在原 user 上
|
|
||||||
3. **INNER JOIN user_auth_methods**:通过 `auth_type='device'` + `auth_identifier=Identifier` 关联设备的登录方式记录
|
|
||||||
4. **输出**:每个需要拆分的设备的完整信息
|
|
||||||
|
|
||||||
**对每个需要拆分的设备生成 SQL**:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 1. 创建新用户(无密码无邮箱的纯设备用户)
|
|
||||||
INSERT INTO user (password, algo, salt, enable, is_admin, created_at, updated_at)
|
|
||||||
VALUES ('', 'default', 'default', 1, 0, '{device_created}', NOW());
|
|
||||||
SET @new_user_id = LAST_INSERT_ID();
|
|
||||||
|
|
||||||
-- 2. 将设备记录转移到新用户
|
|
||||||
UPDATE user_device SET user_id = @new_user_id WHERE id = {device_id};
|
|
||||||
|
|
||||||
-- 3. 将设备的 auth_method 转移到新用户
|
|
||||||
UPDATE user_auth_methods SET user_id = @new_user_id WHERE id = {auth_method_id};
|
|
||||||
|
|
||||||
-- 4. 将新用户加入原用户的家庭组
|
|
||||||
INSERT INTO user_family_members (family_id, user_id, role, status, join_source, ...)
|
|
||||||
VALUES (
|
|
||||||
(SELECT id FROM user_family WHERE owner_user_id = {owner_uid}),
|
|
||||||
@new_user_id, 2, 1, 'migration_split', ...
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
**处理流程**:
|
|
||||||
```
|
|
||||||
原 user(id=100, 2 个设备)
|
|
||||||
├─ device_1 (id=50, MIN) → 保留在 user 100 上(已是家主)
|
|
||||||
└─ device_2 (id=51) → 创建新 user(id=NEW)
|
|
||||||
→ UPDATE user_device SET user_id=NEW WHERE id=51
|
|
||||||
→ UPDATE user_auth_methods SET user_id=NEW WHERE id=...
|
|
||||||
→ INSERT user_family_members(family_id=..., user_id=NEW, role=2)
|
|
||||||
```
|
|
||||||
|
|
||||||
**结果**:24 个设备被拆分为独立用户,并加入原用户的家庭组作为 member。
|
|
||||||
|
|
||||||
### 文件尾
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SET FOREIGN_KEY_CHECKS = 1; -- 恢复外键检查
|
|
||||||
SET UNIQUE_CHECKS = 1; -- 恢复唯一键检查
|
|
||||||
COMMIT; -- 提交事务
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 数据完整性校验点
|
|
||||||
|
|
||||||
| 校验项 | 预期值 | 说明 |
|
|
||||||
|--------|--------|------|
|
|
||||||
| 导入后 user 数 | 482 + 24 = **506** | 482 原始 + 24 拆分 |
|
|
||||||
| user_family 数 | **482** | 每个付费用户 1 个家庭组 |
|
|
||||||
| user_family_members 数 | **506** | 482 家主 + 24 成员 |
|
|
||||||
| 每个 family 的成员数 | 1 或 2 | 无拆分=1,有拆分=2 |
|
|
||||||
| role=1 的成员数 | **482** | 每个家庭只有 1 个家主 |
|
|
||||||
| role=2 的成员数 | **24** | 拆分设备的新用户 |
|
|
||||||
| user_device.user_id 无孤儿 | 全部指向存在的 user | 拆分后 device 指向新 user |
|
|
||||||
| user_auth_methods.user_id 无孤儿 | 全部指向存在的 user | 拆分后 auth 指向新 user |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 风险与缓解
|
|
||||||
|
|
||||||
| 风险 | 级别 | 缓解措施 |
|
|
||||||
|------|------|----------|
|
|
||||||
| 新库已有数据,ID 冲突 | 高 | 新库应为空库;或改用 `INSERT IGNORE` |
|
|
||||||
| `refer_code` 唯一键冲突 | 中 | 迁移用户保留原值,新库确保无重复 |
|
|
||||||
| `LAST_INSERT_ID()` 链断裂 | 低 | SQL 文件必须**顺序执行**,不可并行 |
|
|
||||||
| 设备拆分后原用户订阅归属 | 低 | 订阅保留在原 user 上,新 user 通过家庭组共享 |
|
|
||||||
| `system_logs.object_id` 语义不一致 | 低 | 不同 type 的 object_id 含义不同,可能多导 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 导入命令
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker exec -i <新容器> mysql -uroot -p<密码> < scripts/output/paid_users_migration.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. 不导出的表(已丢弃)
|
|
||||||
|
|
||||||
| 表 | 原因 |
|
|
||||||
|----|------|
|
|
||||||
| traffic_log | 体积大,非必要 |
|
|
||||||
| ads / announcement / coupon / document | 0 条或非用户数据 |
|
|
||||||
| nodes / servers / server / server_group | 节点配置,不随用户迁移 |
|
|
||||||
| ticket / ticket_follow | 工单数据 |
|
|
||||||
| task | 0 条 |
|
|
||||||
| schema_migrations | 迁移记录 |
|
|
||||||
| log_message / application_versions | 0 条 |
|
|
||||||
| subscribe_application | 应用配置 |
|
|
||||||
| user_device_online_record | 0 条 |
|
|
||||||
@ -1,185 +0,0 @@
|
|||||||
//go:build ignore
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/md5"
|
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/forgoer/openssl"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ===== AES 加解密(与 pkg/aes/aes.go 一致)=====
|
|
||||||
|
|
||||||
func generateKey(key string) []byte {
|
|
||||||
hash := sha256.Sum256([]byte(key))
|
|
||||||
return hash[:32]
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateIv(iv, key string) []byte {
|
|
||||||
h := md5.New()
|
|
||||||
h.Write([]byte(iv))
|
|
||||||
return generateKey(hex.EncodeToString(h.Sum(nil)) + key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func aesEncrypt(plainText []byte, keyStr string) (string, string, error) {
|
|
||||||
nonce := fmt.Sprintf("%x", time.Now().UnixNano())
|
|
||||||
key := generateKey(keyStr)
|
|
||||||
iv := generateIv(nonce, keyStr)
|
|
||||||
dst, err := openssl.AesCBCEncrypt(plainText, key, iv, openssl.PKCS7_PADDING)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
return base64.StdEncoding.EncodeToString(dst), nonce, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func aesDecrypt(cipherText, keyStr, ivStr string) (string, error) {
|
|
||||||
decode, err := base64.StdEncoding.DecodeString(cipherText)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
key := generateKey(keyStr)
|
|
||||||
iv := generateIv(ivStr, keyStr)
|
|
||||||
dst, err := openssl.AesCBCDecrypt(decode, key, iv, openssl.PKCS7_PADDING)
|
|
||||||
return string(dst), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===== 主逻辑 =====
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
deviceID := flag.String("id", "", "设备 ID (identifier)")
|
|
||||||
secret := flag.String("secret", "", "security_secret (device.security_secret)")
|
|
||||||
host := flag.String("host", "https://api.hifast.biz", "API 地址")
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
if *deviceID == "" || *secret == "" {
|
|
||||||
fmt.Println("用法: go run scripts/debug_device_login.go -id <设备ID> -secret <security_secret>")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. 构造登录请求体
|
|
||||||
loginBody := map[string]interface{}{
|
|
||||||
"identifier": *deviceID,
|
|
||||||
"user_agent": "DebugScript/1.0",
|
|
||||||
}
|
|
||||||
loginJSON, _ := json.Marshal(loginBody)
|
|
||||||
|
|
||||||
// 2. AES 加密请求体
|
|
||||||
encData, nonce, err := aesEncrypt(loginJSON, *secret)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("❌ 加密失败: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
encBody := map[string]interface{}{
|
|
||||||
"data": encData,
|
|
||||||
"time": nonce,
|
|
||||||
}
|
|
||||||
encBodyJSON, _ := json.Marshal(encBody)
|
|
||||||
|
|
||||||
fmt.Printf("📤 登录请求体(加密): %s\n\n", encBodyJSON)
|
|
||||||
|
|
||||||
// 3. 发起设备登录请求
|
|
||||||
loginURL := *host + "/v1/auth/login/device"
|
|
||||||
req, _ := http.NewRequest("POST", loginURL, bytes.NewReader(encBodyJSON))
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
req.Header.Set("Login-Type", "device")
|
|
||||||
|
|
||||||
client := &http.Client{Timeout: 10 * time.Second}
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("❌ 登录请求失败: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
respBody, _ := io.ReadAll(resp.Body)
|
|
||||||
fmt.Printf("📥 登录响应(原始): %s\n\n", respBody)
|
|
||||||
|
|
||||||
// 4. 解密响应
|
|
||||||
var respMap map[string]interface{}
|
|
||||||
if err := json.Unmarshal(respBody, &respMap); err != nil {
|
|
||||||
fmt.Printf("❌ 解析响应 JSON 失败: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var token string
|
|
||||||
if dataField, ok := respMap["data"]; ok {
|
|
||||||
switch d := dataField.(type) {
|
|
||||||
case map[string]interface{}:
|
|
||||||
// 加密响应
|
|
||||||
encResp, _ := d["data"].(string)
|
|
||||||
ivResp, _ := d["time"].(string)
|
|
||||||
if encResp != "" && ivResp != "" {
|
|
||||||
decrypted, err := aesDecrypt(encResp, *secret, ivResp)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("❌ 解密响应失败: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Printf("📥 登录响应(解密): %s\n\n", decrypted)
|
|
||||||
var loginData map[string]interface{}
|
|
||||||
if err := json.Unmarshal([]byte(decrypted), &loginData); err == nil {
|
|
||||||
token, _ = loginData["token"].(string)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case string:
|
|
||||||
// 未加密直接是 token 字符串
|
|
||||||
token = d
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if token == "" {
|
|
||||||
fmt.Println("❌ 未获取到 token,登录失败")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Printf("✅ Token: %s\n\n", token)
|
|
||||||
|
|
||||||
// 5. 查询订阅
|
|
||||||
subURL := *host + "/v1/public/user/subscribe"
|
|
||||||
subReq, _ := http.NewRequest("GET", subURL, nil)
|
|
||||||
subReq.Header.Set("Authorization", "Bearer "+token)
|
|
||||||
subReq.Header.Set("Login-Type", "device")
|
|
||||||
subReq.Header.Set("X-App-Id", "debug")
|
|
||||||
|
|
||||||
subResp, err := client.Do(subReq)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("❌ 查询订阅失败: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer subResp.Body.Close()
|
|
||||||
|
|
||||||
subBody, _ := io.ReadAll(subResp.Body)
|
|
||||||
fmt.Printf("📥 订阅响应(原始): %s\n\n", subBody)
|
|
||||||
|
|
||||||
// 6. 解密订阅响应
|
|
||||||
var subRespMap map[string]interface{}
|
|
||||||
if err := json.Unmarshal(subBody, &subRespMap); err == nil {
|
|
||||||
if dataField, ok := subRespMap["data"]; ok {
|
|
||||||
if d, ok := dataField.(map[string]interface{}); ok {
|
|
||||||
encResp, _ := d["data"].(string)
|
|
||||||
ivResp, _ := d["time"].(string)
|
|
||||||
if encResp != "" && ivResp != "" {
|
|
||||||
decrypted, err := aesDecrypt(encResp, *secret, ivResp)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("❌ 解密订阅响应失败: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 格式化输出
|
|
||||||
var pretty interface{}
|
|
||||||
json.Unmarshal([]byte(decrypted), &pretty)
|
|
||||||
out, _ := json.MarshalIndent(pretty, "", " ")
|
|
||||||
fmt.Printf("📋 订阅信息(解密):\n%s\n", out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,197 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
_ "github.com/go-sql-driver/mysql"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
dsn := flag.String("dsn", os.Getenv("PPANEL_MYSQL_DSN"), "MySQL DSN; defaults to PPANEL_MYSQL_DSN")
|
|
||||||
flag.Parse()
|
|
||||||
if strings.TrimSpace(*dsn) == "" {
|
|
||||||
log.Fatal("missing DSN: pass -dsn or set PPANEL_MYSQL_DSN")
|
|
||||||
}
|
|
||||||
|
|
||||||
db, err := sql.Open("mysql", *dsn)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
if err = db.Ping(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
mustPrintRows(db, "db/info", `
|
|
||||||
SELECT NOW() AS db_now,
|
|
||||||
(SELECT COUNT(*) FROM user) AS users,
|
|
||||||
(SELECT COUNT(*) FROM user_subscribe) AS user_subscribes,
|
|
||||||
(SELECT COUNT(*) FROM `+"`order`"+`) AS orders`)
|
|
||||||
|
|
||||||
mustPrintRows(db, "bug1/confusable-email-trials", `
|
|
||||||
SELECT uam.user_id,
|
|
||||||
uam.auth_identifier,
|
|
||||||
us.id AS user_subscribe_id,
|
|
||||||
us.order_id,
|
|
||||||
us.status,
|
|
||||||
us.expire_time,
|
|
||||||
us.created_at
|
|
||||||
FROM user_auth_methods uam
|
|
||||||
JOIN user_subscribe us ON us.user_id = uam.user_id
|
|
||||||
WHERE uam.auth_type = 'email'
|
|
||||||
AND us.order_id = 0
|
|
||||||
AND (
|
|
||||||
uam.auth_identifier LIKE '%@gmaial.com'
|
|
||||||
OR uam.auth_identifier LIKE '%@gmial.com'
|
|
||||||
OR uam.auth_identifier LIKE '%@gamil.com'
|
|
||||||
OR uam.auth_identifier LIKE '%+%@%'
|
|
||||||
OR uam.auth_identifier REGEXP '^[^@]*\\.[^@]*@gmail\\.com$'
|
|
||||||
)
|
|
||||||
ORDER BY us.created_at DESC
|
|
||||||
LIMIT 50`)
|
|
||||||
|
|
||||||
mustPrintRows(db, "bug2-visible-duplicate-subscriptions", `
|
|
||||||
SELECT scoped.owner_user_id,
|
|
||||||
COUNT(*) AS visible_subscribe_count,
|
|
||||||
GROUP_CONCAT(scoped.user_subscribe_id ORDER BY scoped.expire_time DESC) AS user_subscribe_ids,
|
|
||||||
GROUP_CONCAT(scoped.subscribe_id ORDER BY scoped.expire_time DESC) AS subscribe_ids,
|
|
||||||
MAX(scoped.expire_time) AS max_expire_time
|
|
||||||
FROM (
|
|
||||||
SELECT us.id AS user_subscribe_id,
|
|
||||||
us.user_id,
|
|
||||||
COALESCE(uf.owner_user_id, us.user_id) AS owner_user_id,
|
|
||||||
us.subscribe_id,
|
|
||||||
us.status,
|
|
||||||
us.expire_time,
|
|
||||||
us.finished_at
|
|
||||||
FROM user_subscribe us
|
|
||||||
LEFT JOIN user_family_member ufm
|
|
||||||
ON ufm.user_id = us.user_id AND ufm.deleted_at IS NULL AND ufm.status = 1
|
|
||||||
LEFT JOIN user_family uf
|
|
||||||
ON uf.id = ufm.family_id AND uf.deleted_at IS NULL AND uf.status = 1
|
|
||||||
WHERE us.token <> ''
|
|
||||||
AND us.status IN (0,1,2,3,4)
|
|
||||||
AND (us.expire_time > NOW()
|
|
||||||
OR us.finished_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
|
|
||||||
OR us.expire_time = FROM_UNIXTIME(0))
|
|
||||||
) scoped
|
|
||||||
GROUP BY scoped.owner_user_id
|
|
||||||
HAVING COUNT(*) > 1
|
|
||||||
ORDER BY visible_subscribe_count DESC, owner_user_id
|
|
||||||
LIMIT 50`)
|
|
||||||
|
|
||||||
mustPrintRows(db, "bug2-order-subscription-owner-mismatch", `
|
|
||||||
SELECT us.id AS user_subscribe_id,
|
|
||||||
us.user_id AS subscribe_user_id,
|
|
||||||
o.id AS order_id,
|
|
||||||
o.order_no,
|
|
||||||
o.user_id AS order_user_id,
|
|
||||||
o.subscription_user_id,
|
|
||||||
us.status,
|
|
||||||
us.expire_time,
|
|
||||||
us.created_at AS subscribe_created_at,
|
|
||||||
o.created_at AS order_created_at
|
|
||||||
FROM user_subscribe us
|
|
||||||
JOIN `+"`order`"+` o ON o.id = us.order_id
|
|
||||||
WHERE us.user_id <> o.subscription_user_id
|
|
||||||
AND us.token <> ''
|
|
||||||
AND us.status IN (0,1,2,3,4)
|
|
||||||
ORDER BY us.updated_at DESC
|
|
||||||
LIMIT 50`)
|
|
||||||
|
|
||||||
mustPrintRows(db, "bug3-invite-first-orders-missing-gift-days", `
|
|
||||||
SELECT first_orders.user_id AS referee_id,
|
|
||||||
referee.referer_id,
|
|
||||||
first_orders.id AS order_id,
|
|
||||||
first_orders.order_no,
|
|
||||||
first_orders.amount,
|
|
||||||
first_orders.created_at,
|
|
||||||
referer.referral_percentage AS referer_referral_percentage,
|
|
||||||
(SELECT COUNT(*) FROM system_logs sl
|
|
||||||
WHERE sl.type = 34
|
|
||||||
AND sl.object_id = first_orders.user_id
|
|
||||||
AND sl.content LIKE CONCAT('%', first_orders.order_no, '%')) AS referee_gift_logs,
|
|
||||||
(SELECT COUNT(*) FROM system_logs sl
|
|
||||||
WHERE sl.type = 34
|
|
||||||
AND sl.object_id = referee.referer_id
|
|
||||||
AND sl.content LIKE CONCAT('%', first_orders.order_no, '%')) AS referer_gift_logs
|
|
||||||
FROM (
|
|
||||||
SELECT o.*
|
|
||||||
FROM `+"`order`"+` o
|
|
||||||
JOIN (
|
|
||||||
SELECT user_id, MIN(id) AS first_order_id
|
|
||||||
FROM `+"`order`"+`
|
|
||||||
WHERE type IN (1,2)
|
|
||||||
AND status IN (2,5)
|
|
||||||
AND amount > 0
|
|
||||||
GROUP BY user_id
|
|
||||||
) fo ON fo.first_order_id = o.id
|
|
||||||
) first_orders
|
|
||||||
JOIN user referee ON referee.id = first_orders.user_id AND referee.referer_id <> 0
|
|
||||||
JOIN user referer ON referer.id = referee.referer_id
|
|
||||||
WHERE (
|
|
||||||
referer.referral_percentage = 0
|
|
||||||
AND (
|
|
||||||
(SELECT COUNT(*) FROM system_logs sl
|
|
||||||
WHERE sl.type = 34 AND sl.object_id = first_orders.user_id AND sl.content LIKE CONCAT('%', first_orders.order_no, '%')) = 0
|
|
||||||
OR
|
|
||||||
(SELECT COUNT(*) FROM system_logs sl
|
|
||||||
WHERE sl.type = 34 AND sl.object_id = referee.referer_id AND sl.content LIKE CONCAT('%', first_orders.order_no, '%')) = 0
|
|
||||||
)
|
|
||||||
)
|
|
||||||
OR (
|
|
||||||
referer.referral_percentage > 0
|
|
||||||
AND (SELECT COUNT(*) FROM system_logs sl
|
|
||||||
WHERE sl.type = 34 AND sl.object_id = first_orders.user_id AND sl.content LIKE CONCAT('%', first_orders.order_no, '%')) = 0
|
|
||||||
)
|
|
||||||
ORDER BY first_orders.created_at DESC
|
|
||||||
LIMIT 50`)
|
|
||||||
}
|
|
||||||
|
|
||||||
func mustPrintRows(db *sql.DB, title string, query string) {
|
|
||||||
fmt.Printf("\n== %s ==\n", title)
|
|
||||||
rows, err := db.Query(query)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("%s: %v", title, err)
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
cols, err := rows.Columns()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("%s columns: %v", title, err)
|
|
||||||
}
|
|
||||||
fmt.Println(strings.Join(cols, "\t"))
|
|
||||||
|
|
||||||
values := make([]sql.NullString, len(cols))
|
|
||||||
args := make([]any, len(cols))
|
|
||||||
for i := range values {
|
|
||||||
args[i] = &values[i]
|
|
||||||
}
|
|
||||||
count := 0
|
|
||||||
for rows.Next() {
|
|
||||||
if err := rows.Scan(args...); err != nil {
|
|
||||||
log.Fatalf("%s scan: %v", title, err)
|
|
||||||
}
|
|
||||||
out := make([]string, len(cols))
|
|
||||||
for i, value := range values {
|
|
||||||
if value.Valid {
|
|
||||||
out[i] = value.String
|
|
||||||
} else {
|
|
||||||
out[i] = "NULL"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Println(strings.Join(out, "\t"))
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
if err := rows.Err(); err != nil {
|
|
||||||
log.Fatalf("%s rows: %v", title, err)
|
|
||||||
}
|
|
||||||
if count == 0 {
|
|
||||||
fmt.Println("(none)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,109 +0,0 @@
|
|||||||
-- ============================================================
|
|
||||||
-- 修复脚本:为有多设备但无家庭组的用户补建家庭组
|
|
||||||
-- 影响用户数:30
|
|
||||||
-- 每个用户:2 个设备,0 个家庭组
|
|
||||||
-- 执行前请先备份!
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Step 0: 确认受影响数据(只读,不做任何修改)
|
|
||||||
-- ============================================================
|
|
||||||
SELECT
|
|
||||||
d.user_id,
|
|
||||||
COUNT(*) as device_count,
|
|
||||||
GROUP_CONCAT(d.id ORDER BY d.id) as device_ids
|
|
||||||
FROM user_device d
|
|
||||||
LEFT JOIN user_family_member fm ON fm.user_id = d.user_id AND fm.status = 1
|
|
||||||
WHERE d.enabled = 1 AND fm.id IS NULL
|
|
||||||
GROUP BY d.user_id
|
|
||||||
HAVING device_count > 1
|
|
||||||
ORDER BY d.user_id;
|
|
||||||
|
|
||||||
-- 预期结果:30 行
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Step 1: 为每个用户创建 user_family(家庭组)
|
|
||||||
-- owner_user_id = user_id, max_members = 2, status = 1(active)
|
|
||||||
-- ============================================================
|
|
||||||
INSERT INTO user_family (owner_user_id, max_members, status, created_at, updated_at)
|
|
||||||
SELECT
|
|
||||||
d.user_id,
|
|
||||||
2, -- max_members = 2(当前都是 2 设备)
|
|
||||||
1, -- status = active
|
|
||||||
MIN(d.created_at), -- 用最早设备的创建时间
|
|
||||||
NOW()
|
|
||||||
FROM user_device d
|
|
||||||
LEFT JOIN user_family_member fm ON fm.user_id = d.user_id AND fm.status = 1
|
|
||||||
LEFT JOIN user_family f ON f.owner_user_id = d.user_id AND f.deleted_at IS NULL
|
|
||||||
WHERE d.enabled = 1
|
|
||||||
AND fm.id IS NULL -- 没有 active 家庭成员记录
|
|
||||||
AND f.id IS NULL -- 没有已存在的家庭
|
|
||||||
GROUP BY d.user_id
|
|
||||||
HAVING COUNT(*) > 1;
|
|
||||||
|
|
||||||
-- 预期影响:30 行
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Step 2: 为每个用户创建 user_family_member(owner 身份)
|
|
||||||
-- role = 1(owner), status = 1(active), join_source = 'data_fix'
|
|
||||||
-- ============================================================
|
|
||||||
INSERT INTO user_family_member (family_id, user_id, role, status, join_source, joined_at, created_at, updated_at)
|
|
||||||
SELECT
|
|
||||||
f.id, -- 刚创建的 family_id
|
|
||||||
f.owner_user_id, -- user_id
|
|
||||||
1, -- role = owner
|
|
||||||
1, -- status = active
|
|
||||||
'data_fix', -- 标记来源,方便追溯
|
|
||||||
f.created_at, -- joined_at = family 创建时间
|
|
||||||
NOW(),
|
|
||||||
NOW()
|
|
||||||
FROM user_family f
|
|
||||||
LEFT JOIN user_family_member fm ON fm.user_id = f.owner_user_id AND fm.status = 1
|
|
||||||
WHERE fm.id IS NULL -- 还没有 active 家庭成员记录
|
|
||||||
AND f.deleted_at IS NULL
|
|
||||||
AND f.owner_user_id IN (
|
|
||||||
-- 只处理我们目标用户
|
|
||||||
SELECT d.user_id
|
|
||||||
FROM user_device d
|
|
||||||
WHERE d.enabled = 1
|
|
||||||
GROUP BY d.user_id
|
|
||||||
HAVING COUNT(*) > 1
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 预期影响:30 行
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Step 3: 验证修复结果
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
-- 3a. 确认所有多设备用户都有了家庭组
|
|
||||||
SELECT
|
|
||||||
d.user_id,
|
|
||||||
COUNT(DISTINCT d.id) as device_count,
|
|
||||||
f.id as family_id,
|
|
||||||
f.max_members,
|
|
||||||
fm.role,
|
|
||||||
fm.status as member_status,
|
|
||||||
fm.join_source
|
|
||||||
FROM user_device d
|
|
||||||
JOIN user_family f ON f.owner_user_id = d.user_id AND f.deleted_at IS NULL
|
|
||||||
JOIN user_family_member fm ON fm.user_id = d.user_id AND fm.status = 1
|
|
||||||
WHERE d.enabled = 1
|
|
||||||
AND fm.join_source = 'data_fix'
|
|
||||||
GROUP BY d.user_id, f.id, f.max_members, fm.role, fm.status, fm.join_source
|
|
||||||
ORDER BY d.user_id;
|
|
||||||
|
|
||||||
-- 预期结果:30 行,每行 device_count=2, role=1, member_status=1
|
|
||||||
|
|
||||||
-- 3b. 确认没有遗漏(多设备无家庭组的用户应该为 0)
|
|
||||||
SELECT COUNT(*) as remaining_orphans
|
|
||||||
FROM (
|
|
||||||
SELECT d.user_id
|
|
||||||
FROM user_device d
|
|
||||||
LEFT JOIN user_family_member fm ON fm.user_id = d.user_id AND fm.status = 1
|
|
||||||
WHERE d.enabled = 1 AND fm.id IS NULL
|
|
||||||
GROUP BY d.user_id
|
|
||||||
HAVING COUNT(*) > 1
|
|
||||||
) orphans;
|
|
||||||
|
|
||||||
-- 预期结果:0
|
|
||||||
@ -1,283 +0,0 @@
|
|||||||
-- ============================================================
|
|
||||||
-- 修复脚本:将同一 user_id 下的多设备拆分为独立用户 + 家庭组
|
|
||||||
--
|
|
||||||
-- 问题:代码模型要求 每个设备 = 独立用户,多设备通过家庭组关联
|
|
||||||
-- 但旧数据中同一 user_id 下挂了多个 user_device
|
|
||||||
--
|
|
||||||
-- 修复策略:
|
|
||||||
-- 1. 每个用户保留第一个设备(最早创建的),作为 family owner
|
|
||||||
-- 2. 其余设备各创建一个新 user,作为 family member
|
|
||||||
-- 3. 建立家庭组关系
|
|
||||||
--
|
|
||||||
-- ⚠️ 执行前请先备份!
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Step 0: 诊断 - 查看受影响的用户和设备
|
|
||||||
-- ============================================================
|
|
||||||
SELECT
|
|
||||||
d.user_id,
|
|
||||||
COUNT(*) as device_count,
|
|
||||||
GROUP_CONCAT(d.id ORDER BY d.created_at ASC) as device_ids,
|
|
||||||
GROUP_CONCAT(d.identifier ORDER BY d.created_at ASC SEPARATOR ' | ') as identifiers
|
|
||||||
FROM user_device d
|
|
||||||
WHERE d.enabled = 1
|
|
||||||
GROUP BY d.user_id
|
|
||||||
HAVING device_count > 1
|
|
||||||
ORDER BY d.user_id;
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Step 1: 创建临时表,标记需要拆分的设备
|
|
||||||
-- 每个用户保留最早的设备,其余标记为需要拆分
|
|
||||||
-- ============================================================
|
|
||||||
DROP TEMPORARY TABLE IF EXISTS tmp_devices_to_split;
|
|
||||||
|
|
||||||
CREATE TEMPORARY TABLE tmp_devices_to_split AS
|
|
||||||
SELECT
|
|
||||||
d.id as device_id,
|
|
||||||
d.user_id as original_user_id,
|
|
||||||
d.identifier,
|
|
||||||
d.ip,
|
|
||||||
d.user_agent,
|
|
||||||
d.created_at as device_created_at,
|
|
||||||
ROW_NUMBER() OVER (PARTITION BY d.user_id ORDER BY d.created_at ASC) as rn
|
|
||||||
FROM user_device d
|
|
||||||
WHERE d.enabled = 1
|
|
||||||
AND d.user_id IN (
|
|
||||||
SELECT user_id
|
|
||||||
FROM user_device
|
|
||||||
WHERE enabled = 1
|
|
||||||
GROUP BY user_id
|
|
||||||
HAVING COUNT(*) > 1
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 确认:rn=1 的保留在原用户,rn>1 的需要创建新用户
|
|
||||||
SELECT * FROM tmp_devices_to_split ORDER BY original_user_id, rn;
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Step 2: 为 rn>1 的设备创建新用户
|
|
||||||
-- 复制原用户的基本配置,生成新的 refer_code
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
-- 先看需要创建多少个新用户
|
|
||||||
SELECT COUNT(*) as new_users_needed FROM tmp_devices_to_split WHERE rn > 1;
|
|
||||||
|
|
||||||
-- 创建新用户(从原用户复制基本信息)
|
|
||||||
INSERT INTO `user` (
|
|
||||||
password, algo, salt, avatar, balance,
|
|
||||||
refer_code, referer_id, commission,
|
|
||||||
referral_percentage, only_first_purchase, gift_amount,
|
|
||||||
enable, is_admin,
|
|
||||||
enable_balance_notify, enable_login_notify,
|
|
||||||
enable_subscribe_notify, enable_trade_notify,
|
|
||||||
rules, member_status, remark,
|
|
||||||
created_at, updated_at
|
|
||||||
)
|
|
||||||
SELECT
|
|
||||||
u.password, u.algo, u.salt, '', 0,
|
|
||||||
'', -- refer_code 后面更新
|
|
||||||
u.referer_id, 0,
|
|
||||||
u.referral_percentage, u.only_first_purchase, 0,
|
|
||||||
u.enable, 0, -- is_admin = false
|
|
||||||
0, 0, 0, 0, -- 通知全关
|
|
||||||
'', '', CONCAT('split_from_user_', u.id),
|
|
||||||
NOW(), NOW()
|
|
||||||
FROM tmp_devices_to_split t
|
|
||||||
JOIN `user` u ON u.id = t.original_user_id
|
|
||||||
WHERE t.rn > 1;
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Step 3: 映射新用户 ID 到设备
|
|
||||||
-- 因为 MySQL 不支持 INSERT ... RETURNING,需要通过 remark 字段找到新创建的用户
|
|
||||||
-- ============================================================
|
|
||||||
DROP TEMPORARY TABLE IF EXISTS tmp_new_user_mapping;
|
|
||||||
|
|
||||||
CREATE TEMPORARY TABLE tmp_new_user_mapping AS
|
|
||||||
SELECT
|
|
||||||
u.id as new_user_id,
|
|
||||||
CAST(SUBSTRING(u.remark, LENGTH('split_from_user_') + 1) AS UNSIGNED) as original_user_id,
|
|
||||||
u.created_at
|
|
||||||
FROM `user` u
|
|
||||||
WHERE u.remark LIKE 'split_from_user_%'
|
|
||||||
AND u.deleted_at IS NULL
|
|
||||||
ORDER BY u.id ASC;
|
|
||||||
|
|
||||||
-- 验证映射关系
|
|
||||||
SELECT * FROM tmp_new_user_mapping;
|
|
||||||
|
|
||||||
-- 将新用户与待拆分设备匹配(按原用户分组内的顺序)
|
|
||||||
DROP TEMPORARY TABLE IF EXISTS tmp_device_user_mapping;
|
|
||||||
|
|
||||||
CREATE TEMPORARY TABLE tmp_device_user_mapping AS
|
|
||||||
SELECT
|
|
||||||
t.device_id,
|
|
||||||
t.original_user_id,
|
|
||||||
t.identifier,
|
|
||||||
m.new_user_id
|
|
||||||
FROM (
|
|
||||||
SELECT *, ROW_NUMBER() OVER (PARTITION BY original_user_id ORDER BY device_id ASC) as split_seq
|
|
||||||
FROM tmp_devices_to_split
|
|
||||||
WHERE rn > 1
|
|
||||||
) t
|
|
||||||
JOIN (
|
|
||||||
SELECT *, ROW_NUMBER() OVER (PARTITION BY original_user_id ORDER BY new_user_id ASC) as split_seq
|
|
||||||
FROM tmp_new_user_mapping
|
|
||||||
) m ON t.original_user_id = m.original_user_id AND t.split_seq = m.split_seq;
|
|
||||||
|
|
||||||
-- 确认映射
|
|
||||||
SELECT * FROM tmp_device_user_mapping;
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Step 4: 更新设备的 user_id 指向新用户
|
|
||||||
-- ============================================================
|
|
||||||
UPDATE user_device d
|
|
||||||
JOIN tmp_device_user_mapping m ON d.id = m.device_id
|
|
||||||
SET d.user_id = m.new_user_id;
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Step 5: 为新用户创建 device auth_method
|
|
||||||
-- ============================================================
|
|
||||||
INSERT INTO user_auth_methods (user_id, auth_type, auth_identifier, verified, created_at, updated_at)
|
|
||||||
SELECT
|
|
||||||
m.new_user_id,
|
|
||||||
'device',
|
|
||||||
m.identifier,
|
|
||||||
1,
|
|
||||||
NOW(),
|
|
||||||
NOW()
|
|
||||||
FROM tmp_device_user_mapping m;
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Step 6: 更新新用户的 refer_code
|
|
||||||
-- 用 CONCAT('u', CONV(new_user_id + UNIX_TIMESTAMP(), 10, 36)) 生成简单唯一码
|
|
||||||
-- (Go 代码用 Base62,SQL 里用 Base36 近似,长度足够唯一)
|
|
||||||
-- ============================================================
|
|
||||||
UPDATE `user` u
|
|
||||||
JOIN tmp_new_user_mapping m ON u.id = m.new_user_id
|
|
||||||
SET u.refer_code = CONCAT('u', LOWER(CONV(u.id + UNIX_TIMESTAMP(NOW()), 10, 36)));
|
|
||||||
|
|
||||||
-- 清理 remark 标记
|
|
||||||
UPDATE `user` u
|
|
||||||
JOIN tmp_new_user_mapping m ON u.id = m.new_user_id
|
|
||||||
SET u.remark = '';
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Step 7: 创建/确保家庭组(原用户为 owner)
|
|
||||||
-- 先处理已有家庭组的情况,再处理没有的
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
-- 7a. 为没有 active 家庭组的原用户创建家庭组
|
|
||||||
INSERT INTO user_family (owner_user_id, max_members, status, created_at, updated_at)
|
|
||||||
SELECT DISTINCT
|
|
||||||
t.original_user_id,
|
|
||||||
2,
|
|
||||||
1, -- active
|
|
||||||
NOW(),
|
|
||||||
NOW()
|
|
||||||
FROM tmp_device_user_mapping t
|
|
||||||
LEFT JOIN user_family f ON f.owner_user_id = t.original_user_id
|
|
||||||
AND f.status = 1 AND f.deleted_at IS NULL
|
|
||||||
WHERE f.id IS NULL;
|
|
||||||
|
|
||||||
-- 7b. 确保原用户在家庭组中有 owner 成员记录
|
|
||||||
INSERT INTO user_family_member (family_id, user_id, role, status, join_source, joined_at, created_at, updated_at)
|
|
||||||
SELECT
|
|
||||||
f.id,
|
|
||||||
f.owner_user_id,
|
|
||||||
1, -- role = owner
|
|
||||||
1, -- status = active
|
|
||||||
'data_fix_split',
|
|
||||||
NOW(),
|
|
||||||
NOW(),
|
|
||||||
NOW()
|
|
||||||
FROM user_family f
|
|
||||||
WHERE f.owner_user_id IN (SELECT DISTINCT original_user_id FROM tmp_device_user_mapping)
|
|
||||||
AND f.status = 1
|
|
||||||
AND f.deleted_at IS NULL
|
|
||||||
AND NOT EXISTS (
|
|
||||||
SELECT 1 FROM user_family_member fm
|
|
||||||
WHERE fm.family_id = f.id
|
|
||||||
AND fm.user_id = f.owner_user_id
|
|
||||||
AND fm.status = 1
|
|
||||||
AND fm.deleted_at IS NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 7c. 将新用户加入家庭组作为 member
|
|
||||||
INSERT INTO user_family_member (family_id, user_id, role, status, join_source, joined_at, created_at, updated_at)
|
|
||||||
SELECT
|
|
||||||
f.id,
|
|
||||||
m.new_user_id,
|
|
||||||
2, -- role = member
|
|
||||||
1, -- status = active
|
|
||||||
'data_fix_split',
|
|
||||||
NOW(),
|
|
||||||
NOW(),
|
|
||||||
NOW()
|
|
||||||
FROM tmp_device_user_mapping m
|
|
||||||
JOIN user_family f ON f.owner_user_id = m.original_user_id
|
|
||||||
AND f.status = 1 AND f.deleted_at IS NULL;
|
|
||||||
|
|
||||||
-- 7d. 更新 max_members(如果原用户有 >2 个设备)
|
|
||||||
UPDATE user_family f
|
|
||||||
SET f.max_members = (
|
|
||||||
SELECT COUNT(*)
|
|
||||||
FROM user_family_member fm
|
|
||||||
WHERE fm.family_id = f.id AND fm.status = 1 AND fm.deleted_at IS NULL
|
|
||||||
)
|
|
||||||
WHERE f.owner_user_id IN (SELECT DISTINCT original_user_id FROM tmp_device_user_mapping)
|
|
||||||
AND f.status = 1 AND f.deleted_at IS NULL;
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Step 8: 转移订阅(原用户的订阅保留,新用户不需要订阅)
|
|
||||||
-- 家庭成员共享 owner 的订阅,所以新用户不需要自己的订阅
|
|
||||||
-- 如果原用户已有订阅,新用户通过家庭组共享
|
|
||||||
-- ============================================================
|
|
||||||
-- (无需操作,代码中 familyBindingHelper.clearMemberSubscribes 会在 joinFamily 时清理)
|
|
||||||
-- 新用户是刚创建的,没有任何订阅记录,无需清理
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Step 9: 验证修复结果
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
-- 9a. 确认没有用户拥有多个设备了
|
|
||||||
SELECT
|
|
||||||
d.user_id,
|
|
||||||
COUNT(*) as device_count,
|
|
||||||
GROUP_CONCAT(d.id ORDER BY d.id) as device_ids
|
|
||||||
FROM user_device d
|
|
||||||
WHERE d.enabled = 1
|
|
||||||
GROUP BY d.user_id
|
|
||||||
HAVING device_count > 1;
|
|
||||||
-- 预期结果:0 行
|
|
||||||
|
|
||||||
-- 9b. 确认家庭组关系正确
|
|
||||||
SELECT
|
|
||||||
f.id as family_id,
|
|
||||||
f.owner_user_id,
|
|
||||||
f.max_members,
|
|
||||||
f.status as family_status,
|
|
||||||
GROUP_CONCAT(CONCAT(fm.user_id, '(role=', fm.role, ')') ORDER BY fm.role) as members
|
|
||||||
FROM user_family f
|
|
||||||
JOIN user_family_member fm ON fm.family_id = f.id AND fm.status = 1 AND fm.deleted_at IS NULL
|
|
||||||
WHERE f.owner_user_id IN (SELECT DISTINCT original_user_id FROM tmp_device_user_mapping)
|
|
||||||
AND f.status = 1 AND f.deleted_at IS NULL
|
|
||||||
GROUP BY f.id, f.owner_user_id, f.max_members, f.status;
|
|
||||||
|
|
||||||
-- 9c. 确认新用户都有 device auth_method
|
|
||||||
SELECT
|
|
||||||
m.new_user_id,
|
|
||||||
m.original_user_id,
|
|
||||||
m.identifier,
|
|
||||||
am.id as auth_method_id,
|
|
||||||
d.id as device_id,
|
|
||||||
d.user_id as device_user_id
|
|
||||||
FROM tmp_device_user_mapping m
|
|
||||||
JOIN user_auth_methods am ON am.user_id = m.new_user_id AND am.auth_type = 'device'
|
|
||||||
JOIN user_device d ON d.id = m.device_id;
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- 清理临时表
|
|
||||||
-- ============================================================
|
|
||||||
DROP TEMPORARY TABLE IF EXISTS tmp_devices_to_split;
|
|
||||||
DROP TEMPORARY TABLE IF EXISTS tmp_new_user_mapping;
|
|
||||||
DROP TEMPORARY TABLE IF EXISTS tmp_device_user_mapping;
|
|
||||||
@ -1,274 +0,0 @@
|
|||||||
"""Generate ppanel-server test case Excel file."""
|
|
||||||
import os
|
|
||||||
from openpyxl import Workbook
|
|
||||||
from openpyxl.styles import (
|
|
||||||
Font, PatternFill, Alignment, Border, Side
|
|
||||||
)
|
|
||||||
from openpyxl.utils import get_column_letter
|
|
||||||
|
|
||||||
OUTPUT_PATH = os.path.join(os.path.dirname(__file__), "..", "tests", "ppanel_test_cases.xlsx")
|
|
||||||
|
|
||||||
# ── Color palette ──────────────────────────────────────────────────────────────
|
|
||||||
C_HEADER_BG = "1F4E79" # dark blue – header row
|
|
||||||
C_HEADER_FONT = "FFFFFF" # white
|
|
||||||
C_SHEET_TITLE = "2E75B6" # mid blue – sheet title row
|
|
||||||
C_P0_BG = "FFE2E2" # light red – P0
|
|
||||||
C_P1_BG = "FFF2CC" # light yellow – P1
|
|
||||||
C_P2_BG = "E2EFDA" # light green – P2
|
|
||||||
C_BORDER = "BFBFBF"
|
|
||||||
|
|
||||||
def thin_border():
|
|
||||||
s = Side(style="thin", color=C_BORDER)
|
|
||||||
return Border(left=s, right=s, top=s, bottom=s)
|
|
||||||
|
|
||||||
def header_fill(hex_color):
|
|
||||||
return PatternFill("solid", fgColor=hex_color)
|
|
||||||
|
|
||||||
def row_fill(hex_color):
|
|
||||||
return PatternFill("solid", fgColor=hex_color)
|
|
||||||
|
|
||||||
# ── Column definitions ─────────────────────────────────────────────────────────
|
|
||||||
COLUMNS = ["用例ID", "模块", "功能点", "前置条件", "测试步骤", "预期结果",
|
|
||||||
"实际结果", "测试状态", "优先级", "备注"]
|
|
||||||
COL_WIDTHS = [16, 16, 28, 32, 40, 40, 20, 12, 8, 20]
|
|
||||||
|
|
||||||
# ── Test data ──────────────────────────────────────────────────────────────────
|
|
||||||
SHEET1_ORDER = {
|
|
||||||
"name": "订单核心流程",
|
|
||||||
"rows": [
|
|
||||||
# id, 模块, 功能点, 前置, 步骤, 预期, 优先
|
|
||||||
("TC-ORDER-001","订单/预创建","正常预览订单价格","用户已登录,套餐存在且在售","传入有效 subscribe_id, quantity=1","返回 price/amount/discount 字段正确","P0"),
|
|
||||||
("TC-ORDER-002","订单/预创建","数量为0时自动修正为1","用户已登录","quantity=0","自动设为1,正常返回价格","P1"),
|
|
||||||
("TC-ORDER-003","订单/预创建","套餐购买数量限制(Quota)","用户已达到该套餐购买上限","再次预创建同套餐订单","返回 SubscribeQuotaLimit 错误","P0"),
|
|
||||||
("TC-ORDER-004","订单/预创建","新用户专属折扣(24h内注册)","用户注册在24h内,套餐有 new_user_only 折扣","预创建订单","折扣生效,amount < price","P0"),
|
|
||||||
("TC-ORDER-005","订单/预创建","老用户不享受新用户折扣","用户注册超过24h","预创建有 new_user_only 折扣的套餐","返回 SubscribeNewUserOnly 错误","P0"),
|
|
||||||
("TC-ORDER-006","订单/预创建","新用户已购过不重复享受新用户折扣","用户24h内注册,但已购买过该套餐","预创建同套餐","折扣不生效,按原价计算","P0"),
|
|
||||||
("TC-ORDER-007","订单/预创建","优惠券不存在","用户已登录","传入不存在的 coupon code","返回 CouponNotExist 错误","P1"),
|
|
||||||
("TC-ORDER-008","订单/预创建","优惠券已用完(count限制)","优惠券 used_count >= count","传入该优惠券","返回 CouponAlreadyUsed 错误","P1"),
|
|
||||||
("TC-ORDER-009","订单/预创建","优惠券个人使用次数超限","用户已使用该优惠券达 user_limit 次","再次使用","返回 CouponInsufficientUsage 错误","P1"),
|
|
||||||
("TC-ORDER-010","订单/预创建","优惠券不适用于该套餐","优惠券绑定了特定套餐,与当前套餐不符","传入该优惠券","返回 CouponNotApplicable 错误","P1"),
|
|
||||||
("TC-ORDER-011","订单/预创建","支付手续费计算","支付方式有手续费配置","传入 payment_id","feeAmount 正确,amount = 原金额 + 手续费","P1"),
|
|
||||||
("TC-ORDER-012","订单/预创建","礼品金额抵扣","用户 gift_amount > 0","预创建订单","deduction_amount 正确,amount 减去礼品金额","P1"),
|
|
||||||
("TC-ORDER-013","订单/预创建","礼品金额全额抵扣(amount归零)","用户 gift_amount >= 订单金额","预创建订单","amount=0,deduction_amount = 原订单金额","P1"),
|
|
||||||
("TC-ORDER-014","订单/购买","正常购买订阅","用户已登录,套餐在售有库存","发起购买请求","订单创建成功,返回 order_no","P0"),
|
|
||||||
("TC-ORDER-015","订单/购买","套餐库存为0不允许购买","套餐 inventory=0","发起购买","返回 SubscribeOutOfStock 错误","P0"),
|
|
||||||
("TC-ORDER-016","订单/购买","单订阅模式:已有 pending 订单自动关闭","SingleModel=true,用户已有 pending 订单","对同套餐再次购买","旧 pending 订单关闭,新订单创建成功","P0"),
|
|
||||||
("TC-ORDER-017","订单/购买","单订阅模式:自动路由为续费","SingleModel=true,用户已有有效订阅","购买相同套餐","订单类型=2(续费),parent_id 指向原订单","P0"),
|
|
||||||
("TC-ORDER-018","订单/购买","数量超过 MaxQuantity 限制","—","quantity > MaxQuantity","返回 InvalidParams 错误","P1"),
|
|
||||||
("TC-ORDER-019","订单/购买","金额超过 MaxOrderAmount","套餐单价极高","购买","返回 InvalidParams 错误","P1"),
|
|
||||||
("TC-ORDER-020","订单/购买","15分钟后自动关闭未支付订单","订单已创建,未支付","等待15分钟后触发队列","订单状态变为 Close(3)","P0"),
|
|
||||||
("TC-ORDER-021","订单/激活","订单激活(NewPurchase)","订单状态=已支付(2),类型=1","触发激活队列","用户订阅创建,订单状态变为 Finished(5)","P0"),
|
|
||||||
("TC-ORDER-022","订单/激活","订单激活(Renewal)","订单状态=已支付,类型=2","触发激活","订阅到期时间延长","P0"),
|
|
||||||
("TC-ORDER-023","订单/激活","订单激活(ResetTraffic)","订单状态=已支付,类型=3","触发激活","用户流量重置","P0"),
|
|
||||||
("TC-ORDER-024","订单/激活","订单激活(Recharge)","订单状态=已支付,类型=4","触发激活","用户余额增加","P0"),
|
|
||||||
("TC-ORDER-025","订单/激活","订单激活(Redemption)","订单状态=已支付,类型=5","触发激活","兑换码激活成功","P0"),
|
|
||||||
("TC-ORDER-026","订单/激活","幂等性:已完成订单不重复处理","订单状态=Finished(5)","再次触发激活","直接跳过,不重复执行","P0"),
|
|
||||||
("TC-ORDER-027","订单/激活","非已支付状态订单不处理","订单状态=Pending(1) 或 Close(3)","触发激活","跳过,返回 ErrInvalidOrderStatus","P0"),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
SHEET2_USER = {
|
|
||||||
"name": "用户模块",
|
|
||||||
"rows": [
|
|
||||||
("TC-USER-001","用户/注册","邮箱注册","邮箱未注册","提交有效邮箱+密码","用户创建成功,返回 token","P0"),
|
|
||||||
("TC-USER-002","用户/登录","邮箱密码登录","用户已注册","提交正确邮箱+密码","返回 JWT token,session 写入 Redis","P0"),
|
|
||||||
("TC-USER-003","用户/设备登录","AES-CBC 加密设备登录","配置 security_secret","Body 使用正确密钥加密","登录成功","P0"),
|
|
||||||
("TC-USER-004","用户/设备登录","错误密钥设备登录","—","Body 使用错误密钥加密","返回认证失败错误","P0"),
|
|
||||||
("TC-USER-005","用户/退出登录","解绑设备(退出家庭组)","用户在家庭组中","调用 unbind_device","用户从家庭组移除,device 记录不删除、不禁用","P0"),
|
|
||||||
("TC-USER-006","用户/注销账号","正常注销","用户已登录","调用 delete_account","账号软删除,auth_methods 软删除,Redis 缓存清理","P0"),
|
|
||||||
("TC-USER-007","用户/注销账号","家主注销 → 解散家庭","用户是家庭组家主","注销账号","家庭所有成员 status=removed,family status=disabled","P0"),
|
|
||||||
("TC-USER-008","用户/注销账号","成员注销 → 仅退出家庭","用户是家庭组成员","注销账号","仅该成员退出,家庭组继续存在","P0"),
|
|
||||||
("TC-USER-009","用户/注销","缓存清理(email key 残留问题)","用户已注销,email 缓存可能残留","注销后检查 Redis","cache:user:email:{email} 已删除","P0"),
|
|
||||||
("TC-USER-010","用户/邀请","绑定邀请码","用户未绑定过邀请码","提交有效邀请码","referer_id 写入,邀请关系建立","P1"),
|
|
||||||
("TC-USER-011","用户/邀请","重复绑定邀请码","用户已绑定邀请码","再次绑定","返回错误,不允许重复绑定","P1"),
|
|
||||||
("TC-USER-012","用户/佣金","首购返佣","用户通过邀请码注册,完成首次付款","订单激活","邀请人佣金增加","P1"),
|
|
||||||
("TC-USER-013","用户/佣金","only_first_purchase=true 仅首购返佣","配置仅首购","被邀请人第二次购买","不再发佣金","P1"),
|
|
||||||
("TC-USER-014","用户/佣金","赠送天数(双方)","邀请关系建立,被邀请人购买","订单激活","邀请人和被邀请人各获得赠送天数","P1"),
|
|
||||||
("TC-USER-015","用户/家庭组","踢出家庭成员","用户是家庭组家主","踢出某成员","该成员退出家庭组,设备记录不变","P1"),
|
|
||||||
("TC-USER-016","用户/订阅","查看订阅状态(含节点分组名和限速时间)","用户有有效订阅","查询订阅状态","返回节点分组名、限速起止时间","P1"),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
SHEET3_SUB = {
|
|
||||||
"name": "订阅套餐",
|
|
||||||
"rows": [
|
|
||||||
("TC-SUB-001","套餐/列表","获取可用套餐列表","—","调用套餐列表接口","返回所有在售套餐","P1"),
|
|
||||||
("TC-SUB-002","套餐/列表","老版本客户端裁剪套餐列表","请求头含 X-App-Id(老版本标识)","调用套餐列表","每个套餐的 discount 列表去掉最后一项","P1"),
|
|
||||||
("TC-SUB-003","套餐/购买限制","Quota 限制(每用户购买上限)","套餐设置 quota=1","用户购买2次同套餐","第二次返回 SubscribeQuotaLimit","P0"),
|
|
||||||
("TC-SUB-004","套餐/折扣","数量折扣梯度","套餐配置多级数量折扣","购买不同数量","对应折扣率正确应用","P1"),
|
|
||||||
("TC-SUB-005","套餐/库存","库存充足时正常购买","inventory > 0","购买","成功,inventory -1","P1"),
|
|
||||||
("TC-SUB-006","套餐/库存","库存=-1(无限库存)","inventory=-1","多次购买","不减少库存,始终可购","P1"),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
SHEET4_PAY = {
|
|
||||||
"name": "支付与优惠券",
|
|
||||||
"rows": [
|
|
||||||
("TC-PAY-001","支付/方式","获取可用支付方式列表","—","调用支付方式接口","返回当前配置的支付方式","P1"),
|
|
||||||
("TC-PAY-002","支付/手续费","固定手续费计算","支付方式配置固定手续费","下单","feeAmount = 配置值","P1"),
|
|
||||||
("TC-PAY-003","支付/手续费","百分比手续费计算","支付方式配置百分比手续费","下单","feeAmount = amount × 百分比","P1"),
|
|
||||||
("TC-PAY-004","支付/手续费","amount=0 时不计算手续费","礼品金额全额抵扣后 amount=0","下单","feeAmount=0","P1"),
|
|
||||||
("TC-CPN-001","优惠券/固定减免","固定金额优惠券","优惠券类型=固定,value=100","使用优惠券","订单减免100","P1"),
|
|
||||||
("TC-CPN-002","优惠券/百分比","百分比优惠券","优惠券类型=百分比,value=0.8","使用优惠券","订单金额×0.8","P1"),
|
|
||||||
("TC-CPN-003","优惠券/过期","过期优惠券不可用","优惠券 expire_at < now","使用","返回错误(CouponExpired)","P1"),
|
|
||||||
("TC-CPN-004","优惠券/套餐绑定","仅限指定套餐使用","优惠券绑定套餐A","用于套餐B","返回 CouponNotApplicable","P1"),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
SHEET5_IAP = {
|
|
||||||
"name": "IAP苹果内购",
|
|
||||||
"rows": [
|
|
||||||
("TC-IAP-001","IAP/绑定","绑定苹果内购 transaction","苹果 transaction 有效","提交 transaction_id","订单创建并激活,订阅开通","P1"),
|
|
||||||
("TC-IAP-002","IAP/绑定","重复绑定同一 transaction","transaction 已绑定","再次提交","幂等处理,不重复创建订单","P1"),
|
|
||||||
("TC-IAP-003","IAP/单订阅模式","内购续费路由","SingleModel=true,用户已有订阅","提交续费 transaction","路由为续费类型订单","P1"),
|
|
||||||
("TC-IAP-004","IAP/对账","日对账任务","配置了 IAP 对账","触发日对账","检查并补处理漏掉的 transaction","P2"),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
SHEET6_LOG = {
|
|
||||||
"name": "日志与缓存",
|
|
||||||
"rows": [
|
|
||||||
("TC-LOG-001","日志/佣金","佣金记录写入 system_logs","发生佣金发放","触发订单激活","type=33 的记录写入,content.type 为 331 或 332","P2"),
|
|
||||||
("TC-LOG-002","日志/礼品金额","礼品金额扣除记录","用户有 gift_amount,下单扣除","购买","GiftTypeReduce 记录写入 system_logs","P2"),
|
|
||||||
("TC-CACHE-001","缓存/用户","注销后 user email 缓存清理","用户已注销","检查 Redis","cache:user:email:{email} 已删除","P0"),
|
|
||||||
("TC-CACHE-002","缓存/订阅","订阅 token 缓存有效","用户有订阅","查询订阅","从 cache:user:subscribe:token:{token} 命中","P2"),
|
|
||||||
("TC-CACHE-003","缓存/签名","X-App-Id 签名验证","AppSecrets 已配置","发送带签名请求","验签通过,正常处理","P1"),
|
|
||||||
("TC-CACHE-004","缓存/签名","无 X-App-Id 跳过签名","—","发送无签名请求","直接通过,不验签","P1"),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
SHEET7_QUEUE = {
|
|
||||||
"name": "队列任务",
|
|
||||||
"rows": [
|
|
||||||
("TC-QUEUE-001","队列/订单关闭","超时自动关闭订单","未支付订单存在","等待15分钟","订单状态=Close","P0"),
|
|
||||||
("TC-QUEUE-002","队列/订阅检查","定期检查订阅到期","用户订阅即将到期","触发 checkSubscription","到期通知发送","P2"),
|
|
||||||
("TC-QUEUE-003","队列/流量统计","服务器流量统计写入","有流量数据上报","触发 trafficStat","流量数据正确写入 DB","P2"),
|
|
||||||
("TC-QUEUE-004","队列/邮件","批量发送邮件任务","已创建批量邮件任务","触发队列","邮件发送成功,任务状态更新","P2"),
|
|
||||||
("TC-QUEUE-005","队列/流量重置","定期重置用户流量","配置了流量重置周期","触发 resetTraffic","用户流量归零","P2"),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
ALL_SHEETS = [SHEET1_ORDER, SHEET2_USER, SHEET3_SUB, SHEET4_PAY, SHEET5_IAP, SHEET6_LOG, SHEET7_QUEUE]
|
|
||||||
|
|
||||||
PRIORITY_FILL = {
|
|
||||||
"P0": row_fill(C_P0_BG),
|
|
||||||
"P1": row_fill(C_P1_BG),
|
|
||||||
"P2": row_fill(C_P2_BG),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def write_sheet(wb: Workbook, sheet_def: dict):
|
|
||||||
ws = wb.create_sheet(title=sheet_def["name"])
|
|
||||||
rows = sheet_def["rows"]
|
|
||||||
|
|
||||||
# ── Title row ──────────────────────────────────────────────────────────────
|
|
||||||
ws.merge_cells(start_row=1, start_column=1, end_row=1, end_column=len(COLUMNS))
|
|
||||||
title_cell = ws.cell(row=1, column=1, value=f"ppanel-server 测试用例 — {sheet_def['name']}")
|
|
||||||
title_cell.font = Font(name="微软雅黑", bold=True, size=13, color=C_HEADER_FONT)
|
|
||||||
title_cell.fill = header_fill(C_SHEET_TITLE)
|
|
||||||
title_cell.alignment = Alignment(horizontal="center", vertical="center")
|
|
||||||
ws.row_dimensions[1].height = 28
|
|
||||||
|
|
||||||
# ── Header row ─────────────────────────────────────────────────────────────
|
|
||||||
for col_idx, col_name in enumerate(COLUMNS, start=1):
|
|
||||||
cell = ws.cell(row=2, column=col_idx, value=col_name)
|
|
||||||
cell.font = Font(name="微软雅黑", bold=True, size=10, color=C_HEADER_FONT)
|
|
||||||
cell.fill = header_fill(C_HEADER_BG)
|
|
||||||
cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
|
|
||||||
cell.border = thin_border()
|
|
||||||
ws.row_dimensions[2].height = 22
|
|
||||||
|
|
||||||
# ── Data rows ──────────────────────────────────────────────────────────────
|
|
||||||
for r_idx, row in enumerate(rows, start=3):
|
|
||||||
tc_id, module, feature, precond, steps, expected, priority = row
|
|
||||||
values = [tc_id, module, feature, precond, steps, expected, "", "", priority, ""]
|
|
||||||
fill = PRIORITY_FILL.get(priority, None)
|
|
||||||
for c_idx, val in enumerate(values, start=1):
|
|
||||||
cell = ws.cell(row=r_idx, column=c_idx, value=val)
|
|
||||||
cell.font = Font(name="微软雅黑", size=9)
|
|
||||||
cell.alignment = Alignment(horizontal="left", vertical="center", wrap_text=True)
|
|
||||||
cell.border = thin_border()
|
|
||||||
if fill:
|
|
||||||
cell.fill = fill
|
|
||||||
ws.row_dimensions[r_idx].height = 45
|
|
||||||
|
|
||||||
# ── Column widths ──────────────────────────────────────────────────────────
|
|
||||||
for col_idx, width in enumerate(COL_WIDTHS, start=1):
|
|
||||||
ws.column_dimensions[get_column_letter(col_idx)].width = width
|
|
||||||
|
|
||||||
# ── Freeze panes ──────────────────────────────────────────────────────────
|
|
||||||
ws.freeze_panes = "A3"
|
|
||||||
|
|
||||||
# ── Auto filter ───────────────────────────────────────────────────────────
|
|
||||||
ws.auto_filter.ref = f"A2:{get_column_letter(len(COLUMNS))}2"
|
|
||||||
|
|
||||||
|
|
||||||
def write_legend_sheet(wb: Workbook):
|
|
||||||
ws = wb.create_sheet(title="说明", index=0)
|
|
||||||
ws.column_dimensions["A"].width = 18
|
|
||||||
ws.column_dimensions["B"].width = 50
|
|
||||||
|
|
||||||
title = ws.cell(row=1, column=1, value="ppanel-server 测试用例说明")
|
|
||||||
ws.merge_cells("A1:B1")
|
|
||||||
title.font = Font(name="微软雅黑", bold=True, size=13, color=C_HEADER_FONT)
|
|
||||||
title.fill = header_fill(C_SHEET_TITLE)
|
|
||||||
title.alignment = Alignment(horizontal="center", vertical="center")
|
|
||||||
ws.row_dimensions[1].height = 28
|
|
||||||
|
|
||||||
legend_data = [
|
|
||||||
("项目", "说明"),
|
|
||||||
("测试框架", "ppanel-server — go-zero + Gin"),
|
|
||||||
("数据库", "本地 MySQL(真实),禁止 SQLite"),
|
|
||||||
("Redis", "本地 Redis 或 miniredis"),
|
|
||||||
("时间戳规范", "后端统一返回秒级 Unix(),前端 ×1000"),
|
|
||||||
("", ""),
|
|
||||||
("优先级", "含义"),
|
|
||||||
("P0(红色)", "核心业务,必须通过。订单/认证/缓存清理等"),
|
|
||||||
("P1(黄色)", "重要功能,强烈建议测试。折扣/优惠券/邀请等"),
|
|
||||||
("P2(绿色)", "辅助功能,建议测试。日志/队列/IAP 等"),
|
|
||||||
("", ""),
|
|
||||||
("测试状态", "填写规范"),
|
|
||||||
("Pass", "用例通过"),
|
|
||||||
("Fail", "用例失败,需记录实际结果"),
|
|
||||||
("Block", "用例被阻塞(依赖功能未就绪)"),
|
|
||||||
("Skip", "本轮跳过"),
|
|
||||||
("", ""),
|
|
||||||
("Sheet 说明", ""),
|
|
||||||
("Sheet1 订单核心流程", "27 条:预创建/购买/激活全流程"),
|
|
||||||
("Sheet2 用户模块", "16 条:注册/登录/注销/邀请/家庭组"),
|
|
||||||
("Sheet3 订阅套餐", "6 条:库存/折扣/限额"),
|
|
||||||
("Sheet4 支付与优惠券", "8 条:手续费/优惠券各类型"),
|
|
||||||
("Sheet5 IAP苹果内购", "4 条:内购/对账"),
|
|
||||||
("Sheet6 日志与缓存", "6 条:日志写入/缓存清理"),
|
|
||||||
("Sheet7 队列任务", "5 条:队列任务验证"),
|
|
||||||
]
|
|
||||||
|
|
||||||
for r_idx, (key, val) in enumerate(legend_data, start=2):
|
|
||||||
c1 = ws.cell(row=r_idx, column=1, value=key)
|
|
||||||
c2 = ws.cell(row=r_idx, column=2, value=val)
|
|
||||||
for c in (c1, c2):
|
|
||||||
c.font = Font(name="微软雅黑", size=9)
|
|
||||||
c.alignment = Alignment(vertical="center", wrap_text=True)
|
|
||||||
c.border = thin_border()
|
|
||||||
if key in ("项目", "优先级", "测试状态", "Sheet 说明"):
|
|
||||||
for c in (c1, c2):
|
|
||||||
c.font = Font(name="微软雅黑", bold=True, size=9, color=C_HEADER_FONT)
|
|
||||||
c.fill = header_fill(C_HEADER_BG)
|
|
||||||
ws.row_dimensions[r_idx].height = 18
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
os.makedirs(os.path.dirname(os.path.abspath(OUTPUT_PATH)), exist_ok=True)
|
|
||||||
wb = Workbook()
|
|
||||||
wb.remove(wb.active) # remove default sheet
|
|
||||||
|
|
||||||
write_legend_sheet(wb)
|
|
||||||
for sheet_def in ALL_SHEETS:
|
|
||||||
write_sheet(wb, sheet_def)
|
|
||||||
|
|
||||||
wb.save(OUTPUT_PATH)
|
|
||||||
print(f"Excel saved: {os.path.abspath(OUTPUT_PATH)}")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@ -1,204 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
_ "github.com/go-sql-driver/mysql"
|
|
||||||
)
|
|
||||||
|
|
||||||
type duplicateGroup struct {
|
|
||||||
OwnerUserID int64 `json:"owner_user_id"`
|
|
||||||
Count int64 `json:"count"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type subscriptionRow struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
UserID int64 `json:"user_id"`
|
|
||||||
OrderID int64 `json:"order_id"`
|
|
||||||
SubscribeID int64 `json:"subscribe_id"`
|
|
||||||
ExpireTime time.Time `json:"expire_time"`
|
|
||||||
Traffic int64 `json:"traffic"`
|
|
||||||
Download int64 `json:"download"`
|
|
||||||
Upload int64 `json:"upload"`
|
|
||||||
ExpiredDownload int64 `json:"expired_download"`
|
|
||||||
ExpiredUpload int64 `json:"expired_upload"`
|
|
||||||
Status uint8 `json:"status"`
|
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type mergePlan struct {
|
|
||||||
OwnerUserID int64 `json:"owner_user_id"`
|
|
||||||
Keep subscriptionRow `json:"keep"`
|
|
||||||
Merge []subscriptionRow `json:"merge"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
dsn := flag.String("dsn", os.Getenv("PPANEL_MYSQL_DSN"), "MySQL DSN; defaults to PPANEL_MYSQL_DSN")
|
|
||||||
execute := flag.Bool("execute", false, "apply changes; default is dry-run")
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
if strings.TrimSpace(*dsn) == "" {
|
|
||||||
log.Fatal("missing DSN: pass -dsn or set PPANEL_MYSQL_DSN")
|
|
||||||
}
|
|
||||||
|
|
||||||
db, err := sql.Open("mysql", *dsn)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
groups, err := findDuplicateGroups(db)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
plans := make([]mergePlan, 0, len(groups))
|
|
||||||
for _, group := range groups {
|
|
||||||
plan, err := buildPlan(db, group.OwnerUserID)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
if len(plan.Merge) > 0 {
|
|
||||||
plans = append(plans, plan)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enc := json.NewEncoder(os.Stdout)
|
|
||||||
enc.SetIndent("", " ")
|
|
||||||
if err := enc.Encode(plans); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !*execute {
|
|
||||||
fmt.Fprintf(os.Stderr, "dry-run only: %d duplicate owner groups found\n", len(plans))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, plan := range plans {
|
|
||||||
if err := applyPlan(db, plan); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Fprintf(os.Stderr, "merged %d duplicate owner groups\n", len(plans))
|
|
||||||
}
|
|
||||||
|
|
||||||
func findDuplicateGroups(db *sql.DB) ([]duplicateGroup, error) {
|
|
||||||
rows, err := db.Query(`
|
|
||||||
SELECT owner_user_id, COUNT(1) AS cnt
|
|
||||||
FROM (
|
|
||||||
SELECT us.id,
|
|
||||||
COALESCE(uf.owner_user_id, us.user_id) AS owner_user_id
|
|
||||||
FROM user_subscribe us
|
|
||||||
LEFT JOIN user_family_member ufm
|
|
||||||
ON ufm.user_id = us.user_id AND ufm.deleted_at IS NULL AND ufm.status = 1
|
|
||||||
LEFT JOIN user_family uf
|
|
||||||
ON uf.id = ufm.family_id AND uf.deleted_at IS NULL AND uf.status = 1
|
|
||||||
WHERE us.token <> ''
|
|
||||||
AND us.status IN (0, 1, 2, 3, 4)
|
|
||||||
) scoped
|
|
||||||
GROUP BY owner_user_id
|
|
||||||
HAVING COUNT(1) > 1
|
|
||||||
ORDER BY owner_user_id`)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
var groups []duplicateGroup
|
|
||||||
for rows.Next() {
|
|
||||||
var g duplicateGroup
|
|
||||||
if err := rows.Scan(&g.OwnerUserID, &g.Count); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
groups = append(groups, g)
|
|
||||||
}
|
|
||||||
return groups, rows.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildPlan(db *sql.DB, ownerUserID int64) (mergePlan, error) {
|
|
||||||
rows, err := db.Query(`
|
|
||||||
SELECT us.id, us.user_id, us.order_id, us.subscribe_id, us.expire_time, us.traffic,
|
|
||||||
us.download, us.upload, us.expired_download, us.expired_upload, us.status, us.updated_at
|
|
||||||
FROM user_subscribe us
|
|
||||||
LEFT JOIN user_family_member ufm
|
|
||||||
ON ufm.user_id = us.user_id AND ufm.deleted_at IS NULL AND ufm.status = 1
|
|
||||||
LEFT JOIN user_family uf
|
|
||||||
ON uf.id = ufm.family_id AND uf.deleted_at IS NULL AND uf.status = 1
|
|
||||||
WHERE COALESCE(uf.owner_user_id, us.user_id) = ?
|
|
||||||
AND us.token <> ''
|
|
||||||
AND us.status IN (0, 1, 2, 3, 4)
|
|
||||||
ORDER BY us.expire_time DESC, us.updated_at DESC, us.id DESC`, ownerUserID)
|
|
||||||
if err != nil {
|
|
||||||
return mergePlan{}, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
var all []subscriptionRow
|
|
||||||
for rows.Next() {
|
|
||||||
var r subscriptionRow
|
|
||||||
if err := rows.Scan(&r.ID, &r.UserID, &r.OrderID, &r.SubscribeID, &r.ExpireTime, &r.Traffic, &r.Download, &r.Upload, &r.ExpiredDownload, &r.ExpiredUpload, &r.Status, &r.UpdatedAt); err != nil {
|
|
||||||
return mergePlan{}, err
|
|
||||||
}
|
|
||||||
all = append(all, r)
|
|
||||||
}
|
|
||||||
if err := rows.Err(); err != nil {
|
|
||||||
return mergePlan{}, err
|
|
||||||
}
|
|
||||||
if len(all) == 0 {
|
|
||||||
return mergePlan{OwnerUserID: ownerUserID}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
keep := all[0]
|
|
||||||
for _, r := range all[1:] {
|
|
||||||
keep.Download += r.Download
|
|
||||||
keep.Upload += r.Upload
|
|
||||||
keep.ExpiredDownload += r.ExpiredDownload
|
|
||||||
keep.ExpiredUpload += r.ExpiredUpload
|
|
||||||
if r.Traffic > keep.Traffic {
|
|
||||||
keep.Traffic = r.Traffic
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, r := range all {
|
|
||||||
if r.UpdatedAt.After(keep.UpdatedAt) {
|
|
||||||
keep.SubscribeID = r.SubscribeID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return mergePlan{OwnerUserID: ownerUserID, Keep: keep, Merge: all[1:]}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func applyPlan(db *sql.DB, plan mergePlan) error {
|
|
||||||
tx, err := db.Begin()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
if _, err = tx.Exec(`
|
|
||||||
UPDATE user_subscribe
|
|
||||||
SET user_id = ?, subscribe_id = ?, traffic = ?, download = ?, upload = ?,
|
|
||||||
expired_download = ?, expired_upload = ?, status = 1, note = CONCAT(COALESCE(note, ''), ' [merged duplicate subscriptions]')
|
|
||||||
WHERE id = ?`,
|
|
||||||
plan.OwnerUserID, plan.Keep.SubscribeID, plan.Keep.Traffic, plan.Keep.Download, plan.Keep.Upload,
|
|
||||||
plan.Keep.ExpiredDownload, plan.Keep.ExpiredUpload, plan.Keep.ID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, r := range plan.Merge {
|
|
||||||
if _, err = tx.Exec(`
|
|
||||||
UPDATE user_subscribe
|
|
||||||
SET status = 5, note = CONCAT(COALESCE(note, ''), ' [merged into subscription #', ?, ']')
|
|
||||||
WHERE id = ?`, plan.Keep.ID, r.ID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return tx.Commit()
|
|
||||||
}
|
|
||||||
@ -1,875 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"gorm.io/driver/mysql"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"gorm.io/gorm/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ── 模型定义(与项目一致) ──
|
|
||||||
|
|
||||||
type User struct {
|
|
||||||
Id int64 `gorm:"primaryKey"`
|
|
||||||
Password string `gorm:"type:varchar(255)"`
|
|
||||||
Algo string `gorm:"type:varchar(255)"`
|
|
||||||
Salt string `gorm:"type:varchar(255)"`
|
|
||||||
Avatar string `gorm:"type:varchar(255)"`
|
|
||||||
Balance int64 `gorm:"type:int"`
|
|
||||||
ReferCode string `gorm:"type:varchar(255)"`
|
|
||||||
RefererId int64 `gorm:"type:bigint"`
|
|
||||||
Commission int64 `gorm:"type:int"`
|
|
||||||
ReferralPercentage int64 `gorm:"type:int"`
|
|
||||||
OnlyFirstPurchase *bool `gorm:"type:tinyint(1)"`
|
|
||||||
GiftAmount int64 `gorm:"type:int"`
|
|
||||||
Enable *bool `gorm:"type:tinyint(1)"`
|
|
||||||
IsAdmin *bool `gorm:"type:tinyint(1)"`
|
|
||||||
EnableBalanceNotify *bool `gorm:"type:tinyint(1)"`
|
|
||||||
EnableLoginNotify *bool `gorm:"type:tinyint(1)"`
|
|
||||||
EnableSubscribeNotify *bool `gorm:"type:tinyint(1)"`
|
|
||||||
EnableTradeNotify *bool `gorm:"type:tinyint(1)"`
|
|
||||||
LastLoginTime *time.Time
|
|
||||||
MemberStatus string `gorm:"type:varchar(255)"`
|
|
||||||
Remark string `gorm:"type:text"`
|
|
||||||
CreatedAt time.Time
|
|
||||||
UpdatedAt time.Time
|
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (User) TableName() string { return "user" }
|
|
||||||
|
|
||||||
type AuthMethod struct {
|
|
||||||
Id int64 `gorm:"primaryKey"`
|
|
||||||
UserId int64 `gorm:"type:bigint"`
|
|
||||||
AuthType string `gorm:"type:varchar(50)"`
|
|
||||||
AuthIdentifier string `gorm:"type:varchar(255)"`
|
|
||||||
Verified *bool `gorm:"type:tinyint(1)"`
|
|
||||||
CreatedAt time.Time
|
|
||||||
UpdatedAt time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (AuthMethod) TableName() string { return "user_auth_methods" }
|
|
||||||
|
|
||||||
type Device struct {
|
|
||||||
Id int64 `gorm:"primaryKey"`
|
|
||||||
Ip string `gorm:"type:varchar(255)"`
|
|
||||||
UserId int64 `gorm:"type:bigint"`
|
|
||||||
UserAgent string `gorm:"type:text"`
|
|
||||||
Identifier string `gorm:"type:varchar(255)"`
|
|
||||||
ShortCode string `gorm:"type:varchar(50)"`
|
|
||||||
Online *bool `gorm:"type:tinyint(1)"`
|
|
||||||
Enabled *bool `gorm:"type:tinyint(1)"`
|
|
||||||
CreatedAt time.Time
|
|
||||||
UpdatedAt time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (Device) TableName() string { return "user_device" }
|
|
||||||
|
|
||||||
type UserSubscribe struct {
|
|
||||||
Id int64 `gorm:"primaryKey"`
|
|
||||||
UserId int64 `gorm:"type:bigint"`
|
|
||||||
OrderId int64 `gorm:"type:bigint"`
|
|
||||||
SubscribeId int64 `gorm:"type:bigint"`
|
|
||||||
StartTime time.Time `gorm:"type:datetime(3)"`
|
|
||||||
ExpireTime *time.Time `gorm:"type:datetime(3)"`
|
|
||||||
FinishedAt *time.Time `gorm:"type:datetime"`
|
|
||||||
Traffic int64 `gorm:"type:bigint"`
|
|
||||||
Download int64 `gorm:"type:bigint"`
|
|
||||||
Upload int64 `gorm:"type:bigint"`
|
|
||||||
Token string `gorm:"type:varchar(255)"`
|
|
||||||
UUID string `gorm:"type:varchar(255)"`
|
|
||||||
Status uint8 `gorm:"type:tinyint(1)"`
|
|
||||||
Note string `gorm:"type:varchar(500)"`
|
|
||||||
CreatedAt time.Time
|
|
||||||
UpdatedAt time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (UserSubscribe) TableName() string { return "user_subscribe" }
|
|
||||||
|
|
||||||
type Order struct {
|
|
||||||
Id int64 `gorm:"primaryKey"`
|
|
||||||
ParentId int64 `gorm:"type:bigint"`
|
|
||||||
UserId int64 `gorm:"type:bigint"`
|
|
||||||
SubscriptionUserId int64 `gorm:"type:bigint"`
|
|
||||||
OrderNo string `gorm:"type:varchar(255)"`
|
|
||||||
Type uint8 `gorm:"type:tinyint(1)"`
|
|
||||||
Quantity int64 `gorm:"type:bigint"`
|
|
||||||
Price int64 `gorm:"type:int"`
|
|
||||||
Amount int64 `gorm:"type:int"`
|
|
||||||
GiftAmount int64 `gorm:"type:int"`
|
|
||||||
Discount int64 `gorm:"type:int"`
|
|
||||||
Coupon string `gorm:"type:varchar(255)"`
|
|
||||||
CouponDiscount int64 `gorm:"type:int"`
|
|
||||||
Commission int64 `gorm:"type:int"`
|
|
||||||
PaymentId int64 `gorm:"type:bigint"`
|
|
||||||
Method string `gorm:"type:varchar(255)"`
|
|
||||||
FeeAmount int64 `gorm:"type:int"`
|
|
||||||
TradeNo string `gorm:"type:varchar(255)"`
|
|
||||||
Status uint8 `gorm:"type:tinyint(1)"`
|
|
||||||
SubscribeId int64 `gorm:"type:bigint"`
|
|
||||||
SubscribeToken string `gorm:"type:varchar(255)"`
|
|
||||||
AppAccountToken string `gorm:"type:varchar(36)"`
|
|
||||||
IsNew bool `gorm:"type:tinyint(1)"`
|
|
||||||
CreatedAt time.Time
|
|
||||||
UpdatedAt time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (Order) TableName() string { return "order" }
|
|
||||||
|
|
||||||
type IAPTransaction struct {
|
|
||||||
Id int64 `gorm:"primaryKey"`
|
|
||||||
UserId int64 `gorm:"type:bigint"`
|
|
||||||
OriginalTransactionId string `gorm:"type:varchar(255)"`
|
|
||||||
TransactionId string `gorm:"type:varchar(255)"`
|
|
||||||
ProductId string `gorm:"type:varchar(255)"`
|
|
||||||
PurchaseAt *time.Time
|
|
||||||
RevocationAt *time.Time
|
|
||||||
JWSHash string `gorm:"type:varchar(255)"`
|
|
||||||
CreatedAt time.Time
|
|
||||||
UpdatedAt time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (IAPTransaction) TableName() string { return "apple_iap_transactions" }
|
|
||||||
|
|
||||||
type Subscribe struct {
|
|
||||||
Id int64 `gorm:"primaryKey"`
|
|
||||||
Name string `gorm:"type:varchar(255)"`
|
|
||||||
Language string `gorm:"type:varchar(255)"`
|
|
||||||
Description string `gorm:"type:text"`
|
|
||||||
UnitPrice int64 `gorm:"type:int"`
|
|
||||||
UnitTime string `gorm:"type:varchar(255)"`
|
|
||||||
Discount string `gorm:"type:text"`
|
|
||||||
Replacement int64 `gorm:"type:int"`
|
|
||||||
Inventory int64 `gorm:"type:int"`
|
|
||||||
Traffic int64 `gorm:"type:int"`
|
|
||||||
SpeedLimit int64 `gorm:"type:int"`
|
|
||||||
DeviceLimit int64 `gorm:"type:int"`
|
|
||||||
Quota int64 `gorm:"type:int"`
|
|
||||||
NewUserOnly *bool `gorm:"type:tinyint(1)"`
|
|
||||||
Nodes string `gorm:"type:varchar(255)"`
|
|
||||||
NodeTags string `gorm:"type:varchar(255)"`
|
|
||||||
Show *bool `gorm:"type:tinyint(1)"`
|
|
||||||
Sell *bool `gorm:"type:tinyint(1)"`
|
|
||||||
Sort int64 `gorm:"type:int"`
|
|
||||||
DeductionRatio int64 `gorm:"type:int"`
|
|
||||||
AllowDeduction *bool `gorm:"type:tinyint(1)"`
|
|
||||||
ResetCycle int64 `gorm:"type:int"`
|
|
||||||
RenewalReset *bool `gorm:"type:tinyint(1)"`
|
|
||||||
ShowOriginalPrice bool `gorm:"type:tinyint(1)"`
|
|
||||||
CreatedAt time.Time
|
|
||||||
UpdatedAt time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (Subscribe) TableName() string { return "subscribe" }
|
|
||||||
|
|
||||||
type Payment struct {
|
|
||||||
Id int64 `gorm:"primaryKey"`
|
|
||||||
Name string `gorm:"type:varchar(100)"`
|
|
||||||
Platform string `gorm:"type:varchar(100)"`
|
|
||||||
Icon string `gorm:"type:varchar(255)"`
|
|
||||||
Domain string `gorm:"type:varchar(255)"`
|
|
||||||
Config string `gorm:"type:text"`
|
|
||||||
Description string `gorm:"type:text"`
|
|
||||||
FeeMode uint `gorm:"type:tinyint(1)"`
|
|
||||||
FeePercent int64 `gorm:"type:int"`
|
|
||||||
FeeAmount int64 `gorm:"type:int"`
|
|
||||||
Enable *bool `gorm:"type:tinyint(1)"`
|
|
||||||
Token string `gorm:"type:varchar(255)"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (Payment) TableName() string { return "payment" }
|
|
||||||
|
|
||||||
type UserFamily struct {
|
|
||||||
Id int64 `gorm:"primaryKey"`
|
|
||||||
OwnerUserId int64 `gorm:"uniqueIndex"`
|
|
||||||
MaxMembers int64 `gorm:"default:3"`
|
|
||||||
Status uint8 `gorm:"type:tinyint(1);default:1"`
|
|
||||||
CreatedAt time.Time
|
|
||||||
UpdatedAt time.Time
|
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (UserFamily) TableName() string { return "user_family" }
|
|
||||||
|
|
||||||
type UserFamilyMember struct {
|
|
||||||
Id int64 `gorm:"primaryKey"`
|
|
||||||
FamilyId int64
|
|
||||||
UserId int64 `gorm:"uniqueIndex"`
|
|
||||||
Role uint8 `gorm:"type:tinyint(1);default:2"`
|
|
||||||
Status uint8 `gorm:"type:tinyint(1);default:1"`
|
|
||||||
JoinSource string `gorm:"type:varchar(32)"`
|
|
||||||
JoinedAt time.Time
|
|
||||||
LeftAt *time.Time
|
|
||||||
CreatedAt time.Time
|
|
||||||
UpdatedAt time.Time
|
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (UserFamilyMember) TableName() string { return "user_family_member" }
|
|
||||||
|
|
||||||
// ── 主程序 ──
|
|
||||||
|
|
||||||
const (
|
|
||||||
defaultFamilyMaxSize = 3
|
|
||||||
orderStatusCompleted = 3
|
|
||||||
familyRoleOwner = 1
|
|
||||||
familyRoleMember = 2
|
|
||||||
batchSize = 100
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
srcDSN := flag.String("src", "", "源数据库 DSN,格式: user:password@tcp(host:port)/dbname")
|
|
||||||
dstDSN := flag.String("dst", "", "目标数据库 DSN,格式: user:password@tcp(host:port)/dbname")
|
|
||||||
clean := flag.Bool("clean", false, "导入前清空目标库相关表")
|
|
||||||
dryRun := flag.Bool("dry-run", false, "仅分析不写入,打印统计信息")
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
if *srcDSN == "" || *dstDSN == "" {
|
|
||||||
fmt.Println("付费用户数据迁移工具")
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println("用法:")
|
|
||||||
fmt.Println(" go run scripts/migrate_paid_users.go \\")
|
|
||||||
fmt.Println(" -src 'root:rootpassword@tcp(127.0.0.1:3306)/ppanel?charset=utf8mb4&parseTime=True&loc=Local' \\")
|
|
||||||
fmt.Println(" -dst 'root:jpcV41ppanel@tcp(154.12.35.103:3306)/ppanel?charset=utf8mb4&parseTime=True&loc=Local' \\")
|
|
||||||
fmt.Println(" -clean")
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println("参数:")
|
|
||||||
fmt.Println(" -src 源数据库 DSN(旧备份库)")
|
|
||||||
fmt.Println(" -dst 目标数据库 DSN(新线上库)")
|
|
||||||
fmt.Println(" -clean 导入前清空目标库的用户/订单等表(保留表结构)")
|
|
||||||
fmt.Println(" -dry-run 仅分析不写入")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
gormCfg := &gorm.Config{
|
|
||||||
Logger: logger.Default.LogMode(logger.Warn),
|
|
||||||
}
|
|
||||||
|
|
||||||
// 连接源库
|
|
||||||
fmt.Println("=== 付费用户数据迁移 ===")
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Print("[1/10] 连接源数据库... ")
|
|
||||||
srcDB, err := gorm.Open(mysql.Open(*srcDSN), gormCfg)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("源库连接失败: %v", err)
|
|
||||||
}
|
|
||||||
fmt.Println("OK")
|
|
||||||
|
|
||||||
// 连接目标库
|
|
||||||
fmt.Print("[2/10] 连接目标数据库... ")
|
|
||||||
dstDB, err := gorm.Open(mysql.Open(*dstDSN), gormCfg)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("目标库连接失败: %v", err)
|
|
||||||
}
|
|
||||||
fmt.Println("OK")
|
|
||||||
|
|
||||||
// ── Step 3: 查询付费用户 ID ──
|
|
||||||
fmt.Print("[3/10] 查询付费用户... ")
|
|
||||||
var paidIDs []int64
|
|
||||||
err = srcDB.Raw(`
|
|
||||||
SELECT DISTINCT t.uid FROM (
|
|
||||||
SELECT user_id AS uid FROM ` + "`order`" + ` WHERE status = ? AND user_id > 0
|
|
||||||
UNION
|
|
||||||
SELECT user_id AS uid FROM apple_iap_transactions WHERE user_id > 0
|
|
||||||
UNION
|
|
||||||
SELECT user_id AS uid FROM user_subscribe WHERE user_id > 0 AND (expire_time IS NULL OR expire_time > NOW())
|
|
||||||
) t
|
|
||||||
INNER JOIN user u ON u.id = t.uid
|
|
||||||
WHERE u.id NOT IN (
|
|
||||||
SELECT user_id FROM user_auth_methods WHERE auth_type = 'email' AND auth_identifier = 'devneeds52@gmail.com'
|
|
||||||
)
|
|
||||||
ORDER BY t.uid
|
|
||||||
`, orderStatusCompleted).Scan(&paidIDs).Error
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("查询付费用户失败: %v", err)
|
|
||||||
}
|
|
||||||
fmt.Printf("%d 个付费用户\n", len(paidIDs))
|
|
||||||
|
|
||||||
if len(paidIDs) == 0 {
|
|
||||||
fmt.Println("没有找到付费用户,退出")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Step 4: 读取源库数据 ──
|
|
||||||
fmt.Print("[4/10] 读取源库数据... ")
|
|
||||||
var (
|
|
||||||
users []User
|
|
||||||
auths []AuthMethod
|
|
||||||
devices []Device
|
|
||||||
orders []Order
|
|
||||||
subscribes []UserSubscribe
|
|
||||||
iaps []IAPTransaction
|
|
||||||
// 全量表
|
|
||||||
subPlans []Subscribe
|
|
||||||
payments []Payment
|
|
||||||
)
|
|
||||||
|
|
||||||
srcDB.Where("id IN ?", paidIDs).Find(&users)
|
|
||||||
srcDB.Where("user_id IN ?", paidIDs).Find(&auths)
|
|
||||||
srcDB.Where("user_id IN ?", paidIDs).Find(&devices)
|
|
||||||
srcDB.Where("user_id IN ? AND status = ?", paidIDs, orderStatusCompleted).Find(&orders)
|
|
||||||
srcDB.Where("user_id IN ?", paidIDs).Find(&subscribes)
|
|
||||||
srcDB.Where("user_id IN ?", paidIDs).Find(&iaps)
|
|
||||||
srcDB.Find(&subPlans)
|
|
||||||
srcDB.Find(&payments)
|
|
||||||
|
|
||||||
// ── 处理多订阅:如果用户有多个订阅,仅保留未过期的 ──
|
|
||||||
nowTime := time.Now()
|
|
||||||
subByUser := make(map[int64][]UserSubscribe)
|
|
||||||
for _, s := range subscribes {
|
|
||||||
subByUser[s.UserId] = append(subByUser[s.UserId], s)
|
|
||||||
}
|
|
||||||
|
|
||||||
var validSubscribes []UserSubscribe
|
|
||||||
for _, subs := range subByUser {
|
|
||||||
if len(subs) <= 1 {
|
|
||||||
// 单个订阅直接保留
|
|
||||||
validSubscribes = append(validSubscribes, subs...)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var unexpired []UserSubscribe
|
|
||||||
var latest *UserSubscribe
|
|
||||||
|
|
||||||
for i := range subs {
|
|
||||||
s := subs[i]
|
|
||||||
// 如果没有过期时间,或者过期时间在当前时间之后
|
|
||||||
if s.ExpireTime == nil || s.ExpireTime.After(nowTime) {
|
|
||||||
unexpired = append(unexpired, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 记录到期时间最晚的一个,以防全部都过期了
|
|
||||||
if latest == nil {
|
|
||||||
latest = &s
|
|
||||||
} else if latest.ExpireTime != nil && s.ExpireTime != nil && s.ExpireTime.After(*latest.ExpireTime) {
|
|
||||||
latest = &s
|
|
||||||
} else if latest.ExpireTime != nil && s.ExpireTime == nil {
|
|
||||||
latest = &s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(unexpired) > 0 {
|
|
||||||
// 存在未过期的订阅,仅保留所有未过期的
|
|
||||||
validSubscribes = append(validSubscribes, unexpired...)
|
|
||||||
} else if latest != nil {
|
|
||||||
// 如果全部过期,仅保留到期时间最晚的那一个
|
|
||||||
validSubscribes = append(validSubscribes, *latest)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
subscribes = validSubscribes
|
|
||||||
|
|
||||||
fmt.Println("OK")
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println(" 数据统计:")
|
|
||||||
fmt.Printf(" user: %d\n", len(users))
|
|
||||||
fmt.Printf(" user_auth_methods: %d\n", len(auths))
|
|
||||||
fmt.Printf(" user_device: %d\n", len(devices))
|
|
||||||
fmt.Printf(" order: %d\n", len(orders))
|
|
||||||
fmt.Printf(" user_subscribe: %d\n", len(subscribes))
|
|
||||||
fmt.Printf(" apple_iap: %d\n", len(iaps))
|
|
||||||
fmt.Printf(" subscribe(全量): %d\n", len(subPlans))
|
|
||||||
fmt.Printf(" payment(全量): %d\n", len(payments))
|
|
||||||
|
|
||||||
// ── 识别多设备用户 ──
|
|
||||||
deviceByUser := make(map[int64][]Device)
|
|
||||||
for _, d := range devices {
|
|
||||||
deviceByUser[d.UserId] = append(deviceByUser[d.UserId], d)
|
|
||||||
}
|
|
||||||
authByUserDevice := make(map[string]*AuthMethod) // key: "userId:identifier"
|
|
||||||
for i := range auths {
|
|
||||||
a := &auths[i]
|
|
||||||
if a.AuthType == "device" {
|
|
||||||
key := fmt.Sprintf("%d:%s", a.UserId, a.AuthIdentifier)
|
|
||||||
authByUserDevice[key] = a
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type splitInfo struct {
|
|
||||||
OwnerUID int64
|
|
||||||
Device Device
|
|
||||||
AuthMethod *AuthMethod
|
|
||||||
}
|
|
||||||
var splits []splitInfo
|
|
||||||
|
|
||||||
for uid, devs := range deviceByUser {
|
|
||||||
if len(devs) <= 1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// 按 ID 排序:最小的保留,其余拆分
|
|
||||||
minID := devs[0].Id
|
|
||||||
for _, d := range devs[1:] {
|
|
||||||
if d.Id < minID {
|
|
||||||
minID = d.Id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, d := range devs {
|
|
||||||
if d.Id == minID {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
key := fmt.Sprintf("%d:%s", uid, d.Identifier)
|
|
||||||
auth := authByUserDevice[key]
|
|
||||||
splits = append(splits, splitInfo{
|
|
||||||
OwnerUID: uid,
|
|
||||||
Device: d,
|
|
||||||
AuthMethod: auth,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("\n 多设备拆分: %d 个设备 → 独立用户\n", len(splits))
|
|
||||||
fmt.Println()
|
|
||||||
|
|
||||||
// ── ID 重建:将所有记录主键从 1 开始连续重赋值,并同步更新所有外键 ──
|
|
||||||
fmt.Println(" 重建 ID...")
|
|
||||||
|
|
||||||
// 各表 old→new 映射(仅对有外键引用的表建立映射)
|
|
||||||
userIDMap := make(map[int64]int64, len(users))
|
|
||||||
orderIDMap := make(map[int64]int64, len(orders))
|
|
||||||
subPlanIDMap := make(map[int64]int64, len(subPlans))
|
|
||||||
paymentIDMap := make(map[int64]int64, len(payments))
|
|
||||||
deviceIDMap := make(map[int64]int64, len(devices))
|
|
||||||
|
|
||||||
// 1. 重建 user ID(从 1 开始连续)
|
|
||||||
for i := range users {
|
|
||||||
newID := int64(i + 1)
|
|
||||||
userIDMap[users[i].Id] = newID
|
|
||||||
users[i].Id = newID
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 重建 subscribe 配置表 ID
|
|
||||||
for i := range subPlans {
|
|
||||||
newID := int64(i + 1)
|
|
||||||
subPlanIDMap[subPlans[i].Id] = newID
|
|
||||||
subPlans[i].Id = newID
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 重建 payment 配置表 ID
|
|
||||||
for i := range payments {
|
|
||||||
newID := int64(i + 1)
|
|
||||||
paymentIDMap[payments[i].Id] = newID
|
|
||||||
payments[i].Id = newID
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. 重建 auth_methods ID + 更新 user_id 外键
|
|
||||||
for i := range auths {
|
|
||||||
auths[i].Id = int64(i + 1)
|
|
||||||
if v, ok := userIDMap[auths[i].UserId]; ok {
|
|
||||||
auths[i].UserId = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. 重建 device ID + 更新 user_id 外键
|
|
||||||
for i := range devices {
|
|
||||||
newID := int64(i + 1)
|
|
||||||
deviceIDMap[devices[i].Id] = newID
|
|
||||||
devices[i].Id = newID
|
|
||||||
if v, ok := userIDMap[devices[i].UserId]; ok {
|
|
||||||
devices[i].UserId = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6. 重建 order ID + 外键(user_id / subscription_user_id / payment_id / subscribe_id)
|
|
||||||
for i := range orders {
|
|
||||||
newID := int64(i + 1)
|
|
||||||
orderIDMap[orders[i].Id] = newID
|
|
||||||
orders[i].Id = newID
|
|
||||||
if v, ok := userIDMap[orders[i].UserId]; ok {
|
|
||||||
orders[i].UserId = v
|
|
||||||
}
|
|
||||||
if orders[i].SubscriptionUserId > 0 {
|
|
||||||
if v, ok := userIDMap[orders[i].SubscriptionUserId]; ok {
|
|
||||||
orders[i].SubscriptionUserId = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if orders[i].PaymentId > 0 {
|
|
||||||
if v, ok := paymentIDMap[orders[i].PaymentId]; ok {
|
|
||||||
orders[i].PaymentId = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if orders[i].SubscribeId > 0 {
|
|
||||||
if v, ok := subPlanIDMap[orders[i].SubscribeId]; ok {
|
|
||||||
orders[i].SubscribeId = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 二次处理 order.ParentId(父子订单指向同表,需在 orderIDMap 完整建立后再处理)
|
|
||||||
for i := range orders {
|
|
||||||
if orders[i].ParentId > 0 {
|
|
||||||
if v, ok := orderIDMap[orders[i].ParentId]; ok {
|
|
||||||
orders[i].ParentId = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 7. 重建 user_subscribe ID + 外键
|
|
||||||
for i := range subscribes {
|
|
||||||
subscribes[i].Id = int64(i + 1)
|
|
||||||
if v, ok := userIDMap[subscribes[i].UserId]; ok {
|
|
||||||
subscribes[i].UserId = v
|
|
||||||
}
|
|
||||||
if subscribes[i].OrderId > 0 {
|
|
||||||
if v, ok := orderIDMap[subscribes[i].OrderId]; ok {
|
|
||||||
subscribes[i].OrderId = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if subscribes[i].SubscribeId > 0 {
|
|
||||||
if v, ok := subPlanIDMap[subscribes[i].SubscribeId]; ok {
|
|
||||||
subscribes[i].SubscribeId = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 8. 重建 iap ID + 更新 user_id 外键
|
|
||||||
for i := range iaps {
|
|
||||||
iaps[i].Id = int64(i + 1)
|
|
||||||
if v, ok := userIDMap[iaps[i].UserId]; ok {
|
|
||||||
iaps[i].UserId = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 9. 更新 paidIDs(Step 8 家庭组创建使用新 user ID)
|
|
||||||
for i, uid := range paidIDs {
|
|
||||||
if v, ok := userIDMap[uid]; ok {
|
|
||||||
paidIDs[i] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 10. 更新 splits 中的 OwnerUID 和 Device 副本
|
|
||||||
// Device 是值拷贝,需通过 deviceIDMap 单独更新;AuthMethod 是指针,已随 auths[i] 同步
|
|
||||||
for i := range splits {
|
|
||||||
if v, ok := userIDMap[splits[i].OwnerUID]; ok {
|
|
||||||
splits[i].OwnerUID = v
|
|
||||||
}
|
|
||||||
if v, ok := deviceIDMap[splits[i].Device.Id]; ok {
|
|
||||||
splits[i].Device.Id = v
|
|
||||||
}
|
|
||||||
if v, ok := userIDMap[splits[i].Device.UserId]; ok {
|
|
||||||
splits[i].Device.UserId = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf(" OK — user:%d auth:%d device:%d order:%d subscribe:%d iap:%d\n",
|
|
||||||
len(userIDMap), len(auths), len(deviceIDMap), len(orderIDMap), len(subscribes), len(iaps))
|
|
||||||
|
|
||||||
// ── 注入默认管理员用户(devneeds52@gmail.com) ──
|
|
||||||
// 该用户不涉及付费订单,ID 紧接在已迁移用户之后,避免冲突
|
|
||||||
{
|
|
||||||
defaultCreatedAt := time.Date(2025, 9, 30, 9, 33, 45, 780_000_000, time.UTC)
|
|
||||||
lastLogin := time.Date(2026, 3, 15, 17, 13, 45, 0, time.UTC)
|
|
||||||
defaultUID := int64(len(users) + 1)
|
|
||||||
|
|
||||||
defaultUser := User{
|
|
||||||
Id: defaultUID,
|
|
||||||
Password: "$pbkdf2-sha512$kyFSMS4eAnupW7bX$38953ce0e7ec8415c39603bdc3010050ddab2e433f0383222215bbec013450e3",
|
|
||||||
Algo: "default",
|
|
||||||
Salt: "default",
|
|
||||||
Avatar: "",
|
|
||||||
Balance: 0,
|
|
||||||
ReferCode: "uuEPXVjS",
|
|
||||||
Commission: 0,
|
|
||||||
ReferralPercentage: 0,
|
|
||||||
OnlyFirstPurchase: boolPtr(true),
|
|
||||||
GiftAmount: 0,
|
|
||||||
Enable: boolPtr(true),
|
|
||||||
IsAdmin: boolPtr(true),
|
|
||||||
EnableBalanceNotify: boolPtr(false),
|
|
||||||
EnableLoginNotify: boolPtr(false),
|
|
||||||
EnableSubscribeNotify: boolPtr(false),
|
|
||||||
EnableTradeNotify: boolPtr(false),
|
|
||||||
LastLoginTime: &lastLogin,
|
|
||||||
MemberStatus: "",
|
|
||||||
Remark: "",
|
|
||||||
CreatedAt: defaultCreatedAt,
|
|
||||||
UpdatedAt: time.Now(),
|
|
||||||
}
|
|
||||||
users = append(users, defaultUser)
|
|
||||||
|
|
||||||
defaultAuth := AuthMethod{
|
|
||||||
Id: int64(len(auths) + 1),
|
|
||||||
UserId: defaultUID,
|
|
||||||
AuthType: "email",
|
|
||||||
AuthIdentifier: "devneeds52@gmail.com",
|
|
||||||
Verified: boolPtr(true),
|
|
||||||
CreatedAt: defaultCreatedAt,
|
|
||||||
UpdatedAt: defaultCreatedAt,
|
|
||||||
}
|
|
||||||
auths = append(auths, defaultAuth)
|
|
||||||
|
|
||||||
fmt.Printf(" 注入管理员: uid=%d email=devneeds52@gmail.com\n", defaultUID)
|
|
||||||
}
|
|
||||||
fmt.Println()
|
|
||||||
|
|
||||||
if *dryRun {
|
|
||||||
fmt.Println("[DRY-RUN] 仅分析,不写入目标库")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Step 5: 清空目标库(可选) ──
|
|
||||||
if *clean {
|
|
||||||
fmt.Print("[5/10] 清空目标库... ")
|
|
||||||
dstDB.Exec("SET FOREIGN_KEY_CHECKS = 0")
|
|
||||||
for _, tbl := range []string{
|
|
||||||
"user", "user_auth_methods", "user_device",
|
|
||||||
"`order`", "user_subscribe", "apple_iap_transactions",
|
|
||||||
"user_family", "user_family_member",
|
|
||||||
} {
|
|
||||||
dstDB.Exec(fmt.Sprintf("TRUNCATE TABLE %s", tbl))
|
|
||||||
}
|
|
||||||
dstDB.Exec("SET FOREIGN_KEY_CHECKS = 1")
|
|
||||||
fmt.Println("OK")
|
|
||||||
} else {
|
|
||||||
fmt.Println("[5/10] 跳过清空(未指定 -clean)")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Step 6: 写入全量配置表 ──
|
|
||||||
fmt.Print("[6/10] 写入全量配置表... ")
|
|
||||||
if len(subPlans) > 0 {
|
|
||||||
dstDB.Exec("DELETE FROM subscribe") // 先清再插
|
|
||||||
for _, s := range subPlans {
|
|
||||||
dstDB.Create(&s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(payments) > 0 {
|
|
||||||
dstDB.Exec("DELETE FROM payment")
|
|
||||||
for _, p := range payments {
|
|
||||||
dstDB.Create(&p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Println("OK")
|
|
||||||
|
|
||||||
// ── Step 7: 写入付费用户数据(事务) ──
|
|
||||||
fmt.Print("[7/10] 写入付费用户数据... ")
|
|
||||||
err = dstDB.Transaction(func(tx *gorm.DB) error {
|
|
||||||
tx.Exec("SET FOREIGN_KEY_CHECKS = 0")
|
|
||||||
|
|
||||||
if err := tx.CreateInBatches(&users, batchSize).Error; err != nil {
|
|
||||||
return fmt.Errorf("写入 user 失败: %w", err)
|
|
||||||
}
|
|
||||||
if err := tx.CreateInBatches(&auths, batchSize).Error; err != nil {
|
|
||||||
return fmt.Errorf("写入 auth_methods 失败: %w", err)
|
|
||||||
}
|
|
||||||
if err := tx.CreateInBatches(&devices, batchSize).Error; err != nil {
|
|
||||||
return fmt.Errorf("写入 device 失败: %w", err)
|
|
||||||
}
|
|
||||||
if err := tx.CreateInBatches(&orders, batchSize).Error; err != nil {
|
|
||||||
return fmt.Errorf("写入 order 失败: %w", err)
|
|
||||||
}
|
|
||||||
if err := tx.CreateInBatches(&subscribes, batchSize).Error; err != nil {
|
|
||||||
return fmt.Errorf("写入 subscribe 失败: %w", err)
|
|
||||||
}
|
|
||||||
if err := tx.CreateInBatches(&iaps, batchSize).Error; err != nil {
|
|
||||||
return fmt.Errorf("写入 iap 失败: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tx.Exec("SET FOREIGN_KEY_CHECKS = 1")
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("写入失败: %v", err)
|
|
||||||
}
|
|
||||||
fmt.Println("OK")
|
|
||||||
|
|
||||||
// ── Step 8: 创建家庭组 ──
|
|
||||||
fmt.Print("[8/10] 创建家庭组... ")
|
|
||||||
now := time.Now()
|
|
||||||
familyCount := 0
|
|
||||||
|
|
||||||
// ── 为了配合基于新 UID 的条件检查,预先构建映射 ──
|
|
||||||
deviceCountByNewUID := make(map[int64]int)
|
|
||||||
for i := range devices {
|
|
||||||
deviceCountByNewUID[devices[i].UserId]++
|
|
||||||
}
|
|
||||||
hasEmailByNewUID := make(map[int64]bool)
|
|
||||||
for i := range auths {
|
|
||||||
if auths[i].AuthType == "email" {
|
|
||||||
hasEmailByNewUID[auths[i].UserId] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = dstDB.Transaction(func(tx *gorm.DB) error {
|
|
||||||
for _, uid := range paidIDs {
|
|
||||||
// 只为多设备且有邮箱的用户创建家庭组
|
|
||||||
if deviceCountByNewUID[uid] <= 1 || !hasEmailByNewUID[uid] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
family := UserFamily{
|
|
||||||
OwnerUserId: uid,
|
|
||||||
MaxMembers: defaultFamilyMaxSize,
|
|
||||||
Status: 1,
|
|
||||||
CreatedAt: now,
|
|
||||||
UpdatedAt: now,
|
|
||||||
}
|
|
||||||
if err := tx.Create(&family).Error; err != nil {
|
|
||||||
return fmt.Errorf("创建家庭组(uid=%d)失败: %w", uid, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
member := UserFamilyMember{
|
|
||||||
FamilyId: family.Id,
|
|
||||||
UserId: uid,
|
|
||||||
Role: familyRoleOwner,
|
|
||||||
Status: 1,
|
|
||||||
JoinSource: "migration",
|
|
||||||
JoinedAt: now,
|
|
||||||
CreatedAt: now,
|
|
||||||
UpdatedAt: now,
|
|
||||||
}
|
|
||||||
if err := tx.Create(&member).Error; err != nil {
|
|
||||||
return fmt.Errorf("创建家主成员(uid=%d)失败: %w", uid, err)
|
|
||||||
}
|
|
||||||
familyCount++
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("家庭组创建失败: %v", err)
|
|
||||||
}
|
|
||||||
fmt.Printf("%d 个家庭组\n", familyCount)
|
|
||||||
|
|
||||||
// ── Step 9: 多设备拆分 ──
|
|
||||||
fmt.Print("[9/10] 多设备拆分... ")
|
|
||||||
splitCount := 0
|
|
||||||
|
|
||||||
err = dstDB.Transaction(func(tx *gorm.DB) error {
|
|
||||||
for _, s := range splits {
|
|
||||||
// 1. 创建新用户
|
|
||||||
newUser := User{
|
|
||||||
Password: "",
|
|
||||||
Algo: "default",
|
|
||||||
Salt: "default",
|
|
||||||
Enable: boolPtr(true),
|
|
||||||
IsAdmin: boolPtr(false),
|
|
||||||
OnlyFirstPurchase: boolPtr(true),
|
|
||||||
EnableBalanceNotify: boolPtr(false),
|
|
||||||
EnableLoginNotify: boolPtr(false),
|
|
||||||
EnableSubscribeNotify: boolPtr(false),
|
|
||||||
EnableTradeNotify: boolPtr(false),
|
|
||||||
CreatedAt: s.Device.CreatedAt,
|
|
||||||
UpdatedAt: now,
|
|
||||||
}
|
|
||||||
if err := tx.Create(&newUser).Error; err != nil {
|
|
||||||
return fmt.Errorf("创建拆分用户失败(owner=%d, device=%d): %w", s.OwnerUID, s.Device.Id, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 转移设备到新用户
|
|
||||||
if err := tx.Model(&Device{}).Where("id = ?", s.Device.Id).
|
|
||||||
Update("user_id", newUser.Id).Error; err != nil {
|
|
||||||
return fmt.Errorf("转移设备失败: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 转移 auth_method 到新用户
|
|
||||||
if s.AuthMethod != nil {
|
|
||||||
if err := tx.Model(&AuthMethod{}).Where("id = ?", s.AuthMethod.Id).
|
|
||||||
Update("user_id", newUser.Id).Error; err != nil {
|
|
||||||
return fmt.Errorf("转移 auth_method 失败: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 仅仅当原用户有邮箱时,才尝试将其加入家庭组(无邮箱的仅拆分为独立用户)
|
|
||||||
if hasEmailByNewUID[s.OwnerUID] {
|
|
||||||
// 4. 查找原用户的家庭组(如果不存在则创建,虽然理论上 Step 8 已经为多设备用户创建了)
|
|
||||||
var family UserFamily
|
|
||||||
if err := tx.Where("owner_user_id = ?", s.OwnerUID).First(&family).Error; err != nil {
|
|
||||||
if err == gorm.ErrRecordNotFound {
|
|
||||||
// 补救措施:为该用户创建一个家庭组
|
|
||||||
family = UserFamily{
|
|
||||||
OwnerUserId: s.OwnerUID,
|
|
||||||
MaxMembers: defaultFamilyMaxSize,
|
|
||||||
Status: 1,
|
|
||||||
CreatedAt: now,
|
|
||||||
UpdatedAt: now,
|
|
||||||
}
|
|
||||||
if err := tx.Create(&family).Error; err != nil {
|
|
||||||
return fmt.Errorf("创建家庭组补救失败(owner=%d): %w", s.OwnerUID, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建家主成员
|
|
||||||
ownerMember := UserFamilyMember{
|
|
||||||
FamilyId: family.Id,
|
|
||||||
UserId: s.OwnerUID,
|
|
||||||
Role: familyRoleOwner,
|
|
||||||
Status: 1,
|
|
||||||
JoinSource: "migration_split_recovery",
|
|
||||||
JoinedAt: now,
|
|
||||||
CreatedAt: now,
|
|
||||||
UpdatedAt: now,
|
|
||||||
}
|
|
||||||
if err := tx.Create(&ownerMember).Error; err != nil {
|
|
||||||
return fmt.Errorf("创建家主成员补救失败(owner=%d): %w", s.OwnerUID, err)
|
|
||||||
}
|
|
||||||
familyCount++ // 更新计数器
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("查找家庭组失败(owner=%d): %w", s.OwnerUID, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. 加入家庭组
|
|
||||||
member := UserFamilyMember{
|
|
||||||
FamilyId: family.Id,
|
|
||||||
UserId: newUser.Id,
|
|
||||||
Role: familyRoleMember,
|
|
||||||
Status: 1,
|
|
||||||
JoinSource: "migration_split",
|
|
||||||
JoinedAt: s.Device.CreatedAt,
|
|
||||||
CreatedAt: now,
|
|
||||||
UpdatedAt: now,
|
|
||||||
}
|
|
||||||
if err := tx.Create(&member).Error; err != nil {
|
|
||||||
return fmt.Errorf("添加家庭成员失败: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
splitCount++
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("设备拆分失败: %v", err)
|
|
||||||
}
|
|
||||||
fmt.Printf("%d 个设备\n", splitCount)
|
|
||||||
|
|
||||||
// ── Step 10: 修复各表 AUTO_INCREMENT ──
|
|
||||||
// 确保迁移后新写入的记录不会触发主键冲突
|
|
||||||
fmt.Print("[10/10] 修复 AUTO_INCREMENT... ")
|
|
||||||
type autoIncTable struct {
|
|
||||||
table string // 表名(不含反引号)
|
|
||||||
quoted string // SQL 中使用的表名(含反引号)
|
|
||||||
}
|
|
||||||
autoIncTables := []autoIncTable{
|
|
||||||
{"user", "`user`"},
|
|
||||||
{"user_auth_methods", "`user_auth_methods`"},
|
|
||||||
{"user_device", "`user_device`"},
|
|
||||||
{"order", "`order`"},
|
|
||||||
{"user_subscribe", "`user_subscribe`"},
|
|
||||||
{"apple_iap_transactions", "`apple_iap_transactions`"},
|
|
||||||
{"user_family", "`user_family`"},
|
|
||||||
{"user_family_member", "`user_family_member`"},
|
|
||||||
}
|
|
||||||
for _, t := range autoIncTables {
|
|
||||||
var maxID int64
|
|
||||||
dstDB.Raw(fmt.Sprintf("SELECT COALESCE(MAX(id), 0) FROM %s", t.quoted)).Scan(&maxID)
|
|
||||||
nextID := maxID + 1
|
|
||||||
if err := dstDB.Exec(fmt.Sprintf("ALTER TABLE %s AUTO_INCREMENT = %d", t.quoted, nextID)).Error; err != nil {
|
|
||||||
log.Printf(" 警告: 修复 %s AUTO_INCREMENT 失败: %v", t.table, err)
|
|
||||||
} else {
|
|
||||||
fmt.Printf("\n %-30s MAX(id)=%-8d AUTO_INCREMENT→%d", t.table, maxID, nextID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Println("\nOK")
|
|
||||||
|
|
||||||
// ── 结果 ──
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println("=== 迁移完成 ===")
|
|
||||||
fmt.Printf(" 用户: %d (原始) + %d (拆分) = %d\n", len(users), splitCount, len(users)+splitCount)
|
|
||||||
fmt.Printf(" 家庭组: %d\n", familyCount)
|
|
||||||
fmt.Printf(" 家庭成员: %d (家主) + %d (拆分) = %d\n", familyCount, splitCount, familyCount+splitCount)
|
|
||||||
fmt.Printf(" 订单: %d\n", len(orders))
|
|
||||||
fmt.Printf(" 订阅: %d\n", len(subscribes))
|
|
||||||
}
|
|
||||||
|
|
||||||
func boolPtr(b bool) *bool { return &b }
|
|
||||||
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,787 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hibiken/asynq"
|
|
||||||
"github.com/perfect-panel/server/internal/config"
|
|
||||||
authlogic "github.com/perfect-panel/server/internal/logic/auth"
|
|
||||||
modelLog "github.com/perfect-panel/server/internal/model/log"
|
|
||||||
modelOrder "github.com/perfect-panel/server/internal/model/order"
|
|
||||||
modelSubscribe "github.com/perfect-panel/server/internal/model/subscribe"
|
|
||||||
modelUser "github.com/perfect-panel/server/internal/model/user"
|
|
||||||
"github.com/perfect-panel/server/internal/svc"
|
|
||||||
"github.com/perfect-panel/server/pkg/conf"
|
|
||||||
"github.com/perfect-panel/server/pkg/orm"
|
|
||||||
"github.com/perfect-panel/server/pkg/uuidx"
|
|
||||||
orderLogic "github.com/perfect-panel/server/queue/logic/order"
|
|
||||||
queueTypes "github.com/perfect-panel/server/queue/types"
|
|
||||||
"github.com/redis/go-redis/v9"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
const marker = "codex-replay-business-bugs"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
var (
|
|
||||||
configPath = flag.String("config", "etc/ppanel.yaml", "ppanel config path for test server DB/Redis")
|
|
||||||
dsn = flag.String("dsn", "", "optional MySQL DSN override: user:pass@tcp(host:3306)/db?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai")
|
|
||||||
writeDB = flag.Bool("write-db", false, "create isolated test rows and execute activation replay against the configured test DB")
|
|
||||||
force = flag.Bool("force", false, "allow -write-db even when the config name does not clearly look like test/dev/staging")
|
|
||||||
keep = flag.Bool("keep", false, "keep replay rows for manual inspection")
|
|
||||||
cleanupOnly = flag.Bool("cleanup-only", false, "delete leftover replay rows by marker and exit")
|
|
||||||
skipCodeTests = flag.Bool("skip-code-tests", false, "skip go test checks")
|
|
||||||
)
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
started := time.Now()
|
|
||||||
fmt.Println("== replay business bug tests ==")
|
|
||||||
fmt.Printf("marker: %s\n", marker)
|
|
||||||
|
|
||||||
if !*skipCodeTests {
|
|
||||||
must(runCodeTests())
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := loadConfig(*configPath, *dsn)
|
|
||||||
runEmailTrialAssertions(cfg)
|
|
||||||
|
|
||||||
if *cleanupOnly {
|
|
||||||
env := mustNewReplayEnv(ctx, cfg)
|
|
||||||
env.cleanupByMarker(ctx)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !*writeDB {
|
|
||||||
fmt.Println("\nDB replay skipped. Add -write-db to create isolated rows in the TEST database and run activation flows.")
|
|
||||||
fmt.Println("Example:")
|
|
||||||
fmt.Printf(" go run scripts/replay_business_bugs.go -config %s -write-db\n", *configPath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if looksLikeProduction(cfg) && !*force {
|
|
||||||
fatalf("refusing to write DB because config does not look like a test environment: db=%s host=%s; add -force only on the test server", cfg.MySQL.Dbname, cfg.Site.Host)
|
|
||||||
}
|
|
||||||
|
|
||||||
env := mustNewReplayEnv(ctx, cfg)
|
|
||||||
if !*keep {
|
|
||||||
defer env.cleanup(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
must(env.replaySingleSubscription(ctx))
|
|
||||||
must(env.replayInviteRulesMatrix(ctx))
|
|
||||||
must(env.replayFamilyInviteGiftToOwner(ctx))
|
|
||||||
|
|
||||||
fmt.Printf("\nPASS all replay checks in %s\n", time.Since(started).Round(time.Millisecond))
|
|
||||||
if *keep {
|
|
||||||
fmt.Println("Replay rows kept for inspection. Delete rows with remark/name/order_no containing:", marker)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func runCodeTests() error {
|
|
||||||
fmt.Println("\n-- code-level tests --")
|
|
||||||
args := []string{"test",
|
|
||||||
"./internal/logic/auth",
|
|
||||||
"./internal/logic/common",
|
|
||||||
"./internal/logic/public/order",
|
|
||||||
"./queue/logic/order",
|
|
||||||
}
|
|
||||||
cmd := exec.Command("go", args...)
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
return fmt.Errorf("go test failed: %w", err)
|
|
||||||
}
|
|
||||||
fmt.Println("PASS code-level tests")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadConfig(path, dsn string) config.Config {
|
|
||||||
var cfg config.Config
|
|
||||||
conf.MustLoad(path, &cfg)
|
|
||||||
if dsn != "" {
|
|
||||||
cfg.MySQL = parseDSN(dsn)
|
|
||||||
}
|
|
||||||
return cfg
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseDSN(dsn string) orm.Config {
|
|
||||||
cfg := orm.ParseDSN(dsn)
|
|
||||||
if cfg == nil {
|
|
||||||
fatalf("invalid dsn")
|
|
||||||
}
|
|
||||||
return *cfg
|
|
||||||
}
|
|
||||||
|
|
||||||
func runEmailTrialAssertions(cfg config.Config) {
|
|
||||||
fmt.Println("\n-- bug1 email trial whitelist assertions --")
|
|
||||||
cfg.Register.EnableTrial = true
|
|
||||||
cfg.Register.EnableTrialEmailWhitelist = true
|
|
||||||
if cfg.Register.TrialEmailDomainWhitelist == "" {
|
|
||||||
cfg.Register.TrialEmailDomainWhitelist = "gmail.com,163.com"
|
|
||||||
}
|
|
||||||
|
|
||||||
cases := []struct {
|
|
||||||
email string
|
|
||||||
want bool
|
|
||||||
}{
|
|
||||||
{"1.2.3.4xxx@gmaial.com", false},
|
|
||||||
{"a.b.c@gmail.com", false},
|
|
||||||
{"user+tag@gmail.com", false},
|
|
||||||
{"user@fake.gmail.com", false},
|
|
||||||
{"normaluser@gmail.com", true},
|
|
||||||
}
|
|
||||||
for _, tc := range cases {
|
|
||||||
got := authlogic.ShouldGrantTrialForEmail(cfg.Register, tc.email)
|
|
||||||
if got != tc.want {
|
|
||||||
fatalf("email trial assertion failed: email=%s got=%v want=%v", tc.email, got, tc.want)
|
|
||||||
}
|
|
||||||
fmt.Printf("PASS %-32s grant=%v\n", tc.email, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type replayEnv struct {
|
|
||||||
db *gorm.DB
|
|
||||||
rds *redis.Client
|
|
||||||
cfg config.Config
|
|
||||||
svcCtx *svc.ServiceContext
|
|
||||||
ids struct {
|
|
||||||
users []int64
|
|
||||||
subscribes []int64
|
|
||||||
plans []int64
|
|
||||||
orders []int64
|
|
||||||
logs []int64
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func mustNewReplayEnv(ctx context.Context, cfg config.Config) *replayEnv {
|
|
||||||
fmt.Println("\n-- connecting test DB/Redis --")
|
|
||||||
db, err := orm.ConnectMysql(orm.Mysql{Config: cfg.MySQL})
|
|
||||||
must(err)
|
|
||||||
rds := redis.NewClient(&redis.Options{
|
|
||||||
Addr: cfg.Redis.Host,
|
|
||||||
Password: cfg.Redis.Pass,
|
|
||||||
DB: cfg.Redis.DB,
|
|
||||||
PoolSize: cfg.Redis.PoolSize,
|
|
||||||
MinIdleConns: cfg.Redis.MinIdleConns,
|
|
||||||
})
|
|
||||||
must(rds.Ping(ctx).Err())
|
|
||||||
|
|
||||||
svcCtx := &svc.ServiceContext{
|
|
||||||
DB: db,
|
|
||||||
Redis: rds,
|
|
||||||
Config: cfg,
|
|
||||||
UserModel: modelUser.NewModel(db, rds),
|
|
||||||
OrderModel: modelOrder.NewModel(db, rds),
|
|
||||||
SubscribeModel: modelSubscribe.NewModel(db, rds),
|
|
||||||
LogModel: modelLog.NewModel(db),
|
|
||||||
}
|
|
||||||
fmt.Printf("connected: mysql=%s/%s redis=%s\n", cfg.MySQL.Addr, cfg.MySQL.Dbname, cfg.Redis.Host)
|
|
||||||
return &replayEnv{db: db, rds: rds, cfg: cfg, svcCtx: svcCtx}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *replayEnv) replaySingleSubscription(ctx context.Context) error {
|
|
||||||
fmt.Println("\n-- bug2 replay: paid purchase must reuse existing subscription --")
|
|
||||||
planA, planB, err := e.createPlans(ctx, "bug2")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
owner, err := e.createUser(ctx, "bug2-owner", 0, 0)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
existing, err := e.createUserSubscribe(ctx, owner.Id, 0, planA.Id, time.Now().Add(7*24*time.Hour))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
order, err := e.createPaidOrder(ctx, owner.Id, owner.Id, planB.Id, true, "bug2")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
payload, _ := json.Marshal(queueTypes.ForthwithActivateOrderPayload{OrderNo: order.OrderNo})
|
|
||||||
worker := orderLogic.NewActivateOrderLogic(e.svcCtx)
|
|
||||||
if err = worker.ProcessTask(ctx, asynq.NewTask(queueTypes.ForthwithActivateOrder, payload)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = worker.ProcessTask(ctx, asynq.NewTask(queueTypes.ForthwithActivateOrder, payload)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var rows []modelUser.Subscribe
|
|
||||||
if err = e.db.WithContext(ctx).
|
|
||||||
Where("user_id = ? AND token <> '' AND status IN ?", owner.Id, []int{0, 1, 2, 3, 4}).
|
|
||||||
Order("id ASC").
|
|
||||||
Find(&rows).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(rows) != 1 {
|
|
||||||
return fmt.Errorf("bug2 failed: expected one visible subscription, got %d", len(rows))
|
|
||||||
}
|
|
||||||
if rows[0].Id != existing.Id {
|
|
||||||
return fmt.Errorf("bug2 failed: expected original subscription id=%d to be reused, got id=%d", existing.Id, rows[0].Id)
|
|
||||||
}
|
|
||||||
if rows[0].SubscribeId != planB.Id || rows[0].OrderId != order.Id {
|
|
||||||
return fmt.Errorf("bug2 failed: reused subscription not updated, subscribe_id=%d order_id=%d", rows[0].SubscribeId, rows[0].OrderId)
|
|
||||||
}
|
|
||||||
fmt.Printf("PASS user=%d user_subscribe=%d plan %d -> %d order=%s\n", owner.Id, rows[0].Id, planA.Id, planB.Id, order.OrderNo)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *replayEnv) replayInviteGiftDays(ctx context.Context) error {
|
|
||||||
fmt.Println("\n-- bug3 replay: commission=0 invite should grant gift days to both users --")
|
|
||||||
giftDays := e.cfg.Invite.GiftDays
|
|
||||||
if giftDays <= 0 {
|
|
||||||
giftDays = 2
|
|
||||||
e.svcCtx.Config.Invite.GiftDays = giftDays
|
|
||||||
}
|
|
||||||
e.svcCtx.Config.Invite.ReferralPercentage = 0
|
|
||||||
e.svcCtx.Config.Invite.OnlyFirstPurchase = true
|
|
||||||
|
|
||||||
planA, _, err := e.createPlans(ctx, "bug3")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
referer, err := e.createUser(ctx, "bug3-referer", 0, 0)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
referee, err := e.createUser(ctx, "bug3-referee", referer.Id, 0)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
baseExpire := time.Now().Add(10 * 24 * time.Hour).Truncate(time.Millisecond)
|
|
||||||
refererSub, err := e.createUserSubscribe(ctx, referer.Id, 0, planA.Id, baseExpire)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
refereeSub, err := e.createUserSubscribe(ctx, referee.Id, 0, planA.Id, baseExpire)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
order, err := e.createPaidOrder(ctx, referee.Id, referee.Id, planA.Id, true, "bug3")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
payload, _ := json.Marshal(queueTypes.ForthwithActivateOrderPayload{OrderNo: order.OrderNo})
|
|
||||||
worker := orderLogic.NewActivateOrderLogic(e.svcCtx)
|
|
||||||
if err = worker.ProcessTask(ctx, asynq.NewTask(queueTypes.ForthwithActivateOrder, payload)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = e.waitForGiftLogs(ctx, order.OrderNo, referer.Id, referee.Id); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var refererAfter, refereeAfter modelUser.Subscribe
|
|
||||||
if err = e.db.WithContext(ctx).First(&refererAfter, refererSub.Id).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = e.db.WithContext(ctx).First(&refereeAfter, refereeSub.Id).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
minRefererExpire := baseExpire.Add(time.Duration(giftDays) * 24 * time.Hour)
|
|
||||||
if refererAfter.ExpireTime.Before(minRefererExpire.Add(-time.Second)) {
|
|
||||||
return fmt.Errorf("bug3 failed: referer expire not increased by gift days, got=%s want>=%s", refererAfter.ExpireTime, minRefererExpire)
|
|
||||||
}
|
|
||||||
if !refereeAfter.ExpireTime.After(baseExpire) {
|
|
||||||
return fmt.Errorf("bug3 failed: referee expire did not increase, got=%s base=%s", refereeAfter.ExpireTime, baseExpire)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Idempotency: repeat the same order task and make sure gift logs are still one per user.
|
|
||||||
if err = worker.ProcessTask(ctx, asynq.NewTask(queueTypes.ForthwithActivateOrder, payload)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var giftCount int64
|
|
||||||
if err = e.db.WithContext(ctx).Model(&modelLog.SystemLog{}).
|
|
||||||
Where("type = ? AND object_id IN ? AND content LIKE ?", modelLog.TypeGift.Uint8(), []int64{referer.Id, referee.Id}, "%"+order.OrderNo+"%").
|
|
||||||
Count(&giftCount).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if giftCount != 2 {
|
|
||||||
return fmt.Errorf("bug3 failed: expected 2 gift logs after duplicate task, got %d", giftCount)
|
|
||||||
}
|
|
||||||
fmt.Printf("PASS referer=%d referee=%d order=%s gift_days=%d logs=%d\n", referer.Id, referee.Id, order.OrderNo, giftDays, giftCount)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *replayEnv) replayInviteRulesMatrix(ctx context.Context) error {
|
|
||||||
fmt.Println("\n-- bug3 replay matrix: invite gift/commission rules --")
|
|
||||||
giftDays := e.cfg.Invite.GiftDays
|
|
||||||
if giftDays <= 0 {
|
|
||||||
giftDays = 2
|
|
||||||
}
|
|
||||||
e.svcCtx.Config.Invite.GiftDays = giftDays
|
|
||||||
e.svcCtx.Config.Invite.OnlyFirstPurchase = false
|
|
||||||
|
|
||||||
planA, _, err := e.createPlans(ctx, "bug3-matrix")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cases := []struct {
|
|
||||||
name string
|
|
||||||
hasReferer bool
|
|
||||||
globalReferralPct int64
|
|
||||||
isNewOrder bool
|
|
||||||
wantGiftLogs int64
|
|
||||||
wantCommissionLogs int64
|
|
||||||
wantCommission int64
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "no invite relation first order no gift",
|
|
||||||
hasReferer: false,
|
|
||||||
isNewOrder: true,
|
|
||||||
wantGiftLogs: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "ordinary invite commission 0 first order gifts both",
|
|
||||||
hasReferer: true,
|
|
||||||
isNewOrder: true,
|
|
||||||
wantGiftLogs: 2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "ordinary invite commission 0 non-first order no gift",
|
|
||||||
hasReferer: true,
|
|
||||||
isNewOrder: false,
|
|
||||||
wantGiftLogs: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "channel commission positive first order gifts referee only",
|
|
||||||
hasReferer: true,
|
|
||||||
globalReferralPct: 10,
|
|
||||||
isNewOrder: true,
|
|
||||||
wantGiftLogs: 1,
|
|
||||||
wantCommissionLogs: 1,
|
|
||||||
wantCommission: 59,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "channel commission positive non-first order commission only",
|
|
||||||
hasReferer: true,
|
|
||||||
globalReferralPct: 10,
|
|
||||||
isNewOrder: false,
|
|
||||||
wantGiftLogs: 0,
|
|
||||||
wantCommissionLogs: 1,
|
|
||||||
wantCommission: 59,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for idx, tc := range cases {
|
|
||||||
e.svcCtx.Config.Invite.ReferralPercentage = tc.globalReferralPct
|
|
||||||
scope := fmt.Sprintf("bug3-rule-%d", idx+1)
|
|
||||||
var referer *modelUser.User
|
|
||||||
if tc.hasReferer {
|
|
||||||
referer, err = e.createUser(ctx, scope+"-referer", 0, 0)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err = e.createUserSubscribe(ctx, referer.Id, 0, planA.Id, time.Now().Add(10*24*time.Hour)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var refererID int64
|
|
||||||
if referer != nil {
|
|
||||||
refererID = referer.Id
|
|
||||||
}
|
|
||||||
referee, err := e.createUser(ctx, scope+"-referee", refererID, 0)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err = e.createUserSubscribe(ctx, referee.Id, 0, planA.Id, time.Now().Add(10*24*time.Hour)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
order, err := e.createPaidOrder(ctx, referee.Id, referee.Id, planA.Id, tc.isNewOrder, scope)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = e.activateOrderTwice(ctx, order.OrderNo); err != nil {
|
|
||||||
return fmt.Errorf("%s: %w", tc.name, err)
|
|
||||||
}
|
|
||||||
if err = e.waitForLogCounts(ctx, order.OrderNo, tc.wantGiftLogs, tc.wantCommissionLogs); err != nil {
|
|
||||||
return fmt.Errorf("%s: %w", tc.name, err)
|
|
||||||
}
|
|
||||||
giftLogs, err := e.countLogs(ctx, modelLog.TypeGift.Uint8(), order.OrderNo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
commissionLogs, err := e.countLogs(ctx, modelLog.TypeCommission.Uint8(), order.OrderNo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if giftLogs != tc.wantGiftLogs {
|
|
||||||
return fmt.Errorf("%s: expected gift logs=%d got=%d", tc.name, tc.wantGiftLogs, giftLogs)
|
|
||||||
}
|
|
||||||
if commissionLogs != tc.wantCommissionLogs {
|
|
||||||
return fmt.Errorf("%s: expected commission logs=%d got=%d", tc.name, tc.wantCommissionLogs, commissionLogs)
|
|
||||||
}
|
|
||||||
if referer != nil && tc.wantCommission > 0 {
|
|
||||||
var after modelUser.User
|
|
||||||
if err = e.db.WithContext(ctx).First(&after, referer.Id).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if after.Commission != tc.wantCommission {
|
|
||||||
return fmt.Errorf("%s: expected referer commission=%d got=%d", tc.name, tc.wantCommission, after.Commission)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Printf("PASS %-58s gifts=%d commission_logs=%d\n", tc.name, giftLogs, commissionLogs)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *replayEnv) replayFamilyInviteGiftToOwner(ctx context.Context) error {
|
|
||||||
fmt.Println("\n-- bug3 family replay: member purchase gift days go to owner --")
|
|
||||||
giftDays := e.cfg.Invite.GiftDays
|
|
||||||
if giftDays <= 0 {
|
|
||||||
giftDays = 2
|
|
||||||
}
|
|
||||||
e.svcCtx.Config.Invite.GiftDays = giftDays
|
|
||||||
e.svcCtx.Config.Invite.ReferralPercentage = 0
|
|
||||||
e.svcCtx.Config.Invite.OnlyFirstPurchase = false
|
|
||||||
|
|
||||||
planA, _, err := e.createPlans(ctx, "bug3-family")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
referer, err := e.createUser(ctx, "bug3-family-referer", 0, 0)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
owner, err := e.createUser(ctx, "bug3-family-owner", 0, 0)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
member, err := e.createUser(ctx, "bug3-family-member", referer.Id, 0)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = e.createFamily(ctx, owner.Id, member.Id); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
baseExpire := time.Now().Add(10 * 24 * time.Hour).Truncate(time.Millisecond)
|
|
||||||
ownerSub, err := e.createUserSubscribe(ctx, owner.Id, 0, planA.Id, baseExpire)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
memberSub, err := e.createUserSubscribe(ctx, member.Id, 0, planA.Id, baseExpire)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
refererSub, err := e.createUserSubscribe(ctx, referer.Id, 0, planA.Id, baseExpire)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
order, err := e.createPaidOrder(ctx, member.Id, owner.Id, planA.Id, true, "bug3-family")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = e.activateOrderTwice(ctx, order.OrderNo); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = e.waitForLogCounts(ctx, order.OrderNo, 2, 0); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var ownerAfter, memberAfter, refererAfter modelUser.Subscribe
|
|
||||||
if err = e.db.WithContext(ctx).First(&ownerAfter, ownerSub.Id).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = e.db.WithContext(ctx).First(&memberAfter, memberSub.Id).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = e.db.WithContext(ctx).First(&refererAfter, refererSub.Id).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !ownerAfter.ExpireTime.After(baseExpire) {
|
|
||||||
return fmt.Errorf("family gift failed: owner expire not increased")
|
|
||||||
}
|
|
||||||
if !refererAfter.ExpireTime.After(baseExpire) {
|
|
||||||
return fmt.Errorf("family gift failed: referer expire not increased")
|
|
||||||
}
|
|
||||||
if memberAfter.ExpireTime.After(baseExpire.Add(time.Second)) {
|
|
||||||
return fmt.Errorf("family gift failed: member subscription should not receive gift days")
|
|
||||||
}
|
|
||||||
|
|
||||||
var memberGiftLogs int64
|
|
||||||
if err = e.db.WithContext(ctx).Model(&modelLog.SystemLog{}).
|
|
||||||
Where("type = ? AND object_id = ? AND content LIKE ?", modelLog.TypeGift.Uint8(), member.Id, "%"+order.OrderNo+"%").
|
|
||||||
Count(&memberGiftLogs).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if memberGiftLogs != 0 {
|
|
||||||
return fmt.Errorf("family gift failed: expected no member gift logs, got %d", memberGiftLogs)
|
|
||||||
}
|
|
||||||
fmt.Printf("PASS family member purchase gift target owner owner=%d member=%d referer=%d gift_days=%d\n", owner.Id, member.Id, referer.Id, giftDays)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *replayEnv) activateOrderTwice(ctx context.Context, orderNo string) error {
|
|
||||||
payload, _ := json.Marshal(queueTypes.ForthwithActivateOrderPayload{OrderNo: orderNo})
|
|
||||||
worker := orderLogic.NewActivateOrderLogic(e.svcCtx)
|
|
||||||
if err := worker.ProcessTask(ctx, asynq.NewTask(queueTypes.ForthwithActivateOrder, payload)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return worker.ProcessTask(ctx, asynq.NewTask(queueTypes.ForthwithActivateOrder, payload))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *replayEnv) waitForLogCounts(ctx context.Context, orderNo string, wantGiftLogs, wantCommissionLogs int64) error {
|
|
||||||
deadline := time.Now().Add(8 * time.Second)
|
|
||||||
for {
|
|
||||||
giftLogs, err := e.countLogs(ctx, modelLog.TypeGift.Uint8(), orderNo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
commissionLogs, err := e.countLogs(ctx, modelLog.TypeCommission.Uint8(), orderNo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if giftLogs >= wantGiftLogs && commissionLogs >= wantCommissionLogs {
|
|
||||||
if wantGiftLogs == 0 && wantCommissionLogs == 0 {
|
|
||||||
time.Sleep(500 * time.Millisecond)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if time.Now().After(deadline) {
|
|
||||||
return fmt.Errorf("timed out waiting for logs: order=%s gift=%d/%d commission=%d/%d", orderNo, giftLogs, wantGiftLogs, commissionLogs, wantCommissionLogs)
|
|
||||||
}
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *replayEnv) countLogs(ctx context.Context, logType uint8, orderNo string) (int64, error) {
|
|
||||||
var count int64
|
|
||||||
err := e.db.WithContext(ctx).Model(&modelLog.SystemLog{}).
|
|
||||||
Where("type = ? AND content LIKE ?", logType, "%"+orderNo+"%").
|
|
||||||
Count(&count).Error
|
|
||||||
return count, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *replayEnv) waitForGiftLogs(ctx context.Context, orderNo string, userIDs ...int64) error {
|
|
||||||
deadline := time.Now().Add(5 * time.Second)
|
|
||||||
for time.Now().Before(deadline) {
|
|
||||||
var count int64
|
|
||||||
if err := e.db.WithContext(ctx).Model(&modelLog.SystemLog{}).
|
|
||||||
Where("type = ? AND object_id IN ? AND content LIKE ?", modelLog.TypeGift.Uint8(), userIDs, "%"+orderNo+"%").
|
|
||||||
Count(&count).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if count == int64(len(userIDs)) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("timed out waiting for gift logs for order=%s", orderNo)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *replayEnv) createPlans(ctx context.Context, scope string) (*modelSubscribe.Subscribe, *modelSubscribe.Subscribe, error) {
|
|
||||||
a := &modelSubscribe.Subscribe{
|
|
||||||
Name: marker + "-" + scope + "-A",
|
|
||||||
Language: "en",
|
|
||||||
UnitPrice: 599,
|
|
||||||
UnitTime: "Month",
|
|
||||||
Traffic: 1024 * 1024 * 1024,
|
|
||||||
Inventory: -1,
|
|
||||||
Quota: 0,
|
|
||||||
NodeGroupIds: modelSubscribe.JSONInt64Slice{},
|
|
||||||
}
|
|
||||||
b := &modelSubscribe.Subscribe{
|
|
||||||
Name: marker + "-" + scope + "-B",
|
|
||||||
Language: "en",
|
|
||||||
UnitPrice: 699,
|
|
||||||
UnitTime: "Month",
|
|
||||||
Traffic: 2 * 1024 * 1024 * 1024,
|
|
||||||
Inventory: -1,
|
|
||||||
Quota: 0,
|
|
||||||
NodeGroupIds: modelSubscribe.JSONInt64Slice{},
|
|
||||||
}
|
|
||||||
if err := e.db.WithContext(ctx).Create(a).Error; err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if err := e.db.WithContext(ctx).Create(b).Error; err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
e.ids.plans = append(e.ids.plans, a.Id, b.Id)
|
|
||||||
return a, b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *replayEnv) createUser(ctx context.Context, scope string, refererID int64, referralPercentage uint8) (*modelUser.User, error) {
|
|
||||||
onlyFirst := true
|
|
||||||
enable := true
|
|
||||||
isAdmin := false
|
|
||||||
u := &modelUser.User{
|
|
||||||
Password: marker,
|
|
||||||
Algo: "default",
|
|
||||||
Salt: "default",
|
|
||||||
RefererId: refererID,
|
|
||||||
ReferralPercentage: referralPercentage,
|
|
||||||
OnlyFirstPurchase: &onlyFirst,
|
|
||||||
Enable: &enable,
|
|
||||||
IsAdmin: &isAdmin,
|
|
||||||
EnableBalanceNotify: &enable,
|
|
||||||
EnableLoginNotify: &enable,
|
|
||||||
EnableSubscribeNotify: &enable,
|
|
||||||
EnableTradeNotify: &enable,
|
|
||||||
Remark: marker + "-" + scope,
|
|
||||||
}
|
|
||||||
if err := e.db.WithContext(ctx).Create(u).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
u.ReferCode = uuidx.UserInviteCode(u.Id)
|
|
||||||
if err := e.db.WithContext(ctx).Model(&modelUser.User{}).Where("id = ?", u.Id).Update("refer_code", u.ReferCode).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
e.ids.users = append(e.ids.users, u.Id)
|
|
||||||
return u, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *replayEnv) createFamily(ctx context.Context, ownerID, memberID int64) error {
|
|
||||||
now := time.Now()
|
|
||||||
family := &modelUser.UserFamily{
|
|
||||||
OwnerUserId: ownerID,
|
|
||||||
MaxMembers: modelUser.DefaultFamilyMaxSize,
|
|
||||||
Status: modelUser.FamilyStatusActive,
|
|
||||||
}
|
|
||||||
if err := e.db.WithContext(ctx).Create(family).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
members := []modelUser.UserFamilyMember{
|
|
||||||
{
|
|
||||||
FamilyId: family.Id,
|
|
||||||
UserId: ownerID,
|
|
||||||
Role: modelUser.FamilyRoleOwner,
|
|
||||||
Status: modelUser.FamilyMemberActive,
|
|
||||||
JoinSource: marker,
|
|
||||||
JoinedAt: now,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
FamilyId: family.Id,
|
|
||||||
UserId: memberID,
|
|
||||||
Role: modelUser.FamilyRoleMember,
|
|
||||||
Status: modelUser.FamilyMemberActive,
|
|
||||||
JoinSource: marker,
|
|
||||||
JoinedAt: now,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return e.db.WithContext(ctx).Create(&members).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *replayEnv) createUserSubscribe(ctx context.Context, userID, orderID, planID int64, expire time.Time) (*modelUser.Subscribe, error) {
|
|
||||||
groupLocked := false
|
|
||||||
sub := &modelUser.Subscribe{
|
|
||||||
UserId: userID,
|
|
||||||
OrderId: orderID,
|
|
||||||
SubscribeId: planID,
|
|
||||||
GroupLocked: &groupLocked,
|
|
||||||
StartTime: time.Now().Add(-time.Hour),
|
|
||||||
ExpireTime: expire,
|
|
||||||
Traffic: 1024 * 1024 * 1024,
|
|
||||||
Token: marker + "-" + uuidx.NewUUID().String(),
|
|
||||||
UUID: uuidx.NewUUID().String(),
|
|
||||||
Status: 1,
|
|
||||||
Note: marker,
|
|
||||||
}
|
|
||||||
if err := e.db.WithContext(ctx).Create(sub).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
e.ids.subscribes = append(e.ids.subscribes, sub.Id)
|
|
||||||
return sub, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *replayEnv) createPaidOrder(ctx context.Context, userID, subscriptionUserID, planID int64, isNew bool, scope string) (*modelOrder.Order, error) {
|
|
||||||
orderNo := fmt.Sprintf("%s-%s-%d", marker, scope, time.Now().UnixNano())
|
|
||||||
order := &modelOrder.Order{
|
|
||||||
UserId: userID,
|
|
||||||
SubscriptionUserId: subscriptionUserID,
|
|
||||||
OrderNo: orderNo,
|
|
||||||
Type: 1,
|
|
||||||
Quantity: 1,
|
|
||||||
Price: 599,
|
|
||||||
Amount: 599,
|
|
||||||
Status: 2,
|
|
||||||
SubscribeId: planID,
|
|
||||||
Method: "replay",
|
|
||||||
IsNew: isNew,
|
|
||||||
}
|
|
||||||
if err := e.db.WithContext(ctx).Create(order).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
e.ids.orders = append(e.ids.orders, order.Id)
|
|
||||||
return order, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *replayEnv) cleanup(ctx context.Context) {
|
|
||||||
fmt.Println("\n-- cleanup replay rows --")
|
|
||||||
e.cleanupByMarker(ctx)
|
|
||||||
if len(e.ids.subscribes) > 0 {
|
|
||||||
_ = e.db.WithContext(ctx).Where("id IN ?", e.ids.subscribes).Delete(&modelUser.Subscribe{}).Error
|
|
||||||
}
|
|
||||||
if len(e.ids.orders) > 0 {
|
|
||||||
_ = e.db.WithContext(ctx).Where("id IN ?", e.ids.orders).Delete(&modelOrder.Order{}).Error
|
|
||||||
}
|
|
||||||
if len(e.ids.plans) > 0 {
|
|
||||||
_ = e.db.WithContext(ctx).Where("id IN ?", e.ids.plans).Delete(&modelSubscribe.Subscribe{}).Error
|
|
||||||
}
|
|
||||||
if len(e.ids.users) > 0 {
|
|
||||||
_ = e.db.WithContext(ctx).Unscoped().Where("id IN ?", e.ids.users).Delete(&modelUser.User{}).Error
|
|
||||||
}
|
|
||||||
fmt.Println("cleanup done")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *replayEnv) cleanupByMarker(ctx context.Context) {
|
|
||||||
_ = e.db.WithContext(ctx).
|
|
||||||
Where("join_source = ?", marker).
|
|
||||||
Delete(&modelUser.UserFamilyMember{}).Error
|
|
||||||
_ = e.db.WithContext(ctx).
|
|
||||||
Where("owner_user_id IN (SELECT id FROM `user` WHERE remark LIKE ?)", marker+"%").
|
|
||||||
Delete(&modelUser.UserFamily{}).Error
|
|
||||||
_ = e.db.WithContext(ctx).
|
|
||||||
Where("type IN (33, 34) AND content LIKE ?", "%"+marker+"%").
|
|
||||||
Delete(&modelLog.SystemLog{}).Error
|
|
||||||
_ = e.db.WithContext(ctx).
|
|
||||||
Where("order_no LIKE ?", marker+"%").
|
|
||||||
Delete(&modelOrder.Order{}).Error
|
|
||||||
_ = e.db.WithContext(ctx).
|
|
||||||
Where("note = ? OR token LIKE ?", marker, marker+"%").
|
|
||||||
Delete(&modelUser.Subscribe{}).Error
|
|
||||||
_ = e.db.WithContext(ctx).
|
|
||||||
Where("name LIKE ?", marker+"%").
|
|
||||||
Delete(&modelSubscribe.Subscribe{}).Error
|
|
||||||
_ = e.db.WithContext(ctx).Unscoped().
|
|
||||||
Where("remark LIKE ?", marker+"%").
|
|
||||||
Delete(&modelUser.User{}).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func looksLikeProduction(cfg config.Config) bool {
|
|
||||||
joined := strings.ToLower(strings.Join([]string{cfg.MySQL.Dbname, cfg.Site.Host, cfg.Host}, " "))
|
|
||||||
if strings.Contains(joined, "prod") || strings.Contains(joined, "production") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if cfg.Debug {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if strings.Contains(joined, "test") || strings.Contains(joined, "dev") || strings.Contains(joined, "staging") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func must(err error) {
|
|
||||||
if err != nil {
|
|
||||||
fatalf("%v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func fatalf(format string, args ...interface{}) {
|
|
||||||
fmt.Fprintf(os.Stderr, "FAIL: "+format+"\n", args...)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
@ -1,345 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
// Apple App Store Server API 测试脚本
|
|
||||||
// 用法: go run scripts/test_apple_lookup.go
|
|
||||||
// 功能: 通过 Apple Server API 获取交易历史,并按 appAccountToken (UUID) 过滤匹配交易
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/sha256"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"encoding/pem"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ==================== 配置区域 ====================
|
|
||||||
// 请填入你的 Apple App Store Connect API 凭证
|
|
||||||
const (
|
|
||||||
keyID = "" // App Store Connect Key ID
|
|
||||||
issuerID = "" // App Store Connect Issuer ID
|
|
||||||
bundleID = "com.hifastvpn.vip" // 你的 App Bundle ID
|
|
||||||
sandbox = true // true=沙盒环境, false=生产环境
|
|
||||||
)
|
|
||||||
|
|
||||||
// 私钥内容 (PEM 格式)
|
|
||||||
// 从 App Store Connect 下载的 .p8 文件内容
|
|
||||||
var privateKeyPEM = ``
|
|
||||||
|
|
||||||
// ==================== 主逻辑 ====================
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if len(os.Args) < 2 {
|
|
||||||
fmt.Println("用法:")
|
|
||||||
fmt.Println(" go run scripts/test_apple_lookup.go <originalTransactionId> [appAccountToken]")
|
|
||||||
fmt.Println("")
|
|
||||||
fmt.Println("参数:")
|
|
||||||
fmt.Println(" originalTransactionId Apple 交易原始 ID")
|
|
||||||
fmt.Println(" appAccountToken 可选, 用于过滤的 UUID (服务端下单时生成)")
|
|
||||||
fmt.Println("")
|
|
||||||
fmt.Println("示例:")
|
|
||||||
fmt.Println(" go run scripts/test_apple_lookup.go 2000001132940893")
|
|
||||||
fmt.Println(" go run scripts/test_apple_lookup.go 2000001132940893 f0eb8c62-4be9-4be7-9266-58d1a0a4e7bf")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
originalTransactionId := os.Args[1]
|
|
||||||
filterToken := ""
|
|
||||||
if len(os.Args) >= 3 {
|
|
||||||
filterToken = os.Args[2]
|
|
||||||
}
|
|
||||||
|
|
||||||
if keyID == "" || issuerID == "" || privateKeyPEM == "" {
|
|
||||||
fmt.Println("❌ 请先在脚本中填入 Apple API 凭证 (keyID, issuerID, privateKeyPEM)")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("═══════════════════════════════════════════════")
|
|
||||||
fmt.Println(" Apple App Store Server API - 交易历史查询")
|
|
||||||
fmt.Println("═══════════════════════════════════════════════")
|
|
||||||
fmt.Printf(" 环境: %s\n", envName())
|
|
||||||
fmt.Printf(" TransactionID: %s\n", originalTransactionId)
|
|
||||||
if filterToken != "" {
|
|
||||||
fmt.Printf(" 过滤 Token: %s\n", filterToken)
|
|
||||||
}
|
|
||||||
fmt.Println("═══════════════════════════════════════════════")
|
|
||||||
|
|
||||||
// 1. 生成 JWT
|
|
||||||
token, err := buildJWT()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("❌ 生成 JWT 失败: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
fmt.Println("✅ JWT 生成成功")
|
|
||||||
|
|
||||||
// 2. 查询交易历史
|
|
||||||
fmt.Printf("\n📡 正在查询交易历史...\n")
|
|
||||||
transactions, err := getTransactionHistory(token, originalTransactionId)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("❌ 查询失败: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("✅ 共获取 %d 条交易记录\n\n", len(transactions))
|
|
||||||
|
|
||||||
// 3. 解析并展示交易
|
|
||||||
for i, jws := range transactions {
|
|
||||||
info, err := parseJWS(jws)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf(" [%d] ❌ 解析失败: %v\n", i+1, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
txToken := info["appAccountToken"]
|
|
||||||
matched := ""
|
|
||||||
if filterToken != "" && fmt.Sprintf("%v", txToken) == filterToken {
|
|
||||||
matched = " ✅ 匹配!"
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf(" [%d]%s\n", i+1, matched)
|
|
||||||
fmt.Printf(" TransactionID: %v\n", info["transactionId"])
|
|
||||||
fmt.Printf(" OriginalTxID: %v\n", info["originalTransactionId"])
|
|
||||||
fmt.Printf(" ProductID: %v\n", info["productId"])
|
|
||||||
fmt.Printf(" AppAccountToken: %v\n", txToken)
|
|
||||||
fmt.Printf(" PurchaseDate: %v\n", formatTimestamp(info["purchaseDate"]))
|
|
||||||
fmt.Printf(" Type: %v\n", info["type"])
|
|
||||||
fmt.Println()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. 如果指定了过滤 token,显示匹配结果
|
|
||||||
if filterToken != "" {
|
|
||||||
found := false
|
|
||||||
for _, jws := range transactions {
|
|
||||||
info, err := parseJWS(jws)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if fmt.Sprintf("%v", info["appAccountToken"]) == filterToken {
|
|
||||||
found = true
|
|
||||||
fmt.Println("═══════════════════════════════════════════════")
|
|
||||||
fmt.Printf("🎯 找到匹配的交易!AppAccountToken: %s\n", filterToken)
|
|
||||||
fmt.Printf(" TransactionID: %v\n", info["transactionId"])
|
|
||||||
fmt.Printf(" ProductID: %v\n", info["productId"])
|
|
||||||
fmt.Println("═══════════════════════════════════════════════")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
fmt.Println("═══════════════════════════════════════════════")
|
|
||||||
fmt.Printf("⚠️ 未找到 AppAccountToken=%s 的交易\n", filterToken)
|
|
||||||
fmt.Println("═══════════════════════════════════════════════")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== Apple API 调用 ====================
|
|
||||||
|
|
||||||
// getTransactionHistory 获取指定交易 ID 的完整交易历史
|
|
||||||
// Apple API: GET /inApps/v2/history/{transactionId}
|
|
||||||
func getTransactionHistory(jwt, transactionId string) ([]string, error) {
|
|
||||||
var allTransactions []string
|
|
||||||
revision := ""
|
|
||||||
|
|
||||||
for {
|
|
||||||
host := apiHost()
|
|
||||||
url := fmt.Sprintf("%s/inApps/v2/history/%s?sort=DESCENDING", host, transactionId)
|
|
||||||
if revision != "" {
|
|
||||||
url += "&revision=" + revision
|
|
||||||
}
|
|
||||||
|
|
||||||
req, _ := http.NewRequest("GET", url, nil)
|
|
||||||
req.Header.Set("Authorization", "Bearer "+jwt)
|
|
||||||
|
|
||||||
resp, err := http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("HTTP 请求失败: %v", err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
body, _ := io.ReadAll(resp.Body)
|
|
||||||
|
|
||||||
if resp.StatusCode != 200 {
|
|
||||||
// 尝试另一个环境
|
|
||||||
host2 := apiHostSecondary()
|
|
||||||
url2 := fmt.Sprintf("%s/inApps/v2/history/%s?sort=DESCENDING", host2, transactionId)
|
|
||||||
if revision != "" {
|
|
||||||
url2 += "&revision=" + revision
|
|
||||||
}
|
|
||||||
req2, _ := http.NewRequest("GET", url2, nil)
|
|
||||||
req2.Header.Set("Authorization", "Bearer "+jwt)
|
|
||||||
resp2, err2 := http.DefaultClient.Do(req2)
|
|
||||||
if err2 != nil {
|
|
||||||
return nil, fmt.Errorf("两个环境都失败: primary[%d:%s]", resp.StatusCode, string(body))
|
|
||||||
}
|
|
||||||
defer resp2.Body.Close()
|
|
||||||
body2, _ := io.ReadAll(resp2.Body)
|
|
||||||
if resp2.StatusCode != 200 {
|
|
||||||
return nil, fmt.Errorf("两个环境都失败: primary[%d:%s], secondary[%d:%s]",
|
|
||||||
resp.StatusCode, string(body), resp2.StatusCode, string(body2))
|
|
||||||
}
|
|
||||||
body = body2
|
|
||||||
}
|
|
||||||
|
|
||||||
var result struct {
|
|
||||||
SignedTransactions []string `json:"signedTransactions"`
|
|
||||||
Revision string `json:"revision"`
|
|
||||||
HasMore bool `json:"hasMore"`
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(body, &result); err != nil {
|
|
||||||
return nil, fmt.Errorf("解析响应失败: %v, body: %s", err, string(body))
|
|
||||||
}
|
|
||||||
|
|
||||||
allTransactions = append(allTransactions, result.SignedTransactions...)
|
|
||||||
|
|
||||||
if !result.HasMore || result.Revision == "" {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
revision = result.Revision
|
|
||||||
}
|
|
||||||
|
|
||||||
return allTransactions, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== JWT 构建 ====================
|
|
||||||
|
|
||||||
// buildJWT 构建 Apple Server API 的 ES256 JWT Token
|
|
||||||
func buildJWT() (string, error) {
|
|
||||||
header := map[string]interface{}{
|
|
||||||
"alg": "ES256",
|
|
||||||
"kid": keyID,
|
|
||||||
"typ": "JWT",
|
|
||||||
}
|
|
||||||
now := time.Now().Unix()
|
|
||||||
payload := map[string]interface{}{
|
|
||||||
"iss": issuerID,
|
|
||||||
"iat": now,
|
|
||||||
"exp": now + 1800,
|
|
||||||
"aud": "appstoreconnect-v1",
|
|
||||||
}
|
|
||||||
if bundleID != "" {
|
|
||||||
payload["bid"] = bundleID
|
|
||||||
}
|
|
||||||
|
|
||||||
hb, _ := json.Marshal(header)
|
|
||||||
pb, _ := json.Marshal(payload)
|
|
||||||
enc := func(b []byte) string {
|
|
||||||
return base64.RawURLEncoding.EncodeToString(b)
|
|
||||||
}
|
|
||||||
unsigned := fmt.Sprintf("%s.%s", enc(hb), enc(pb))
|
|
||||||
|
|
||||||
key := fixPEM(privateKeyPEM)
|
|
||||||
block, _ := pem.Decode([]byte(key))
|
|
||||||
if block == nil {
|
|
||||||
return "", fmt.Errorf("invalid private key PEM")
|
|
||||||
}
|
|
||||||
keyAny, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("parse private key failed: %v", err)
|
|
||||||
}
|
|
||||||
priv, ok := keyAny.(*ecdsa.PrivateKey)
|
|
||||||
if !ok {
|
|
||||||
return "", fmt.Errorf("private key is not ECDSA")
|
|
||||||
}
|
|
||||||
|
|
||||||
h := sha256.New()
|
|
||||||
h.Write([]byte(unsigned))
|
|
||||||
digest := h.Sum(nil)
|
|
||||||
|
|
||||||
r, s, err := ecdsa.Sign(rand.Reader, priv, digest)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
curveBits := priv.Curve.Params().BitSize
|
|
||||||
keyBytes := curveBits / 8
|
|
||||||
if curveBits%8 > 0 {
|
|
||||||
keyBytes++
|
|
||||||
}
|
|
||||||
rBytes := r.Bytes()
|
|
||||||
rPadded := make([]byte, keyBytes)
|
|
||||||
copy(rPadded[keyBytes-len(rBytes):], rBytes)
|
|
||||||
sBytes := s.Bytes()
|
|
||||||
sPadded := make([]byte, keyBytes)
|
|
||||||
copy(sPadded[keyBytes-len(sBytes):], sBytes)
|
|
||||||
|
|
||||||
sig := append(rPadded, sPadded...)
|
|
||||||
return unsigned + "." + base64.RawURLEncoding.EncodeToString(sig), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== 工具函数 ====================
|
|
||||||
|
|
||||||
// parseJWS 解析 Apple 返回的 JWS (只取 payload 部分)
|
|
||||||
func parseJWS(jws string) (map[string]interface{}, error) {
|
|
||||||
parts := strings.Split(jws, ".")
|
|
||||||
if len(parts) != 3 {
|
|
||||||
return nil, fmt.Errorf("invalid JWS format")
|
|
||||||
}
|
|
||||||
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
|
|
||||||
if err != nil {
|
|
||||||
// 尝试标准 base64
|
|
||||||
payload, err = base64.RawStdEncoding.DecodeString(parts[1])
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("decode payload failed: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var result map[string]interface{}
|
|
||||||
if err := json.Unmarshal(payload, &result); err != nil {
|
|
||||||
return nil, fmt.Errorf("unmarshal payload failed: %v", err)
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// formatTimestamp 格式化 Apple 返回的毫秒时间戳
|
|
||||||
func formatTimestamp(v interface{}) string {
|
|
||||||
if v == nil {
|
|
||||||
return "N/A"
|
|
||||||
}
|
|
||||||
switch t := v.(type) {
|
|
||||||
case float64:
|
|
||||||
ts := time.UnixMilli(int64(t))
|
|
||||||
return ts.Format("2006-01-02 15:04:05")
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("%v", v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func fixPEM(key string) string {
|
|
||||||
if !strings.Contains(key, "\n") && strings.Contains(key, "BEGIN PRIVATE KEY") {
|
|
||||||
key = strings.ReplaceAll(key, " ", "\n")
|
|
||||||
key = strings.ReplaceAll(key, "-----BEGIN\nPRIVATE\nKEY-----", "-----BEGIN PRIVATE KEY-----")
|
|
||||||
key = strings.ReplaceAll(key, "-----END\nPRIVATE\nKEY-----", "-----END PRIVATE KEY-----")
|
|
||||||
}
|
|
||||||
return key
|
|
||||||
}
|
|
||||||
|
|
||||||
func apiHost() string {
|
|
||||||
if sandbox {
|
|
||||||
return "https://api.storekit-sandbox.itunes.apple.com"
|
|
||||||
}
|
|
||||||
return "https://api.storekit.itunes.apple.com"
|
|
||||||
}
|
|
||||||
|
|
||||||
func apiHostSecondary() string {
|
|
||||||
if sandbox {
|
|
||||||
return "https://api.storekit.itunes.apple.com"
|
|
||||||
}
|
|
||||||
return "https://api.storekit-sandbox.itunes.apple.com"
|
|
||||||
}
|
|
||||||
|
|
||||||
func envName() string {
|
|
||||||
if sandbox {
|
|
||||||
return "🏖️ Sandbox"
|
|
||||||
}
|
|
||||||
return "🏭 Production"
|
|
||||||
}
|
|
||||||
|
|
||||||
// 忽略未使用导入
|
|
||||||
var _ = bytes.NewBuffer
|
|
||||||
@ -1,295 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
// 设备登录测试脚本
|
|
||||||
// 用法: go run scripts/test_device_login.go
|
|
||||||
// 功能: 模拟客户端设备登录,自动加密请求体,解密响应,打印 token
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/hmac"
|
|
||||||
"crypto/md5"
|
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/forgoer/openssl"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ==================== 配置区域 ====================
|
|
||||||
|
|
||||||
const (
|
|
||||||
serverURL = "https://tapi.hifast.biz" // 服务地址
|
|
||||||
securitySecret = "c0qhq99a-nq8h-ropg-wrlc-ezj4dlkxqpzx" // device.security_secret
|
|
||||||
identifier = "test-device-script-001" // 设备唯一标识
|
|
||||||
userAgent = "TestScript/1.0" // UserAgent
|
|
||||||
|
|
||||||
appId = "android-client" // AppSignature.AppSecrets 中的 key
|
|
||||||
appSecret = "uB4G,XxL2{7b" // AppSignature.AppSecrets 中的 value
|
|
||||||
)
|
|
||||||
|
|
||||||
// ==================== AES 工具(与服务端 pkg/aes/aes.go 一致)====================
|
|
||||||
|
|
||||||
func generateKey(key string) []byte {
|
|
||||||
hash := sha256.Sum256([]byte(key))
|
|
||||||
return hash[:32]
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateIv(iv, key string) []byte {
|
|
||||||
h := md5.New()
|
|
||||||
h.Write([]byte(iv))
|
|
||||||
return generateKey(hex.EncodeToString(h.Sum(nil)) + key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func aesEncrypt(plainText []byte, keyStr string) (data string, nonce string, err error) {
|
|
||||||
nonce = fmt.Sprintf("%x", time.Now().UnixNano())
|
|
||||||
key := generateKey(keyStr)
|
|
||||||
iv := generateIv(nonce, keyStr)
|
|
||||||
dst, err := openssl.AesCBCEncrypt(plainText, key, iv, openssl.PKCS7_PADDING)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
return base64.StdEncoding.EncodeToString(dst), nonce, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func aesDecrypt(cipherText string, keyStr string, ivStr string) (string, error) {
|
|
||||||
decode, err := base64.StdEncoding.DecodeString(cipherText)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
key := generateKey(keyStr)
|
|
||||||
iv := generateIv(ivStr, keyStr)
|
|
||||||
dst, err := openssl.AesCBCDecrypt(decode, key, iv, openssl.PKCS7_PADDING)
|
|
||||||
return string(dst), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== 签名工具(与服务端 pkg/signature 一致)====================
|
|
||||||
|
|
||||||
func buildStringToSign(method, path, rawQuery string, body []byte, xAppId, timestamp, nonce string) string {
|
|
||||||
canonical := canonicalQuery(rawQuery)
|
|
||||||
bodyHash := sha256Hex(body)
|
|
||||||
parts := []string{
|
|
||||||
strings.ToUpper(method),
|
|
||||||
path,
|
|
||||||
canonical,
|
|
||||||
bodyHash,
|
|
||||||
xAppId,
|
|
||||||
timestamp,
|
|
||||||
nonce,
|
|
||||||
}
|
|
||||||
return strings.Join(parts, "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func canonicalQuery(rawQuery string) string {
|
|
||||||
if rawQuery == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
pairs := strings.Split(rawQuery, "&")
|
|
||||||
sort.Strings(pairs)
|
|
||||||
return strings.Join(pairs, "&")
|
|
||||||
}
|
|
||||||
|
|
||||||
func sha256Hex(data []byte) string {
|
|
||||||
h := sha256.Sum256(data)
|
|
||||||
return fmt.Sprintf("%x", h)
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildSignature(stringToSign, secret string) string {
|
|
||||||
mac := hmac.New(sha256.New, []byte(secret))
|
|
||||||
mac.Write([]byte(stringToSign))
|
|
||||||
return hex.EncodeToString(mac.Sum(nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
func signedRequest(method, url, rawQuery string, body []byte, token string) (*http.Request, error) {
|
|
||||||
var bodyReader io.Reader
|
|
||||||
if body != nil {
|
|
||||||
bodyReader = bytes.NewReader(body)
|
|
||||||
}
|
|
||||||
req, err := http.NewRequest(method, url, bodyReader)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
if token != "" {
|
|
||||||
req.Header.Set("Authorization", token) // 不带 Bearer 前缀,服务端直接 Parse token
|
|
||||||
}
|
|
||||||
|
|
||||||
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
|
|
||||||
nonce := fmt.Sprintf("%x", time.Now().UnixNano())
|
|
||||||
|
|
||||||
// 提取 path
|
|
||||||
path := req.URL.Path
|
|
||||||
|
|
||||||
sts := buildStringToSign(method, path, rawQuery, body, appId, timestamp, nonce)
|
|
||||||
sig := buildSignature(sts, appSecret)
|
|
||||||
|
|
||||||
req.Header.Set("X-App-Id", appId)
|
|
||||||
req.Header.Set("X-Timestamp", timestamp)
|
|
||||||
req.Header.Set("X-Nonce", nonce)
|
|
||||||
req.Header.Set("X-Signature", sig)
|
|
||||||
return req, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== 主逻辑 ====================
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
fmt.Println("=== 设备登录测试 ===")
|
|
||||||
fmt.Printf("Server: %s\n", serverURL)
|
|
||||||
fmt.Printf("Identifier: %s\n", identifier)
|
|
||||||
fmt.Println()
|
|
||||||
|
|
||||||
// 1. 构造原始请求体
|
|
||||||
payload := map[string]string{
|
|
||||||
"identifier": identifier,
|
|
||||||
"user_agent": userAgent,
|
|
||||||
}
|
|
||||||
plainBytes, err := json.Marshal(payload)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("[ERROR] marshal payload: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Printf("原始请求体: %s\n", string(plainBytes))
|
|
||||||
|
|
||||||
// 2. AES 加密请求体
|
|
||||||
encData, nonce, err := aesEncrypt(plainBytes, securitySecret)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("[ERROR] encrypt: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
encBody := map[string]string{
|
|
||||||
"data": encData,
|
|
||||||
"time": nonce,
|
|
||||||
}
|
|
||||||
encBytes, _ := json.Marshal(encBody)
|
|
||||||
fmt.Printf("加密请求体: %s\n\n", string(encBytes))
|
|
||||||
|
|
||||||
// 3. 发送请求
|
|
||||||
req, err := signedRequest("POST", serverURL+"/v1/auth/login/device", "", encBytes, "")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("[ERROR] new request: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
req.Header.Set("Login-Type", "device")
|
|
||||||
|
|
||||||
client := &http.Client{Timeout: 10 * time.Second}
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("[ERROR] request failed: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
respBody, _ := io.ReadAll(resp.Body)
|
|
||||||
fmt.Printf("HTTP Status: %d\n", resp.StatusCode)
|
|
||||||
fmt.Printf("原始响应: %s\n\n", string(respBody))
|
|
||||||
|
|
||||||
// 4. 解密响应
|
|
||||||
// 响应格式: {"code":200,"data":{"data":"<encrypted>","time":"<nonce>"},"message":""}
|
|
||||||
var outer struct {
|
|
||||||
Code int `json:"code"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
Data json.RawMessage `json:"data"`
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(respBody, &outer); err != nil {
|
|
||||||
fmt.Printf("[ERROR] parse response: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if outer.Code != 200 {
|
|
||||||
fmt.Printf("[FAIL] 登录失败: code=%d message=%s\n", outer.Code, outer.Message)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// data 字段是加密对象
|
|
||||||
var encResp struct {
|
|
||||||
Data string `json:"data"`
|
|
||||||
Time string `json:"time"`
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(outer.Data, &encResp); err != nil {
|
|
||||||
// 如果 Device.Enable=false,data 直接就是明文对象
|
|
||||||
fmt.Printf("响应 data 非加密格式,直接解析: %s\n", string(outer.Data))
|
|
||||||
var loginResp struct {
|
|
||||||
Token string `json:"token"`
|
|
||||||
}
|
|
||||||
if err2 := json.Unmarshal(outer.Data, &loginResp); err2 == nil && loginResp.Token != "" {
|
|
||||||
fmt.Printf("[OK] Token: %s\n", loginResp.Token)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
decrypted, err := aesDecrypt(encResp.Data, securitySecret, encResp.Time)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("[ERROR] decrypt response: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Printf("解密后响应: %s\n\n", decrypted)
|
|
||||||
|
|
||||||
var loginResp struct {
|
|
||||||
Token string `json:"token"`
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal([]byte(decrypted), &loginResp); err != nil {
|
|
||||||
fmt.Printf("[ERROR] parse decrypted: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("[OK] Token: %s\n", loginResp.Token)
|
|
||||||
|
|
||||||
// 5. 用 token 请求订阅列表
|
|
||||||
fmt.Println("\n=== 请求订阅列表 ===")
|
|
||||||
subReq, err := signedRequest("GET", serverURL+"/v1/public/subscribe/list", "", nil, loginResp.Token)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("[ERROR] build subscribe request: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
subReq.Header.Set("Login-Type", "device")
|
|
||||||
|
|
||||||
subResp, err := client.Do(subReq)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("[ERROR] subscribe list request: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer subResp.Body.Close()
|
|
||||||
|
|
||||||
subBody, _ := io.ReadAll(subResp.Body)
|
|
||||||
fmt.Printf("HTTP Status: %d\n", subResp.StatusCode)
|
|
||||||
fmt.Printf("原始响应: %s\n", string(subBody))
|
|
||||||
|
|
||||||
// 解密订阅列表响应
|
|
||||||
var subOuter struct {
|
|
||||||
Code int `json:"code"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
Data json.RawMessage `json:"data"`
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(subBody, &subOuter); err != nil {
|
|
||||||
fmt.Printf("[ERROR] parse subscribe response: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if subOuter.Code != 200 {
|
|
||||||
fmt.Printf("[FAIL] 订阅列表失败: code=%d message=%s\n", subOuter.Code, subOuter.Message)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var subEnc struct {
|
|
||||||
Data string `json:"data"`
|
|
||||||
Time string `json:"time"`
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(subOuter.Data, &subEnc); err != nil || subEnc.Data == "" {
|
|
||||||
// 无加密,直接打印
|
|
||||||
fmt.Printf("\n[OK] 订阅列表(明文): %s\n", string(subOuter.Data))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
subDecrypted, err := aesDecrypt(subEnc.Data, securitySecret, subEnc.Time)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("[ERROR] decrypt subscribe list: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Printf("\n[OK] 订阅列表(解密): %s\n", subDecrypted)
|
|
||||||
}
|
|
||||||
@ -1,238 +0,0 @@
|
|||||||
//go:build ignore
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/perfect-panel/server/initialize"
|
|
||||||
"github.com/perfect-panel/server/internal/config"
|
|
||||||
authlogic "github.com/perfect-panel/server/internal/logic/auth"
|
|
||||||
modelAuth "github.com/perfect-panel/server/internal/model/auth"
|
|
||||||
modelLog "github.com/perfect-panel/server/internal/model/log"
|
|
||||||
modelNode "github.com/perfect-panel/server/internal/model/node"
|
|
||||||
modelSubscribe "github.com/perfect-panel/server/internal/model/subscribe"
|
|
||||||
modelSystem "github.com/perfect-panel/server/internal/model/system"
|
|
||||||
modelUser "github.com/perfect-panel/server/internal/model/user"
|
|
||||||
"github.com/perfect-panel/server/internal/svc"
|
|
||||||
"github.com/perfect-panel/server/internal/types"
|
|
||||||
"github.com/perfect-panel/server/pkg/conf"
|
|
||||||
"github.com/perfect-panel/server/pkg/orm"
|
|
||||||
"github.com/perfect-panel/server/pkg/tool"
|
|
||||||
"github.com/redis/go-redis/v9"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
var (
|
|
||||||
configPath = flag.String("config", "etc/ppanel.yaml", "config file path on the test server")
|
|
||||||
dsn = flag.String("dsn", "", "optional MySQL DSN override")
|
|
||||||
identifier = flag.String("identifier", "", "optional device identifier; defaults to a unique test identifier")
|
|
||||||
ip = flag.String("ip", "", "optional request IP; defaults to a reserved test IP")
|
|
||||||
userAgent = flag.String("user-agent", "CodexDeviceTrialTest/1.0", "device user agent")
|
|
||||||
write = flag.Bool("write", false, "actually create a test device user by running DeviceLogin")
|
|
||||||
cleanup = flag.Bool("cleanup", false, "delete the test user/device/subscription/log rows after verification")
|
|
||||||
)
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
if !*write {
|
|
||||||
fmt.Println("Refusing to write DB without -write.")
|
|
||||||
fmt.Println("Example:")
|
|
||||||
fmt.Printf(" go run scripts/test_device_trial_registration.go -config %s -write\n", *configPath)
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
cfg := loadConfig(*configPath, *dsn)
|
|
||||||
env := mustNewDeviceTrialEnv(ctx, cfg)
|
|
||||||
defer env.close()
|
|
||||||
|
|
||||||
initialize.Device(env.svcCtx)
|
|
||||||
initialize.Register(env.svcCtx)
|
|
||||||
|
|
||||||
if *identifier == "" {
|
|
||||||
*identifier = fmt.Sprintf("codex-device-trial-%d", time.Now().UnixNano())
|
|
||||||
}
|
|
||||||
if *ip == "" {
|
|
||||||
now := time.Now().UnixNano()
|
|
||||||
*ip = fmt.Sprintf("198.18.%d.%d", now%200+1, now/200%200+1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("== device registration no-trial test ==")
|
|
||||||
fmt.Printf("mysql: %s/%s\n", env.cfg.MySQL.Addr, env.cfg.MySQL.Dbname)
|
|
||||||
fmt.Printf("redis: %s db=%d\n", env.cfg.Redis.Host, env.cfg.Redis.DB)
|
|
||||||
fmt.Printf("device.enable=%v\n", env.svcCtx.Config.Device.Enable)
|
|
||||||
fmt.Printf("register.enable_trial=%v trial_subscribe=%d trial_time=%d trial_time_unit=%s\n",
|
|
||||||
env.svcCtx.Config.Register.EnableTrial,
|
|
||||||
env.svcCtx.Config.Register.TrialSubscribe,
|
|
||||||
env.svcCtx.Config.Register.TrialTime,
|
|
||||||
env.svcCtx.Config.Register.TrialTimeUnit,
|
|
||||||
)
|
|
||||||
fmt.Printf("identifier=%s ip=%s user_agent=%s\n", *identifier, *ip, *userAgent)
|
|
||||||
|
|
||||||
if err := ensureIdentifierUnused(ctx, env.db, *identifier); err != nil {
|
|
||||||
fail(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
logic := authlogic.NewDeviceLoginLogic(ctx, env.svcCtx)
|
|
||||||
resp, err := logic.DeviceLogin(&types.DeviceLoginRequest{
|
|
||||||
Identifier: *identifier,
|
|
||||||
IP: *ip,
|
|
||||||
UserAgent: *userAgent,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
fail(fmt.Errorf("DeviceLogin failed: %w", err))
|
|
||||||
}
|
|
||||||
if resp == nil || strings.TrimSpace(resp.Token) == "" {
|
|
||||||
fail(fmt.Errorf("DeviceLogin returned empty token"))
|
|
||||||
}
|
|
||||||
fmt.Printf("login token: ok len=%d\n", len(resp.Token))
|
|
||||||
|
|
||||||
device, err := env.svcCtx.UserModel.FindOneDeviceByIdentifier(ctx, *identifier)
|
|
||||||
if err != nil {
|
|
||||||
fail(fmt.Errorf("query created device failed: %w", err))
|
|
||||||
}
|
|
||||||
fmt.Printf("device: id=%d sn=%s user_id=%d created_at=%s\n",
|
|
||||||
device.Id,
|
|
||||||
tool.DeviceIdToHash(device.Id),
|
|
||||||
device.UserId,
|
|
||||||
device.CreatedAt.Format(time.RFC3339),
|
|
||||||
)
|
|
||||||
|
|
||||||
var subs []modelUser.Subscribe
|
|
||||||
if err = env.db.WithContext(ctx).
|
|
||||||
Where("user_id = ?", device.UserId).
|
|
||||||
Order("id ASC").
|
|
||||||
Find(&subs).Error; err != nil {
|
|
||||||
fail(fmt.Errorf("query user_subscribe failed: %w", err))
|
|
||||||
}
|
|
||||||
for i := range subs {
|
|
||||||
sub := &subs[i]
|
|
||||||
fmt.Printf("subscribe: id=%d order_id=%d subscribe_id=%d status=%d start=%s expire=%s token_empty=%v\n",
|
|
||||||
sub.Id,
|
|
||||||
sub.OrderId,
|
|
||||||
sub.SubscribeId,
|
|
||||||
sub.Status,
|
|
||||||
sub.StartTime.Format(time.RFC3339),
|
|
||||||
sub.ExpireTime.Format(time.RFC3339),
|
|
||||||
sub.Token == "",
|
|
||||||
)
|
|
||||||
if sub.OrderId == 0 &&
|
|
||||||
sub.SubscribeId == env.svcCtx.Config.Register.TrialSubscribe &&
|
|
||||||
(sub.Status == 0 || sub.Status == 1) &&
|
|
||||||
sub.ExpireTime.After(time.Now()) {
|
|
||||||
fail(fmt.Errorf("FAIL: device registration unexpectedly granted trial user_subscribe_id=%d user_id=%d", sub.Id, device.UserId))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("PASS: device registration created no active trial subscription for user_id=%d\n", device.UserId)
|
|
||||||
|
|
||||||
if *cleanup {
|
|
||||||
if err = cleanupTestRows(ctx, env.db, device.UserId); err != nil {
|
|
||||||
fail(fmt.Errorf("cleanup failed: %w", err))
|
|
||||||
}
|
|
||||||
fmt.Printf("cleanup: deleted test rows for user_id=%d\n", device.UserId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type deviceTrialEnv struct {
|
|
||||||
db *gorm.DB
|
|
||||||
rds *redis.Client
|
|
||||||
cfg config.Config
|
|
||||||
svcCtx *svc.ServiceContext
|
|
||||||
}
|
|
||||||
|
|
||||||
func mustNewDeviceTrialEnv(ctx context.Context, cfg config.Config) *deviceTrialEnv {
|
|
||||||
db, err := orm.ConnectMysql(orm.Mysql{Config: cfg.MySQL})
|
|
||||||
must(err)
|
|
||||||
|
|
||||||
rds := redis.NewClient(&redis.Options{
|
|
||||||
Addr: cfg.Redis.Host,
|
|
||||||
Password: cfg.Redis.Pass,
|
|
||||||
DB: cfg.Redis.DB,
|
|
||||||
PoolSize: cfg.Redis.PoolSize,
|
|
||||||
MinIdleConns: cfg.Redis.MinIdleConns,
|
|
||||||
})
|
|
||||||
must(rds.Ping(ctx).Err())
|
|
||||||
|
|
||||||
svcCtx := &svc.ServiceContext{
|
|
||||||
DB: db,
|
|
||||||
Redis: rds,
|
|
||||||
Config: cfg,
|
|
||||||
AuthModel: modelAuth.NewModel(db, rds),
|
|
||||||
LogModel: modelLog.NewModel(db),
|
|
||||||
NodeModel: modelNode.NewModel(db, rds),
|
|
||||||
SystemModel: modelSystem.NewModel(db, rds),
|
|
||||||
UserModel: modelUser.NewModel(db, rds),
|
|
||||||
SubscribeModel: modelSubscribe.NewModel(db, rds),
|
|
||||||
}
|
|
||||||
return &deviceTrialEnv{db: db, rds: rds, cfg: cfg, svcCtx: svcCtx}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *deviceTrialEnv) close() {
|
|
||||||
if e == nil || e.rds == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_ = e.rds.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadConfig(path, dsn string) config.Config {
|
|
||||||
var cfg config.Config
|
|
||||||
conf.MustLoad(path, &cfg)
|
|
||||||
if dsn != "" {
|
|
||||||
parsed := orm.ParseDSN(dsn)
|
|
||||||
if parsed == nil {
|
|
||||||
fail(fmt.Errorf("invalid dsn"))
|
|
||||||
}
|
|
||||||
cfg.MySQL = *parsed
|
|
||||||
}
|
|
||||||
return cfg
|
|
||||||
}
|
|
||||||
|
|
||||||
func ensureIdentifierUnused(ctx context.Context, db *gorm.DB, identifier string) error {
|
|
||||||
var count int64
|
|
||||||
if err := db.WithContext(ctx).
|
|
||||||
Model(&modelUser.Device{}).
|
|
||||||
Where("identifier = ?", identifier).
|
|
||||||
Count(&count).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if count > 0 {
|
|
||||||
return fmt.Errorf("identifier already exists: %s", identifier)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func cleanupTestRows(ctx context.Context, db *gorm.DB, userID int64) error {
|
|
||||||
return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
||||||
if err := tx.Where("object_id = ?", userID).Delete(&modelLog.SystemLog{}).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := tx.Where("user_id = ?", userID).Delete(&modelUser.Subscribe{}).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := tx.Where("user_id = ?", userID).Delete(&modelUser.AuthMethods{}).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := tx.Where("user_id = ?", userID).Delete(&modelUser.Device{}).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return tx.Where("id = ?", userID).Delete(&modelUser.User{}).Error
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func must(err error) {
|
|
||||||
if err != nil {
|
|
||||||
fail(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func fail(err error) {
|
|
||||||
fmt.Fprintln(os.Stderr, err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
@ -1,485 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
_ "github.com/go-sql-driver/mysql"
|
|
||||||
)
|
|
||||||
|
|
||||||
const inviteGiftMarker = "codex-test-invite-gift-days"
|
|
||||||
|
|
||||||
type giftLog struct {
|
|
||||||
Type uint16 `json:"type"`
|
|
||||||
OrderNo string `json:"order_no"`
|
|
||||||
SubscribeId int64 `json:"subscribe_id"`
|
|
||||||
Amount int64 `json:"amount"`
|
|
||||||
Balance int64 `json:"balance"`
|
|
||||||
Remark string `json:"remark,omitempty"`
|
|
||||||
Timestamp int64 `json:"timestamp"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type commissionLog struct {
|
|
||||||
Type uint16 `json:"type"`
|
|
||||||
Amount int64 `json:"amount"`
|
|
||||||
OrderNo string `json:"order_no"`
|
|
||||||
Timestamp int64 `json:"timestamp"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type userSubscribe struct {
|
|
||||||
ID int64
|
|
||||||
UserID int64
|
|
||||||
ExpireTime time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
var (
|
|
||||||
dsn = flag.String("dsn", "", "MySQL DSN, for example root:pass@tcp(host:3306)/ppanel?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai")
|
|
||||||
writeDB = flag.Bool("write-db", false, "create isolated rows, simulate invite gifts, and clean them up")
|
|
||||||
keep = flag.Bool("keep", false, "keep rows for manual inspection")
|
|
||||||
cleanupOnly = flag.Bool("cleanup-only", false, "delete leftover rows created by this script and exit")
|
|
||||||
giftDays = flag.Int("gift-days", 3, "days to add to both invite users")
|
|
||||||
commission = flag.Int64("commission-percent", 10, "commission percent for commission-path simulation")
|
|
||||||
)
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
if *dsn == "" {
|
|
||||||
exitf("-dsn is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
db, err := sql.Open("mysql", *dsn)
|
|
||||||
mustNoErr(err)
|
|
||||||
defer db.Close()
|
|
||||||
db.SetMaxIdleConns(1)
|
|
||||||
db.SetMaxOpenConns(1)
|
|
||||||
mustNoErr(db.PingContext(ctx))
|
|
||||||
|
|
||||||
if *cleanupOnly {
|
|
||||||
mustNoErr(cleanup(ctx, db))
|
|
||||||
fmt.Println("cleanup done")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !*writeDB {
|
|
||||||
fmt.Println("dry run only. Add -write-db to create isolated invite rows in the TEST database.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if *giftDays <= 0 {
|
|
||||||
exitf("-gift-days must be positive")
|
|
||||||
}
|
|
||||||
|
|
||||||
mustNoErr(cleanup(ctx, db))
|
|
||||||
if !*keep {
|
|
||||||
defer func() {
|
|
||||||
if err := cleanup(context.Background(), db); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "cleanup failed: %v\n", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
planID := mustCreatePlan(ctx, db)
|
|
||||||
runSelfInviteScenario(ctx, db, planID, *giftDays)
|
|
||||||
runFamilyInviteScenario(ctx, db, planID, *giftDays)
|
|
||||||
runCommissionScenario(ctx, db, planID, *giftDays, *commission)
|
|
||||||
|
|
||||||
if *keep {
|
|
||||||
fmt.Println("rows kept; cleanup with -cleanup-only. inviteGiftMarker:", inviteGiftMarker)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func runSelfInviteScenario(ctx context.Context, db *sql.DB, planID int64, giftDays int) {
|
|
||||||
refererID := mustCreateUser(ctx, db, "self-referer", 0)
|
|
||||||
refereeID := mustCreateUser(ctx, db, "self-referee", refererID)
|
|
||||||
|
|
||||||
baseExpire := time.Now().Add(10 * 24 * time.Hour).Truncate(time.Second)
|
|
||||||
refererSubID := mustCreateUserSubscribe(ctx, db, refererID, planID, baseExpire)
|
|
||||||
refereeSubID := mustCreateUserSubscribe(ctx, db, refereeID, planID, baseExpire)
|
|
||||||
|
|
||||||
orderNo := fmt.Sprintf("%s-self-order-%d", inviteGiftMarker, time.Now().UnixNano())
|
|
||||||
mustNoErr(simulateInviteGiftBoth(ctx, db, orderNo, refererID, refereeID, 0, giftDays))
|
|
||||||
mustNoErr(simulateInviteGiftBoth(ctx, db, orderNo, refererID, refereeID, 0, giftDays))
|
|
||||||
|
|
||||||
assertExpire(ctx, db, "referer", refererSubID, baseExpire, giftDays)
|
|
||||||
assertExpire(ctx, db, "referee", refereeSubID, baseExpire, giftDays)
|
|
||||||
|
|
||||||
logs := mustGiftLogCount(ctx, db, orderNo)
|
|
||||||
if logs != 2 {
|
|
||||||
exitf("gift log count mismatch after duplicate simulation: got=%d want=2", logs)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("PASS self invite: referer=%d referee=%d order=%s gift_days=%d logs=%d\n", refererID, refereeID, orderNo, giftDays, logs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func runFamilyInviteScenario(ctx context.Context, db *sql.DB, planID int64, giftDays int) {
|
|
||||||
refererOwnerID := mustCreateUser(ctx, db, "family-referer-owner", 0)
|
|
||||||
refererMemberID := mustCreateUser(ctx, db, "family-referer-member", 0)
|
|
||||||
refereeOwnerID := mustCreateUser(ctx, db, "family-referee-owner", 0)
|
|
||||||
refereeMemberID := mustCreateUser(ctx, db, "family-referee-member", refererMemberID)
|
|
||||||
mustCreateFamily(ctx, db, refererOwnerID, refererMemberID)
|
|
||||||
mustCreateFamily(ctx, db, refereeOwnerID, refereeMemberID)
|
|
||||||
|
|
||||||
baseExpire := time.Now().Add(10 * 24 * time.Hour).Truncate(time.Second)
|
|
||||||
refererOwnerSubID := mustCreateUserSubscribe(ctx, db, refererOwnerID, planID, baseExpire)
|
|
||||||
refereeOwnerSubID := mustCreateUserSubscribe(ctx, db, refereeOwnerID, planID, baseExpire)
|
|
||||||
refererMemberSubID := mustCreateUserSubscribe(ctx, db, refererMemberID, planID, baseExpire)
|
|
||||||
refereeMemberSubID := mustCreateUserSubscribe(ctx, db, refereeMemberID, planID, baseExpire)
|
|
||||||
|
|
||||||
orderNo := fmt.Sprintf("%s-family-order-%d", inviteGiftMarker, time.Now().UnixNano())
|
|
||||||
mustNoErr(simulateInviteGiftBoth(ctx, db, orderNo, refererMemberID, refereeMemberID, refereeOwnerID, giftDays))
|
|
||||||
mustNoErr(simulateInviteGiftBoth(ctx, db, orderNo, refererMemberID, refereeMemberID, refereeOwnerID, giftDays))
|
|
||||||
|
|
||||||
assertExpire(ctx, db, "referer owner", refererOwnerSubID, baseExpire, giftDays)
|
|
||||||
assertExpire(ctx, db, "referee owner", refereeOwnerSubID, baseExpire, giftDays)
|
|
||||||
assertExpire(ctx, db, "referer member", refererMemberSubID, baseExpire, 0)
|
|
||||||
assertExpire(ctx, db, "referee member", refereeMemberSubID, baseExpire, 0)
|
|
||||||
|
|
||||||
logs := mustGiftLogCount(ctx, db, orderNo)
|
|
||||||
if logs != 2 {
|
|
||||||
exitf("family gift log count mismatch after duplicate simulation: got=%d want=2", logs)
|
|
||||||
}
|
|
||||||
fmt.Printf("PASS family invite: referer_member=%d->owner=%d referee_member=%d->owner=%d order=%s gift_days=%d logs=%d\n",
|
|
||||||
refererMemberID, refererOwnerID, refereeMemberID, refereeOwnerID, orderNo, giftDays, logs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func runCommissionScenario(ctx context.Context, db *sql.DB, planID int64, giftDays int, commissionPercent int64) {
|
|
||||||
if commissionPercent <= 0 {
|
|
||||||
fmt.Println("SKIP commission invite: commission-percent <= 0")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const amount int64 = 599
|
|
||||||
|
|
||||||
refererID := mustCreateUser(ctx, db, "commission-referer", 0)
|
|
||||||
refereeID := mustCreateUser(ctx, db, "commission-referee", refererID)
|
|
||||||
baseExpire := time.Now().Add(10 * 24 * time.Hour).Truncate(time.Second)
|
|
||||||
refererSubID := mustCreateUserSubscribe(ctx, db, refererID, planID, baseExpire)
|
|
||||||
refereeSubID := mustCreateUserSubscribe(ctx, db, refereeID, planID, baseExpire)
|
|
||||||
|
|
||||||
orderNo := fmt.Sprintf("%s-commission-first-order-%d", inviteGiftMarker, time.Now().UnixNano())
|
|
||||||
mustNoErr(simulateInviteCommission(ctx, db, orderNo, refererID, refereeID, 0, giftDays, amount, commissionPercent, true))
|
|
||||||
mustNoErr(simulateInviteCommission(ctx, db, orderNo, refererID, refereeID, 0, giftDays, amount, commissionPercent, true))
|
|
||||||
|
|
||||||
wantCommission := amount * commissionPercent / 100
|
|
||||||
assertExpire(ctx, db, "commission referer", refererSubID, baseExpire, 0)
|
|
||||||
assertExpire(ctx, db, "commission referee", refereeSubID, baseExpire, giftDays)
|
|
||||||
assertCommission(ctx, db, refererID, wantCommission)
|
|
||||||
assertLogCount(ctx, db, "commission first gift", 34, orderNo, 1)
|
|
||||||
assertLogCount(ctx, db, "commission first commission", 33, orderNo, 1)
|
|
||||||
|
|
||||||
nonFirstRefererID := mustCreateUser(ctx, db, "commission-nonfirst-referer", 0)
|
|
||||||
nonFirstRefereeID := mustCreateUser(ctx, db, "commission-nonfirst-referee", nonFirstRefererID)
|
|
||||||
nonFirstRefererSubID := mustCreateUserSubscribe(ctx, db, nonFirstRefererID, planID, baseExpire)
|
|
||||||
nonFirstRefereeSubID := mustCreateUserSubscribe(ctx, db, nonFirstRefereeID, planID, baseExpire)
|
|
||||||
nonFirstOrderNo := fmt.Sprintf("%s-commission-nonfirst-order-%d", inviteGiftMarker, time.Now().UnixNano())
|
|
||||||
mustNoErr(simulateInviteCommission(ctx, db, nonFirstOrderNo, nonFirstRefererID, nonFirstRefereeID, 0, giftDays, amount, commissionPercent, false))
|
|
||||||
mustNoErr(simulateInviteCommission(ctx, db, nonFirstOrderNo, nonFirstRefererID, nonFirstRefereeID, 0, giftDays, amount, commissionPercent, false))
|
|
||||||
|
|
||||||
assertExpire(ctx, db, "commission non-first referer", nonFirstRefererSubID, baseExpire, 0)
|
|
||||||
assertExpire(ctx, db, "commission non-first referee", nonFirstRefereeSubID, baseExpire, 0)
|
|
||||||
assertCommission(ctx, db, nonFirstRefererID, wantCommission)
|
|
||||||
assertLogCount(ctx, db, "commission non-first gift", 34, nonFirstOrderNo, 0)
|
|
||||||
assertLogCount(ctx, db, "commission non-first commission", 33, nonFirstOrderNo, 1)
|
|
||||||
|
|
||||||
fmt.Printf("PASS commission invite: percent=%d first_order_commission=%d non_first_commission=%d\n",
|
|
||||||
commissionPercent, wantCommission, wantCommission)
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertExpire(ctx context.Context, db *sql.DB, label string, subID int64, before time.Time, addedDays int) {
|
|
||||||
got := mustExpire(ctx, db, subID)
|
|
||||||
want := before.Add(time.Duration(addedDays) * 24 * time.Hour)
|
|
||||||
if !got.Equal(want) {
|
|
||||||
exitf("%s expire mismatch: got=%s want=%s", label, got, want)
|
|
||||||
}
|
|
||||||
fmt.Printf("PASS %s subscribe=%d expire %s -> %s\n", label, subID, before.Format(time.RFC3339), got.Format(time.RFC3339))
|
|
||||||
}
|
|
||||||
|
|
||||||
func simulateInviteGiftBoth(ctx context.Context, db *sql.DB, orderNo string, refererID, refereeID, forcedRefereeOwnerID int64, days int) error {
|
|
||||||
refereeTargetID, err := resolveGiftTargetUser(ctx, db, refereeID, forcedRefereeOwnerID)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("resolve referee gift target: %w", err)
|
|
||||||
}
|
|
||||||
refererTargetID, err := resolveGiftTargetUser(ctx, db, refererID, 0)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("resolve referer gift target: %w", err)
|
|
||||||
}
|
|
||||||
if err := grantGiftDays(ctx, db, refereeTargetID, orderNo, days); err != nil {
|
|
||||||
return fmt.Errorf("grant referee gift: %w", err)
|
|
||||||
}
|
|
||||||
if err := grantGiftDays(ctx, db, refererTargetID, orderNo, days); err != nil {
|
|
||||||
return fmt.Errorf("grant referer gift: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func simulateInviteCommission(ctx context.Context, db *sql.DB, orderNo string, refererID, refereeID, forcedRefereeOwnerID int64, days int, amount int64, commissionPercent int64, isFirstOrder bool) error {
|
|
||||||
if err := grantCommission(ctx, db, refererID, orderNo, amount, commissionPercent); err != nil {
|
|
||||||
return fmt.Errorf("grant commission: %w", err)
|
|
||||||
}
|
|
||||||
if isFirstOrder {
|
|
||||||
refereeTargetID, err := resolveGiftTargetUser(ctx, db, refereeID, forcedRefereeOwnerID)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("resolve referee gift target: %w", err)
|
|
||||||
}
|
|
||||||
if err := grantGiftDays(ctx, db, refereeTargetID, orderNo, days); err != nil {
|
|
||||||
return fmt.Errorf("grant commission-path referee gift: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func resolveGiftTargetUser(ctx context.Context, db *sql.DB, userID int64, forcedOwnerID int64) (int64, error) {
|
|
||||||
if forcedOwnerID > 0 {
|
|
||||||
return forcedOwnerID, nil
|
|
||||||
}
|
|
||||||
var ownerID int64
|
|
||||||
err := db.QueryRowContext(ctx, `
|
|
||||||
SELECT uf.owner_user_id
|
|
||||||
FROM user_family_member ufm
|
|
||||||
JOIN user_family uf ON uf.id = ufm.family_id AND uf.deleted_at IS NULL
|
|
||||||
WHERE ufm.user_id = ?
|
|
||||||
AND ufm.deleted_at IS NULL
|
|
||||||
AND ufm.status = 1
|
|
||||||
AND ufm.role = 2
|
|
||||||
AND uf.status = 1
|
|
||||||
ORDER BY ufm.role
|
|
||||||
LIMIT 1`, userID).Scan(&ownerID)
|
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
return userID, nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
if ownerID > 0 && ownerID != userID {
|
|
||||||
return ownerID, nil
|
|
||||||
}
|
|
||||||
return userID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func grantCommission(ctx context.Context, db *sql.DB, refererID int64, orderNo string, amount int64, commissionPercent int64) error {
|
|
||||||
var existing int64
|
|
||||||
err := db.QueryRowContext(ctx,
|
|
||||||
"SELECT COUNT(*) FROM system_logs WHERE type = 33 AND object_id = ? AND content LIKE ?",
|
|
||||||
refererID, "%\""+orderNo+"\"%",
|
|
||||||
).Scan(&existing)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if existing > 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
commissionAmount := amount * commissionPercent / 100
|
|
||||||
if _, err = db.ExecContext(ctx,
|
|
||||||
"UPDATE `user` SET commission = commission + ?, updated_at = ? WHERE id = ?",
|
|
||||||
commissionAmount, time.Now(), refererID,
|
|
||||||
); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
content, err := json.Marshal(commissionLog{
|
|
||||||
Type: 331,
|
|
||||||
Amount: commissionAmount,
|
|
||||||
OrderNo: orderNo,
|
|
||||||
Timestamp: time.Now().UnixMilli(),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = db.ExecContext(ctx,
|
|
||||||
"INSERT INTO system_logs (`type`, object_id, content, created_at, `date`) VALUES (33, ?, ?, ?, ?)",
|
|
||||||
refererID, string(content), time.Now(), time.Now().Format("2006-01-02"),
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func grantGiftDays(ctx context.Context, db *sql.DB, userID int64, orderNo string, days int) error {
|
|
||||||
var existing int64
|
|
||||||
err := db.QueryRowContext(ctx,
|
|
||||||
"SELECT COUNT(*) FROM system_logs WHERE type = 34 AND object_id = ? AND content LIKE ?",
|
|
||||||
userID, "%\""+orderNo+"\"%",
|
|
||||||
).Scan(&existing)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if existing > 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
sub, err := findActiveSubscribe(ctx, db, userID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
nextExpire := sub.ExpireTime
|
|
||||||
if !sub.ExpireTime.Equal(time.UnixMilli(0)) {
|
|
||||||
nextExpire = sub.ExpireTime.Add(time.Duration(days) * 24 * time.Hour)
|
|
||||||
if _, err = db.ExecContext(ctx,
|
|
||||||
"UPDATE user_subscribe SET expire_time = ?, updated_at = ? WHERE id = ?",
|
|
||||||
nextExpire, time.Now(), sub.ID,
|
|
||||||
); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
content, err := json.Marshal(giftLog{
|
|
||||||
Type: 341,
|
|
||||||
OrderNo: orderNo,
|
|
||||||
SubscribeId: sub.ID,
|
|
||||||
Amount: int64(days),
|
|
||||||
Balance: 0,
|
|
||||||
Remark: "邀请赠送",
|
|
||||||
Timestamp: time.Now().UnixMilli(),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = db.ExecContext(ctx,
|
|
||||||
"INSERT INTO system_logs (`type`, object_id, content, created_at, `date`) VALUES (34, ?, ?, ?, ?)",
|
|
||||||
userID, string(content), time.Now(), time.Now().Format("2006-01-02"),
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func findActiveSubscribe(ctx context.Context, db *sql.DB, userID int64) (*userSubscribe, error) {
|
|
||||||
var row userSubscribe
|
|
||||||
err := db.QueryRowContext(ctx, `
|
|
||||||
SELECT id, user_id, expire_time
|
|
||||||
FROM user_subscribe
|
|
||||||
WHERE user_id = ?
|
|
||||||
AND status IN (0, 1)
|
|
||||||
AND (expire_time > ? OR expire_time = '1970-01-01 08:00:00')
|
|
||||||
ORDER BY expire_time DESC, id DESC
|
|
||||||
LIMIT 1`, userID, time.Now()).Scan(&row.ID, &row.UserID, &row.ExpireTime)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &row, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func mustCreatePlan(ctx context.Context, db *sql.DB) int64 {
|
|
||||||
var sort int64
|
|
||||||
mustNoErr(db.QueryRowContext(ctx, "SELECT COALESCE(MAX(sort), 0) + 1 FROM subscribe").Scan(&sort))
|
|
||||||
res, err := db.ExecContext(ctx, `
|
|
||||||
INSERT INTO subscribe
|
|
||||||
(name, language, description, unit_price, unit_time, discount, replacement, inventory, traffic, speed_limit, device_limit, quota, new_user_only, nodes, node_tags, node_group_ids, node_group_id, traffic_limit, `+"`show`"+`, sell, sort, deduction_ratio, allow_deduction, reset_cycle, renewal_reset, show_original_price, created_at, updated_at)
|
|
||||||
VALUES (?, 'en', '', 599, 'Month', '', 0, -1, 1073741824, 0, 0, 0, false, '', '', '[]', 0, '', false, false, ?, 0, true, 0, false, true, ?, ?)`,
|
|
||||||
inviteGiftMarker+"-plan", sort, time.Now(), time.Now())
|
|
||||||
mustNoErr(err)
|
|
||||||
id, err := res.LastInsertId()
|
|
||||||
mustNoErr(err)
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
|
|
||||||
func mustCreateUser(ctx context.Context, db *sql.DB, role string, refererID int64) int64 {
|
|
||||||
res, err := db.ExecContext(ctx, `
|
|
||||||
INSERT INTO `+"`user`"+`
|
|
||||||
(password, algo, avatar, balance, refer_code, referer_id, commission, referral_percentage, only_first_purchase, gift_amount, enable, is_admin, enable_balance_notify, enable_login_notify, enable_subscribe_notify, enable_trade_notify, rules, member_status, remark, created_at, updated_at, salt)
|
|
||||||
VALUES (?, 'default', '', 0, '', ?, 0, 0, true, 0, true, false, true, true, true, true, '', '', ?, ?, ?, 'default')`,
|
|
||||||
inviteGiftMarker, refererID, inviteGiftMarker+"-"+role, time.Now(), time.Now())
|
|
||||||
mustNoErr(err)
|
|
||||||
id, err := res.LastInsertId()
|
|
||||||
mustNoErr(err)
|
|
||||||
_, err = db.ExecContext(ctx, "UPDATE `user` SET refer_code = ?, updated_at = ? WHERE id = ?", fmt.Sprintf("codex%d", id), time.Now(), id)
|
|
||||||
mustNoErr(err)
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
|
|
||||||
func mustCreateFamily(ctx context.Context, db *sql.DB, ownerID, memberID int64) int64 {
|
|
||||||
res, err := db.ExecContext(ctx, `
|
|
||||||
INSERT INTO user_family
|
|
||||||
(owner_user_id, max_members, status, created_at, updated_at)
|
|
||||||
VALUES (?, 3, 1, ?, ?)`, ownerID, time.Now(), time.Now())
|
|
||||||
mustNoErr(err)
|
|
||||||
familyID, err := res.LastInsertId()
|
|
||||||
mustNoErr(err)
|
|
||||||
|
|
||||||
now := time.Now()
|
|
||||||
_, err = db.ExecContext(ctx, `
|
|
||||||
INSERT INTO user_family_member
|
|
||||||
(family_id, user_id, role, status, join_source, joined_at, created_at, updated_at)
|
|
||||||
VALUES
|
|
||||||
(?, ?, 1, 1, ?, ?, ?, ?),
|
|
||||||
(?, ?, 2, 1, ?, ?, ?, ?)`,
|
|
||||||
familyID, ownerID, inviteGiftMarker, now, now, now,
|
|
||||||
familyID, memberID, inviteGiftMarker, now, now, now)
|
|
||||||
mustNoErr(err)
|
|
||||||
return familyID
|
|
||||||
}
|
|
||||||
|
|
||||||
func mustCreateUserSubscribe(ctx context.Context, db *sql.DB, userID, planID int64, expire time.Time) int64 {
|
|
||||||
token := fmt.Sprintf("%s-token-%d-%d", inviteGiftMarker, userID, time.Now().UnixNano())
|
|
||||||
uuid := fmt.Sprintf("%08d-0000-4000-8000-%012d", userID, time.Now().UnixNano()%1_000_000_000_000)
|
|
||||||
res, err := db.ExecContext(ctx, `
|
|
||||||
INSERT INTO user_subscribe
|
|
||||||
(user_id, order_id, subscribe_id, node_group_id, group_locked, traffic, download, upload, expired_download, expired_upload, token, uuid, status, note, created_at, updated_at, start_time, expire_time)
|
|
||||||
VALUES (?, 0, ?, 0, false, 1073741824, 0, 0, 0, 0, ?, ?, 1, ?, ?, ?, ?, ?)`,
|
|
||||||
userID, planID, token, uuid, inviteGiftMarker, time.Now(), time.Now(), time.Now().Add(-time.Hour), expire)
|
|
||||||
mustNoErr(err)
|
|
||||||
id, err := res.LastInsertId()
|
|
||||||
mustNoErr(err)
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
|
|
||||||
func mustExpire(ctx context.Context, db *sql.DB, subID int64) time.Time {
|
|
||||||
var expire time.Time
|
|
||||||
mustNoErr(db.QueryRowContext(ctx, "SELECT expire_time FROM user_subscribe WHERE id = ?", subID).Scan(&expire))
|
|
||||||
return expire
|
|
||||||
}
|
|
||||||
|
|
||||||
func mustGiftLogCount(ctx context.Context, db *sql.DB, orderNo string) int64 {
|
|
||||||
var count int64
|
|
||||||
mustNoErr(db.QueryRowContext(ctx, "SELECT COUNT(*) FROM system_logs WHERE type = 34 AND content LIKE ?", "%"+orderNo+"%").Scan(&count))
|
|
||||||
return count
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertCommission(ctx context.Context, db *sql.DB, userID int64, want int64) {
|
|
||||||
var got int64
|
|
||||||
mustNoErr(db.QueryRowContext(ctx, "SELECT commission FROM `user` WHERE id = ?", userID).Scan(&got))
|
|
||||||
if got != want {
|
|
||||||
exitf("commission mismatch: user=%d got=%d want=%d", userID, got, want)
|
|
||||||
}
|
|
||||||
fmt.Printf("PASS commission user=%d amount=%d\n", userID, got)
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertLogCount(ctx context.Context, db *sql.DB, label string, logType uint8, orderNo string, want int64) {
|
|
||||||
var got int64
|
|
||||||
mustNoErr(db.QueryRowContext(ctx, "SELECT COUNT(*) FROM system_logs WHERE type = ? AND content LIKE ?", logType, "%"+orderNo+"%").Scan(&got))
|
|
||||||
if got != want {
|
|
||||||
exitf("%s log count mismatch: got=%d want=%d", label, got, want)
|
|
||||||
}
|
|
||||||
fmt.Printf("PASS %s logs=%d\n", label, got)
|
|
||||||
}
|
|
||||||
|
|
||||||
func cleanup(ctx context.Context, db *sql.DB) error {
|
|
||||||
stmts := []string{
|
|
||||||
"DELETE FROM user_family_member WHERE join_source = '" + inviteGiftMarker + "'",
|
|
||||||
"DELETE FROM user_family WHERE owner_user_id IN (SELECT id FROM `user` WHERE remark LIKE '" + inviteGiftMarker + "%')",
|
|
||||||
"DELETE FROM system_logs WHERE type IN (33, 34) AND content LIKE '%" + inviteGiftMarker + "%'",
|
|
||||||
"DELETE FROM user_subscribe WHERE note = '" + inviteGiftMarker + "' OR token LIKE '" + inviteGiftMarker + "%'",
|
|
||||||
"DELETE FROM subscribe WHERE name LIKE '" + inviteGiftMarker + "%'",
|
|
||||||
"DELETE FROM `user` WHERE remark LIKE '" + inviteGiftMarker + "%'",
|
|
||||||
}
|
|
||||||
for _, stmt := range stmts {
|
|
||||||
if _, err := db.ExecContext(ctx, stmt); err != nil {
|
|
||||||
return fmt.Errorf("%s: %w", stmt, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func mustNoErr(err error) {
|
|
||||||
if err != nil {
|
|
||||||
exitf("%v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func exitf(format string, args ...interface{}) {
|
|
||||||
msg := fmt.Sprintf(format, args...)
|
|
||||||
fmt.Fprintln(os.Stderr, "FAIL:", strings.TrimSpace(msg))
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
35
说明文档.md
35
说明文档.md
@ -1,35 +0,0 @@
|
|||||||
# 说明文档.md
|
|
||||||
|
|
||||||
## 项目规划
|
|
||||||
本任务旨在帮助用户在云端服务器上替换 SSL 证书。用户已解压 `airoport.co_ecc.zip`,需将得到的 `.cer` 和 `.key` 文件替换到 `/etc/letsencrypt/archive/airoport.co` 目录下对应的 `.pem` 文件中。
|
|
||||||
|
|
||||||
## 实施方案
|
|
||||||
1. **文件对应关系确认**:映射解压后的文件与 `archive` 目录下的 PEM 文件。
|
|
||||||
2. **备份与替换**:备份旧证书,执行 `cp` 命令覆盖现有文件。
|
|
||||||
3. **服务重启建议**:提示用户替换后需重启 Nginx/Gateway 服务以使证书生效。
|
|
||||||
|
|
||||||
## 进度记录
|
|
||||||
| 时间节点 | 任务说明 | 进度 | 结果说明 |
|
|
||||||
| :--- | :--- | :--- | :--- |
|
|
||||||
| 2026-03-11 | 初始化文档并提供指令 | [x] 已完成 | 已提供查找日志的命令并记录在文档中 |
|
|
||||||
| 2026-03-11 | 提供今天所有 ERROR 报错指令 | [x] 已完成 | 已提供根据日期过滤 ERROR 的命令 |
|
|
||||||
| 2026-03-12 | 分析并确认 Unknown column 错误 | [x] 已完成 | 确认为 `user_device` 缺少 `short_code` 字段,已提供 SQL |
|
|
||||||
| 2026-03-12 | 提供 SSL 证书替换指令 | [x] 已完成 | 已提供备份与替换证书的组合指令 |
|
|
||||||
| 2026-03-17 | 合并 internal 到 internal/main | [x] 已完成 | 已查验均为fast-forward,受限网络/权限,需手动push完成合并 |
|
|
||||||
| 2026-04-14 | 排查支付成功但订阅未下发问题 | [x] 已完成 | 已提供 Docker 相关的日志排查与数据库核对命令 |
|
|
||||||
|
|
||||||
certbot certonly --manual --preferred-challenges dns -d airoport.win -d "*.airoport.win" -d hifastapp.com
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
gunzip -c mysql_dump_20260318_052811.sql.gz \
|
|
||||||
| docker exec -i ppanel-mysql mysql -uroot -pjpcV41ppanel
|
|
||||||
|
|
||||||
|
|
||||||
go run scripts/migrate_paid_users.go -src 'root:rootpassword@tcp(127.0.0.1:3306)/ppanel?charset=utf8mb4&parseTime=True&loc=Local' -dst 'root:jpcV41ppanel@tcp(103.150.215.44:3306)/hifast?charset=utf8mb4&parseTime=True&loc=Local' -clean
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
docker exec ppanel-redis redis-cli --scan --pattern "*" \
|
|
||||||
grep -vE "^auth:session_id:|^auth:user_sessions:" \
|
|
||||||
xargs -r -n 500 docker exec -i ppanel-redis redis-cli DEL
|
|
||||||
Loading…
x
Reference in New Issue
Block a user