Compare commits
163 Commits
master
..
07409eb602
| Author | SHA1 | Date | |
|---|---|---|---|
| 07409eb602 | |||
| 117dc0d6a7 | |||
| c92495c5b9 | |||
| 92cf2921dd | |||
| ce3babcc33 | |||
| a46fb83054 | |||
| 9933d34bdd | |||
| d2710d356f | |||
| f0a5288e20 | |||
| 77fa0cadd2 | |||
| 3d1a31a19f | |||
| 08434cfa32 | |||
| be09a115ec | |||
| 077dba3d98 | |||
| 39bd36b2f8 | |||
| cfb253d96f | |||
| f11097ab83 | |||
| 3e6318dcdf | |||
| c39bfd39dd | |||
| de388ac1ef | |||
| 6157b2c571 | |||
| 268ae1ebf8 | |||
| f5ad26b943 | |||
| 9b5ff89ee8 | |||
| e74958e17f | |||
| 21811f4d63 | |||
| 24caf58987 | |||
| b98d718f3c | |||
| bcb8cd222c | |||
| 34cd1c524e | |||
| 377f13da48 | |||
| 5e4cc33ff6 | |||
| 53fb541846 | |||
| f6f2ca9a29 | |||
| 19d28a8f89 | |||
| 8ff992e74c | |||
| 84d222576a | |||
| cfc9cf790b | |||
| c540091ef9 | |||
| c837999573 | |||
| e33af1450b | |||
| e567821e07 | |||
| 5e30794db1 | |||
| ccbdab55aa | |||
| ed181886dd | |||
| 9ddd8257c5 | |||
| fc6f193479 | |||
| 3df59ef345 | |||
| 87ebfa1fac | |||
| ac25eb4d91 | |||
| 54379976ec | |||
| 750b7be424 | |||
| aa11588c8f | |||
| c3050821d5 | |||
| 5b9f384f81 | |||
| 7236ca4cf2 | |||
| ae126296e3 | |||
| 1e99cfb83c | |||
| 0659a930f8 | |||
| c2d1b5a0d8 | |||
| 3644e9ce3f | |||
| e5d6539d79 | |||
| e17dc4a273 | |||
| b162022d39 | |||
| 075c1215ca | |||
| 8ba4471791 | |||
| 3e265bd837 | |||
| fefbd4f56a | |||
| 197fed7d12 | |||
| f452f80100 | |||
| 1022160ff8 | |||
| 82eff47f38 | |||
| d351b50066 | |||
| 4366a9be8b | |||
| b5e50d1ee5 | |||
| 02b41e7a2c | |||
| d12c340743 | |||
| c90edac630 | |||
| b9192db042 | |||
| 48e507783e | |||
| d6efcb8e0b | |||
| d2f9289338 | |||
| 92e303aaa7 | |||
| 54328b197d | |||
| 5ebfe0a981 | |||
| 22d9773f1b | |||
| d89798f001 | |||
| 81c3059892 | |||
| f9fa4756e9 | |||
| 27f1203282 | |||
| f7f890c990 | |||
| 8b4e7561f4 | |||
| a6e9e2bdb8 | |||
| b798f520c8 | |||
| bae234fe15 | |||
| 79ab4460bc | |||
| 0169a16ada | |||
| b6b5bccde6 | |||
| eba256bdc9 | |||
| 72f2b94263 | |||
| bb67ebcb79 | |||
| 0bd7560b64 | |||
| a0f2d8a7b8 | |||
| 30221232c9 | |||
| 80751eb8ef | |||
| 3bbce5ce84 | |||
| 1726a584fa | |||
| fd522b6c71 | |||
| a1184ef5ed | |||
| 7c6efe9dfe | |||
| c0ece054a0 | |||
| 0d57450283 | |||
| f2033fd4b9 | |||
| 3284cb45f0 | |||
| 6041bc3419 | |||
| 4581a6fc17 | |||
| 2bdce44e12 | |||
| 4fa9fcd232 | |||
| f6911965dc | |||
| c4b2ebf7e1 | |||
| f946504cb8 | |||
| 7d2f98b7c9 | |||
| 8bc8e81e95 | |||
| 54daa923da | |||
| 463d3e2315 | |||
| 559d59b4f8 | |||
| d4f0d559cf | |||
| cbb451d18c | |||
| 595e4c62f9 | |||
| 9dd5dcb9d2 | |||
| 110c97ada4 | |||
| d748a7e75d | |||
| cf70838142 | |||
| 280437be91 | |||
| 59b7056a20 | |||
| 769622f087 | |||
| 91935e3109 | |||
| 3b3ed7b3c1 | |||
| b52e01eaa2 | |||
| 32e3dc3c73 | |||
| 6b64e8c461 | |||
| 47696b9e68 | |||
| 79427c9f4c | |||
| bcefb274ab | |||
| 3ae85f68ea | |||
| ac57272018 | |||
| 68c7b0a8ec | |||
| 0ec0e2b9d2 | |||
| ab38cd4943 | |||
| 5b49aa8242 | |||
| 9db4762904 | |||
| ae62ecc6b3 | |||
| 4b73cd4d3c | |||
| 2c9833df58 | |||
| 23a7a292ef | |||
| f1bfc78d66 | |||
| 9912df9ac6 | |||
| bafb13cf06 | |||
| 9a8ae8b6fd | |||
| c8258dc93b | |||
| c0d839deb9 | |||
| 800f9c8460 | |||
| 954b19c332 |
@@ -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"]
|
||||
}
|
||||
+2
-8
@@ -1,11 +1,5 @@
|
||||
# 复制此文件为 .env 并填写真实值
|
||||
# cp .env.example .env
|
||||
|
||||
# MySQL root 密码(同时需要在 configs/ppanel.yaml 的 MySQL.Password 中填写相同的值)
|
||||
MYSQL_ROOT_PASSWORD=CHANGE_ME_TO_STRONG_PASSWORD
|
||||
|
||||
# Grafana 管理员密码
|
||||
GRAFANA_PASSWORD=CHANGE_ME_TO_STRONG_PASSWORD
|
||||
|
||||
# PPanel Server 镜像标签(留空使用 latest)
|
||||
PPANEL_SERVER_TAG=latest
|
||||
# PPanel Server 镜像标签(由 CI/CD 传入不可变 tag,如 git SHA)
|
||||
PPANEL_SERVER_TAG=CHANGE_ME_TO_GIT_SHA
|
||||
|
||||
@@ -1,236 +0,0 @@
|
||||
name: Build docker and publish
|
||||
run-name: 简化的Docker构建和部署流程
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- internal
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- internal
|
||||
|
||||
env:
|
||||
# Docker镜像仓库
|
||||
REPO: ${{ vars.REPO || 'registry.kxsw.us/vpn-server' }}
|
||||
# SSH连接信息 (根据分支自动选择)
|
||||
SSH_HOST: ${{ github.ref_name == 'main' && vars.SSH_HOST || vars.DEV_SSH_HOST }}
|
||||
SSH_PORT: ${{ vars.SSH_PORT }}
|
||||
SSH_USER: ${{ vars.SSH_USER }}
|
||||
SSH_PASSWORD: ${{ github.ref_name == 'main' && vars.SSH_PASSWORD || vars.DEV_SSH_PASSWORD }}
|
||||
# TG通知
|
||||
TG_BOT_TOKEN: 8114337882:AAHkEx03HSu7RxN4IHBJJEnsK9aPPzNLIk0
|
||||
TG_CHAT_ID: "-4940243803"
|
||||
# Go构建变量
|
||||
SERVICE: vpn
|
||||
SERVICE_STYLE: vpn
|
||||
VERSION: ${{ github.sha }}
|
||||
BUILDTIME: ${{ github.event.head_commit.timestamp }}
|
||||
GOARCH: amd64
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ario-server
|
||||
container:
|
||||
image: node:20
|
||||
strategy:
|
||||
matrix:
|
||||
# 只有node支持版本号别名
|
||||
node: ['20.15.1']
|
||||
steps:
|
||||
# 步骤1: 下载代码
|
||||
- name: 📥 下载代码
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# 步骤2: 设置动态环境变量
|
||||
- name: ⚙️ 设置动态环境变量
|
||||
run: |
|
||||
if [ "${{ github.ref_name }}" = "main" ]; then
|
||||
echo "DOCKER_TAG_SUFFIX=latest" >> $GITHUB_ENV
|
||||
echo "CONTAINER_NAME=ppanel-server" >> $GITHUB_ENV
|
||||
echo "DEPLOY_PATH=/root/bindbox" >> $GITHUB_ENV
|
||||
echo "为 main 分支设置生产环境变量"
|
||||
elif [ "${{ github.ref_name }}" = "internal" ]; then
|
||||
echo "DOCKER_TAG_SUFFIX=internal" >> $GITHUB_ENV
|
||||
echo "CONTAINER_NAME=ppanel-server-internal" >> $GITHUB_ENV
|
||||
echo "DEPLOY_PATH=/root/bindbox" >> $GITHUB_ENV
|
||||
echo "为 internal 分支设置开发环境变量"
|
||||
else
|
||||
echo "DOCKER_TAG_SUFFIX=${{ github.ref_name }}" >> $GITHUB_ENV
|
||||
echo "CONTAINER_NAME=ppanel-server-${{ github.ref_name }}" >> $GITHUB_ENV
|
||||
echo "DEPLOY_PATH=/root/vpn_server_other" >> $GITHUB_ENV
|
||||
echo "为其他分支 (${{ github.ref_name }}) 设置环境变量"
|
||||
fi
|
||||
|
||||
# 步骤3: 安装系统工具 (curl, jq) 并升级 Docker CLI 到 1.44+
|
||||
- name: 🔧 安装系统工具并升级 Docker CLI
|
||||
run: |
|
||||
set -e
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
echo "等待 apt/dpkg 锁释放 (unattended-upgrades)..."
|
||||
end=$((SECONDS+300))
|
||||
while true; do
|
||||
LOCKS_BUSY=0
|
||||
if pgrep -x unattended-upgrades >/dev/null 2>&1; then LOCKS_BUSY=1; fi
|
||||
if command -v fuser >/dev/null 2>&1; then
|
||||
if fuser /var/lib/dpkg/lock >/dev/null 2>&1 \
|
||||
|| fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1 \
|
||||
|| fuser /var/lib/apt/lists/lock >/dev/null 2>&1; then
|
||||
LOCKS_BUSY=1
|
||||
fi
|
||||
fi
|
||||
if [ "$LOCKS_BUSY" -eq 0 ]; then break; fi
|
||||
if [ $SECONDS -ge $end ]; then
|
||||
echo "等待 apt/dpkg 锁超时,使用 Dpkg::Lock::Timeout 继续..."
|
||||
break
|
||||
fi
|
||||
echo "仍在等待锁释放..."; sleep 5
|
||||
done
|
||||
|
||||
# 基础工具
|
||||
apt-get update -y -o Dpkg::Lock::Timeout=600
|
||||
apt-get install -y -o Dpkg::Lock::Timeout=600 jq curl ca-certificates gnupg lsb-release
|
||||
|
||||
# 移除旧版 docker.io,避免客户端过旧 (API 1.41)
|
||||
if dpkg -s docker.io >/dev/null 2>&1; then
|
||||
apt-get remove -y docker.io || true
|
||||
fi
|
||||
|
||||
# 安装 Docker 官方仓库的 CLI (确保 API >= 1.44)
|
||||
distro_codename=$(. /etc/os-release && echo "$VERSION_CODENAME")
|
||||
install_repo="deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian ${distro_codename} stable"
|
||||
mkdir -p /etc/apt/keyrings
|
||||
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
||||
echo "$install_repo" > /etc/apt/sources.list.d/docker.list
|
||||
apt-get update -y -o Dpkg::Lock::Timeout=600
|
||||
apt-get install -y -o Dpkg::Lock::Timeout=600 docker-ce-cli docker-buildx-plugin
|
||||
|
||||
# 版本检查
|
||||
docker --version || true
|
||||
docker version || true
|
||||
echo "客户端 API 版本:" $(docker version --format '{{.Client.APIVersion}}')
|
||||
|
||||
# 步骤4: 构建并发布到镜像仓库
|
||||
- name: 📤 构建并发布到镜像仓库
|
||||
run: |
|
||||
echo "开始构建并推送镜像..."
|
||||
echo "仓库: ${{ env.REPO }}"
|
||||
echo "版本标签: ${{ env.VERSION }}"
|
||||
echo "分支标签: ${{ env.DOCKER_TAG_SUFFIX }}"
|
||||
|
||||
# 构建镜像,同时打上版本和分支两个标签
|
||||
docker build -f Dockerfile \
|
||||
--platform linux/amd64 \
|
||||
--build-arg TARGETARCH=amd64 \
|
||||
--build-arg VERSION=${{ env.VERSION }} \
|
||||
--build-arg BUILDTIME=${{ env.BUILDTIME }} \
|
||||
-t ${{ env.REPO }}:${{ env.VERSION }} \
|
||||
-t ${{ env.REPO }}:${{ env.DOCKER_TAG_SUFFIX }} \
|
||||
.
|
||||
|
||||
echo "推送版本标签镜像: ${{ env.REPO }}:${{ env.VERSION }}"
|
||||
docker push ${{ env.REPO }}:${{ env.VERSION }}
|
||||
|
||||
echo "推送分支标签镜像: ${{ env.REPO }}:${{ env.DOCKER_TAG_SUFFIX }}"
|
||||
docker push ${{ env.REPO }}:${{ env.DOCKER_TAG_SUFFIX }}
|
||||
|
||||
echo "镜像推送完成"
|
||||
|
||||
# 调试: 打印 SSH 连接信息
|
||||
- name: 🔍 调试 - 打印 SSH 连接信息
|
||||
run: |
|
||||
echo "========== SSH 连接信息调试 =========="
|
||||
echo "当前分支: ${{ github.ref_name }}"
|
||||
echo "SSH_HOST: ${{ env.SSH_HOST }}"
|
||||
echo "SSH_PORT: ${{ env.SSH_PORT }}"
|
||||
echo "SSH_USER: ${{ env.SSH_USER }}"
|
||||
echo "SSH_PASSWORD 长度: ${#SSH_PASSWORD}"
|
||||
echo "SSH_PASSWORD 前3位: $(echo "$SSH_PASSWORD" | cut -c1-3)***"
|
||||
echo "SSH_PASSWORD 完整值: ${{ env.SSH_PASSWORD }}"
|
||||
echo "DEPLOY_PATH: ${{ env.DEPLOY_PATH }}"
|
||||
echo "====================================="
|
||||
|
||||
# 步骤5: 传输配置文件
|
||||
- name: 📂 传输配置文件
|
||||
uses: appleboy/scp-action@v0.1.7
|
||||
with:
|
||||
host: ${{ env.SSH_HOST }}
|
||||
username: ${{ env.SSH_USER }}
|
||||
password: ${{ env.SSH_PASSWORD }}
|
||||
port: ${{ env.SSH_PORT }}
|
||||
source: "docker-compose.cloud.yml"
|
||||
target: "${{ env.DEPLOY_PATH }}/"
|
||||
|
||||
# 步骤6: 连接服务器更新并启动
|
||||
- name: 🚀 连接服务器更新并启动
|
||||
uses: appleboy/ssh-action@v1.0.3
|
||||
with:
|
||||
host: ${{ env.SSH_HOST }}
|
||||
username: ${{ env.SSH_USER }}
|
||||
password: ${{ env.SSH_PASSWORD }}
|
||||
port: ${{ env.SSH_PORT }}
|
||||
timeout: 300s
|
||||
command_timeout: 600s
|
||||
script: |
|
||||
echo "连接服务器成功,开始部署..."
|
||||
echo "部署目录: ${{ env.DEPLOY_PATH }}"
|
||||
echo "部署标签: ${{ env.DOCKER_TAG_SUFFIX }}"
|
||||
|
||||
# 进入部署目录
|
||||
cd ${{ env.DEPLOY_PATH }}
|
||||
|
||||
# 创建/更新环境变量文件
|
||||
# echo "PPANEL_SERVER_TAG=${{ env.DOCKER_TAG_SUFFIX }}" > .env
|
||||
|
||||
# 拉取最新镜像
|
||||
echo "📥 拉取镜像..."
|
||||
docker-compose -f docker-compose.cloud.yml pull ppanel-server
|
||||
|
||||
# 启动服务
|
||||
echo "🚀 启动服务..."
|
||||
docker-compose -f docker-compose.cloud.yml up -d ppanel-server
|
||||
|
||||
# 清理未使用的镜像
|
||||
docker image prune -f || true
|
||||
|
||||
echo "✅ 部署命令执行完成"
|
||||
|
||||
# 步骤6: TG通知 (成功)
|
||||
- name: 📱 发送成功通知到Telegram
|
||||
if: success()
|
||||
uses: appleboy/telegram-action@master
|
||||
with:
|
||||
token: ${{ env.TG_BOT_TOKEN }}
|
||||
to: ${{ env.TG_CHAT_ID }}
|
||||
message: |
|
||||
✅ 部署成功!
|
||||
|
||||
📦 项目: ${{ github.repository }}
|
||||
🌿 分支: ${{ github.ref_name }}
|
||||
📝 提交: ${{ github.sha }}
|
||||
👤 提交者: ${{ github.actor }}
|
||||
🕐 时间: ${{ github.event.head_commit.timestamp }}
|
||||
|
||||
🚀 服务已成功部署到生产环境
|
||||
parse_mode: Markdown
|
||||
|
||||
# 步骤5: TG通知 (失败)
|
||||
- name: 📱 发送失败通知到Telegram
|
||||
if: failure()
|
||||
uses: appleboy/telegram-action@master
|
||||
with:
|
||||
token: ${{ env.TG_BOT_TOKEN }}
|
||||
to: ${{ env.TG_CHAT_ID }}
|
||||
message: |
|
||||
❌ 部署失败!
|
||||
|
||||
📦 项目: ${{ github.repository }}
|
||||
🌿 分支: ${{ github.ref_name }}
|
||||
📝 提交: ${{ github.sha }}
|
||||
👤 提交者: ${{ github.actor }}
|
||||
🕐 时间: ${{ github.event.head_commit.timestamp }}
|
||||
|
||||
⚠️ 请检查构建日志获取详细信息
|
||||
parse_mode: Markdown
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# Code owners — 自动 request review
|
||||
#
|
||||
# 仓库私有 + 当前 plan 不支持 branch protection(详见 doc/development-workflow-zh.md
|
||||
# 「平台层约束的现状」一节),CODEOWNERS 在此用作"自动 request review + 显性责任划分",
|
||||
# 而非强制门禁。
|
||||
#
|
||||
# 任何 PR 默认 request 给 @shanshanzhong147 (owner) review。若后续引入团队
|
||||
# handle(例如 @TawCorp/backend),把对应 path 改成 team handle 即可。
|
||||
|
||||
# 全部路径 — owner 默认 reviewer
|
||||
* @shanshanzhong147
|
||||
|
||||
# 部署/CI/Docker — 改这些要再确认一次(涉及生产部署链路)
|
||||
/.github/ @shanshanzhong147
|
||||
/Dockerfile @shanshanzhong147
|
||||
/docker-compose.*.yml @shanshanzhong147
|
||||
/scripts/ @shanshanzhong147
|
||||
/Makefile @shanshanzhong147
|
||||
|
||||
# 流程文档自身 — 改这里就是改流程
|
||||
/CONTRIBUTING.md @shanshanzhong147
|
||||
/CONTRIBUTING_ZH.md @shanshanzhong147
|
||||
/doc/development-workflow-zh.md @shanshanzhong147
|
||||
/.github/CODEOWNERS @shanshanzhong147
|
||||
@@ -0,0 +1,51 @@
|
||||
<!--
|
||||
完整流程见 doc/development-workflow-zh.md
|
||||
-->
|
||||
|
||||
## 关联 Issue
|
||||
|
||||
Closes HIF-XXX
|
||||
<!-- 如关联多个:Closes HIF-XXX, Closes HIF-YYY -->
|
||||
|
||||
## 改动摘要
|
||||
|
||||
<!-- 1-3 句话说清楚做了什么、为什么 -->
|
||||
|
||||
## 改动细节
|
||||
|
||||
<!-- 按文件/模块逐条列;引用代码用 `file.go:行号` 格式 -->
|
||||
|
||||
-
|
||||
-
|
||||
|
||||
## 测试计划
|
||||
|
||||
- [ ] `go build ./...` 通过
|
||||
- [ ] `go vet ./...` 通过
|
||||
- [ ] `go test -race ./... -count=1` 通过
|
||||
- [ ] golangci-lint 通过
|
||||
- [ ] 新增/修改的逻辑有对应单测覆盖
|
||||
- [ ] (如涉及 DB 变更)migration up/down 双向验证
|
||||
- [ ] (如涉及 API)curl / Postman 验证命令贴在下面
|
||||
|
||||
<!-- 贴 curl 或测试输出 -->
|
||||
|
||||
```
|
||||
```
|
||||
|
||||
## 风险 / 回滚
|
||||
|
||||
<!-- 这次改动失败时怎么回滚;是否影响线上数据;是否需要 feature flag -->
|
||||
|
||||
-
|
||||
|
||||
## Reviewer 自检清单
|
||||
|
||||
- [ ] PR 标题符合 commitlint 规范(`修复/新功能/重构/文档/配置(#<num>): ...`)
|
||||
- [ ] 分支命名 `fix/<num>-…` / `feat/<num>-…` / `chore/…`
|
||||
- [ ] 目标分支 = `internal`
|
||||
- [ ] 改动 scope 与 Issue 描述一致,无 scope creep
|
||||
- [ ] **无无关代码改动**(架构师红线)
|
||||
- [ ] 无密钥/凭证泄露
|
||||
- [ ] CI 全绿
|
||||
- [ ] 测试工程师已验收(如涉及业务逻辑)
|
||||
@@ -1,27 +0,0 @@
|
||||
# Production Environment Configuration for GitHub Actions
|
||||
# This file defines production-specific deployment settings
|
||||
|
||||
environment:
|
||||
name: production
|
||||
url: https://api.ppanel.example.com
|
||||
protection_rules:
|
||||
- type: wait_timer
|
||||
minutes: 5
|
||||
- type: reviewers
|
||||
reviewers:
|
||||
- "@admin-team"
|
||||
- "@devops-team"
|
||||
variables:
|
||||
ENVIRONMENT: production
|
||||
LOG_LEVEL: info
|
||||
DEPLOY_TIMEOUT: 300
|
||||
|
||||
# Environment-specific secrets required:
|
||||
# PRODUCTION_HOST - Production server hostname/IP
|
||||
# PRODUCTION_USER - SSH username for production server
|
||||
# PRODUCTION_SSH_KEY - SSH private key for production server
|
||||
# PRODUCTION_PORT - SSH port (default: 22)
|
||||
# PRODUCTION_URL - Application URL for health checks
|
||||
# DATABASE_PASSWORD - Production database password
|
||||
# REDIS_PASSWORD - Production Redis password
|
||||
# JWT_SECRET - JWT secret key for production
|
||||
@@ -1,23 +0,0 @@
|
||||
# Staging Environment Configuration for GitHub Actions
|
||||
# This file defines staging-specific deployment settings
|
||||
|
||||
environment:
|
||||
name: staging
|
||||
url: https://staging-api.ppanel.example.com
|
||||
protection_rules:
|
||||
- type: wait_timer
|
||||
minutes: 2
|
||||
variables:
|
||||
ENVIRONMENT: staging
|
||||
LOG_LEVEL: debug
|
||||
DEPLOY_TIMEOUT: 180
|
||||
|
||||
# Environment-specific secrets required:
|
||||
# STAGING_HOST - Staging server hostname/IP
|
||||
# STAGING_USER - SSH username for staging server
|
||||
# STAGING_SSH_KEY - SSH private key for staging server
|
||||
# STAGING_PORT - SSH port (default: 22)
|
||||
# STAGING_URL - Application URL for health checks
|
||||
# DATABASE_PASSWORD - Staging database password
|
||||
# REDIS_PASSWORD - Staging Redis password
|
||||
# JWT_SECRET - JWT secret key for staging
|
||||
@@ -0,0 +1,72 @@
|
||||
name: 持续集成
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- internal
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
name: 构建/Vet/测试
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
|
||||
steps:
|
||||
- name: 检出代码
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: 配置 Go 环境
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: true
|
||||
|
||||
- name: 下载依赖模块
|
||||
run: go mod download
|
||||
|
||||
- name: 构建
|
||||
run: go build ./...
|
||||
|
||||
- name: 运行 go vet
|
||||
run: go vet ./...
|
||||
|
||||
- name: 运行测试
|
||||
run: go test -race -count=1 ./...
|
||||
|
||||
lint:
|
||||
name: golangci-lint
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- name: 检出代码
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
# Fetch base ref so golangci-lint can diff against it for only-new-issues.
|
||||
fetch-depth: 0
|
||||
|
||||
- name: 配置 Go 环境
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: true
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v9
|
||||
with:
|
||||
version: latest
|
||||
args: --timeout=5m
|
||||
# Legacy codebase has ~47 pre-existing lint issues (errcheck / unused
|
||||
# carried over from upstream perfect-panel/server). Only flag NEW
|
||||
# issues introduced by this PR so CI stays useful without forcing a
|
||||
# mass cleanup. Backlog cleanup tracked separately.
|
||||
only-new-issues: true
|
||||
@@ -1,79 +0,0 @@
|
||||
name: Build Linux Binary
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, master ]
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Version to build (leave empty for auto)'
|
||||
required: false
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build Linux Binary
|
||||
runs-on: ario-server
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.23.3'
|
||||
cache: true
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
CGO_ENABLED: 0
|
||||
GOOS: linux
|
||||
GOARCH: amd64
|
||||
run: |
|
||||
VERSION=${{ github.event.inputs.version }}
|
||||
if [ -z "$VERSION" ]; then
|
||||
VERSION=$(git describe --tags --always --dirty)
|
||||
fi
|
||||
|
||||
echo "Building ppanel-server $VERSION"
|
||||
BUILD_TIME=$(date +"%Y-%m-%d_%H:%M:%S")
|
||||
go build -ldflags="-w -s -X github.com/perfect-panel/server/pkg/constant.Version=$VERSION -X github.com/perfect-panel/server/pkg/constant.BuildTime=$BUILD_TIME" -o ppanel-server ./ppanel.go
|
||||
tar -czf ppanel-server-${VERSION}-linux-amd64.tar.gz ppanel-server
|
||||
sha256sum ppanel-server ppanel-server-${VERSION}-linux-amd64.tar.gz > checksum.txt
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ppanel-server-linux-amd64
|
||||
path: |
|
||||
ppanel-server
|
||||
ppanel-server-*-linux-amd64.tar.gz
|
||||
checksum.txt
|
||||
|
||||
- name: Create Release
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
VERSION=${GITHUB_REF#refs/tags/}
|
||||
|
||||
# Check if release exists
|
||||
if gh release view $VERSION >/dev/null 2>&1; then
|
||||
echo "Release $VERSION already exists, deleting old assets..."
|
||||
# Delete existing assets if they exist
|
||||
gh release delete-asset $VERSION ppanel-server-${VERSION}-linux-amd64.tar.gz --yes 2>/dev/null || true
|
||||
gh release delete-asset $VERSION checksum.txt --yes 2>/dev/null || true
|
||||
else
|
||||
echo "Creating new release $VERSION..."
|
||||
gh release create $VERSION --title "PPanel Server $VERSION" --notes "Release $VERSION"
|
||||
fi
|
||||
|
||||
# Upload assets (will overwrite if --clobber is supported, otherwise will fail gracefully)
|
||||
echo "Uploading assets..."
|
||||
gh release upload $VERSION ppanel-server-${VERSION}-linux-amd64.tar.gz checksum.txt --clobber
|
||||
@@ -0,0 +1,253 @@
|
||||
name: 测试环境部署
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- internal
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: deploy-staging-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
REGISTRY_HOST: ${{ vars.REGISTRY_HOST || 'registry.kxsw.us' }}
|
||||
IMAGE_NAME: ${{ vars.REGISTRY_IMAGE || 'registry.kxsw.us/vpn-server' }}
|
||||
STAGING_HOST: ${{ vars.STAGING_HOST || '154.12.35.103' }}
|
||||
STAGING_DEPLOY_PATH: ${{ vars.STAGING_DEPLOY_PATH || '/opt/hifast-server' }}
|
||||
STAGING_HEALTHCHECK_URL: ${{ vars.STAGING_HEALTHCHECK_URL || 'http://127.0.0.1:8080/v1/common/heartbeat' }}
|
||||
# 关闭 docker/build-push-action 自动生成的英文 job summary
|
||||
# 我们用 Telegram 通知传递部署结果,不需要 GitHub Actions 页面上那段英文
|
||||
DOCKER_BUILD_SUMMARY: false
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
name: 构建镜像并部署到测试环境
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
- name: 检出代码
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: 收集发布说明
|
||||
id: release-notes
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [ "${{ github.event_name }}" = "push" ] && [ "${{ github.event.before }}" != "0000000000000000000000000000000000000000" ]; then
|
||||
RANGE="${{ github.event.before }}..${{ github.sha }}"
|
||||
else
|
||||
RANGE="-5"
|
||||
fi
|
||||
|
||||
NOTES="$(git log "$RANGE" --pretty=format:'- %h %s' --no-merges | head -20)"
|
||||
if [ -z "$NOTES" ]; then
|
||||
NOTES="$(git log -1 --pretty=format:'- %h %s')"
|
||||
fi
|
||||
|
||||
{
|
||||
echo "notes<<EOF"
|
||||
echo "$NOTES"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: 配置 Go 环境
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: true
|
||||
|
||||
- name: 运行测试
|
||||
run: go test ./...
|
||||
|
||||
- name: 配置 Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: 构建并推送镜像
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
build-args: |
|
||||
TARGETARCH=amd64
|
||||
VERSION=${{ github.sha }}
|
||||
tags: |
|
||||
${{ env.IMAGE_NAME }}:${{ github.sha }}
|
||||
${{ env.IMAGE_NAME }}:staging
|
||||
|
||||
- name: 上传 compose 配置
|
||||
uses: appleboy/scp-action@v1.0.0
|
||||
with:
|
||||
host: ${{ env.STAGING_HOST }}
|
||||
username: ${{ secrets.STAGING_SSH_USER }}
|
||||
password: ${{ secrets.STAGING_SSH_PASSWORD }}
|
||||
port: ${{ secrets.STAGING_SSH_PORT || 22 }}
|
||||
source: docker-compose.cloud.yml
|
||||
target: /tmp/hifast-server-deploy/
|
||||
|
||||
- name: 在测试服务器上部署
|
||||
uses: appleboy/ssh-action@v1.2.5
|
||||
with:
|
||||
host: ${{ env.STAGING_HOST }}
|
||||
username: ${{ secrets.STAGING_SSH_USER }}
|
||||
password: ${{ secrets.STAGING_SSH_PASSWORD }}
|
||||
port: ${{ secrets.STAGING_SSH_PORT || 22 }}
|
||||
timeout: 300s
|
||||
command_timeout: 600s
|
||||
script: |
|
||||
set -euo pipefail
|
||||
|
||||
IMAGE_NAME="${{ env.IMAGE_NAME }}"
|
||||
NEW_TAG="${{ github.sha }}"
|
||||
DEPLOY_PATH="${{ env.STAGING_DEPLOY_PATH }}"
|
||||
HEALTHCHECK_URL="${{ env.STAGING_HEALTHCHECK_URL }}"
|
||||
ROLLBACK_TAG="rollback-${NEW_TAG}"
|
||||
|
||||
if command -v sudo >/dev/null 2>&1 && ! docker ps >/dev/null 2>&1; then
|
||||
SUDO="sudo"
|
||||
else
|
||||
SUDO=""
|
||||
fi
|
||||
|
||||
docker_cmd() {
|
||||
if [ -n "$SUDO" ]; then
|
||||
sudo docker "$@"
|
||||
else
|
||||
docker "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
compose_cmd() {
|
||||
tag="$1"
|
||||
shift
|
||||
if docker compose version >/dev/null 2>&1; then
|
||||
if [ -n "$SUDO" ]; then
|
||||
sudo env PPANEL_SERVER_IMAGE="$IMAGE_NAME" PPANEL_SERVER_TAG="$tag" docker compose -f docker-compose.cloud.yml "$@"
|
||||
else
|
||||
PPANEL_SERVER_IMAGE="$IMAGE_NAME" PPANEL_SERVER_TAG="$tag" docker compose -f docker-compose.cloud.yml "$@"
|
||||
fi
|
||||
else
|
||||
if [ -n "$SUDO" ]; then
|
||||
sudo env PPANEL_SERVER_IMAGE="$IMAGE_NAME" PPANEL_SERVER_TAG="$tag" docker-compose -f docker-compose.cloud.yml "$@"
|
||||
else
|
||||
PPANEL_SERVER_IMAGE="$IMAGE_NAME" PPANEL_SERVER_TAG="$tag" docker-compose -f docker-compose.cloud.yml "$@"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
health_check() {
|
||||
attempt=1
|
||||
while [ "$attempt" -le 6 ]; do
|
||||
if curl -fsS "$HEALTHCHECK_URL"; then
|
||||
echo
|
||||
return 0
|
||||
fi
|
||||
echo "Health check ${attempt}/6 failed; retrying in 10s..."
|
||||
attempt=$((attempt + 1))
|
||||
sleep 10
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
if [ -n "$SUDO" ]; then
|
||||
sudo mkdir -p "$DEPLOY_PATH"
|
||||
sudo cp /tmp/hifast-server-deploy/docker-compose.cloud.yml "$DEPLOY_PATH/docker-compose.cloud.yml"
|
||||
else
|
||||
mkdir -p "$DEPLOY_PATH"
|
||||
cp /tmp/hifast-server-deploy/docker-compose.cloud.yml "$DEPLOY_PATH/docker-compose.cloud.yml"
|
||||
fi
|
||||
|
||||
cd "$DEPLOY_PATH"
|
||||
|
||||
PREVIOUS_IMAGE_ID="$(docker_cmd inspect --format '{{.Image}}' ppanel-server 2>/dev/null || true)"
|
||||
echo "Previous image ID: ${PREVIOUS_IMAGE_ID:-none}"
|
||||
|
||||
echo "Pulling ${IMAGE_NAME}:${NEW_TAG}"
|
||||
compose_cmd "$NEW_TAG" pull ppanel-server
|
||||
|
||||
echo "Starting ppanel-server"
|
||||
compose_cmd "$NEW_TAG" up -d ppanel-server
|
||||
|
||||
echo "Checking ${HEALTHCHECK_URL}"
|
||||
if health_check; then
|
||||
docker_cmd image prune -f || true
|
||||
echo "Staging deployment succeeded"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Health check failed; attempting rollback"
|
||||
if [ -n "$PREVIOUS_IMAGE_ID" ]; then
|
||||
docker_cmd tag "$PREVIOUS_IMAGE_ID" "${IMAGE_NAME}:${ROLLBACK_TAG}"
|
||||
compose_cmd "$ROLLBACK_TAG" up -d ppanel-server
|
||||
health_check || true
|
||||
else
|
||||
echo "No previous image found; rollback skipped"
|
||||
fi
|
||||
|
||||
docker_cmd image prune -f || true
|
||||
exit 1
|
||||
|
||||
- name: Telegram 成功通知
|
||||
if: success()
|
||||
env:
|
||||
TG_BOT_TOKEN: ${{ secrets.TG_BOT_TOKEN }}
|
||||
TG_CHAT_ID: ${{ secrets.TG_CHAT_ID }}
|
||||
run: |
|
||||
if [ -z "${TG_BOT_TOKEN:-}" ] || [ -z "${TG_CHAT_ID:-}" ]; then
|
||||
echo "Telegram 密钥未配置,跳过通知。"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
MESSAGE="$(cat <<'EOF'
|
||||
✅ 测试环境发布成功
|
||||
仓库:${{ github.repository }}
|
||||
分支:${{ github.ref_name }}
|
||||
提交:${{ github.sha }}
|
||||
操作人:${{ github.actor }}
|
||||
镜像:${{ env.IMAGE_NAME }}:${{ github.sha }}
|
||||
本次更新:
|
||||
${{ steps.release-notes.outputs.notes }}
|
||||
运行记录:${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
EOF
|
||||
)"
|
||||
|
||||
curl -fsS -X POST "https://api.telegram.org/bot${TG_BOT_TOKEN}/sendMessage" \
|
||||
--data-urlencode "chat_id=${TG_CHAT_ID}" \
|
||||
--data-urlencode "text=${MESSAGE}" || echo "Telegram 通知发送失败,但发布结果不受影响。"
|
||||
|
||||
- name: Telegram 失败通知
|
||||
if: failure()
|
||||
env:
|
||||
TG_BOT_TOKEN: ${{ secrets.TG_BOT_TOKEN }}
|
||||
TG_CHAT_ID: ${{ secrets.TG_CHAT_ID }}
|
||||
run: |
|
||||
if [ -z "${TG_BOT_TOKEN:-}" ] || [ -z "${TG_CHAT_ID:-}" ]; then
|
||||
echo "Telegram 密钥未配置,跳过通知。"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
MESSAGE="$(cat <<'EOF'
|
||||
❌ 测试环境发布失败
|
||||
仓库:${{ github.repository }}
|
||||
分支:${{ github.ref_name }}
|
||||
提交:${{ github.sha }}
|
||||
操作人:${{ github.actor }}
|
||||
镜像:${{ env.IMAGE_NAME }}:${{ github.sha }}
|
||||
本次更新:
|
||||
${{ steps.release-notes.outputs.notes }}
|
||||
运行记录:${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
EOF
|
||||
)"
|
||||
|
||||
curl -fsS -X POST "https://api.telegram.org/bot${TG_BOT_TOKEN}/sendMessage" \
|
||||
--data-urlencode "chat_id=${TG_CHAT_ID}" \
|
||||
--data-urlencode "text=${MESSAGE}" || echo "Telegram 通知发送失败,工作流失败状态已记录。"
|
||||
@@ -0,0 +1,266 @@
|
||||
name: 发布验收测试
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows:
|
||||
- 测试环境部署
|
||||
types:
|
||||
- completed
|
||||
branches:
|
||||
- internal
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
|
||||
concurrency:
|
||||
group: release-acceptance
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
ACCEPTANCE_ARTIFACT_NAME: release-acceptance-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
ACCEPTANCE_REPORT_DIR: ${{ github.workspace }}/acceptance-artifacts
|
||||
ACCEPTANCE_REPORT_PATH: ${{ github.workspace }}/acceptance-artifacts/acceptance-report.json
|
||||
ACCEPTANCE_TEST_JSON: ${{ github.workspace }}/acceptance-artifacts/go-test.json
|
||||
ACCEPTANCE_FAILURE_LOG: ${{ github.workspace }}/acceptance-artifacts/failure.log
|
||||
ACCEPTANCE_NODE_SERVER_ID: ${{ vars.ACCEPTANCE_NODE_SERVER_ID || '31' }}
|
||||
ACCEPTANCE_NODE_PROTOCOL: ${{ vars.ACCEPTANCE_NODE_PROTOCOL || 'trojan' }}
|
||||
|
||||
jobs:
|
||||
acceptance:
|
||||
name: Staging acceptance
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
env:
|
||||
STAGING_BASE_URL: ${{ secrets.STAGING_BASE_URL || vars.STAGING_BASE_URL || 'https://tapi.hifast.biz' }}
|
||||
if: >-
|
||||
${{
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(
|
||||
github.event.workflow_run.conclusion == 'success' &&
|
||||
github.event.workflow_run.event == 'push'
|
||||
)
|
||||
}}
|
||||
|
||||
steps:
|
||||
- name: 检出代码
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.event.workflow_run.head_sha || github.sha }}
|
||||
|
||||
- name: 配置 Go 环境
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: true
|
||||
|
||||
- name: 准备报告目录
|
||||
run: mkdir -p "$ACCEPTANCE_REPORT_DIR"
|
||||
|
||||
- name: 校验必需配置
|
||||
env:
|
||||
ACCEPTANCE_ADMIN_EMAIL: ${{ secrets.ACCEPTANCE_ADMIN_EMAIL }}
|
||||
ACCEPTANCE_ADMIN_PASSWORD: ${{ secrets.ACCEPTANCE_ADMIN_PASSWORD }}
|
||||
ACCEPTANCE_USER_EMAIL: ${{ secrets.ACCEPTANCE_USER_EMAIL }}
|
||||
ACCEPTANCE_USER_PASSWORD: ${{ secrets.ACCEPTANCE_USER_PASSWORD }}
|
||||
STAGING_BASE_URL: ${{ secrets.STAGING_BASE_URL || vars.STAGING_BASE_URL || 'https://tapi.hifast.biz' }}
|
||||
STAGING_DB_HOST: ${{ secrets.STAGING_DB_HOST }}
|
||||
STAGING_DB_USER: ${{ secrets.STAGING_DB_USER }}
|
||||
STAGING_DB_PASSWORD: ${{ secrets.STAGING_DB_PASSWORD }}
|
||||
STAGING_DB_NAME: ${{ secrets.STAGING_DB_NAME }}
|
||||
STAGING_REDIS_ADDR: ${{ secrets.STAGING_REDIS_ADDR }}
|
||||
STAGING_REDIS_PASSWORD: ${{ secrets.STAGING_REDIS_PASSWORD }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [ -z "${STAGING_BASE_URL:-}" ]; then
|
||||
echo "STAGING_BASE_URL 未配置且默认值不可用" | tee "$ACCEPTANCE_FAILURE_LOG"
|
||||
{
|
||||
echo "## 发布验收测试"
|
||||
echo
|
||||
echo "状态:配置错误"
|
||||
echo
|
||||
echo "- `STAGING_BASE_URL` 不能为空。"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
missing_optional=()
|
||||
optional=(
|
||||
ACCEPTANCE_ADMIN_EMAIL
|
||||
ACCEPTANCE_ADMIN_PASSWORD
|
||||
ACCEPTANCE_USER_EMAIL
|
||||
ACCEPTANCE_USER_PASSWORD
|
||||
STAGING_DB_HOST
|
||||
STAGING_DB_USER
|
||||
STAGING_DB_PASSWORD
|
||||
STAGING_DB_NAME
|
||||
STAGING_REDIS_ADDR
|
||||
STAGING_REDIS_PASSWORD
|
||||
)
|
||||
|
||||
for key in "${optional[@]}"; do
|
||||
if [ -z "${!key:-}" ]; then
|
||||
missing_optional+=("$key")
|
||||
fi
|
||||
done
|
||||
|
||||
: > "$ACCEPTANCE_FAILURE_LOG"
|
||||
|
||||
if [ "${#missing_optional[@]}" -gt 0 ]; then
|
||||
printf '缺少可选 GitHub Actions secrets/vars,部分验收用例将被跳过:\n' | tee -a "$ACCEPTANCE_FAILURE_LOG"
|
||||
printf -- '- %s\n' "${missing_optional[@]}" | tee -a "$ACCEPTANCE_FAILURE_LOG"
|
||||
{
|
||||
echo "## 发布验收测试"
|
||||
echo
|
||||
echo "状态:部分配置缺失"
|
||||
echo
|
||||
echo "缺少以下可选 secrets/vars,对应验收用例会在测试阶段自动跳过:"
|
||||
printf -- '- `%s`\n' "${missing_optional[@]}"
|
||||
echo
|
||||
echo "- `STAGING_BASE_URL`:${STAGING_BASE_URL}"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
else
|
||||
{
|
||||
echo "## 发布验收测试"
|
||||
echo
|
||||
echo "状态:配置检查通过"
|
||||
echo
|
||||
echo "- `STAGING_BASE_URL`:${STAGING_BASE_URL}"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
|
||||
- name: 下载依赖模块
|
||||
run: go mod download
|
||||
|
||||
- name: 运行 acceptance 测试
|
||||
env:
|
||||
ACCEPTANCE_ADMIN_EMAIL: ${{ secrets.ACCEPTANCE_ADMIN_EMAIL }}
|
||||
ACCEPTANCE_ADMIN_PASSWORD: ${{ secrets.ACCEPTANCE_ADMIN_PASSWORD }}
|
||||
ACCEPTANCE_USER_EMAIL: ${{ secrets.ACCEPTANCE_USER_EMAIL }}
|
||||
ACCEPTANCE_USER_PASSWORD: ${{ secrets.ACCEPTANCE_USER_PASSWORD }}
|
||||
ACCEPTANCE_NODE_SECRET: ${{ secrets.ACCEPTANCE_NODE_SECRET }}
|
||||
ACCEPTANCE_RUN_ID: qa_${{ github.run_id }}_${{ github.run_attempt }}
|
||||
STAGING_DB_HOST: ${{ secrets.STAGING_DB_HOST }}
|
||||
STAGING_DB_USER: ${{ secrets.STAGING_DB_USER }}
|
||||
STAGING_DB_PASSWORD: ${{ secrets.STAGING_DB_PASSWORD }}
|
||||
STAGING_DB_NAME: ${{ secrets.STAGING_DB_NAME }}
|
||||
STAGING_REDIS_ADDR: ${{ secrets.STAGING_REDIS_ADDR }}
|
||||
STAGING_REDIS_PASSWORD: ${{ secrets.STAGING_REDIS_PASSWORD }}
|
||||
STAGING_REDIS_DB: ${{ vars.STAGING_REDIS_DB || '0' }}
|
||||
run: |
|
||||
set -o pipefail
|
||||
|
||||
go test -json ./tests/acceptance/... \
|
||||
-staging-url="$STAGING_BASE_URL" \
|
||||
-report-path="$ACCEPTANCE_REPORT_PATH" \
|
||||
> >(tee "$ACCEPTANCE_TEST_JSON") \
|
||||
2> >(tee "$ACCEPTANCE_FAILURE_LOG" >&2)
|
||||
|
||||
- name: 生成测试摘要
|
||||
if: always()
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
{
|
||||
echo "## 发布验收测试"
|
||||
echo
|
||||
echo "- 状态:${{ job.status }}"
|
||||
echo "- Staging URL:${STAGING_BASE_URL}"
|
||||
echo "- Artifact:${ACCEPTANCE_ARTIFACT_NAME}"
|
||||
echo "- Run URL:${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
if [ "${{ github.event_name }}" = "workflow_run" ]; then
|
||||
echo "- Deploy run:${{ github.event.workflow_run.html_url }}"
|
||||
echo "- Deploy SHA:${{ github.event.workflow_run.head_sha }}"
|
||||
else
|
||||
echo "- 手动触发 SHA:${{ github.sha }}"
|
||||
fi
|
||||
echo
|
||||
} | tee "$ACCEPTANCE_REPORT_DIR/summary.md" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
if [ "${{ job.status }}" = "failure" ]; then
|
||||
if [ -s "$ACCEPTANCE_FAILURE_LOG" ]; then
|
||||
{
|
||||
echo
|
||||
echo "Run URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
echo "Artifact: ${ACCEPTANCE_ARTIFACT_NAME}"
|
||||
} >> "$ACCEPTANCE_FAILURE_LOG"
|
||||
else
|
||||
{
|
||||
echo "Acceptance 测试失败。"
|
||||
echo "Run URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
echo "Artifact: ${ACCEPTANCE_ARTIFACT_NAME}"
|
||||
echo
|
||||
tail -100 "$ACCEPTANCE_TEST_JSON" 2>/dev/null || true
|
||||
} > "$ACCEPTANCE_FAILURE_LOG"
|
||||
fi
|
||||
elif [ ! -f "$ACCEPTANCE_FAILURE_LOG" ]; then
|
||||
: > "$ACCEPTANCE_FAILURE_LOG"
|
||||
fi
|
||||
|
||||
- name: 上传 acceptance artifact
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{ env.ACCEPTANCE_ARTIFACT_NAME }}
|
||||
path: |
|
||||
${{ env.ACCEPTANCE_REPORT_PATH }}
|
||||
${{ env.ACCEPTANCE_TEST_JSON }}
|
||||
${{ env.ACCEPTANCE_FAILURE_LOG }}
|
||||
${{ env.ACCEPTANCE_REPORT_DIR }}/summary.md
|
||||
if-no-files-found: warn
|
||||
retention-days: 14
|
||||
|
||||
- name: Telegram 成功通知
|
||||
if: success()
|
||||
env:
|
||||
TG_BOT_TOKEN: ${{ secrets.TG_BOT_TOKEN }}
|
||||
TG_CHAT_ID: ${{ secrets.TG_CHAT_ID }}
|
||||
run: |
|
||||
if [ -z "${TG_BOT_TOKEN:-}" ] || [ -z "${TG_CHAT_ID:-}" ]; then
|
||||
echo "Telegram 密钥未配置,跳过通知。"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
MESSAGE="$(cat <<'EOF'
|
||||
✅ 发布验收测试通过
|
||||
仓库:${{ github.repository }}
|
||||
分支:${{ github.event.workflow_run.head_branch || github.ref_name }}
|
||||
提交:${{ github.event.workflow_run.head_sha || github.sha }}
|
||||
Staging:${{ env.STAGING_BASE_URL }}
|
||||
Artifact:${{ env.ACCEPTANCE_ARTIFACT_NAME }}
|
||||
运行记录:${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
EOF
|
||||
)"
|
||||
|
||||
curl -fsS -X POST "https://api.telegram.org/bot${TG_BOT_TOKEN}/sendMessage" \
|
||||
--data-urlencode "chat_id=${TG_CHAT_ID}" \
|
||||
--data-urlencode "text=${MESSAGE}" || echo "Telegram 通知发送失败,但验收结果不受影响。"
|
||||
|
||||
- name: Telegram 失败通知
|
||||
if: failure()
|
||||
env:
|
||||
TG_BOT_TOKEN: ${{ secrets.TG_BOT_TOKEN }}
|
||||
TG_CHAT_ID: ${{ secrets.TG_CHAT_ID }}
|
||||
run: |
|
||||
if [ -z "${TG_BOT_TOKEN:-}" ] || [ -z "${TG_CHAT_ID:-}" ]; then
|
||||
echo "Telegram 密钥未配置,跳过通知。"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
MESSAGE="$(cat <<'EOF'
|
||||
❌ 发布验收测试失败
|
||||
仓库:${{ github.repository }}
|
||||
分支:${{ github.event.workflow_run.head_branch || github.ref_name }}
|
||||
提交:${{ github.event.workflow_run.head_sha || github.sha }}
|
||||
Staging:${{ env.STAGING_BASE_URL }}
|
||||
Artifact:${{ env.ACCEPTANCE_ARTIFACT_NAME }}
|
||||
运行记录:${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
EOF
|
||||
)"
|
||||
|
||||
curl -fsS -X POST "https://api.telegram.org/bot${TG_BOT_TOKEN}/sendMessage" \
|
||||
--data-urlencode "chat_id=${TG_CHAT_ID}" \
|
||||
--data-urlencode "text=${MESSAGE}" || echo "Telegram 通知发送失败,工作流失败状态已记录。"
|
||||
+10
@@ -26,6 +26,13 @@ Thumbs.db
|
||||
*.crt
|
||||
*.key
|
||||
*.pem
|
||||
*.pub
|
||||
*id_rsa*
|
||||
*id_ed25519*
|
||||
*_bak
|
||||
*.go_bak
|
||||
deploy/**/keys/
|
||||
deliverables/
|
||||
|
||||
# ==================== 日志 ====================
|
||||
*.log
|
||||
@@ -35,6 +42,8 @@ logs/
|
||||
# ==================== 测试 ====================
|
||||
/test/
|
||||
*_test.go
|
||||
!tests/acceptance/*_test.go
|
||||
!internal/handler/subscribe_test.go
|
||||
*_test_config.go
|
||||
**/logtest/
|
||||
*_test.yaml
|
||||
@@ -70,6 +79,7 @@ script/*.sh
|
||||
|
||||
# Codex local configuration
|
||||
.codex/
|
||||
.codex-tmp/
|
||||
|
||||
# Claude Flow runtime data
|
||||
.claude-flow/data/
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
project_name: ppanel
|
||||
version: 1
|
||||
release:
|
||||
prerelease: auto
|
||||
builds:
|
||||
- # If true, skip the build.
|
||||
# Useful for library projects.
|
||||
# Default is false
|
||||
skip: true
|
||||
|
||||
changelog:
|
||||
# Set it to true if you wish to skip the changelog generation.
|
||||
# This may result in an empty release notes on GitHub/GitLab/Gitea.
|
||||
disable: false
|
||||
|
||||
# Changelog generation implementation to use.
|
||||
#
|
||||
# Valid options are:
|
||||
# - `git`: uses `git log`;
|
||||
# - `github`: uses the compare GitHub API, appending the author login to the changelog.
|
||||
# - `gitlab`: uses the compare GitLab API, appending the author name and email to the changelog.
|
||||
# - `github-native`: uses the GitHub release notes generation API, disables the groups feature.
|
||||
#
|
||||
# Defaults to `git`.
|
||||
use: github
|
||||
|
||||
# Sorts the changelog by the commit's messages.
|
||||
# Could either be asc, desc or empty
|
||||
# Default is empty
|
||||
sort: asc
|
||||
|
||||
# Format to use for commit formatting.
|
||||
# Only available when use is one of `github`, `gitea`, or `gitlab`.
|
||||
#
|
||||
# Default: '{{ .SHA }}: {{ .Message }} ({{ with .AuthorUsername }}@{{ . }}{{ else }}{{ .AuthorName }} <{{ .AuthorEmail }}>{{ end }})'.
|
||||
# Extra template fields: `SHA`, `Message`, `AuthorName`, `AuthorEmail`, and
|
||||
# `AuthorUsername`.
|
||||
format: "{{ .Message }}"
|
||||
|
||||
# Group commits messages by given regex and title.
|
||||
# Order value defines the order of the groups.
|
||||
# Proving no regex means all commits will be grouped under the default group.
|
||||
# Groups are disabled when using github-native, as it already groups things by itself.
|
||||
#
|
||||
# Default is no groups.
|
||||
groups:
|
||||
- title: "✨ Features"
|
||||
regexp: "^.*feat[(\\w)]*:+.*$"
|
||||
order: 0
|
||||
- title: "🐛 Bug Fixes"
|
||||
regexp: "^.*fix[(\\w)]*:+.*$"
|
||||
order: 1
|
||||
- title: "🎫 Chores"
|
||||
regexp: "^.*chore[(\\w)]*:+.*$"
|
||||
order: 2
|
||||
- title: "🔨 Refactor"
|
||||
regexp: "^.*refactor[(\\w)]*:+.*$"
|
||||
order: 3
|
||||
- title: "🔧 Build"
|
||||
regexp: "^.*?(ci)(\\(.+\\))??!?:.+$"
|
||||
order: 4
|
||||
- title: "📝 Documentation"
|
||||
regexp: "^.*?docs?(\\(.+\\))??!?:.+$"
|
||||
order: 5
|
||||
- title: "✨ Others"
|
||||
order: 999
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -1,5 +1,7 @@
|
||||
# Pull Request Submission Guidelines
|
||||
|
||||
> **TawCorp internal contributors**: read [`doc/development-workflow-zh.md`](doc/development-workflow-zh.md) first. It documents the canonical end-to-end flow (Multica issue → branch → PR → CI → review → squash merge via GitHub UI → Deploy Staging → QA), agent role boundaries, branch / commit conventions, and the soft-constraint model used in place of branch protection. The guidelines below apply to all contributors (internal and external) as the baseline.
|
||||
|
||||
To ensure the quality of the codebase and maintainability of the project, please follow these guidelines before submitting a Pull Request (PR):
|
||||
|
||||
## 1. PR Title and Description
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# Pull Request 提交须知
|
||||
|
||||
> **TawCorp 内部协作者**:请先阅读 [`doc/development-workflow-zh.md`](doc/development-workflow-zh.md)。该文档定义了从 Multica issue 到 PR 合并到 Deploy Staging 到 QA 验收的端到端流程,以及 agent 角色边界、分支/commit 约定、和不依赖 GitHub branch protection 的软约束模型。下面的通用指南仍然适用,但内部协作以 `doc/development-workflow-zh.md` 为准。
|
||||
|
||||
为了确保代码库的质量和项目的可维护性,在提交 Pull Request(PR)之前,请务必遵循以下准则:
|
||||
|
||||
## 1. PR 标题和描述
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ FROM scratch
|
||||
|
||||
# Copy CA certificates and timezone data
|
||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||
COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /usr/share/zoneinfo/Asia/Shanghai
|
||||
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
|
||||
|
||||
ENV TZ=Asia/Shanghai
|
||||
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
<!--
|
||||
TawCorp internal fork header. Upstream PPanel content begins below.
|
||||
Do not remove this header without updating Multica workspace context and doc/development-workflow-zh.md.
|
||||
-->
|
||||
|
||||
> ### TawCorp hifast-server (internal fork)
|
||||
>
|
||||
> This repository is the **canonical** TawCorp fork of [perfect-panel/server](https://github.com/perfect-panel/server). Migrated from `git.kxsw.us/HI-VPN/hi-server` on **2026-06-03**; the old Gitea remote is deprecated — do not push or pull from it.
|
||||
>
|
||||
> - **Development workflow (required reading for internal contributors)**: [`doc/development-workflow-zh.md`](doc/development-workflow-zh.md)
|
||||
> - **Branch model**: `internal` (dev mainline, auto-deploys to staging) → `main` (release)
|
||||
> - **Merge policy**: PR + architect review + GitHub UI "Squash and merge"; direct push to `internal`/`main` is blocked by `lefthook` pre-push and forbidden by policy
|
||||
> - **Issue tracker**: Multica workspace `Hifast` (prefix `HIF-`)
|
||||
>
|
||||
> External contributors: read [`CONTRIBUTING.md`](CONTRIBUTING.md) for the upstream-compatible baseline.
|
||||
|
||||
---
|
||||
|
||||
# PPanel Server
|
||||
|
||||
<div align="center">
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
syntax = "v1"
|
||||
|
||||
info (
|
||||
title: "Invite API"
|
||||
desc: "API for ppanel"
|
||||
author: "Tension"
|
||||
email: "tension@ppanel.com"
|
||||
version: "0.0.1"
|
||||
)
|
||||
|
||||
import "../types.api"
|
||||
|
||||
type (
|
||||
GetInviteManageListRequest {
|
||||
Page int `form:"page"`
|
||||
Size int `form:"size"`
|
||||
Search string `form:"search"`
|
||||
InviterId int64 `form:"inviter_id"`
|
||||
InviteeId int64 `form:"invitee_id"`
|
||||
}
|
||||
InviteManageRecord {
|
||||
InviterId int64 `json:"inviter_id"`
|
||||
InviterIdentifier string `json:"inviter_identifier"`
|
||||
InviterDeviceNo string `json:"inviter_device_no"`
|
||||
InviteeId int64 `json:"invitee_id"`
|
||||
InviteeIdentifier string `json:"invitee_identifier"`
|
||||
InviteeDeviceNo string `json:"invitee_device_no"`
|
||||
InviteeAvatar string `json:"invitee_avatar"`
|
||||
InviteeEnable bool `json:"invitee_enable"`
|
||||
InvitedAt int64 `json:"invited_at"`
|
||||
OrderCount int64 `json:"order_count"`
|
||||
HasPurchased bool `json:"has_purchased"`
|
||||
InviterCommission int64 `json:"inviter_commission"`
|
||||
InviterGiftDays int64 `json:"inviter_gift_days"`
|
||||
InviteeGiftDays int64 `json:"invitee_gift_days"`
|
||||
}
|
||||
GetInviteManageListResponse {
|
||||
Total int64 `json:"total"`
|
||||
List []InviteManageRecord `json:"list"`
|
||||
}
|
||||
)
|
||||
|
||||
@server (
|
||||
prefix: v1/admin/invite
|
||||
group: admin/invite
|
||||
middleware: AuthMiddleware
|
||||
)
|
||||
service ppanel {
|
||||
@doc "Get invite manage list"
|
||||
@handler GetInviteManageList
|
||||
get /list (GetInviteManageListRequest) returns (GetInviteManageListResponse)
|
||||
}
|
||||
@@ -149,6 +149,35 @@ type (
|
||||
Total int64 `json:"total"`
|
||||
List []CommissionLog `json:"list"`
|
||||
}
|
||||
OrderRefundLog {
|
||||
OrderId int64 `json:"order_id"`
|
||||
OrderNo string `json:"order_no"`
|
||||
OperatorUserId int64 `json:"operator_user_id"`
|
||||
OperatorAuthIdentifier string `json:"operator_auth_identifier,omitempty"`
|
||||
TargetUserId int64 `json:"target_user_id"`
|
||||
UserSubscribeId int64 `json:"user_subscribe_id"`
|
||||
RefererUserId int64 `json:"referer_user_id,omitempty"`
|
||||
CommissionAmount int64 `json:"commission_amount"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
OrderStatusBefore uint8 `json:"order_status_before"`
|
||||
OrderStatusAfter uint8 `json:"order_status_after"`
|
||||
SubscribeStatusBefore uint8 `json:"subscribe_status_before"`
|
||||
SubscribeStatusAfter uint8 `json:"subscribe_status_after"`
|
||||
SubscribeExpireBefore int64 `json:"subscribe_expire_before"`
|
||||
SubscribeExpireAfter int64 `json:"subscribe_expire_after"`
|
||||
CommissionBefore int64 `json:"commission_before"`
|
||||
CommissionAfter int64 `json:"commission_after"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
}
|
||||
FilterOrderRefundLogRequest {
|
||||
FilterLogParams
|
||||
OrderId int64 `form:"order_id,optional"`
|
||||
UserId int64 `form:"user_id,optional"`
|
||||
}
|
||||
FilterOrderRefundLogResponse {
|
||||
Total int64 `json:"total"`
|
||||
List []OrderRefundLog `json:"list"`
|
||||
}
|
||||
GiftLog {
|
||||
Type uint16 `json:"type"`
|
||||
userId int64 `json:"user_id"`
|
||||
@@ -239,6 +268,30 @@ type (
|
||||
OccurredAt int64 `json:"occurred_at"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
}
|
||||
GetLogMessageRawRequest {
|
||||
Id int64 `form:"id" validate:"required"`
|
||||
}
|
||||
GetLogMessageRawResponse {
|
||||
Id int64 `json:"id"`
|
||||
Platform string `json:"platform"`
|
||||
AppVersion string `json:"app_version"`
|
||||
OsName string `json:"os_name"`
|
||||
OsVersion string `json:"os_version"`
|
||||
DeviceId string `json:"device_id"`
|
||||
UserId *int64 `json:"user_id"`
|
||||
SessionId string `json:"session_id"`
|
||||
Level uint8 `json:"level"`
|
||||
ErrorCode string `json:"error_code"`
|
||||
Message string `json:"message"`
|
||||
Stack string `json:"stack"`
|
||||
Context interface{} `json:"context"`
|
||||
ClientIP string `json:"client_ip"`
|
||||
UserAgent string `json:"user_agent"`
|
||||
Locale string `json:"locale"`
|
||||
Digest string `json:"digest"`
|
||||
OccurredAt int64 `json:"occurred_at"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
}
|
||||
)
|
||||
|
||||
@server (
|
||||
@@ -291,6 +344,10 @@ service ppanel {
|
||||
@handler FilterCommissionLog
|
||||
get /commission/list (FilterCommissionLogRequest) returns (FilterCommissionLogResponse)
|
||||
|
||||
@doc "Filter order refund log"
|
||||
@handler FilterOrderRefundLog
|
||||
get /order/refund/list (FilterOrderRefundLogRequest) returns (FilterOrderRefundLogResponse)
|
||||
|
||||
@doc "Filter gift log"
|
||||
@handler FilterGiftLog
|
||||
get /gift/list (FilterGiftLogRequest) returns (FilterGiftLogResponse)
|
||||
@@ -314,5 +371,9 @@ service ppanel {
|
||||
@doc "Get error log message detail"
|
||||
@handler GetErrorLogMessageDetail
|
||||
get /error_message/detail returns (GetErrorLogMessageDetailResponse)
|
||||
|
||||
@doc "Get log message raw detail (temporary)"
|
||||
@handler GetLogMessageRaw
|
||||
get /message/detail (GetLogMessageRawRequest) returns (GetLogMessageRawResponse)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
syntax = "v1"
|
||||
|
||||
info (
|
||||
title: "Lottery Admin API"
|
||||
desc: "Admin-facing lottery endpoints for HIF-3 Stage 1"
|
||||
author: "hifast"
|
||||
version: "0.1.0"
|
||||
)
|
||||
|
||||
import "../types.api"
|
||||
|
||||
@server (
|
||||
prefix: v1/admin/lottery
|
||||
group: admin/lottery
|
||||
middleware: AuthMiddleware
|
||||
)
|
||||
service ppanel {
|
||||
@doc "Create a new activity (status=draft)"
|
||||
@handler CreateLotteryActivity
|
||||
post /activities (CreateAdminLotteryActivityRequest) returns (AdminLotteryActivity)
|
||||
|
||||
@doc "Update mutable activity fields"
|
||||
@handler UpdateLotteryActivity
|
||||
put /activities (UpdateAdminLotteryActivityRequest) returns (AdminLotteryActivity)
|
||||
|
||||
@doc "List activities (paginated)"
|
||||
@handler ListLotteryActivities
|
||||
get /activities (ListAdminLotteryActivitiesRequest) returns (ListAdminLotteryActivitiesResponse)
|
||||
|
||||
@doc "Get one activity"
|
||||
@handler GetLotteryActivity
|
||||
get /activities/detail (AdminActivityIdRequest) returns (AdminLotteryActivity)
|
||||
|
||||
@doc "Publish (draft/paused → running)"
|
||||
@handler PublishLotteryActivity
|
||||
post /activities/publish (AdminActivityIdRequest)
|
||||
|
||||
@doc "Pause (running → paused)"
|
||||
@handler PauseLotteryActivity
|
||||
post /activities/pause (AdminActivityIdRequest)
|
||||
|
||||
@doc "Update eligibility/chance_sources (rule-caps enforced)"
|
||||
@handler UpdateLotteryRules
|
||||
put /activities/rules (UpdateAdminLotteryRulesRequest)
|
||||
|
||||
@doc "Create prize"
|
||||
@handler CreateLotteryPrize
|
||||
post /prizes (CreateAdminLotteryPrizeRequest) returns (AdminLotteryPrize)
|
||||
|
||||
@doc "Update prize"
|
||||
@handler UpdateLotteryPrize
|
||||
put /prizes (UpdateAdminLotteryPrizeRequest) returns (AdminLotteryPrize)
|
||||
|
||||
@doc "Delete prize"
|
||||
@handler DeleteLotteryPrize
|
||||
delete /prizes (AdminPrizeIdRequest)
|
||||
|
||||
@doc "List prizes on an activity"
|
||||
@handler ListLotteryPrizes
|
||||
get /prizes (ListAdminLotteryPrizesRequest) returns (ListAdminLotteryPrizesResponse)
|
||||
|
||||
@doc "Manually grant N chances to a user (idempotent by source_ref)"
|
||||
@handler GrantLotteryChance
|
||||
post /chances/grant (GrantAdminLotteryChanceRequest)
|
||||
|
||||
// Stage 2 (HIF-4): 人工奖工单接口
|
||||
@doc "List manual-claim work orders (filter by type/status/activity/user/time)"
|
||||
@handler ListLotteryClaims
|
||||
get /claims (ListAdminLotteryClaimsRequest) returns (ListAdminLotteryClaimsResponse)
|
||||
|
||||
@doc "Summary counts for claims workbench"
|
||||
@handler LotteryClaimsSummary
|
||||
get /claims/summary returns (AdminLotteryClaimsSummary)
|
||||
|
||||
@doc "Approve a claim (reviewing -> paying)"
|
||||
@handler ApproveLotteryClaim
|
||||
post /claims/approve (AdminApproveClaimRequest)
|
||||
|
||||
@doc "Reject a claim (reviewing/paying -> rejected; user may resubmit)"
|
||||
@handler RejectLotteryClaim
|
||||
post /claims/reject (AdminRejectClaimRequest)
|
||||
|
||||
@doc "Mark as paid (paying -> paid, records tx_hash/delivery_ref)"
|
||||
@handler MarkPaidLotteryClaim
|
||||
post /claims/mark-paid (AdminMarkPaidClaimRequest)
|
||||
}
|
||||
@@ -33,6 +33,10 @@ type (
|
||||
PaymentId int64 `json:"payment_id,omitempty"`
|
||||
TradeNo string `json:"trade_no,omitempty"`
|
||||
}
|
||||
RefundOrderRequest {
|
||||
Id int64 `json:"id" validate:"required"`
|
||||
Reason string `json:"reason,omitempty" validate:"omitempty,max=500"`
|
||||
}
|
||||
ActivateOrderRequest {
|
||||
OrderNo string `json:"order_no" validate:"required"`
|
||||
}
|
||||
@@ -68,6 +72,10 @@ service ppanel {
|
||||
@handler UpdateOrderStatus
|
||||
put /status (UpdateOrderStatusRequest)
|
||||
|
||||
@doc "Refund order"
|
||||
@handler RefundOrder
|
||||
post /refund (RefundOrderRequest)
|
||||
|
||||
@doc "Manually activate order"
|
||||
@handler ActivateOrder
|
||||
post /activate (ActivateOrderRequest)
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
syntax = "v1"
|
||||
|
||||
info (
|
||||
title: "promo admin API"
|
||||
desc: "API for ppanel"
|
||||
author: "Tension"
|
||||
email: "tension@ppanel.com"
|
||||
version: "0.0.1"
|
||||
)
|
||||
|
||||
import "../types.api"
|
||||
|
||||
type (
|
||||
CreatePromoRuleRequest {
|
||||
Name string `json:"name" validate:"required,max=100"`
|
||||
Type string `json:"type" validate:"required,oneof=new_user inactive_user campaign"`
|
||||
Params map[string]interface{} `json:"params"`
|
||||
Priority int64 `json:"priority" validate:"gte=0"`
|
||||
Enabled *bool `json:"enabled"`
|
||||
StartTime *int64 `json:"start_time"`
|
||||
EndTime *int64 `json:"end_time"`
|
||||
}
|
||||
UpdatePromoRuleRequest {
|
||||
Id int64 `uri:"id" validate:"required,gt=0"`
|
||||
Name string `json:"name" validate:"required,max=100"`
|
||||
Type string `json:"type" validate:"required,oneof=new_user inactive_user campaign"`
|
||||
Params map[string]interface{} `json:"params"`
|
||||
Priority int64 `json:"priority" validate:"gte=0"`
|
||||
Enabled *bool `json:"enabled"`
|
||||
StartTime *int64 `json:"start_time"`
|
||||
EndTime *int64 `json:"end_time"`
|
||||
}
|
||||
GetPromoRuleDetailRequest {
|
||||
Id int64 `uri:"id" validate:"required,gt=0"`
|
||||
}
|
||||
DeletePromoRuleRequest {
|
||||
Id int64 `uri:"id" validate:"required,gt=0"`
|
||||
}
|
||||
GetPromoRuleListRequest {
|
||||
Page int64 `form:"page" validate:"required,gt=0"`
|
||||
Size int64 `form:"size" validate:"required,gt=0,lte=200"`
|
||||
Type string `form:"type" validate:"omitempty,oneof=new_user inactive_user campaign"`
|
||||
Enabled *bool `form:"enabled"`
|
||||
Search string `form:"search,omitempty"`
|
||||
}
|
||||
GetPromoRuleListResponse {
|
||||
Total int64 `json:"total"`
|
||||
List []PromoRule `json:"list"`
|
||||
}
|
||||
SetPromoPriceRequest {
|
||||
PromoRuleId int64 `json:"promo_rule_id" validate:"required,gt=0"`
|
||||
Items []PromoPriceItem `json:"items" validate:"required,min=1,dive"`
|
||||
}
|
||||
GetPromoPriceListRequest {
|
||||
Page int64 `form:"page" validate:"required,gt=0"`
|
||||
Size int64 `form:"size" validate:"required,gt=0,lte=200"`
|
||||
RuleId int64 `form:"rule_id,omitempty"`
|
||||
SubscribeId int64 `form:"subscribe_id,omitempty"`
|
||||
}
|
||||
GetPromoPriceListResponse {
|
||||
Total int64 `json:"total"`
|
||||
List []PromoPrice `json:"list"`
|
||||
}
|
||||
DeletePromoPriceRequest {
|
||||
Id int64 `uri:"id" validate:"required,gt=0"`
|
||||
}
|
||||
GetPromoUsageListRequest {
|
||||
Page int64 `form:"page" validate:"required,gt=0"`
|
||||
Size int64 `form:"size" validate:"required,gt=0,lte=200"`
|
||||
RuleId int64 `form:"rule_id,omitempty"`
|
||||
UserId int64 `form:"user_id,omitempty"`
|
||||
SubscribeId int64 `form:"subscribe_id,omitempty"`
|
||||
OrderNo string `form:"order_no,omitempty"`
|
||||
}
|
||||
GetPromoUsageListResponse {
|
||||
Total int64 `json:"total"`
|
||||
List []PromoUsage `json:"list"`
|
||||
}
|
||||
)
|
||||
|
||||
@server (
|
||||
prefix: v1/admin/promo
|
||||
group: admin/promo
|
||||
middleware: AuthMiddleware
|
||||
)
|
||||
service ppanel {
|
||||
@doc "Create promo rule"
|
||||
@handler CreateRule
|
||||
post /rule (CreatePromoRuleRequest) returns (PromoRule)
|
||||
|
||||
@doc "Get promo rule list"
|
||||
@handler GetRuleList
|
||||
get /rule/list (GetPromoRuleListRequest) returns (GetPromoRuleListResponse)
|
||||
|
||||
@doc "Get promo rule detail"
|
||||
@handler GetRuleDetail
|
||||
get /rule/:id (GetPromoRuleDetailRequest) returns (PromoRule)
|
||||
|
||||
@doc "Update promo rule"
|
||||
@handler UpdateRule
|
||||
put /rule/:id (UpdatePromoRuleRequest) returns (PromoRule)
|
||||
|
||||
@doc "Delete promo rule"
|
||||
@handler DeleteRule
|
||||
delete /rule/:id (DeletePromoRuleRequest)
|
||||
|
||||
@doc "Set promo prices"
|
||||
@handler SetPrice
|
||||
post /price (SetPromoPriceRequest)
|
||||
|
||||
@doc "Get promo price list"
|
||||
@handler GetPriceList
|
||||
get /price/list (GetPromoPriceListRequest) returns (GetPromoPriceListResponse)
|
||||
|
||||
@doc "Delete promo price"
|
||||
@handler DeletePrice
|
||||
delete /price/:id (DeletePromoPriceRequest)
|
||||
|
||||
@doc "Get promo usage list"
|
||||
@handler GetUsageList
|
||||
get /usage/list (GetPromoUsageListRequest) returns (GetPromoUsageListResponse)
|
||||
}
|
||||
@@ -46,6 +46,7 @@ type (
|
||||
SpeedLimit int64 `json:"speed_limit"`
|
||||
DeviceLimit int64 `json:"device_limit"`
|
||||
Quota int64 `json:"quota"`
|
||||
NewUserOnly *bool `json:"new_user_only"`
|
||||
Nodes []int64 `json:"nodes"`
|
||||
NodeTags []string `json:"node_tags"`
|
||||
NodeGroupIds []int64 `json:"node_group_ids,omitempty"`
|
||||
@@ -74,6 +75,7 @@ type (
|
||||
SpeedLimit int64 `json:"speed_limit"`
|
||||
DeviceLimit int64 `json:"device_limit"`
|
||||
Quota int64 `json:"quota"`
|
||||
NewUserOnly *bool `json:"new_user_only"`
|
||||
Nodes []int64 `json:"nodes"`
|
||||
NodeTags []string `json:"node_tags"`
|
||||
NodeGroupIds []int64 `json:"node_group_ids,omitempty"`
|
||||
@@ -175,4 +177,3 @@ service ppanel {
|
||||
@handler ResetAllSubscribeToken
|
||||
post /reset_all_token returns (ResetAllSubscribeTokenResponse)
|
||||
}
|
||||
|
||||
|
||||
+102
-48
@@ -23,10 +23,12 @@ type (
|
||||
SubscribeId *int64 `form:"subscribe_id,omitempty"`
|
||||
UserSubscribeId *int64 `form:"user_subscribe_id,omitempty"`
|
||||
ShortCode string `form:"short_code,omitempty"`
|
||||
DeviceId *int64 `form:"device_id,omitempty"`
|
||||
FamilyJoined *bool `form:"family_joined,omitempty"`
|
||||
FamilyStatus string `form:"family_status,omitempty"`
|
||||
FamilyOwnerUserId *int64 `form:"family_owner_user_id,omitempty"`
|
||||
FamilyId *int64 `form:"family_id,omitempty"`
|
||||
SortOrder string `form:"sort_order,omitempty"`
|
||||
}
|
||||
// GetUserListResponse
|
||||
GetUserListResponse {
|
||||
@@ -38,20 +40,20 @@ type (
|
||||
Id int64 `form:"id" validate:"required"`
|
||||
}
|
||||
UpdateUserBasiceInfoRequest {
|
||||
UserId int64 `json:"user_id" validate:"required"`
|
||||
Password string `json:"password"`
|
||||
Avatar string `json:"avatar"`
|
||||
Balance int64 `json:"balance"`
|
||||
Commission int64 `json:"commission"`
|
||||
ReferralPercentage uint8 `json:"referral_percentage"`
|
||||
OnlyFirstPurchase bool `json:"only_first_purchase"`
|
||||
GiftAmount int64 `json:"gift_amount"`
|
||||
Telegram int64 `json:"telegram"`
|
||||
ReferCode string `json:"refer_code"`
|
||||
RefererId int64 `json:"referer_id"`
|
||||
Enable bool `json:"enable"`
|
||||
IsAdmin bool `json:"is_admin"`
|
||||
Remark string `json:"remark"`
|
||||
UserId int64 `json:"user_id" validate:"required"`
|
||||
Password string `json:"password"`
|
||||
Avatar string `json:"avatar"`
|
||||
Balance *int64 `json:"balance"`
|
||||
Commission *int64 `json:"commission"`
|
||||
ReferralPercentage uint8 `json:"referral_percentage"`
|
||||
OnlyFirstPurchase *bool `json:"only_first_purchase"`
|
||||
GiftAmount *int64 `json:"gift_amount"`
|
||||
Telegram int64 `json:"telegram"`
|
||||
ReferCode string `json:"refer_code"`
|
||||
RefererId *int64 `json:"referer_id"`
|
||||
Enable *bool `json:"enable"`
|
||||
IsAdmin *bool `json:"is_admin"`
|
||||
Remark *string `json:"remark"`
|
||||
}
|
||||
UpdateUserNotifySettingRequest {
|
||||
UserId int64 `json:"user_id" validate:"required"`
|
||||
@@ -76,29 +78,6 @@ type (
|
||||
GiftAmount int64 `json:"gift_amount"`
|
||||
IsAdmin bool `json:"is_admin"`
|
||||
}
|
||||
UserSubscribeDetail {
|
||||
Id int64 `json:"id"`
|
||||
UserId int64 `json:"user_id"`
|
||||
User User `json:"user"`
|
||||
OrderId int64 `json:"order_id"`
|
||||
SubscribeId int64 `json:"subscribe_id"`
|
||||
Subscribe Subscribe `json:"subscribe"`
|
||||
NodeGroupId int64 `json:"node_group_id"`
|
||||
GroupLocked bool `json:"group_locked"`
|
||||
StartTime int64 `json:"start_time"`
|
||||
ExpireTime int64 `json:"expire_time"`
|
||||
ResetTime int64 `json:"reset_time"`
|
||||
Traffic int64 `json:"traffic"`
|
||||
Download int64 `json:"download"`
|
||||
Upload int64 `json:"upload"`
|
||||
Token string `json:"token"`
|
||||
Status uint8 `json:"status"`
|
||||
EffectiveSpeed int64 `json:"effective_speed"`
|
||||
IsThrottled bool `json:"is_throttled"`
|
||||
ThrottleRule string `json:"throttle_rule,omitempty"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
}
|
||||
BatchDeleteUserRequest {
|
||||
Ids []int64 `json:"ids" validate:"required"`
|
||||
}
|
||||
@@ -158,18 +137,22 @@ type (
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
CreateUserSubscribeRequest {
|
||||
UserId int64 `json:"user_id"`
|
||||
ExpiredAt int64 `json:"expired_at"`
|
||||
Traffic int64 `json:"traffic"`
|
||||
SubscribeId int64 `json:"subscribe_id"`
|
||||
UserId int64 `json:"user_id"`
|
||||
ExpiredAt int64 `json:"expired_at"`
|
||||
Traffic int64 `json:"traffic"`
|
||||
SubscribeId int64 `json:"subscribe_id"`
|
||||
SpeedLimit int64 `json:"speed_limit,optional"`
|
||||
TrafficLimit string `json:"traffic_limit,optional"`
|
||||
}
|
||||
UpdateUserSubscribeRequest {
|
||||
UserSubscribeId int64 `json:"user_subscribe_id"`
|
||||
SubscribeId int64 `json:"subscribe_id"`
|
||||
Traffic int64 `json:"traffic"`
|
||||
ExpiredAt int64 `json:"expired_at"`
|
||||
Upload int64 `json:"upload"`
|
||||
Download int64 `json:"download"`
|
||||
UserSubscribeId int64 `json:"user_subscribe_id"`
|
||||
SubscribeId int64 `json:"subscribe_id"`
|
||||
Traffic int64 `json:"traffic"`
|
||||
ExpiredAt int64 `json:"expired_at"`
|
||||
Upload int64 `json:"upload"`
|
||||
Download int64 `json:"download"`
|
||||
SpeedLimit *int64 `json:"speed_limit,omitempty" validate:"omitempty,gte=0"`
|
||||
TrafficLimit []TrafficLimit `json:"traffic_limit,omitempty"`
|
||||
}
|
||||
GetUserLoginLogsRequest {
|
||||
Page int `form:"page"`
|
||||
@@ -230,6 +213,58 @@ type (
|
||||
FamilyId int64 `json:"family_id" validate:"required,gt=0"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
}
|
||||
GetWithdrawalListRequest {
|
||||
Page int `form:"page"`
|
||||
Size int `form:"size"`
|
||||
UserId *int64 `form:"user_id,omitempty"`
|
||||
Status *uint8 `form:"status,omitempty"`
|
||||
Method *uint8 `form:"method,omitempty"`
|
||||
}
|
||||
GetWithdrawalListResponse {
|
||||
List []WithdrawalLog `json:"list"`
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
ApproveWithdrawalRequest {
|
||||
WithdrawalId int64 `json:"withdrawal_id" validate:"required,gt=0"`
|
||||
}
|
||||
RejectWithdrawalRequest {
|
||||
WithdrawalId int64 `json:"withdrawal_id" validate:"required,gt=0"`
|
||||
Reason string `json:"reason" validate:"required,max=500"`
|
||||
}
|
||||
GetAdminUserInviteStatsRequest {
|
||||
UserId int64 `form:"user_id" validate:"required"`
|
||||
}
|
||||
GetAdminUserInviteStatsResponse {
|
||||
InviteCount int64 `json:"invite_count"`
|
||||
TotalCommission int64 `json:"total_commission"`
|
||||
CurrentCommission int64 `json:"current_commission"`
|
||||
ReferralPercentage uint8 `json:"referral_percentage"`
|
||||
OnlyFirstPurchase bool `json:"only_first_purchase"`
|
||||
}
|
||||
GetAdminUserInviteListRequest {
|
||||
UserId int64 `form:"user_id" validate:"required"`
|
||||
Page int `form:"page"`
|
||||
Size int `form:"size"`
|
||||
Search string `form:"search"`
|
||||
Enable *int `form:"enable"`
|
||||
UserIdSearch int64 `form:"user_id_search"`
|
||||
}
|
||||
AdminInvitedUser {
|
||||
Id int64 `json:"id"`
|
||||
Avatar string `json:"avatar"`
|
||||
Identifier string `json:"identifier"`
|
||||
Enable bool `json:"enable"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
OrderCount int64 `json:"order_count"`
|
||||
HasPurchased bool `json:"has_purchased"`
|
||||
InviterCommission int64 `json:"inviter_commission"`
|
||||
InviterGiftDays int64 `json:"inviter_gift_days"`
|
||||
InviteeGiftDays int64 `json:"invitee_gift_days"`
|
||||
}
|
||||
GetAdminUserInviteListResponse {
|
||||
Total int64 `json:"total"`
|
||||
List []AdminInvitedUser `json:"list"`
|
||||
}
|
||||
)
|
||||
|
||||
@server (
|
||||
@@ -370,5 +405,24 @@ service ppanel {
|
||||
@doc "Dissolve family"
|
||||
@handler DissolveFamily
|
||||
put /family/dissolve (DissolveFamilyRequest)
|
||||
}
|
||||
|
||||
@doc "Get withdrawal list"
|
||||
@handler GetWithdrawalList
|
||||
get /withdrawal/list (GetWithdrawalListRequest) returns (GetWithdrawalListResponse)
|
||||
|
||||
@doc "Approve withdrawal"
|
||||
@handler ApproveWithdrawal
|
||||
post /withdrawal/approve (ApproveWithdrawalRequest)
|
||||
|
||||
@doc "Reject withdrawal"
|
||||
@handler RejectWithdrawal
|
||||
post /withdrawal/reject (RejectWithdrawalRequest)
|
||||
|
||||
@doc "Get admin user invite stats"
|
||||
@handler GetAdminUserInviteStats
|
||||
get /invite/stats (GetAdminUserInviteStatsRequest) returns (GetAdminUserInviteStatsResponse)
|
||||
|
||||
@doc "Get admin user invite list"
|
||||
@handler GetAdminUserInviteList
|
||||
get /invite/list (GetAdminUserInviteListRequest) returns (GetAdminUserInviteListResponse)
|
||||
}
|
||||
|
||||
+6
-5
@@ -149,11 +149,12 @@ type (
|
||||
State string `form:"state"`
|
||||
}
|
||||
DeviceLoginRequest {
|
||||
Identifier string `json:"identifier" validate:"required"`
|
||||
IP string `header:"X-Original-Forwarded-For"`
|
||||
UserAgent string `json:"user_agent" validate:"required"`
|
||||
CfToken string `json:"cf_token,optional"`
|
||||
ShortCode string `json:"short_code,optional"`
|
||||
Identifier string `json:"identifier" validate:"required"`
|
||||
IP string `header:"X-Original-Forwarded-For"`
|
||||
UserAgent string `json:"user_agent" validate:"required"`
|
||||
CfToken string `json:"cf_token,optional"`
|
||||
ShortCode string `json:"short_code,optional"`
|
||||
BasePayload string `json:"base_payload,optional"`
|
||||
}
|
||||
GenerateCaptchaResponse {
|
||||
Id string `json:"id"`
|
||||
|
||||
+4
-3
@@ -64,6 +64,8 @@ type (
|
||||
ServerUser {
|
||||
Id int64 `json:"id"`
|
||||
UUID string `json:"uuid"`
|
||||
// SpeedLimit 单位为 Mbps,0 表示不限速。
|
||||
// 节点端 (V2bX/XrayR 等) 按 Mbps 解释该值,服务端透传不做单位换算。
|
||||
SpeedLimit int64 `json:"speed_limit"`
|
||||
DeviceLimit int64 `json:"device_limit"`
|
||||
}
|
||||
@@ -111,7 +113,7 @@ type (
|
||||
|
||||
@server (
|
||||
prefix: v1/server
|
||||
group: server
|
||||
group: node/server
|
||||
middleware: ServerMiddleware
|
||||
)
|
||||
service ppanel {
|
||||
@@ -138,11 +140,10 @@ service ppanel {
|
||||
|
||||
@server (
|
||||
prefix: v2/server
|
||||
group: server
|
||||
group: node/server
|
||||
)
|
||||
service ppanel {
|
||||
@doc "Get Server Protocol Config"
|
||||
@handler QueryServerProtocolConfig
|
||||
get /:server_id (QueryServerConfigRequest) returns (QueryServerConfigResponse)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
syntax = "v1"
|
||||
|
||||
info (
|
||||
title: "File API"
|
||||
desc: "API for ppanel file upload"
|
||||
author: "Codex"
|
||||
email: "codex@openai.com"
|
||||
version: "0.0.1"
|
||||
)
|
||||
|
||||
import "../types.api"
|
||||
|
||||
type (
|
||||
FileUploadRequest {
|
||||
BizType string `form:"biz_type" validate:"required"`
|
||||
}
|
||||
|
||||
FileUploadResponse {
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
FileUploadInitRequest {
|
||||
BizType string `json:"biz_type" validate:"required"`
|
||||
FileName string `json:"file_name" validate:"required"`
|
||||
ContentType string `json:"content_type" validate:"required"`
|
||||
Size int64 `json:"size" validate:"required"`
|
||||
Sha256 string `json:"sha256"`
|
||||
}
|
||||
|
||||
FileUploadInitResponse {
|
||||
FileId string `json:"file_id"`
|
||||
ObjectKey string `json:"object_key"`
|
||||
UploadURL string `json:"upload_url"`
|
||||
Method string `json:"method"`
|
||||
Headers map[string]string `json:"headers"`
|
||||
ExpiredAt int64 `json:"expired_at"`
|
||||
}
|
||||
|
||||
FileUploadCompleteRequest {
|
||||
FileId string `json:"file_id" validate:"required"`
|
||||
}
|
||||
|
||||
FileUploadCompleteResponse {
|
||||
Url string `json:"url"`
|
||||
}
|
||||
)
|
||||
|
||||
@server (
|
||||
prefix: v1/public/file
|
||||
group: public/file
|
||||
middleware: AuthMiddleware,DeviceMiddleware
|
||||
)
|
||||
service ppanel {
|
||||
@doc "Upload file to RustFS"
|
||||
@handler FileUpload
|
||||
post /upload (FileUploadRequest) returns (FileUploadResponse)
|
||||
|
||||
@doc "Init file upload"
|
||||
@handler FileUploadInit
|
||||
post /upload/init (FileUploadInitRequest) returns (FileUploadInitResponse)
|
||||
|
||||
@doc "Complete file upload"
|
||||
@handler FileUploadComplete
|
||||
post /upload/complete (FileUploadCompleteRequest) returns (FileUploadCompleteResponse)
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
syntax = "v1"
|
||||
|
||||
info (
|
||||
title: "Lottery API"
|
||||
desc: "User-facing lottery endpoints for HIF-3 Stage 1"
|
||||
author: "hifast"
|
||||
version: "0.1.0"
|
||||
)
|
||||
|
||||
import "../types.api"
|
||||
|
||||
@server (
|
||||
prefix: v1/lottery
|
||||
group: public/lottery
|
||||
middleware: AuthMiddleware,DeviceMiddleware
|
||||
)
|
||||
service ppanel {
|
||||
@doc "Get lottery activity config + user status"
|
||||
@handler QueryLotteryConfig
|
||||
get /config (GetLotteryConfigRequest) returns (GetLotteryConfigResponse)
|
||||
|
||||
@doc "Draw once (nonce idempotent, rate limited 1/sec)"
|
||||
@handler DrawLottery
|
||||
post /draw (DrawLotteryRequest) returns (DrawLotteryResponse)
|
||||
|
||||
@doc "List my draws"
|
||||
@handler QueryLotteryRecords
|
||||
get /records (GetLotteryRecordsRequest) returns (GetLotteryRecordsResponse)
|
||||
|
||||
@doc "Claim a prize (Stage 1 returns 100010 not_claimable)"
|
||||
@handler ClaimLotteryPrize
|
||||
post /claim (ClaimLotteryPrizeRequest) returns (ClaimLotteryPrizeResponse)
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
syntax = "v1"
|
||||
|
||||
info (
|
||||
title: "recovery API"
|
||||
desc: "API for order recovery"
|
||||
author: "Tension"
|
||||
email: "tension@ppanel.com"
|
||||
version: "0.0.1"
|
||||
)
|
||||
|
||||
import "../types.api"
|
||||
|
||||
type (
|
||||
RecoverySendCodeRequest {
|
||||
Email string `json:"email" validate:"required,email"`
|
||||
}
|
||||
RecoverOrderRequest {
|
||||
OrderNo string `json:"order_no" validate:"required"`
|
||||
Email string `json:"email" validate:"required,email"`
|
||||
Code string `json:"code" validate:"required"`
|
||||
}
|
||||
RecoverOrderResponse {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
)
|
||||
|
||||
@server (
|
||||
prefix: v1/public/recovery
|
||||
group: public/recovery
|
||||
middleware: DeviceMiddleware
|
||||
)
|
||||
service ppanel {
|
||||
@doc "Send recovery verification code"
|
||||
@handler SendCode
|
||||
post /send_code (RecoverySendCodeRequest) returns (SendCodeResponse)
|
||||
|
||||
@doc "Recover order subscription"
|
||||
@handler RecoverOrder
|
||||
post /order (RecoverOrderRequest) returns (RecoverOrderResponse)
|
||||
}
|
||||
+72
-6
@@ -109,26 +109,63 @@ type (
|
||||
Rules []string `json:"rules" validate:"required"`
|
||||
}
|
||||
CommissionWithdrawRequest {
|
||||
Amount int64 `json:"amount"`
|
||||
Content string `json:"content"`
|
||||
Amount int64 `json:"amount"`
|
||||
Content string `json:"content"`
|
||||
Method uint8 `json:"method" validate:"oneof=0 1 2 3"`
|
||||
Account string `json:"account,omitempty"`
|
||||
QrCodeUrl string `json:"qr_code_url,omitempty"`
|
||||
}
|
||||
WithdrawalLog {
|
||||
Id int64 `json:"id"`
|
||||
BizType string `json:"biz_type"`
|
||||
UserId int64 `json:"user_id"`
|
||||
Amount int64 `json:"amount"`
|
||||
Content string `json:"content"`
|
||||
Status uint8 `json:"status"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
Method uint8 `json:"method"`
|
||||
Account string `json:"account"`
|
||||
QrCodeUrl string `json:"qr_code_url"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
}
|
||||
CancelWithdrawalRequest {
|
||||
WithdrawalId int64 `json:"withdrawal_id" validate:"required,gt=0"`
|
||||
}
|
||||
QueryWithdrawalLogListRequest {
|
||||
Page int `form:"page"`
|
||||
Size int `form:"size"`
|
||||
BizType string `form:"biz_type" validate:"omitempty,oneof=withdrawal commission_refund"`
|
||||
}
|
||||
WithdrawalLogSummary {
|
||||
CommissionBalance int64 `json:"commission_balance"`
|
||||
LockedByPending int64 `json:"locked_by_pending"`
|
||||
AvailableToWithdraw int64 `json:"available_to_withdraw"`
|
||||
TotalHistoricalAmount int64 `json:"total_historical_amount"`
|
||||
TotalRefundedAmount int64 `json:"total_refunded_amount"`
|
||||
TotalIncomeAmount int64 `json:"total_income_amount"`
|
||||
}
|
||||
QueryWithdrawalLogListResponse {
|
||||
List []WithdrawalLog `json:"list"`
|
||||
Total int64 `json:"total"`
|
||||
Summary *WithdrawalLogSummary `json:"summary,omitempty"`
|
||||
}
|
||||
QueryCommissionReturnLogRequest {
|
||||
Page int `form:"page"`
|
||||
Size int `form:"size"`
|
||||
}
|
||||
QueryWithdrawalLogListResponse {
|
||||
List []WithdrawalLog `json:"list"`
|
||||
Total int64 `json:"total"`
|
||||
CommissionReturnLog {
|
||||
Id int64 `json:"id"`
|
||||
UserId int64 `json:"user_id"`
|
||||
Amount int64 `json:"amount"`
|
||||
EventType uint16 `json:"event_type"`
|
||||
Content string `json:"content"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
}
|
||||
QueryCommissionReturnLogResponse {
|
||||
List []CommissionReturnLog `json:"list"`
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
GetDeviceOnlineStatsResponse {
|
||||
WeeklyStats []WeeklyStat `json:"weekly_stats"`
|
||||
@@ -192,6 +229,23 @@ type (
|
||||
GrowthRate string `json:"growth_rate"`
|
||||
PaidGrowthRate string `json:"paid_growth_rate"`
|
||||
}
|
||||
GetInviteRecordsRequest {
|
||||
Page int `form:"page"`
|
||||
Size int `form:"size"`
|
||||
StartTime int64 `form:"start_time"`
|
||||
EndTime int64 `form:"end_time"`
|
||||
}
|
||||
InviteRecord {
|
||||
Role string `json:"role"`
|
||||
PeerHash string `json:"peer_hash"`
|
||||
GiftDays int64 `json:"gift_days"`
|
||||
OrderNo string `json:"order_no"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
}
|
||||
GetInviteRecordsResponse {
|
||||
Total int64 `json:"total"`
|
||||
List []InviteRecord `json:"list"`
|
||||
}
|
||||
GetInviteSalesRequest {
|
||||
Page int `form:"page"`
|
||||
Size int `form:"size"`
|
||||
@@ -352,10 +406,18 @@ service ppanel {
|
||||
@handler CommissionWithdraw
|
||||
post /commission_withdraw (CommissionWithdrawRequest) returns (WithdrawalLog)
|
||||
|
||||
@doc "Query Withdrawal Log"
|
||||
@doc "Cancel pending withdrawal"
|
||||
@handler CancelWithdrawal
|
||||
post /withdrawal_cancel (CancelWithdrawalRequest) returns (WithdrawalLog)
|
||||
|
||||
@doc "Query Withdrawal Log (biz_type=commission_refund deprecated, use /commission_return_log)"
|
||||
@handler QueryWithdrawalLog
|
||||
get /withdrawal_log (QueryWithdrawalLogListRequest) returns (QueryWithdrawalLogListResponse)
|
||||
|
||||
@doc "Query Commission Return Log"
|
||||
@handler QueryCommissionReturnLog
|
||||
get /commission_return_log (QueryCommissionReturnLogRequest) returns (QueryCommissionReturnLogResponse)
|
||||
|
||||
@doc "Device Online Statistics"
|
||||
@handler DeviceOnlineStatistics
|
||||
get /device_online_statistics returns (GetDeviceOnlineStatsResponse)
|
||||
@@ -384,6 +446,10 @@ service ppanel {
|
||||
@handler GetAgentRealtime
|
||||
get /agent_realtime (GetAgentRealtimeRequest) returns (GetAgentRealtimeResponse)
|
||||
|
||||
@doc "Get Invite Records"
|
||||
@handler GetInviteRecords
|
||||
get /invite_records (GetInviteRecordsRequest) returns (GetInviteRecordsResponse)
|
||||
|
||||
@doc "Get Invite Sales"
|
||||
@handler GetInviteSales
|
||||
get /invite_sales (GetInviteSalesRequest) returns (GetInviteSalesResponse)
|
||||
|
||||
+105
-21
@@ -27,6 +27,7 @@ type (
|
||||
EnableLoginNotify bool `json:"enable_login_notify"`
|
||||
EnableSubscribeNotify bool `json:"enable_subscribe_notify"`
|
||||
EnableTradeNotify bool `json:"enable_trade_notify"`
|
||||
UseStatus bool `json:"use_status"` // Whether to show the "bind email to get free trial" prompt
|
||||
AuthMethods []UserAuthMethod `json:"auth_methods"`
|
||||
UserDevices []UserDevice `json:"user_devices"`
|
||||
Rules []string `json:"rules"`
|
||||
@@ -159,24 +160,26 @@ type (
|
||||
OnlyRealDevice bool `json:"only_real_device"`
|
||||
}
|
||||
RegisterConfig {
|
||||
StopRegister bool `json:"stop_register"`
|
||||
EnableTrial bool `json:"enable_trial"`
|
||||
TrialSubscribe int64 `json:"trial_subscribe"`
|
||||
TrialTime int64 `json:"trial_time"`
|
||||
TrialTimeUnit string `json:"trial_time_unit"`
|
||||
EnableIpRegisterLimit bool `json:"enable_ip_register_limit"`
|
||||
IpRegisterLimit int64 `json:"ip_register_limit"`
|
||||
IpRegisterLimitDuration int64 `json:"ip_register_limit_duration"`
|
||||
DeviceLimit int64 `json:"device_limit"`
|
||||
StopRegister bool `json:"stop_register"`
|
||||
EnableTrial bool `json:"enable_trial"`
|
||||
EnableTrialEmailWhitelist bool `json:"enable_trial_email_whitelist"`
|
||||
TrialSubscribe int64 `json:"trial_subscribe"`
|
||||
TrialTime int64 `json:"trial_time"`
|
||||
TrialTimeUnit string `json:"trial_time_unit"`
|
||||
TrialEmailDomainWhitelist string `json:"trial_email_domain_whitelist"`
|
||||
EnableIpRegisterLimit bool `json:"enable_ip_register_limit"`
|
||||
IpRegisterLimit int64 `json:"ip_register_limit"`
|
||||
IpRegisterLimitDuration int64 `json:"ip_register_limit_duration"`
|
||||
DeviceLimit int64 `json:"device_limit"`
|
||||
}
|
||||
VerifyConfig {
|
||||
CaptchaType string `json:"captcha_type"` // local or turnstile
|
||||
TurnstileSiteKey string `json:"turnstile_site_key"`
|
||||
TurnstileSecret string `json:"turnstile_secret"`
|
||||
EnableUserLoginCaptcha bool `json:"enable_user_login_captcha"` // User login captcha
|
||||
EnableUserRegisterCaptcha bool `json:"enable_user_register_captcha"` // User register captcha
|
||||
EnableAdminLoginCaptcha bool `json:"enable_admin_login_captcha"` // Admin login captcha
|
||||
EnableUserResetPasswordCaptcha bool `json:"enable_user_reset_password_captcha"` // User reset password captcha
|
||||
CaptchaType string `json:"captcha_type"` // local or turnstile
|
||||
TurnstileSiteKey string `json:"turnstile_site_key"`
|
||||
TurnstileSecret string `json:"turnstile_secret"`
|
||||
EnableUserLoginCaptcha bool `json:"enable_user_login_captcha"` // User login captcha
|
||||
EnableUserRegisterCaptcha bool `json:"enable_user_register_captcha"` // User register captcha
|
||||
EnableAdminLoginCaptcha bool `json:"enable_admin_login_captcha"` // Admin login captcha
|
||||
EnableUserResetPasswordCaptcha bool `json:"enable_user_reset_password_captcha"` // User reset password captcha
|
||||
}
|
||||
NodeConfig {
|
||||
NodeSecret string `json:"node_secret"`
|
||||
@@ -225,8 +228,52 @@ type (
|
||||
CurrencySymbol string `json:"currency_symbol"`
|
||||
}
|
||||
SubscribeDiscount {
|
||||
Quantity int64 `json:"quantity"`
|
||||
Discount float64 `json:"discount"`
|
||||
Quantity int64 `json:"quantity"`
|
||||
Discount float64 `json:"discount"`
|
||||
NewUserOnly bool `json:"new_user_only"`
|
||||
MapApple string `json:"map_apple"`
|
||||
Promo *SubscribePromo `json:"promo"`
|
||||
}
|
||||
PromoPrice {
|
||||
Id int64 `json:"id"`
|
||||
SubscribeId int64 `json:"subscribe_id"`
|
||||
PromoRuleId int64 `json:"promo_rule_id"`
|
||||
Quantity int64 `json:"quantity"`
|
||||
PromoPrice int64 `json:"promo_price"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
}
|
||||
PromoPriceItem {
|
||||
SubscribeId int64 `json:"subscribe_id" validate:"required,gt=0"`
|
||||
Quantity int64 `json:"quantity" validate:"required,gt=0,lte=1000"`
|
||||
PromoPrice int64 `json:"promo_price" validate:"required,gt=0"`
|
||||
}
|
||||
PromoRule {
|
||||
Id int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Params map[string]interface{} `json:"params"`
|
||||
Priority int64 `json:"priority"`
|
||||
Enabled bool `json:"enabled"`
|
||||
StartTime *int64 `json:"start_time"`
|
||||
EndTime *int64 `json:"end_time"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
}
|
||||
PromoUsage {
|
||||
Id int64 `json:"id"`
|
||||
UserId int64 `json:"user_id"`
|
||||
PromoRuleId int64 `json:"promo_rule_id"`
|
||||
SubscribeId int64 `json:"subscribe_id"`
|
||||
OrderNo string `json:"order_no"`
|
||||
PromoPrice int64 `json:"promo_price"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
}
|
||||
SubscribePromo {
|
||||
RuleName string `json:"rule_name"`
|
||||
RuleType string `json:"rule_type"`
|
||||
PromoPrice int64 `json:"promo_price"`
|
||||
ExpiresAt int64 `json:"expires_at"`
|
||||
}
|
||||
TrafficLimit {
|
||||
StatType string `json:"stat_type"`
|
||||
@@ -249,6 +296,7 @@ type (
|
||||
SpeedLimit int64 `json:"speed_limit"`
|
||||
DeviceLimit int64 `json:"device_limit"`
|
||||
Quota int64 `json:"quota"`
|
||||
NewUserOnly bool `json:"new_user_only"`
|
||||
Nodes []int64 `json:"nodes"`
|
||||
NodeTags []string `json:"node_tags"`
|
||||
NodeGroupIds []int64 `json:"node_group_ids,omitempty"`
|
||||
@@ -425,6 +473,7 @@ type (
|
||||
FeeAmount int64 `json:"fee_amount"`
|
||||
TradeNo string `json:"trade_no"`
|
||||
Status uint8 `json:"status"`
|
||||
StatusName string `json:"status_name,omitempty"`
|
||||
SubscribeId int64 `json:"subscribe_id"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
@@ -447,6 +496,7 @@ type (
|
||||
FeeAmount int64 `json:"fee_amount"`
|
||||
TradeNo string `json:"trade_no"`
|
||||
Status uint8 `json:"status"`
|
||||
StatusName string `json:"status_name,omitempty"`
|
||||
SubscribeId int64 `json:"subscribe_id"`
|
||||
Subscribe Subscribe `json:"subscribe"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
@@ -511,10 +561,13 @@ type (
|
||||
}
|
||||
UserSubscribe {
|
||||
Id int64 `json:"id"`
|
||||
IdStr string `json:"id_str"`
|
||||
UserId int64 `json:"user_id"`
|
||||
OrderId int64 `json:"order_id"`
|
||||
SubscribeId int64 `json:"subscribe_id"`
|
||||
Subscribe Subscribe `json:"subscribe"`
|
||||
NodeGroupId int64 `json:"node_group_id"`
|
||||
NodeGroupName string `json:"node_group_name"`
|
||||
StartTime int64 `json:"start_time"`
|
||||
ExpireTime int64 `json:"expire_time"`
|
||||
FinishedAt int64 `json:"finished_at"`
|
||||
@@ -522,6 +575,7 @@ type (
|
||||
Traffic int64 `json:"traffic"`
|
||||
Download int64 `json:"download"`
|
||||
Upload int64 `json:"upload"`
|
||||
TrafficLimit []TrafficLimit `json:"user_traffic_limit"`
|
||||
Token string `json:"token"`
|
||||
Status uint8 `json:"status"`
|
||||
EntitlementSource string `json:"entitlement_source"`
|
||||
@@ -532,6 +586,35 @@ type (
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
}
|
||||
UserSubscribeDetail {
|
||||
Id int64 `json:"id"`
|
||||
UserId int64 `json:"user_id"`
|
||||
User User `json:"user"`
|
||||
OrderId int64 `json:"order_id"`
|
||||
SubscribeId int64 `json:"subscribe_id"`
|
||||
Subscribe Subscribe `json:"subscribe"`
|
||||
NodeGroupId int64 `json:"node_group_id"`
|
||||
NodeGroupName string `json:"node_group_name"`
|
||||
GroupLocked bool `json:"group_locked"`
|
||||
StartTime int64 `json:"start_time"`
|
||||
ExpireTime int64 `json:"expire_time"`
|
||||
ResetTime int64 `json:"reset_time"`
|
||||
Traffic int64 `json:"traffic"`
|
||||
Download int64 `json:"download"`
|
||||
Upload int64 `json:"upload"`
|
||||
SpeedLimit int64 `json:"speed_limit"`
|
||||
TrafficLimit []TrafficLimit `json:"user_traffic_limit"`
|
||||
PlanSpeedLimit int64 `json:"plan_speed_limit"`
|
||||
Token string `json:"token"`
|
||||
Status uint8 `json:"status"`
|
||||
EffectiveSpeed int64 `json:"effective_speed"`
|
||||
IsThrottled bool `json:"is_throttled"`
|
||||
ThrottleRule string `json:"throttle_rule,omitempty"`
|
||||
ThrottleStart int64 `json:"throttle_start,omitempty"`
|
||||
ThrottleEnd int64 `json:"throttle_end,omitempty"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
}
|
||||
UserAffiliate {
|
||||
Avatar string `json:"avatar"`
|
||||
Identifier string `json:"identifier"`
|
||||
@@ -675,6 +758,7 @@ type (
|
||||
Price int64 `json:"price"`
|
||||
Amount int64 `json:"amount"`
|
||||
Discount int64 `json:"discount"`
|
||||
PromoDiscount int64 `json:"promo_discount"`
|
||||
GiftAmount int64 `json:"gift_amount"`
|
||||
Coupon string `json:"coupon"`
|
||||
CouponDiscount int64 `json:"coupon_discount"`
|
||||
@@ -830,8 +914,9 @@ type (
|
||||
Sandbox *bool `json:"sandbox,omitempty"`
|
||||
}
|
||||
AttachAppleTransactionResponse {
|
||||
ExpiresAt int64 `json:"expires_at"`
|
||||
Tier string `json:"tier"`
|
||||
ExpiresAt int64 `json:"expires_at"`
|
||||
Tier string `json:"tier"`
|
||||
ExistingOrderNo string `json:"existing_order_no,omitempty"`
|
||||
}
|
||||
RestoreAppleTransactionsRequest {
|
||||
Transactions []string `json:"transactions" validate:"required"`
|
||||
@@ -1003,4 +1088,3 @@ type (
|
||||
ConfigSnapshot map[string]interface{} `json:"config_snapshot,omitempty"`
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,796 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/perfect-panel/server/internal/config"
|
||||
logmodel "github.com/perfect-panel/server/internal/model/log"
|
||||
ordermodel "github.com/perfect-panel/server/internal/model/order"
|
||||
usermodel "github.com/perfect-panel/server/internal/model/user"
|
||||
"github.com/perfect-panel/server/pkg/conf"
|
||||
"github.com/perfect-panel/server/pkg/orm"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/spf13/cobra"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func init() {
|
||||
retroactiveReferralCmd.Flags().StringVar(&retroAgentIdStr, "agent-id", "", "目标代理用户 ID,或 * 表示所有 referral_percentage>0 的代理(必填)")
|
||||
retroactiveReferralCmd.Flags().StringVar(&retroPoolStart, "pool-start", "2025-05-04", "自然流量订单起始时间,格式 YYYY-MM-DD 或 'YYYY-MM-DD HH:MM:SS'(默认 2025-05-04)")
|
||||
retroactiveReferralCmd.Flags().StringVar(&retroPoolEnd, "pool-end", "", "自然流量订单截止时间,格式 YYYY-MM-DD 或 'YYYY-MM-DD HH:MM:SS'(默认今天)")
|
||||
retroactiveReferralCmd.Flags().IntVar(&retroPercentage, "percentage", 120, "补偿百分比,例如 120 表示 120%(默认 120)")
|
||||
retroactiveReferralCmd.Flags().IntVar(&retroForceCommissionPct, "force-commission-pct", 50, "强制指定发佣比例(0=使用数据库/配置,非0时覆盖代理设置,默认 50%)")
|
||||
retroactiveReferralCmd.Flags().StringVar(&retroOutput, "output", "retro_result.txt", "结果输出到指定 txt 文件(默认 retro_result.txt)")
|
||||
retroactiveReferralCmd.Flags().StringVar(&retroConfigPath, "config", "etc/ppanel.yaml", "配置文件路径")
|
||||
retroactiveReferralCmd.Flags().StringVar(&retroAgentCreatedAfter, "agent-created-after", "", "仅处理在此日期之后注册的代理,格式 YYYY-MM-DD(留空=不限)")
|
||||
retroactiveReferralCmd.Flags().StringVar(&retroLossStart, "loss-start", "2026-05-06", "数据丢失起始时间,丢失时长=现在-此时间(默认 2026-05-06)")
|
||||
retroactiveReferralCmd.Flags().StringVar(&retroOrderStart, "order-start", "2026-05-01", "池内用户至少有一笔 updated_at >= 此时间的订单才入池(默认 2026-05-01)")
|
||||
retroactiveReferralCmd.Flags().BoolVar(&retroDryRun, "dry-run", false, "仅预览,不执行写入")
|
||||
rootCmd.AddCommand(retroactiveReferralCmd)
|
||||
}
|
||||
|
||||
var (
|
||||
retroAgentIdStr string
|
||||
retroAgentCreatedAfter string
|
||||
retroLossStart string
|
||||
retroOrderStart string
|
||||
retroPoolStart string
|
||||
retroPoolEnd string
|
||||
retroPercentage int
|
||||
retroForceCommissionPct int
|
||||
retroConfigPath string
|
||||
retroDryRun bool
|
||||
retroOutput string
|
||||
)
|
||||
|
||||
var retroactiveReferralCmd = &cobra.Command{
|
||||
Use: "retro-referral",
|
||||
Short: "补单:按代理历史日均佣金补偿指定比例的用户",
|
||||
Long: `统计代理从首次邀请到 pool-end 的日均佣金,
|
||||
按指定百分比计算目标补偿金额,
|
||||
从 pool-start 到 pool-end 的自然流量用户中随机抽取匹配的用户数量挂载到该代理。
|
||||
--agent-id 支持单个 ID 或 *(处理所有 referral_percentage>0 的代理)。`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if retroAgentIdStr == "" {
|
||||
return fmt.Errorf("--agent-id 必填(单个 ID 或 *)")
|
||||
}
|
||||
return runRetroactiveReferral()
|
||||
},
|
||||
}
|
||||
|
||||
// commissionRule holds resolved commission settings for an agent.
|
||||
type commissionRule struct {
|
||||
Percentage uint8
|
||||
OnlyFirstPurchase bool
|
||||
}
|
||||
|
||||
// candidateUser holds a pool user plus their pre-calculated qualifying orders.
|
||||
type candidateUser struct {
|
||||
Id int64
|
||||
CreatedAt time.Time
|
||||
Identifier string
|
||||
Orders []ordermodel.Order
|
||||
CommissionTotal int64
|
||||
}
|
||||
|
||||
// agentPlan holds one agent's computed allocation plan (preview phase output).
|
||||
type agentPlan struct {
|
||||
Agent *usermodel.User
|
||||
Rule commissionRule
|
||||
Selected []candidateUser
|
||||
TargetAmt float64 // in cents
|
||||
PreviewCommission int64
|
||||
}
|
||||
|
||||
func runRetroactiveReferral() error {
|
||||
// ── 0. 初始化输出(终端 + 可选文件)─────────────────────────
|
||||
var w io.Writer = os.Stdout
|
||||
if retroOutput != "" {
|
||||
f, err := os.Create(retroOutput)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建输出文件失败: %w", err)
|
||||
}
|
||||
defer f.Close()
|
||||
w = io.MultiWriter(os.Stdout, f)
|
||||
fmt.Printf("结果将同步写入: %s\n\n", retroOutput)
|
||||
}
|
||||
|
||||
// ── 1. 加载配置 ──────────────────────────────────────────────
|
||||
var c config.Config
|
||||
conf.MustLoad(retroConfigPath, &c)
|
||||
|
||||
// ── 2. 初始化 DB + Redis ──────────────────────────────────────
|
||||
db, err := orm.ConnectMysql(orm.Mysql{Config: c.MySQL})
|
||||
if err != nil {
|
||||
return fmt.Errorf("连接数据库失败: %w", err)
|
||||
}
|
||||
rds := redis.NewClient(&redis.Options{
|
||||
Addr: c.Redis.Host,
|
||||
Password: c.Redis.Pass,
|
||||
DB: c.Redis.DB,
|
||||
})
|
||||
ctx := context.Background()
|
||||
if err = rds.Ping(ctx).Err(); err != nil {
|
||||
return fmt.Errorf("连接 Redis 失败: %w", err)
|
||||
}
|
||||
um := usermodel.NewModel(db, rds)
|
||||
|
||||
// ── 3. 解析时间参数 ───────────────────────────────────────────
|
||||
poolStart, err := parseFlexibleTime(retroPoolStart)
|
||||
if err != nil {
|
||||
return fmt.Errorf("--pool-start 格式错误: %w", err)
|
||||
}
|
||||
poolEnd := time.Now()
|
||||
if retroPoolEnd != "" {
|
||||
poolEnd, err = parseFlexibleTime(retroPoolEnd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("--pool-end 格式错误: %w", err)
|
||||
}
|
||||
}
|
||||
if !poolEnd.After(poolStart) {
|
||||
return fmt.Errorf("--pool-end 必须晚于 --pool-start")
|
||||
}
|
||||
|
||||
// ── 4. 确定代理列表 ───────────────────────────────────────────
|
||||
var agents []*usermodel.User
|
||||
if retroAgentIdStr == "*" {
|
||||
agents, err = queryAllActiveAgents(ctx, db, retroAgentCreatedAfter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("查询代理列表失败: %w", err)
|
||||
}
|
||||
if len(agents) == 0 {
|
||||
return fmt.Errorf("没有找到任何 referral_percentage>0 的代理用户")
|
||||
}
|
||||
fmt.Fprintf(w, "模式:全量代理,共找到 %d 个代理(referral_percentage>0)\n\n", len(agents))
|
||||
} else {
|
||||
agentID, parseErr := strconv.ParseInt(retroAgentIdStr, 10, 64)
|
||||
if parseErr != nil || agentID <= 0 {
|
||||
return fmt.Errorf("--agent-id 必须是正整数或 *")
|
||||
}
|
||||
agent, findErr := um.FindOne(ctx, agentID)
|
||||
if findErr != nil {
|
||||
return fmt.Errorf("查询代理用户失败: %w", findErr)
|
||||
}
|
||||
if agent.DeletedAt.Valid {
|
||||
return fmt.Errorf("代理用户 %d 已被删除", agentID)
|
||||
}
|
||||
agents = []*usermodel.User{agent}
|
||||
}
|
||||
|
||||
if retroForceCommissionPct > 0 {
|
||||
fmt.Fprintf(w, "⚠️ 强制覆盖所有代理佣金比例为 %d%%\n\n", retroForceCommissionPct)
|
||||
}
|
||||
|
||||
// ── 5. 查询自然流量用户池(所有代理共用同一个池)────────────
|
||||
// 先用 50% 规则(或强制值)预加载池,以便预览;执行时每个代理用自身规则
|
||||
var orderStart time.Time
|
||||
if retroOrderStart != "" {
|
||||
orderStart, err = parseFlexibleTime(retroOrderStart)
|
||||
if err != nil {
|
||||
return fmt.Errorf("--order-start 格式错误: %w", err)
|
||||
}
|
||||
}
|
||||
previewRule := commissionRule{Percentage: uint8(retroForceCommissionPct), OnlyFirstPurchase: false}
|
||||
pool, err := queryNaturalTrafficPool(ctx, db, poolStart, poolEnd, orderStart, previewRule)
|
||||
if err != nil {
|
||||
return fmt.Errorf("查询自然流量用户池失败: %w", err)
|
||||
}
|
||||
orderStartDesc := ""
|
||||
if !orderStart.IsZero() {
|
||||
orderStartDesc = fmt.Sprintf(",订单 updated_at >= %s", orderStart.Format("2006-01-02"))
|
||||
}
|
||||
fmt.Fprintf(w, "自然流量用户池(%s ~ %s,referer_id=0,有已支付订单%s):共 %d 人\n\n",
|
||||
poolStart.Format("2006-01-02 15:04"), poolEnd.Format("2006-01-02 15:04"), orderStartDesc, len(pool))
|
||||
if len(pool) == 0 {
|
||||
return fmt.Errorf("自然流量用户池为空,无法补充")
|
||||
}
|
||||
|
||||
// ── 6. 逐代理生成分配计划(预览阶段)────────────────────────
|
||||
// 池按顺序分配:每个代理从剩余池中取用户,避免重复分配
|
||||
remainingPool := make([]candidateUser, len(pool))
|
||||
copy(remainingPool, pool)
|
||||
|
||||
plans := make([]agentPlan, 0, len(agents))
|
||||
for _, agent := range agents {
|
||||
plan, planErr := buildAgentPlan(w, ctx, db, c, agent, remainingPool, poolEnd)
|
||||
if planErr != nil {
|
||||
fmt.Fprintf(w, "⚠️ 代理 %d 跳过: %v\n\n", agent.Id, planErr)
|
||||
continue
|
||||
}
|
||||
// 从剩余池中移除已分配给该代理的用户
|
||||
assignedSet := make(map[int64]struct{}, len(plan.Selected))
|
||||
for _, u := range plan.Selected {
|
||||
assignedSet[u.Id] = struct{}{}
|
||||
}
|
||||
newRemaining := remainingPool[:0]
|
||||
for _, u := range remainingPool {
|
||||
if _, used := assignedSet[u.Id]; !used {
|
||||
newRemaining = append(newRemaining, u)
|
||||
}
|
||||
}
|
||||
remainingPool = newRemaining
|
||||
plans = append(plans, plan)
|
||||
}
|
||||
|
||||
if len(plans) == 0 {
|
||||
return fmt.Errorf("所有代理均无法生成分配计划")
|
||||
}
|
||||
|
||||
// ── 7. 汇总预览 ───────────────────────────────────────────────
|
||||
var grandTotalUsers int
|
||||
var grandTotalCommission int64
|
||||
var grandTargetAmt float64
|
||||
for _, p := range plans {
|
||||
grandTotalUsers += len(p.Selected)
|
||||
grandTotalCommission += p.PreviewCommission
|
||||
grandTargetAmt += p.TargetAmt
|
||||
}
|
||||
fmt.Fprintln(w, strings.Repeat("═", 75))
|
||||
fmt.Fprintf(w, "汇总:共 %d 个代理,补充 %d 个用户\n", len(plans), grandTotalUsers)
|
||||
fmt.Fprintf(w, " 预计追溯佣金总额: $%.2f(目标补偿金额: $%.2f)\n",
|
||||
float64(grandTotalCommission)/100, grandTargetAmt/100)
|
||||
fmt.Fprintln(w, strings.Repeat("═", 75))
|
||||
fmt.Fprintln(w)
|
||||
|
||||
if retroDryRun {
|
||||
fmt.Fprintln(w, "[dry-run] 预览完成,未执行任何写入。")
|
||||
return nil
|
||||
}
|
||||
|
||||
// ── 8. 执行前汇总打印 ─────────────────────────────────────────
|
||||
fmt.Fprintf(w, "\n┌─────────────────────────────────────────────┐\n")
|
||||
fmt.Fprintf(w, "│ 即将写入数据库 │\n")
|
||||
fmt.Fprintf(w, "│ 代理数量 : %-4d 个 │\n", len(plans))
|
||||
fmt.Fprintf(w, "│ 补充用户 : %-4d 人 │\n", grandTotalUsers)
|
||||
fmt.Fprintf(w, "│ 赠送金额 : $%-10.2f │\n", float64(grandTotalCommission)/100)
|
||||
fmt.Fprintf(w, "└─────────────────────────────────────────────┘\n\n")
|
||||
fmt.Printf("确认执行?(yes/no): ")
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
answer, _ := reader.ReadString('\n')
|
||||
answer = strings.TrimSpace(strings.ToLower(answer))
|
||||
if answer != "yes" && answer != "y" {
|
||||
fmt.Fprintln(w, "已取消。")
|
||||
return nil
|
||||
}
|
||||
|
||||
// ── 9. 逐代理执行 ─────────────────────────────────────────────
|
||||
var totalSuccess, totalFailed int
|
||||
var totalCreditedOrder, totalCreditedAmt int64
|
||||
|
||||
for _, plan := range plans {
|
||||
fmt.Fprintf(w, "\n── 执行代理 %d ──────────────────────────────────────────────\n", plan.Agent.Id)
|
||||
var sc, fc int
|
||||
var co, ca int64
|
||||
for _, eu := range plan.Selected {
|
||||
if eu.Id == plan.Agent.Id {
|
||||
fmt.Fprintf(w, "[SKIP] 用户 %d 与代理相同,跳过\n", eu.Id)
|
||||
fc++
|
||||
continue
|
||||
}
|
||||
credited, amount, execErr := processOneUser(ctx, db, um, plan.Agent.Id, eu.Id, plan.Rule, orderStart)
|
||||
if execErr != nil {
|
||||
fmt.Fprintf(w, "[FAIL] 用户 %d: %v\n", eu.Id, execErr)
|
||||
fc++
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(w, "[OK] 用户 %d → 代理 %d,发佣 %d 单,金额 $%.2f\n",
|
||||
eu.Id, plan.Agent.Id, credited, float64(amount)/100)
|
||||
sc++
|
||||
co += credited
|
||||
ca += amount
|
||||
}
|
||||
// 直接删除代理的缓存 key,下次请求时从 DB 重新加载(避免 FindOne 读到旧缓存再写回)
|
||||
if sc > 0 {
|
||||
cacheKey := fmt.Sprintf("cache:user:id:%d", plan.Agent.Id)
|
||||
_ = rds.Del(ctx, cacheKey).Err()
|
||||
}
|
||||
fmt.Fprintf(w, " 代理 %d 小计:成功 %d 人,失败 %d 人,佣金 $%.2f\n", plan.Agent.Id, sc, fc, float64(ca)/100)
|
||||
totalSuccess += sc
|
||||
totalFailed += fc
|
||||
totalCreditedOrder += co
|
||||
totalCreditedAmt += ca
|
||||
}
|
||||
|
||||
// ── 10. 全局汇总 ──────────────────────────────────────────────
|
||||
fmt.Fprintf(w, "\n══════════════════════════════════════════════\n")
|
||||
fmt.Fprintf(w, " 成功挂载 : %d 人\n", totalSuccess)
|
||||
fmt.Fprintf(w, " 失败/跳过 : %d 人\n", totalFailed)
|
||||
fmt.Fprintf(w, " 追溯佣金 : %d 单,总额 $%.2f\n", totalCreditedOrder, float64(totalCreditedAmt)/100)
|
||||
fmt.Fprintf(w, "══════════════════════════════════════════════\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
// buildAgentPlan 计算一个代理的补单预览,同时打印预览内容,返回分配计划。
|
||||
func buildAgentPlan(w io.Writer, ctx context.Context, db *gorm.DB, c config.Config,
|
||||
agent *usermodel.User, pool []candidateUser, poolEnd time.Time) (agentPlan, error) {
|
||||
|
||||
rule := resolveCommissionRule(agent, c)
|
||||
if retroForceCommissionPct > 0 {
|
||||
rule.Percentage = uint8(retroForceCommissionPct)
|
||||
}
|
||||
// 补单场景:不区分新购/续费,所有已支付订单均参与佣金计算
|
||||
rule.OnlyFirstPurchase = false
|
||||
|
||||
firstReferralTime, lastReferralTime, agentCreatedAt, totalReferred, totalOrders, totalCommission, err :=
|
||||
queryAgentStats(ctx, db, agent.Id, poolEnd)
|
||||
if err != nil {
|
||||
return agentPlan{}, fmt.Errorf("查询历史数据失败: %w", err)
|
||||
}
|
||||
if totalReferred == 0 {
|
||||
return agentPlan{}, fmt.Errorf("在 %s 之前没有任何邀请记录", poolEnd.Format("2006-01-02"))
|
||||
}
|
||||
|
||||
lossStartTime, err := parseFlexibleTime(retroLossStart)
|
||||
if err != nil {
|
||||
return agentPlan{}, fmt.Errorf("--loss-start 格式错误: %w", err)
|
||||
}
|
||||
lossHours := time.Now().Sub(lossStartTime).Hours()
|
||||
|
||||
statsDays := lastReferralTime.Sub(firstReferralTime).Hours() / 24
|
||||
if statsDays < 1 {
|
||||
statsDays = 1
|
||||
}
|
||||
dailyAvgOrders := float64(totalOrders) / statsDays
|
||||
avgOrdersPerUser := float64(totalOrders) / float64(totalReferred)
|
||||
if avgOrdersPerUser < 1 {
|
||||
avgOrdersPerUser = 1
|
||||
}
|
||||
dailyAvgCommission := float64(totalCommission) / statsDays
|
||||
|
||||
// 用用户池自身的平均佣金估算人数(避免历史费率与当前50%费率不匹配导致超发)
|
||||
var poolAvgCommissionPerUser float64
|
||||
if len(pool) > 0 {
|
||||
var poolCommTotal int64
|
||||
for _, u := range pool {
|
||||
poolCommTotal += u.CommissionTotal
|
||||
}
|
||||
poolAvgCommissionPerUser = float64(poolCommTotal) / float64(len(pool))
|
||||
}
|
||||
if poolAvgCommissionPerUser < 1 {
|
||||
poolAvgCommissionPerUser = 1
|
||||
}
|
||||
|
||||
estimatedLostCommission := dailyAvgCommission * (lossHours / 24)
|
||||
targetCommission := estimatedLostCommission * (float64(retroPercentage) / 100)
|
||||
extraCount := int(targetCommission/poolAvgCommissionPerUser + 0.5)
|
||||
if extraCount < 1 {
|
||||
extraCount = 1
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "\n═══════════════════════════════════════════════════════════\n")
|
||||
fmt.Fprintf(w, " 代理 ID : %d(注册于 %s)\n", agent.Id, agentCreatedAt.Format("2006-01-02 15:04:05"))
|
||||
fmt.Fprintf(w, " 统计起点 : %s(首次邀请时间)\n", firstReferralTime.Format("2006-01-02 15:04:05"))
|
||||
fmt.Fprintf(w, " 统计截止 : %s(最后邀请时间)\n", lastReferralTime.Format("2006-01-02 15:04:05"))
|
||||
fmt.Fprintf(w, " 统计天数 : %.2f 天\n", statsDays)
|
||||
fmt.Fprintf(w, " 历史邀请总人数 : %d 人\n", totalReferred)
|
||||
fmt.Fprintf(w, " 下线总订单数 : %d 单(所有下线,不限时间)\n", totalOrders)
|
||||
fmt.Fprintf(w, " 日均订单 : %.4f 单/天\n", dailyAvgOrders)
|
||||
fmt.Fprintf(w, " 每用户平均订单 : %.4f 单\n", avgOrdersPerUser)
|
||||
fmt.Fprintf(w, " 历史佣金总额 : $%.2f\n", float64(totalCommission)/100)
|
||||
fmt.Fprintf(w, " 日均佣金 : $%.4f\n", dailyAvgCommission/100)
|
||||
fmt.Fprintf(w, " 池内用户均佣金 : $%.4f\n", poolAvgCommissionPerUser/100)
|
||||
fmt.Fprintf(w, " 佣金规则 : %d%% 仅首单=%v\n", rule.Percentage, rule.OnlyFirstPurchase)
|
||||
fmt.Fprintf(w, "───────────────────────────────────────────────────────────\n")
|
||||
fmt.Fprintf(w, " 丢失时长 : %.2f 小时(%s → 现在)\n",
|
||||
lossHours, lossStartTime.Format("2006-01-02 15:04:05"))
|
||||
fmt.Fprintf(w, " 预估丢失佣金 : $%.4f($%.4f × %.2f/24)\n",
|
||||
estimatedLostCommission/100, dailyAvgCommission/100, lossHours)
|
||||
fmt.Fprintf(w, " 目标补偿金额 : $%.4f(× %d%%)\n",
|
||||
targetCommission/100, retroPercentage)
|
||||
fmt.Fprintf(w, " 需补充人数 : %d 人($%.4f ÷ $%.4f)\n",
|
||||
extraCount, targetCommission/100, poolAvgCommissionPerUser/100)
|
||||
fmt.Fprintf(w, "═══════════════════════════════════════════════════════════\n\n")
|
||||
|
||||
// 重新按当前代理规则计算池内用户佣金(pool 由调用方传入,已是当前规则计算好的)
|
||||
if len(pool) == 0 {
|
||||
return agentPlan{}, fmt.Errorf("剩余用户池为空")
|
||||
}
|
||||
if len(pool) < extraCount {
|
||||
fmt.Fprintf(w, "⚠️ 剩余用户池只有 %d 人,少于需要的 %d 人,将全部分配\n\n", len(pool), extraCount)
|
||||
extraCount = len(pool)
|
||||
}
|
||||
|
||||
selected := randomSampleCandidates(pool, extraCount)
|
||||
|
||||
// 兜底追加:确保佣金合计 >= 目标
|
||||
{
|
||||
selectedSet := make(map[int64]struct{}, len(selected))
|
||||
var selectedCommTotal int64
|
||||
for _, u := range selected {
|
||||
selectedSet[u.Id] = struct{}{}
|
||||
selectedCommTotal += u.CommissionTotal
|
||||
}
|
||||
if selectedCommTotal < int64(targetCommission) {
|
||||
remaining := make([]candidateUser, 0, len(pool)-len(selected))
|
||||
for _, u := range pool {
|
||||
if _, used := selectedSet[u.Id]; !used {
|
||||
remaining = append(remaining, u)
|
||||
}
|
||||
}
|
||||
// 按佣金从小到大排序,追加时精准补足,减少超发
|
||||
sort.Slice(remaining, func(i, j int) bool {
|
||||
return remaining[i].CommissionTotal < remaining[j].CommissionTotal
|
||||
})
|
||||
for _, u := range remaining {
|
||||
if selectedCommTotal >= int64(targetCommission) {
|
||||
break
|
||||
}
|
||||
selected = append(selected, u)
|
||||
selectedCommTotal += u.CommissionTotal
|
||||
}
|
||||
if selectedCommTotal < int64(targetCommission) {
|
||||
fmt.Fprintf(w, "⚠️ 用户池佣金不足,已抽取全部可用用户(实际 $%.2f < 目标 $%.2f)\n\n",
|
||||
float64(selectedCommTotal)/100, targetCommission/100)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 打印选中用户明细
|
||||
var previewTotalCommission int64
|
||||
fmt.Fprintf(w, "随机抽取 %d 个用户(含待追溯佣金订单):\n", len(selected))
|
||||
fmt.Fprintln(w, strings.Repeat("═", 75))
|
||||
for i, u := range selected {
|
||||
fmt.Fprintf(w, "[%d] 用户 %-10d 注册: %s %s\n",
|
||||
i+1, u.Id, u.CreatedAt.Format("2006-01-02 15:04:05"), u.Identifier)
|
||||
if len(u.Orders) == 0 {
|
||||
fmt.Fprintln(w, " (无符合条件的订单)")
|
||||
} else {
|
||||
fmt.Fprintf(w, " %-38s %10s %8s %10s %s\n", "订单号", "金额", "手续费", "佣金", "类型")
|
||||
fmt.Fprintf(w, " %s\n", strings.Repeat("-", 72))
|
||||
for _, od := range u.Orders {
|
||||
commAmt := calcCommissionAmount(od.Amount, od.FeeAmount, rule.Percentage)
|
||||
orderType := "首购"
|
||||
if od.Type == 2 {
|
||||
orderType = "续费"
|
||||
}
|
||||
fmt.Fprintf(w, " %-38s $%8.2f $%6.2f $%8.2f %s\n",
|
||||
od.OrderNo,
|
||||
float64(od.Amount)/100,
|
||||
float64(od.FeeAmount)/100,
|
||||
float64(commAmt)/100,
|
||||
orderType)
|
||||
}
|
||||
fmt.Fprintf(w, " 本用户追溯佣金合计: $%.2f\n", float64(u.CommissionTotal)/100)
|
||||
}
|
||||
previewTotalCommission += u.CommissionTotal
|
||||
fmt.Fprintln(w)
|
||||
}
|
||||
fmt.Fprintln(w, strings.Repeat("═", 75))
|
||||
fmt.Fprintf(w, "预计追溯佣金总额: $%.2f(目标补偿金额: $%.2f)\n\n",
|
||||
float64(previewTotalCommission)/100, targetCommission/100)
|
||||
|
||||
return agentPlan{
|
||||
Agent: agent,
|
||||
Rule: rule,
|
||||
Selected: selected,
|
||||
TargetAmt: targetCommission,
|
||||
PreviewCommission: previewTotalCommission,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// queryAllActiveAgents returns all agents with referral_percentage > 0, optionally filtered by created_after.
|
||||
func queryAllActiveAgents(ctx context.Context, db *gorm.DB, createdAfter string) ([]*usermodel.User, error) {
|
||||
q := db.WithContext(ctx).Model(&usermodel.User{}).
|
||||
Where("referral_percentage > 0 AND deleted_at IS NULL")
|
||||
if createdAfter != "" {
|
||||
t, err := parseFlexibleTime(createdAfter)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("--agent-created-after 格式错误: %w", err)
|
||||
}
|
||||
q = q.Where("created_at >= ?", t)
|
||||
}
|
||||
var agents []*usermodel.User
|
||||
if err := q.Order("id ASC").Find(&agents).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return agents, nil
|
||||
}
|
||||
|
||||
// parseFlexibleTime parses "YYYY-MM-DD HH:MM:SS" or "YYYY-MM-DD".
|
||||
func parseFlexibleTime(s string) (time.Time, error) {
|
||||
s = strings.TrimSpace(s)
|
||||
if t, err := time.ParseInLocation("2006-01-02 15:04:05", s, time.Local); err == nil {
|
||||
return t, nil
|
||||
}
|
||||
return time.ParseInLocation("2006-01-02", s, time.Local)
|
||||
}
|
||||
|
||||
// queryAgentStats returns (firstReferralTime, lastReferralTime, agentCreatedAt, totalReferred, totalOrders, totalCommission, error).
|
||||
func queryAgentStats(ctx context.Context, db *gorm.DB, agentID int64, endTime time.Time) (time.Time, time.Time, time.Time, int64, int64, int64, error) {
|
||||
var agent usermodel.User
|
||||
if err := db.WithContext(ctx).Model(&usermodel.User{}).
|
||||
Where("id = ?", agentID).
|
||||
First(&agent).Error; err != nil {
|
||||
return time.Time{}, time.Time{}, time.Time{}, 0, 0, 0, err
|
||||
}
|
||||
agentCreatedAt := agent.CreatedAt
|
||||
|
||||
var firstUser usermodel.User
|
||||
if err := db.WithContext(ctx).Model(&usermodel.User{}).
|
||||
Where("referer_id = ? AND created_at >= ? AND created_at <= ? AND deleted_at IS NULL",
|
||||
agentID, agentCreatedAt, endTime).
|
||||
Order("created_at ASC").
|
||||
First(&firstUser).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return time.Time{}, time.Time{}, agentCreatedAt, 0, 0, 0, nil
|
||||
}
|
||||
return time.Time{}, time.Time{}, agentCreatedAt, 0, 0, 0, err
|
||||
}
|
||||
|
||||
var lastUser usermodel.User
|
||||
if err := db.WithContext(ctx).Model(&usermodel.User{}).
|
||||
Where("referer_id = ? AND created_at >= ? AND created_at <= ? AND deleted_at IS NULL",
|
||||
agentID, agentCreatedAt, endTime).
|
||||
Order("created_at DESC").
|
||||
First(&lastUser).Error; err != nil {
|
||||
return time.Time{}, time.Time{}, agentCreatedAt, 0, 0, 0, err
|
||||
}
|
||||
|
||||
var totalReferred int64
|
||||
if err := db.WithContext(ctx).Model(&usermodel.User{}).
|
||||
Where("referer_id = ? AND created_at >= ? AND created_at <= ? AND deleted_at IS NULL",
|
||||
agentID, agentCreatedAt, endTime).
|
||||
Count(&totalReferred).Error; err != nil {
|
||||
return time.Time{}, time.Time{}, agentCreatedAt, 0, 0, 0, err
|
||||
}
|
||||
|
||||
var totalOrders int64
|
||||
if err := db.WithContext(ctx).Model(&ordermodel.Order{}).
|
||||
Joins("JOIN user u ON u.id = `order`.user_id").
|
||||
Where("u.referer_id = ?", agentID).
|
||||
Where("`order`.status IN (2, 5)").
|
||||
Count(&totalOrders).Error; err != nil {
|
||||
return time.Time{}, time.Time{}, agentCreatedAt, 0, 0, 0, err
|
||||
}
|
||||
|
||||
type commResult struct{ Total int64 }
|
||||
var result commResult
|
||||
err := db.WithContext(ctx).Raw(`
|
||||
SELECT COALESCE(SUM(
|
||||
CAST(JSON_UNQUOTE(JSON_EXTRACT(content, '$.amount')) AS SIGNED)
|
||||
), 0) AS total
|
||||
FROM system_logs
|
||||
WHERE type = 33
|
||||
AND object_id = ?
|
||||
AND created_at <= ?
|
||||
AND JSON_UNQUOTE(JSON_EXTRACT(content, '$.type')) IN ('331', '332')
|
||||
`, agentID, endTime).Scan(&result).Error
|
||||
if err != nil {
|
||||
return time.Time{}, time.Time{}, agentCreatedAt, 0, 0, 0, err
|
||||
}
|
||||
|
||||
return firstUser.CreatedAt, lastUser.CreatedAt, agentCreatedAt, totalReferred, totalOrders, result.Total, nil
|
||||
}
|
||||
|
||||
// queryNaturalTrafficPool returns pool candidates with pre-loaded qualifying orders.
|
||||
// orderStart (可为零值):若非零,则只收录至少有一笔 updated_at >= orderStart 订单的用户,
|
||||
// 且只加载/统计 updated_at >= orderStart 的订单(确保分配后代理能在对应月份的销售报表中看到记录)。
|
||||
func queryNaturalTrafficPool(ctx context.Context, db *gorm.DB, start, end time.Time, orderStart time.Time, rule commissionRule) ([]candidateUser, error) {
|
||||
type userRow struct {
|
||||
Id int64
|
||||
CreatedAt time.Time
|
||||
AuthIdentifier string
|
||||
}
|
||||
var userRows []userRow
|
||||
q := db.WithContext(ctx).
|
||||
Table("user u").
|
||||
Select("u.id, u.created_at, COALESCE(am.auth_identifier, '') AS auth_identifier").
|
||||
Joins("JOIN `order` o ON o.user_id = u.id AND o.status IN (2, 5)").
|
||||
Joins("LEFT JOIN user_auth_methods am ON am.user_id = u.id AND am.auth_type = 'email'").
|
||||
Where("u.created_at >= ? AND u.created_at <= ?", start, end).
|
||||
Where("u.referer_id = 0").
|
||||
Where("u.deleted_at IS NULL")
|
||||
if !orderStart.IsZero() {
|
||||
// 只入池那些在 orderStart 之后有过订单的用户(保证代理销售报表里能看到)
|
||||
q = q.Where("o.updated_at >= ?", orderStart)
|
||||
}
|
||||
if err := q.Group("u.id, u.created_at, am.auth_identifier").
|
||||
Order("u.id ASC").
|
||||
Scan(&userRows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(userRows) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
userIDs := make([]int64, len(userRows))
|
||||
for i, r := range userRows {
|
||||
userIDs[i] = r.Id
|
||||
}
|
||||
orderQuery := db.WithContext(ctx).Model(&ordermodel.Order{}).
|
||||
Where("user_id IN ? AND status IN (2, 5)", userIDs)
|
||||
if !orderStart.IsZero() {
|
||||
// 只加载 orderStart 之后的订单:保证佣金统计和销售记录对齐
|
||||
orderQuery = orderQuery.Where("updated_at >= ?", orderStart)
|
||||
}
|
||||
var allOrders []ordermodel.Order
|
||||
if err := orderQuery.Order("user_id ASC, created_at ASC").Find(&allOrders).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ordersByUser := make(map[int64][]ordermodel.Order, len(userRows))
|
||||
for _, od := range allOrders {
|
||||
ordersByUser[od.UserId] = append(ordersByUser[od.UserId], od)
|
||||
}
|
||||
|
||||
candidates := make([]candidateUser, 0, len(userRows))
|
||||
for _, r := range userRows {
|
||||
orders := ordersByUser[r.Id]
|
||||
var commTotal int64
|
||||
for i := range orders {
|
||||
if canCreditOrder(rule, &orders[i]) {
|
||||
commTotal += calcCommissionAmount(orders[i].Amount, orders[i].FeeAmount, rule.Percentage)
|
||||
}
|
||||
}
|
||||
candidates = append(candidates, candidateUser{
|
||||
Id: r.Id,
|
||||
CreatedAt: r.CreatedAt,
|
||||
Identifier: r.AuthIdentifier,
|
||||
Orders: orders,
|
||||
CommissionTotal: commTotal,
|
||||
})
|
||||
}
|
||||
return candidates, nil
|
||||
}
|
||||
|
||||
// randomSampleCandidates picks n random elements from pool without replacement.
|
||||
func randomSampleCandidates(pool []candidateUser, n int) []candidateUser {
|
||||
if n >= len(pool) {
|
||||
return append([]candidateUser{}, pool...)
|
||||
}
|
||||
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
indices := rng.Perm(len(pool))[:n]
|
||||
result := make([]candidateUser, n)
|
||||
for i, idx := range indices {
|
||||
result[i] = pool[idx]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// processOneUser assigns agentId as referer and retroactively credits commission for qualifying orders.
|
||||
func processOneUser(
|
||||
ctx context.Context,
|
||||
db *gorm.DB,
|
||||
um usermodel.Model,
|
||||
agentID, userID int64,
|
||||
rule commissionRule,
|
||||
orderStart time.Time,
|
||||
) (int64, int64, error) {
|
||||
var creditedOrders, creditedAmount int64
|
||||
|
||||
err := db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var target usermodel.User
|
||||
if e := tx.Model(&usermodel.User{}).
|
||||
Where("id = ? AND referer_id = 0 AND deleted_at IS NULL", userID).
|
||||
First(&target).Error; e != nil {
|
||||
if e == gorm.ErrRecordNotFound {
|
||||
return fmt.Errorf("用户不存在或已有代理或已删除")
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
var orders []ordermodel.Order
|
||||
oq := tx.Model(&ordermodel.Order{}).
|
||||
Where("user_id = ? AND status IN (2, 5)", userID)
|
||||
if !orderStart.IsZero() {
|
||||
oq = oq.Where("updated_at >= ?", orderStart)
|
||||
}
|
||||
if e := oq.Order("created_at ASC, id ASC").Find(&orders).Error; e != nil {
|
||||
return e
|
||||
}
|
||||
if len(orders) == 0 {
|
||||
return fmt.Errorf("无合格订单")
|
||||
}
|
||||
|
||||
if e := tx.Model(&usermodel.User{}).
|
||||
Where("id = ? AND referer_id = 0 AND deleted_at IS NULL", userID).
|
||||
Updates(map[string]interface{}{
|
||||
"referer_id": agentID,
|
||||
"updated_at": time.Now(),
|
||||
}).Error; e != nil {
|
||||
return e
|
||||
}
|
||||
|
||||
for i := range orders {
|
||||
od := &orders[i]
|
||||
if !canCreditOrder(rule, od) {
|
||||
continue
|
||||
}
|
||||
amount := calcCommissionAmount(od.Amount, od.FeeAmount, rule.Percentage)
|
||||
if amount <= 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
var existCount int64
|
||||
if e := tx.Model(&logmodel.SystemLog{}).
|
||||
Where("type = ? AND object_id = ? AND content LIKE ?",
|
||||
logmodel.TypeCommission.Uint8(), agentID,
|
||||
fmt.Sprintf("%%\"%s\"%%", od.OrderNo),
|
||||
).Count(&existCount).Error; e != nil {
|
||||
return e
|
||||
}
|
||||
if existCount > 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if e := tx.Model(&usermodel.User{}).
|
||||
Where("id = ? AND deleted_at IS NULL", agentID).
|
||||
UpdateColumn("commission", gorm.Expr("commission + ?", amount)).Error; e != nil {
|
||||
return e
|
||||
}
|
||||
|
||||
commType := logmodel.CommissionTypePurchase
|
||||
if od.Type == 2 {
|
||||
commType = logmodel.CommissionTypeRenewal
|
||||
}
|
||||
payload := &logmodel.Commission{
|
||||
Type: commType,
|
||||
Amount: amount,
|
||||
OrderNo: od.OrderNo,
|
||||
Timestamp: od.CreatedAt.UnixMilli(),
|
||||
}
|
||||
content, _ := payload.Marshal()
|
||||
if e := tx.Create(&logmodel.SystemLog{
|
||||
Type: logmodel.TypeCommission.Uint8(),
|
||||
Date: od.CreatedAt.Format("2006-01-02"),
|
||||
ObjectID: agentID,
|
||||
Content: string(content),
|
||||
CreatedAt: od.CreatedAt,
|
||||
}).Error; e != nil {
|
||||
return e
|
||||
}
|
||||
creditedOrders++
|
||||
creditedAmount += amount
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
if updated, e := um.FindOne(ctx, userID); e == nil {
|
||||
_ = um.UpdateUserCache(ctx, updated)
|
||||
}
|
||||
return creditedOrders, creditedAmount, nil
|
||||
}
|
||||
|
||||
func resolveCommissionRule(agent *usermodel.User, c config.Config) commissionRule {
|
||||
if agent.ReferralPercentage > 0 {
|
||||
onlyFirst := true
|
||||
if agent.OnlyFirstPurchase != nil {
|
||||
onlyFirst = *agent.OnlyFirstPurchase
|
||||
}
|
||||
return commissionRule{Percentage: agent.ReferralPercentage, OnlyFirstPurchase: onlyFirst}
|
||||
}
|
||||
return commissionRule{
|
||||
Percentage: uint8(c.Invite.ReferralPercentage),
|
||||
OnlyFirstPurchase: c.Invite.OnlyFirstPurchase,
|
||||
}
|
||||
}
|
||||
|
||||
func canCreditOrder(rule commissionRule, od *ordermodel.Order) bool {
|
||||
if rule.Percentage == 0 {
|
||||
return false
|
||||
}
|
||||
if rule.OnlyFirstPurchase && !od.IsNew {
|
||||
return false
|
||||
}
|
||||
return od.Status == 2 || od.Status == 5
|
||||
}
|
||||
|
||||
func calcCommissionAmount(amount, feeAmount int64, percentage uint8) int64 {
|
||||
base := amount - feeAmount
|
||||
if base <= 0 || percentage == 0 {
|
||||
return 0
|
||||
}
|
||||
return int64(float64(base) * float64(percentage) / 100)
|
||||
}
|
||||
@@ -4316,6 +4316,9 @@
|
||||
"discount": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"promo": {
|
||||
"$ref": "#/definitions/SubscribePromo"
|
||||
}
|
||||
},
|
||||
"title": "SubscribeDiscount",
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
# MySQL 8.0 master/replica compose for two separate servers.
|
||||
#
|
||||
# Master server:
|
||||
# COMPOSE_PROFILES=master docker compose -f config/docker-compose.mysql-replication.yml up -d
|
||||
#
|
||||
# Replica server:
|
||||
# MASTER_HOST=<master_public_or_private_ip> COMPOSE_PROFILES=replica docker compose -f config/docker-compose.mysql-replication.yml up -d
|
||||
#
|
||||
# Required env on both servers:
|
||||
# MYSQL_ROOT_PASSWORD=<strong-root-password>
|
||||
# MYSQL_REPLICATION_PASSWORD=<strong-replication-password>
|
||||
#
|
||||
# Optional env:
|
||||
# MYSQL_DATABASE=ppanel
|
||||
# MYSQL_REPLICATION_USER=repl
|
||||
# MYSQL_MASTER_PORT=3306
|
||||
# MYSQL_REPLICA_PORT=3306
|
||||
# MYSQL_SERVER_ID=1 # master default
|
||||
# MYSQL_REPLICA_ID=2 # replica default
|
||||
#
|
||||
# If the master already has data, import a GTID-aware dump into the replica
|
||||
# before starting replication. Fresh empty deployments can start master first,
|
||||
# then replica, then point the application at the master.
|
||||
|
||||
services:
|
||||
mysql-master:
|
||||
image: mysql:8.0
|
||||
container_name: ppanel-mysql-master
|
||||
profiles:
|
||||
- master
|
||||
restart: always
|
||||
ports:
|
||||
- "${MYSQL_MASTER_PORT:-3306}:3306"
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD:?please set MYSQL_ROOT_PASSWORD}"
|
||||
MYSQL_DATABASE: "${MYSQL_DATABASE:-ppanel}"
|
||||
MYSQL_REPLICATION_USER: "${MYSQL_REPLICATION_USER:-repl}"
|
||||
MYSQL_REPLICATION_PASSWORD: "${MYSQL_REPLICATION_PASSWORD:?please set MYSQL_REPLICATION_PASSWORD}"
|
||||
TZ: Asia/Shanghai
|
||||
command:
|
||||
- --default-authentication-plugin=mysql_native_password
|
||||
- --server-id=${MYSQL_SERVER_ID:-1}
|
||||
- --log-bin=mysql-bin
|
||||
- --binlog-format=ROW
|
||||
- --gtid-mode=ON
|
||||
- --enforce-gtid-consistency=ON
|
||||
- --log-replica-updates=ON
|
||||
- --binlog-expire-logs-seconds=604800
|
||||
- --max_connections=1000
|
||||
- --character-set-server=utf8mb4
|
||||
- --collation-server=utf8mb4_unicode_ci
|
||||
volumes:
|
||||
- mysql_master_data:/var/lib/mysql
|
||||
configs:
|
||||
- source: mysql_master_init
|
||||
target: /docker-entrypoint-initdb.d/01-create-replication-user.sh
|
||||
mode: 0755
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -uroot -p$${MYSQL_ROOT_PASSWORD}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: 10m
|
||||
max-file: "3"
|
||||
|
||||
mysql-replica:
|
||||
image: mysql:8.0
|
||||
container_name: ppanel-mysql-replica
|
||||
profiles:
|
||||
- replica
|
||||
restart: always
|
||||
ports:
|
||||
- "${MYSQL_REPLICA_PORT:-3306}:3306"
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD:?please set MYSQL_ROOT_PASSWORD}"
|
||||
MYSQL_DATABASE: "${MYSQL_DATABASE:-ppanel}"
|
||||
TZ: Asia/Shanghai
|
||||
command:
|
||||
- --default-authentication-plugin=mysql_native_password
|
||||
- --server-id=${MYSQL_REPLICA_ID:-2}
|
||||
- --relay-log=mysql-relay-bin
|
||||
- --read-only=ON
|
||||
- --super-read-only=ON
|
||||
- --gtid-mode=ON
|
||||
- --enforce-gtid-consistency=ON
|
||||
- --log-replica-updates=ON
|
||||
- --binlog-format=ROW
|
||||
- --max_connections=1000
|
||||
- --character-set-server=utf8mb4
|
||||
- --collation-server=utf8mb4_unicode_ci
|
||||
volumes:
|
||||
- mysql_replica_data:/var/lib/mysql
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -uroot -p$${MYSQL_ROOT_PASSWORD}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: 10m
|
||||
max-file: "3"
|
||||
|
||||
mysql-replica-init:
|
||||
image: mysql:8.0
|
||||
container_name: ppanel-mysql-replica-init
|
||||
profiles:
|
||||
- replica
|
||||
restart: "no"
|
||||
depends_on:
|
||||
mysql-replica:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD:?please set MYSQL_ROOT_PASSWORD}"
|
||||
MYSQL_REPLICATION_USER: "${MYSQL_REPLICATION_USER:-repl}"
|
||||
MYSQL_REPLICATION_PASSWORD: "${MYSQL_REPLICATION_PASSWORD:?please set MYSQL_REPLICATION_PASSWORD}"
|
||||
MASTER_HOST: "${MASTER_HOST:?please set MASTER_HOST to the master server ip or hostname}"
|
||||
MASTER_PORT: "${MASTER_PORT:-3306}"
|
||||
entrypoint:
|
||||
- /bin/sh
|
||||
- -ec
|
||||
- |
|
||||
mysql -hmysql-replica -uroot -p"$${MYSQL_ROOT_PASSWORD}" <<SQL
|
||||
STOP REPLICA;
|
||||
CHANGE REPLICATION SOURCE TO
|
||||
SOURCE_HOST='$${MASTER_HOST}',
|
||||
SOURCE_PORT=$${MASTER_PORT},
|
||||
SOURCE_USER='$${MYSQL_REPLICATION_USER}',
|
||||
SOURCE_PASSWORD='$${MYSQL_REPLICATION_PASSWORD}',
|
||||
SOURCE_AUTO_POSITION=1,
|
||||
GET_SOURCE_PUBLIC_KEY=1;
|
||||
START REPLICA;
|
||||
SQL
|
||||
|
||||
configs:
|
||||
mysql_master_init:
|
||||
content: |
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
mysql -uroot -p"$${MYSQL_ROOT_PASSWORD}" <<SQL
|
||||
CREATE USER IF NOT EXISTS '$${MYSQL_REPLICATION_USER}'@'%' IDENTIFIED WITH mysql_native_password BY '$${MYSQL_REPLICATION_PASSWORD}';
|
||||
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO '$${MYSQL_REPLICATION_USER}'@'%';
|
||||
FLUSH PRIVILEGES;
|
||||
SQL
|
||||
|
||||
volumes:
|
||||
mysql_master_data:
|
||||
mysql_replica_data:
|
||||
@@ -0,0 +1,560 @@
|
||||
# 提现 & 文件上传 & 日志上报 — 用户端 API 接口文档
|
||||
|
||||
> 基于 ppanel-server 源码整理,所有时间戳均为**秒级 Unix**。
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
- [一、提现接口](#一提现接口)
|
||||
- [1.1 申请提现](#11-申请提现)
|
||||
- [1.2 取消提现](#12-取消提现)
|
||||
- [1.3 查询提现记录](#13-查询提现记录)
|
||||
- [二、枚举值与状态流转](#二枚举值与状态流转)
|
||||
- [三、文件上传接口](#三文件上传接口)
|
||||
- [3.1 直传文件(小文件)](#31-直传文件小文件)
|
||||
- [3.2 初始化上传(大文件 — 预签名)](#32-初始化上传大文件--预签名)
|
||||
- [3.3 确认上传完成](#33-确认上传完成)
|
||||
- [四、日志查询接口 (Admin)](#四日志查询接口-admin)
|
||||
- [4.1 错误日志列表](#41-错误日志列表)
|
||||
- [4.2 错误日志详情](#42-错误日志详情)
|
||||
- [4.3 日志消息原始详情](#43-日志消息原始详情)
|
||||
|
||||
---
|
||||
|
||||
## 一、提现接口
|
||||
|
||||
> 认证方式: JWT(用户登录态)
|
||||
>
|
||||
> 路由前缀: `/v1/public/user`
|
||||
|
||||
### 1.1 申请提现
|
||||
|
||||
提交佣金提现申请,创建一条待审核的提现记录。
|
||||
|
||||
```
|
||||
POST /v1/public/user/commission_withdraw
|
||||
```
|
||||
|
||||
**Request Body**
|
||||
|
||||
| 字段 | 类型 | 必填 | 校验 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| `amount` | int64 | 是 | — | 提现金额(分) |
|
||||
| `method` | uint8 | 是 | `oneof=0 1 2 3` | 收款方式(见枚举表) |
|
||||
| `content` | string | 否 | — | 提现备注 |
|
||||
| `account` | string | 条件必填 | — | 收款账号 |
|
||||
| `qr_code_url` | string | 条件必填 | — | 收款码图片 URL |
|
||||
|
||||
**各收款方式的必填字段**
|
||||
|
||||
| method | 收款方式 | 必填字段 |
|
||||
|--------|---------|---------|
|
||||
| `1` 支付宝 | `qr_code_url` | 收款码图片 |
|
||||
| `2` 微信 | `qr_code_url` | 收款码图片 |
|
||||
| `3` 银行卡 | `account` | 收款账号 |
|
||||
| `0` 其他 | `account` 必填 |
|
||||
|
||||
**Request 示例**
|
||||
|
||||
```json
|
||||
{
|
||||
"amount": 5000,
|
||||
"content": "提现到支付宝",
|
||||
"method": 1,
|
||||
"account": "user@example.com",
|
||||
"qr_code_url": "https://cdn.example.com/qrcode/alipay.png"
|
||||
}
|
||||
```
|
||||
|
||||
**Response**: [`WithdrawalLog`](#withdrawallog-对象)
|
||||
|
||||
---
|
||||
|
||||
### 1.2 取消提现
|
||||
|
||||
用户取消自己的待审核提现申请,佣金退回账户。
|
||||
|
||||
```
|
||||
POST /v1/public/user/withdrawal_cancel
|
||||
```
|
||||
|
||||
**Request Body**
|
||||
|
||||
| 字段 | 类型 | 必填 | 校验 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| `withdrawal_id` | int64 | 是 | `required,gt=0` | 提现记录 ID |
|
||||
|
||||
**Request 示例**
|
||||
|
||||
```json
|
||||
{
|
||||
"withdrawal_id": 123
|
||||
}
|
||||
```
|
||||
|
||||
**Response**: [`WithdrawalLog`](#withdrawallog-对象)(状态已变为 `3=已取消`)
|
||||
|
||||
---
|
||||
|
||||
### 1.3 查询提现记录
|
||||
|
||||
分页查询当前用户的提现记录(自动按 JWT 中的 userId 过滤)。
|
||||
|
||||
```
|
||||
GET /v1/public/user/withdrawal_log
|
||||
```
|
||||
|
||||
**Query 参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `page` | int | 否 | 页码,默认 1 |
|
||||
| `size` | int | 否 | 每页条数,默认 10 |
|
||||
|
||||
**Request 示例**
|
||||
|
||||
```
|
||||
GET /v1/public/user/withdrawal_log?page=1&size=10
|
||||
```
|
||||
|
||||
**Response**
|
||||
|
||||
```json
|
||||
{
|
||||
"list": [WithdrawalLog, ...],
|
||||
"total": 25
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、枚举值与状态流转
|
||||
|
||||
### 提现状态 (`status`)
|
||||
|
||||
| 值 | 说明 |
|
||||
|----|------|
|
||||
| 0 | 待审核 |
|
||||
| 1 | 已通过 |
|
||||
| 2 | 已拒绝 |
|
||||
| 3 | 已取消 |
|
||||
|
||||
### 收款方式 (`method`)
|
||||
|
||||
| 值 | 说明 |
|
||||
|----|------|
|
||||
| 0 | 其他 |
|
||||
| 1 | 支付宝 |
|
||||
| 2 | 微信 |
|
||||
| 3 | 银行卡 |
|
||||
|
||||
### 状态流转
|
||||
|
||||
```
|
||||
┌── 管理员通过 ──▶ 已通过 (1)
|
||||
│
|
||||
待审核 (0) ──────┼── 管理员拒绝 ──▶ 已拒绝 (2)
|
||||
│
|
||||
└── 用户取消 ───▶ 已取消 (3)
|
||||
```
|
||||
|
||||
### WithdrawalLog 对象
|
||||
|
||||
所有提现接口共用的响应结构:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"user_id": 100,
|
||||
"amount": 5000,
|
||||
"content": "提现备注",
|
||||
"status": 0,
|
||||
"reason": "",
|
||||
"method": 1,
|
||||
"account": "user@example.com",
|
||||
"qr_code_url": "https://cdn.example.com/qrcode/alipay.png",
|
||||
"created_at": 1716700000,
|
||||
"updated_at": 1716700000
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `id` | int64 | 提现记录 ID |
|
||||
| `user_id` | int64 | 用户 ID |
|
||||
| `amount` | int64 | 提现金额(分) |
|
||||
| `content` | string | 提现备注 |
|
||||
| `status` | uint8 | 状态(见枚举表) |
|
||||
| `reason` | string | 拒绝原因(仅 status=2 时有值,其余 omitempty) |
|
||||
| `method` | uint8 | 收款方式(见枚举表) |
|
||||
| `account` | string | 收款账号 |
|
||||
| `qr_code_url` | string | 收款码图片 URL |
|
||||
| `created_at` | int64 | 创建时间(秒级 Unix) |
|
||||
| `updated_at` | int64 | 更新时间(秒级 Unix) |
|
||||
|
||||
---
|
||||
|
||||
## 三、文件上传接口
|
||||
|
||||
> 认证方式: JWT + DeviceMiddleware(用户登录态 + 设备认证)
|
||||
>
|
||||
> 路由前缀: `/v1/public/file`
|
||||
>
|
||||
> 存储后端: S3 兼容(RustFS)
|
||||
|
||||
提供两种上传方式:
|
||||
|
||||
| 方式 | 适用场景 | 流程 |
|
||||
|------|---------|------|
|
||||
| **直传** | 小文件(收款码等) | 1 次请求,`multipart/form-data` 直接上传 |
|
||||
| **预签名** | 大文件 / 客户端直传 S3 | init → 客户端 PUT 到预签名 URL → complete 确认 |
|
||||
|
||||
---
|
||||
|
||||
### 3.1 直传文件(小文件)
|
||||
|
||||
通过 `multipart/form-data` 直接上传文件到服务端,服务端转存至 S3。
|
||||
|
||||
```
|
||||
POST /v1/public/file/upload
|
||||
Content-Type: multipart/form-data
|
||||
```
|
||||
|
||||
**Form 参数**
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `biz_type` | string | 是 | 业务类型(如 `withdrawal_qrcode`、`avatar` 等) |
|
||||
| `file` | file | 是 | 上传的文件(multipart) |
|
||||
|
||||
**cURL 示例**
|
||||
|
||||
```bash
|
||||
curl -X POST /v1/public/file/upload \
|
||||
-H "Authorization: Bearer <token>" \
|
||||
-F "biz_type=withdrawal_qrcode" \
|
||||
-F "file=@/path/to/alipay_qr.png"
|
||||
```
|
||||
|
||||
**Response**
|
||||
|
||||
```json
|
||||
{
|
||||
"file_id": "a1b2c3d4e5f678901234",
|
||||
"file_name": "alipay_qr.png",
|
||||
"object_key": "app-upload/2026/05/27/100/alipay_qr.png__a1b2c3d4e5f678901234",
|
||||
"size": 52480,
|
||||
"content_type": "image/png",
|
||||
"etag": "\"d41d8cd98f00b204e9800998ecf8427e\"",
|
||||
"status": "completed"
|
||||
}
|
||||
```
|
||||
|
||||
**FileUploadResponse 字段说明**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `file_id` | string | 文件唯一 ID(24 字符 hex) |
|
||||
| `file_name` | string | 原始文件名 |
|
||||
| `object_key` | string | S3 对象路径 |
|
||||
| `size` | int64 | 文件大小(字节) |
|
||||
| `content_type` | string | MIME 类型 |
|
||||
| `etag` | string | S3 ETag |
|
||||
| `status` | string | 状态,直传成功即 `completed` |
|
||||
|
||||
---
|
||||
|
||||
### 3.2 初始化上传(大文件 — 预签名)
|
||||
|
||||
获取 S3 预签名 URL,客户端直接 PUT 到 S3,避免文件经过服务端。
|
||||
|
||||
```
|
||||
POST /v1/public/file/upload/init
|
||||
```
|
||||
|
||||
**Request Body**
|
||||
|
||||
| 字段 | 类型 | 必填 | 校验 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| `biz_type` | string | 是 | `required` | 业务类型 |
|
||||
| `file_name` | string | 是 | `required` | 文件名 |
|
||||
| `content_type` | string | 是 | `required` | MIME 类型(如 `image/png`) |
|
||||
| `size` | int64 | 是 | `required` | 文件大小(字节) |
|
||||
| `sha256` | string | 否 | — | 文件 SHA256(可选校验) |
|
||||
|
||||
**Request 示例**
|
||||
|
||||
```json
|
||||
{
|
||||
"biz_type": "withdrawal_qrcode",
|
||||
"file_name": "wechat_qr.png",
|
||||
"content_type": "image/png",
|
||||
"size": 102400,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb924..."
|
||||
}
|
||||
```
|
||||
|
||||
**Response**
|
||||
|
||||
```json
|
||||
{
|
||||
"file_id": "b2c3d4e5f6789012345a",
|
||||
"object_key": "app-upload/2026/05/27/100/wechat_qr.png__b2c3d4e5f6789012345a",
|
||||
"upload_url": "https://s3.example.com/bucket/app-upload/...?X-Amz-Signature=...",
|
||||
"method": "PUT",
|
||||
"headers": {
|
||||
"Content-Type": "image/png"
|
||||
},
|
||||
"expired_at": 1716700300
|
||||
}
|
||||
```
|
||||
|
||||
**FileUploadInitResponse 字段说明**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `file_id` | string | 文件唯一 ID |
|
||||
| `object_key` | string | S3 对象路径 |
|
||||
| `upload_url` | string | 预签名上传 URL |
|
||||
| `method` | string | HTTP 方法(`PUT`) |
|
||||
| `headers` | map | 上传时需携带的请求头 |
|
||||
| `expired_at` | int64 | 预签名过期时间(秒级 Unix,默认 300 秒) |
|
||||
|
||||
**客户端上传流程**
|
||||
|
||||
```
|
||||
1. 调用 /upload/init 获取 upload_url
|
||||
2. 用返回的 method + headers 直接上传文件到 upload_url
|
||||
3. 上传成功后调用 /upload/complete 确认
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.3 确认上传完成
|
||||
|
||||
客户端通过预签名 URL 上传完成后,调用此接口确认文件状态。
|
||||
|
||||
```
|
||||
POST /v1/public/file/upload/complete
|
||||
```
|
||||
|
||||
**Request Body**
|
||||
|
||||
| 字段 | 类型 | 必填 | 校验 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| `file_id` | string | 是 | `required` | init 返回的 file_id |
|
||||
|
||||
**Request 示例**
|
||||
|
||||
```json
|
||||
{
|
||||
"file_id": "b2c3d4e5f6789012345a"
|
||||
}
|
||||
```
|
||||
|
||||
**Response**
|
||||
|
||||
```json
|
||||
{
|
||||
"file_id": "b2c3d4e5f6789012345a",
|
||||
"object_key": "app-upload/2026/05/27/100/wechat_qr.png__b2c3d4e5f6789012345a",
|
||||
"size": 102400,
|
||||
"content_type": "image/png",
|
||||
"etag": "\"d41d8cd98f00b204e9800998ecf8427e\"",
|
||||
"status": "completed"
|
||||
}
|
||||
```
|
||||
|
||||
**FileUploadCompleteResponse 字段说明**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `file_id` | string | 文件唯一 ID |
|
||||
| `object_key` | string | S3 对象路径 |
|
||||
| `size` | int64 | 实际文件大小(S3 HeadObject 获取) |
|
||||
| `content_type` | string | MIME 类型 |
|
||||
| `etag` | string | S3 ETag |
|
||||
| `status` | string | `completed` |
|
||||
|
||||
**校验规则**
|
||||
|
||||
- 文件大小不能超过配置的 `S3.MaxUploadSize`
|
||||
- Content-Type 必须在配置的 `S3.AllowedContentTypes` 白名单内(若配置了)
|
||||
- complete 时会校验 S3 上的实际文件大小是否与 init 声明的一致
|
||||
- 只能确认自己发起的上传(userId 校验)
|
||||
|
||||
---
|
||||
|
||||
## 四、日志查询接口 (Admin)
|
||||
|
||||
> 认证方式: AuthMiddleware(管理员权限)
|
||||
>
|
||||
> 路由前缀: `/v1/admin/log`
|
||||
>
|
||||
> 数据来源: `log_message` 表(客户端上报的错误/崩溃日志)
|
||||
|
||||
---
|
||||
|
||||
### 4.1 错误日志列表
|
||||
|
||||
分页查询客户端上报的错误日志,支持多维度筛选。
|
||||
|
||||
```
|
||||
GET /v1/admin/log/error_message/list
|
||||
```
|
||||
|
||||
**Query 参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `page` | int | 是 | 页码 |
|
||||
| `size` | int | 是 | 每页条数 |
|
||||
| `platform` | string | 否 | 平台筛选(ios / android / windows / mac / harmony) |
|
||||
| `level` | uint8 | 否 | 日志级别 |
|
||||
| `user_id` | int64 | 否 | 用户 ID |
|
||||
| `device_id` | string | 否 | 设备 ID |
|
||||
| `error_code` | string | 否 | 错误码 |
|
||||
| `keyword` | string | 否 | 关键字搜索(匹配 message) |
|
||||
| `start` | int64 | 否 | 开始时间(秒级 Unix) |
|
||||
| `end` | int64 | 否 | 结束时间(秒级 Unix) |
|
||||
|
||||
**Request 示例**
|
||||
|
||||
```
|
||||
GET /v1/admin/log/error_message/list?page=1&size=20&platform=ios&start=1716600000&end=1716700000
|
||||
```
|
||||
|
||||
**Response**
|
||||
|
||||
```json
|
||||
{
|
||||
"total": 50,
|
||||
"list": [
|
||||
{
|
||||
"id": 1,
|
||||
"platform": "ios",
|
||||
"app_version": "2.1.0",
|
||||
"os_name": "iOS",
|
||||
"os_version": "17.5",
|
||||
"device_id": "A1B2C3D4",
|
||||
"user_id": 100,
|
||||
"session_id": "sess_xxx",
|
||||
"level": 3,
|
||||
"error_code": "VPN_CONNECT_FAIL",
|
||||
"message": "Failed to establish VPN tunnel",
|
||||
"created_at": 1716700000
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**ErrorLogMessage 字段说明**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `id` | int64 | 日志 ID |
|
||||
| `platform` | string | 平台 |
|
||||
| `app_version` | string | 客户端版本 |
|
||||
| `os_name` | string | 操作系统名称 |
|
||||
| `os_version` | string | 操作系统版本 |
|
||||
| `device_id` | string | 设备 ID |
|
||||
| `user_id` | int64 | 用户 ID |
|
||||
| `session_id` | string | 会话 ID |
|
||||
| `level` | uint8 | 日志级别 |
|
||||
| `error_code` | string | 错误码 |
|
||||
| `message` | string | 错误消息 |
|
||||
| `created_at` | int64 | 创建时间(秒级 Unix) |
|
||||
|
||||
---
|
||||
|
||||
### 4.2 错误日志详情
|
||||
|
||||
获取单条错误日志的完整详情(列表字段 + 堆栈/IP/UA 等扩展信息)。
|
||||
|
||||
```
|
||||
GET /v1/admin/log/error_message/detail
|
||||
```
|
||||
|
||||
**Response**
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"platform": "ios",
|
||||
"app_version": "2.1.0",
|
||||
"os_name": "iOS",
|
||||
"os_version": "17.5",
|
||||
"device_id": "A1B2C3D4",
|
||||
"user_id": 100,
|
||||
"session_id": "sess_xxx",
|
||||
"level": 3,
|
||||
"error_code": "VPN_CONNECT_FAIL",
|
||||
"message": "Failed to establish VPN tunnel",
|
||||
"stack": "at VPNManager.connect() line 42\nat ...",
|
||||
"client_ip": "1.2.3.4",
|
||||
"user_agent": "PPanel/2.1.0 iOS/17.5",
|
||||
"locale": "zh-CN",
|
||||
"occurred_at": 1716700000,
|
||||
"created_at": 1716700000
|
||||
}
|
||||
```
|
||||
|
||||
**相比列表额外返回的字段**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `stack` | string | 堆栈信息 |
|
||||
| `client_ip` | string | 客户端 IP |
|
||||
| `user_agent` | string | User-Agent |
|
||||
| `locale` | string | 客户端语言/地区 |
|
||||
| `occurred_at` | int64 | 错误发生时间(秒级 Unix) |
|
||||
|
||||
---
|
||||
|
||||
### 4.3 日志消息原始详情
|
||||
|
||||
获取单条 `log_message` 的完整原始数据(含 context、digest 等全量字段)。
|
||||
|
||||
```
|
||||
GET /v1/admin/log/message/detail
|
||||
```
|
||||
|
||||
**Query 参数**
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `id` | int64 | 是 | 日志消息 ID |
|
||||
|
||||
**Response**
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"platform": "ios",
|
||||
"app_version": "2.1.0",
|
||||
"os_name": "iOS",
|
||||
"os_version": "17.5",
|
||||
"device_id": "A1B2C3D4",
|
||||
"user_id": 100,
|
||||
"session_id": "sess_xxx",
|
||||
"level": 3,
|
||||
"error_code": "VPN_CONNECT_FAIL",
|
||||
"message": "Failed to establish VPN tunnel",
|
||||
"stack": "at VPNManager.connect() line 42\nat ...",
|
||||
"context": { "server_id": 5, "protocol": "vmess" },
|
||||
"client_ip": "1.2.3.4",
|
||||
"user_agent": "PPanel/2.1.0 iOS/17.5",
|
||||
"locale": "zh-CN",
|
||||
"digest": "sha256_abc123...",
|
||||
"occurred_at": 1716700000,
|
||||
"created_at": 1716700000
|
||||
}
|
||||
```
|
||||
|
||||
**相比详情额外返回的字段**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `context` | any | 附加上下文(原始 JSON) |
|
||||
| `digest` | string | 内容摘要(用于去重) |
|
||||
@@ -0,0 +1,559 @@
|
||||
# App 邀请列表与商品套餐折扣需求梳理
|
||||
|
||||
本文基于当前 `ppanel-server` 代码现状,对以下两个需求做整理:
|
||||
|
||||
1. App 需要一个“邀请列表/邀请记录”接口。
|
||||
2. 商品套餐需要补充“折扣信息”,当存在折扣时,App 需要做样式展示,并支持用户继续下单购买。
|
||||
|
||||
目标是帮助产品、前端、后端快速统一口径,明确:
|
||||
|
||||
- 现在已有哪些接口可以复用
|
||||
- 哪些地方确实需要新增
|
||||
- “接口增加三个字段”更适合加在哪一层
|
||||
|
||||
---
|
||||
|
||||
## 1. 需求结论
|
||||
|
||||
### 1.1 邀请列表
|
||||
|
||||
当前公开侧已经有一部分邀请能力,但**没有一个完全匹配“App 邀请记录列表”语义的公开接口**。
|
||||
|
||||
现状:
|
||||
|
||||
- 已有 `GET /v1/public/user/affiliate/list`
|
||||
- 能返回“我邀请了哪些用户”
|
||||
- 但字段较少,只包含基础信息
|
||||
- 已有 `GET /v1/public/user/invite_sales`
|
||||
- 返回的是“被邀请用户的成交订单记录”
|
||||
- 不是“邀请用户记录列表”
|
||||
- 已有 `GET /v1/public/user/invite_stats`
|
||||
- 返回邀请统计
|
||||
- 不是列表
|
||||
|
||||
结论:
|
||||
|
||||
- 如果 App 只是要展示“我邀请了哪些人”,`/affiliate/list` 可以复用。
|
||||
- 如果 App 需要展示“邀请时间、是否购买、购买次数、带来的佣金/赠送天数”等完整邀请记录,则**建议新增一个公开接口**。
|
||||
|
||||
### 1.2 商品套餐折扣
|
||||
|
||||
当前公开侧套餐列表接口 `GET /v1/public/subscribe/list` **已经返回 `discount` 字段**,下单预览接口 `POST /v1/public/order/pre` 也已经支持折扣计算。
|
||||
|
||||
现状:
|
||||
|
||||
- 套餐列表已有折扣规则数组 `discount`
|
||||
- 预下单接口已有:
|
||||
- 原价 `price`
|
||||
- 实付 `amount`
|
||||
- 折扣金额 `discount`
|
||||
- 活动优惠 `promo_discount`
|
||||
- 优惠券减免 `coupon_discount`
|
||||
- 手续费 `fee_amount`
|
||||
|
||||
结论:
|
||||
|
||||
- 后端**不是完全没有折扣能力**,而是“已经有计算能力,但 App 展示层使用起来不够直接”。
|
||||
- 如果需求明确要求“接口增加三个字段”,**更推荐补在套餐列表返回的 `discount[]` 子项里**,而不是直接加在下单接口里。
|
||||
|
||||
---
|
||||
|
||||
## 2. 当前代码现状
|
||||
|
||||
## 2.1 邀请相关
|
||||
|
||||
### 2.1.1 已有公开接口
|
||||
|
||||
#### A. 邀请基础列表
|
||||
|
||||
接口:
|
||||
|
||||
- `GET /v1/public/user/affiliate/list`
|
||||
|
||||
请求:
|
||||
|
||||
- `page`
|
||||
- `size`
|
||||
|
||||
返回结构:
|
||||
|
||||
- `total`
|
||||
- `list[]`
|
||||
- `identifier`
|
||||
- `avatar`
|
||||
- `registered_at`
|
||||
- `enable`
|
||||
|
||||
特点:
|
||||
|
||||
- 能表达“我邀请了谁”
|
||||
- 不能表达“是否购买 / 购买次数 / 给我带来多少收益”
|
||||
|
||||
对应代码:
|
||||
|
||||
- `apis/public/user.api`
|
||||
- `internal/logic/public/user/queryUserAffiliateListLogic.go`
|
||||
|
||||
#### B. 邀请成交记录
|
||||
|
||||
接口:
|
||||
|
||||
- `GET /v1/public/user/invite_sales`
|
||||
|
||||
返回结构:
|
||||
|
||||
- `total`
|
||||
- `list[]`
|
||||
- `amount`
|
||||
- `updated_at`
|
||||
- `user_hash`
|
||||
- `product_name`
|
||||
|
||||
特点:
|
||||
|
||||
- 更像“邀请带来的订单流水”
|
||||
- 不是邀请用户列表
|
||||
|
||||
对应代码:
|
||||
|
||||
- `internal/logic/public/user/getInviteSalesLogic.go`
|
||||
|
||||
#### C. 邀请统计
|
||||
|
||||
接口:
|
||||
|
||||
- `GET /v1/public/user/invite_stats`
|
||||
|
||||
返回结构:
|
||||
|
||||
- `friendly_count`
|
||||
- `history_count`
|
||||
|
||||
特点:
|
||||
|
||||
- 只适合头部统计卡片
|
||||
- 不适合列表页
|
||||
|
||||
对应代码:
|
||||
|
||||
- `internal/logic/public/user/getUserInviteStatsLogic.go`
|
||||
|
||||
### 2.1.2 已有后台接口
|
||||
|
||||
后台已经有更完整的邀请记录能力,可以直接参考:
|
||||
|
||||
- `GetAdminUserInviteList`
|
||||
- `GetInviteManageList`
|
||||
|
||||
这些接口已经能返回:
|
||||
|
||||
- 邀请时间
|
||||
- 是否购买
|
||||
- 购买次数
|
||||
- 邀请人佣金
|
||||
- 邀请人赠送天数
|
||||
- 被邀请人赠送天数
|
||||
|
||||
对应代码:
|
||||
|
||||
- `internal/logic/admin/user/getAdminUserInviteListLogic.go`
|
||||
- `internal/logic/admin/invite/getInviteManageListLogic.go`
|
||||
|
||||
结论:
|
||||
|
||||
- 邀请记录的统计逻辑后端已经有现成实现思路。
|
||||
- 新增 App 公开接口时,建议复用这部分逻辑,不要从零再写一套。
|
||||
|
||||
---
|
||||
|
||||
## 2.2 套餐折扣相关
|
||||
|
||||
### 2.2.1 套餐列表接口已有折扣规则
|
||||
|
||||
接口:
|
||||
|
||||
- `GET /v1/public/subscribe/list`
|
||||
|
||||
当前返回的套餐结构 `Subscribe` 中已包含:
|
||||
|
||||
- `unit_price`
|
||||
- `discount []SubscribeDiscount`
|
||||
|
||||
其中 `SubscribeDiscount` 当前字段为:
|
||||
|
||||
- `quantity`
|
||||
- `discount`
|
||||
- `new_user_only`
|
||||
- `map_apple`
|
||||
- `promo`
|
||||
|
||||
对应代码:
|
||||
|
||||
- `apis/public/subscribe.api`
|
||||
- `internal/logic/public/subscribe/querySubscribeListLogic.go`
|
||||
- `internal/types/types.go`
|
||||
|
||||
说明:
|
||||
|
||||
- `discount` 是按购买数量 `quantity` 生效的阶梯折扣
|
||||
- 不是单个套餐固定只有一个折扣值
|
||||
|
||||
### 2.2.2 预下单接口已有价格计算结果
|
||||
|
||||
接口:
|
||||
|
||||
- `POST /v1/public/order/pre`
|
||||
|
||||
当前已返回:
|
||||
|
||||
- `price`:原价
|
||||
- `amount`:最终应付
|
||||
- `discount`:折扣减免金额
|
||||
- `promo_discount`:活动优惠金额
|
||||
- `gift_amount`:礼品余额抵扣
|
||||
- `coupon_discount`:优惠券减免
|
||||
- `fee_amount`:手续费
|
||||
|
||||
对应代码:
|
||||
|
||||
- `apis/public/order.api`
|
||||
- `internal/logic/public/order/preCreateOrderLogic.go`
|
||||
|
||||
说明:
|
||||
|
||||
- 只要 App 知道 `subscribe_id + quantity [+ coupon] [+ payment]`,就已经能拿到准确的下单金额
|
||||
- 所以“下单购买”这件事本身,后端主链路已经具备
|
||||
|
||||
---
|
||||
|
||||
## 3. 差距分析
|
||||
|
||||
## 3.1 邀请列表的真实缺口
|
||||
|
||||
如果产品要的是“邀请记录页”,通常至少会关心以下内容:
|
||||
|
||||
- 被邀请用户
|
||||
- 邀请时间
|
||||
- 是否已购买
|
||||
- 购买次数
|
||||
- 给邀请人带来的佣金
|
||||
- 双方赠送天数
|
||||
|
||||
而当前:
|
||||
|
||||
- `/affiliate/list` 只有基础用户列表
|
||||
- `/invite_sales` 是订单成交记录
|
||||
- `/invite_stats` 是统计值
|
||||
|
||||
所以当前公开侧缺一个“**邀请关系维度的邀请记录列表**”。
|
||||
|
||||
## 3.2 套餐折扣的真实缺口
|
||||
|
||||
后端目前的主要问题不是“不会算折扣”,而是:
|
||||
|
||||
- `discount[]` 更偏规则定义
|
||||
- App 如果只想直接展示“折后价 / 优惠金额 / 折扣标签”,还需要自己再算一层
|
||||
- 这会增加前端理解成本,也容易和后端口径不一致
|
||||
|
||||
所以当前更合理的改法是:
|
||||
|
||||
- 保留现有折扣规则
|
||||
- 再额外补充几个**面向展示的字段**
|
||||
|
||||
---
|
||||
|
||||
## 4. 推荐方案
|
||||
|
||||
## 4.1 邀请记录接口
|
||||
|
||||
### 方案建议
|
||||
|
||||
新增一个公开接口,例如:
|
||||
|
||||
- `GET /v1/public/user/invite_records`
|
||||
|
||||
说明:
|
||||
|
||||
- 从登录态中取当前用户 ID
|
||||
- 不从前端传 `user_id`
|
||||
- 只查“当前用户邀请的记录”
|
||||
|
||||
### 请求参数建议
|
||||
|
||||
```json
|
||||
{
|
||||
"page": 1,
|
||||
"size": 10
|
||||
}
|
||||
```
|
||||
|
||||
### 返回字段建议
|
||||
|
||||
```json
|
||||
{
|
||||
"total": 2,
|
||||
"list": [
|
||||
{
|
||||
"invitee_id": 1001,
|
||||
"invitee_identifier": "138****8888",
|
||||
"invitee_avatar": "https://...",
|
||||
"invitee_enable": true,
|
||||
"invited_at": 1716800000,
|
||||
"order_count": 3,
|
||||
"has_purchased": true,
|
||||
"inviter_commission": 1200,
|
||||
"inviter_gift_days": 30,
|
||||
"invitee_gift_days": 7
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 字段说明
|
||||
|
||||
- `invitee_id`:被邀请用户 ID
|
||||
- `invitee_identifier`:被邀请用户展示账号
|
||||
- `invitee_avatar`:头像
|
||||
- `invitee_enable`:是否启用
|
||||
- `invited_at`:邀请时间
|
||||
- `order_count`:该被邀请用户产生的有效订单数
|
||||
- `has_purchased`:是否已购买
|
||||
- `inviter_commission`:给邀请人带来的佣金,单位建议继续沿用分
|
||||
- `inviter_gift_days`:邀请人获赠天数
|
||||
- `invitee_gift_days`:被邀请人获赠天数
|
||||
|
||||
### 实现建议
|
||||
|
||||
优先复用现有后台逻辑思路:
|
||||
|
||||
- 参考 `internal/logic/admin/user/getAdminUserInviteListLogic.go`
|
||||
- 或参考 `internal/logic/admin/invite/getInviteManageListLogic.go`
|
||||
|
||||
公开接口与后台接口的主要差异只有两点:
|
||||
|
||||
- 公开接口不允许前端指定 `user_id`
|
||||
- 公开接口按当前登录用户本人维度返回
|
||||
|
||||
### 是否可以不新增接口
|
||||
|
||||
可以,但前提是 App 接受以下拆分:
|
||||
|
||||
- 列表页用 `/affiliate/list`
|
||||
- 顶部统计用 `/invite_stats`
|
||||
- 订单流水页用 `/invite_sales`
|
||||
|
||||
如果产品要的是一个完整“邀请记录页”,不建议这样拆三次请求,前端维护成本偏高。
|
||||
|
||||
---
|
||||
|
||||
## 4.2 套餐折扣字段建议
|
||||
|
||||
### 核心建议
|
||||
|
||||
“接口增加三个字段”建议**加在 `SubscribeDiscount` 子项里**,不要直接加在 `Subscribe` 顶层。
|
||||
|
||||
原因:
|
||||
|
||||
- 折扣是按 `quantity` 生效的
|
||||
- 一个套餐可能有多个折扣档位
|
||||
- 如果加在套餐顶层,很难表达“买 1 个月”和“买 12 个月”对应不同折扣
|
||||
|
||||
### 推荐新增字段
|
||||
|
||||
建议在 `SubscribeDiscount` 中增加以下三个展示字段:
|
||||
|
||||
- `discount_price`
|
||||
- `discount_amount`
|
||||
- `discount_desc`
|
||||
|
||||
推荐结构如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"quantity": 12,
|
||||
"discount": 80,
|
||||
"new_user_only": false,
|
||||
"map_apple": "",
|
||||
"promo": null,
|
||||
"discount_price": 9600,
|
||||
"discount_amount": 2400,
|
||||
"discount_desc": "年付8折"
|
||||
}
|
||||
```
|
||||
|
||||
### 三个字段的含义
|
||||
|
||||
#### 1. `discount_price`
|
||||
|
||||
- 含义:该档位折后总价
|
||||
- 计算建议:`unit_price * quantity * discount / 100`
|
||||
- 单位:分
|
||||
|
||||
作用:
|
||||
|
||||
- App 可直接展示“折后价”
|
||||
- 下单时直接把该项的 `quantity` 带入 `/order/pre` 或 `/order/purchase`
|
||||
|
||||
#### 2. `discount_amount`
|
||||
|
||||
- 含义:该档位比原价便宜多少钱
|
||||
- 计算建议:`unit_price * quantity - discount_price`
|
||||
- 单位:分
|
||||
|
||||
作用:
|
||||
|
||||
- App 可直接展示“立省 xx”
|
||||
|
||||
#### 3. `discount_desc`
|
||||
|
||||
- 含义:折扣展示文案
|
||||
- 示例:
|
||||
- `年付8折`
|
||||
- `季付9折`
|
||||
- `新用户首单8折`
|
||||
|
||||
作用:
|
||||
|
||||
- App 可直接做角标、标签、促销文案展示
|
||||
|
||||
### 为什么不推荐这三个字段加在下单接口
|
||||
|
||||
因为下单接口本来就是“结果型接口”,它已经能返回:
|
||||
|
||||
- 原价
|
||||
- 折扣金额
|
||||
- 实付金额
|
||||
|
||||
如果只是为了 App 卡片展示,再去每个套餐都调一次 `/order/pre`,成本会比较高:
|
||||
|
||||
- 请求次数多
|
||||
- 页面首屏会更慢
|
||||
- 前端链路更复杂
|
||||
|
||||
更合适的做法是:
|
||||
|
||||
- 套餐列表接口负责“展示友好”
|
||||
- 预下单接口负责“结算准确”
|
||||
|
||||
---
|
||||
|
||||
## 5. 推荐改动清单
|
||||
|
||||
## 5.1 邀请列表
|
||||
|
||||
建议新增:
|
||||
|
||||
- 新接口:`GET /v1/public/user/invite_records`
|
||||
|
||||
建议新增类型:
|
||||
|
||||
- `GetUserInviteRecordsRequest`
|
||||
- `UserInviteRecord`
|
||||
- `GetUserInviteRecordsResponse`
|
||||
|
||||
建议实现位置:
|
||||
|
||||
- `apis/public/user.api`
|
||||
- `internal/types/types.go`
|
||||
- `internal/handler/public/user/`
|
||||
- `internal/logic/public/user/`
|
||||
|
||||
## 5.2 套餐折扣
|
||||
|
||||
建议调整:
|
||||
|
||||
- `SubscribeDiscount` 增加 3 个字段:
|
||||
- `discount_price`
|
||||
- `discount_amount`
|
||||
- `discount_desc`
|
||||
|
||||
建议实现位置:
|
||||
|
||||
- `apis/types.api`
|
||||
- `internal/types/types.go`
|
||||
- `internal/logic/public/subscribe/querySubscribeListLogic.go`
|
||||
|
||||
---
|
||||
|
||||
## 6. 前后端协作建议
|
||||
|
||||
## 6.1 App 侧调用建议
|
||||
|
||||
邀请页建议:
|
||||
|
||||
- 头部统计:`/v1/public/user/invite_stats`
|
||||
- 邀请记录列表:`/v1/public/user/invite_records`
|
||||
- 如果还要看成交流水:`/v1/public/user/invite_sales`
|
||||
|
||||
套餐页建议:
|
||||
|
||||
- 先调 `/v1/public/subscribe/list` 渲染套餐和折扣标签
|
||||
- 用户点某个折扣档位时,带 `subscribe_id + quantity` 调 `/v1/public/order/pre`
|
||||
- 用户确认后再调 `/v1/public/order/purchase`
|
||||
|
||||
## 6.2 单位口径建议
|
||||
|
||||
建议继续保持后端金额统一为“分”:
|
||||
|
||||
- `unit_price`
|
||||
- `discount_price`
|
||||
- `discount_amount`
|
||||
- `amount`
|
||||
- `coupon_discount`
|
||||
|
||||
这样可以避免前后端出现小数精度问题。
|
||||
|
||||
---
|
||||
|
||||
## 7. 最终建议
|
||||
|
||||
### 建议一
|
||||
|
||||
如果你们只是要“邀请用户名单”,可直接复用:
|
||||
|
||||
- `GET /v1/public/user/affiliate/list`
|
||||
|
||||
### 建议二
|
||||
|
||||
如果你们要的是完整“邀请记录”,建议新增:
|
||||
|
||||
- `GET /v1/public/user/invite_records`
|
||||
|
||||
这是本次需求里更合理的新增接口。
|
||||
|
||||
### 建议三
|
||||
|
||||
商品套餐“折扣信息”不建议重新设计一整套下单逻辑。
|
||||
|
||||
当前后端已经具备:
|
||||
|
||||
- 套餐折扣规则
|
||||
- 预下单价格计算
|
||||
- 正式下单购买
|
||||
|
||||
更推荐做法是:
|
||||
|
||||
- 在 `SubscribeDiscount` 里补 3 个展示字段:
|
||||
- `discount_price`
|
||||
- `discount_amount`
|
||||
- `discount_desc`
|
||||
|
||||
这样改动最小,也最贴近 App 展示场景。
|
||||
|
||||
---
|
||||
|
||||
## 8. 相关代码位置
|
||||
|
||||
- `internal/logic/public/user/queryUserAffiliateListLogic.go`
|
||||
- `internal/logic/public/user/getInviteSalesLogic.go`
|
||||
- `internal/logic/public/user/getUserInviteStatsLogic.go`
|
||||
- `internal/logic/admin/user/getAdminUserInviteListLogic.go`
|
||||
- `internal/logic/admin/invite/getInviteManageListLogic.go`
|
||||
- `internal/logic/public/subscribe/querySubscribeListLogic.go`
|
||||
- `internal/logic/public/order/preCreateOrderLogic.go`
|
||||
- `internal/logic/public/order/purchaseLogic.go`
|
||||
- `internal/types/types.go`
|
||||
- `apis/public/user.api`
|
||||
- `apis/public/subscribe.api`
|
||||
- `apis/public/order.api`
|
||||
|
||||
@@ -0,0 +1,435 @@
|
||||
# App 端三个接口对接文档
|
||||
|
||||
> 适用:移动端 / 桌面端 App
|
||||
> 维护:基于当前 `internal/handler` + `internal/logic` 代码反向梳理
|
||||
> 时间:2026-05-27
|
||||
|
||||
涉及接口:
|
||||
|
||||
1. [文件上传](#1-文件上传) — `POST /v1/public/file/upload`
|
||||
2. [订阅列表(含促销 promo)](#2-订阅列表含促销-promo) — `GET /v1/public/subscribe/list`
|
||||
3. [邀请赠送记录](#3-邀请赠送记录) — `GET /v1/public/user/invite_records`
|
||||
|
||||
公共说明:
|
||||
|
||||
- BaseURL 示例:`https://tapi.hifast.biz`
|
||||
- 鉴权头:`Authorization: <JWT>`(注意:**不要**写 `Bearer ` 前缀,本项目 `AuthMiddleware` 直接取 token 值)
|
||||
- 业务码包在 `{ code, msg, data }` 信封中,`code = 200` 为成功
|
||||
- 默认 `Accept: application/json`,可选 `lang: zh_CN`
|
||||
- 经过 `AuthMiddleware` + `DeviceMiddleware` 的接口都需要登录态 + 设备绑定校验
|
||||
|
||||
---
|
||||
|
||||
## 1. 文件上传
|
||||
|
||||
### 1.1 Endpoint
|
||||
|
||||
```
|
||||
POST /v1/public/file/upload
|
||||
Content-Type: multipart/form-data
|
||||
```
|
||||
|
||||
- Handler: `internal/handler/public/file/fileUploadHandler.go`
|
||||
- Logic: `internal/logic/public/file/fileuploadlogic.go:33`
|
||||
- 路由: `internal/handler/routes.go:934`
|
||||
- 中间件: `AuthMiddleware` + `DeviceMiddleware`(必须登录)
|
||||
|
||||
### 1.2 请求
|
||||
|
||||
#### Form 参数
|
||||
|
||||
| 字段 | 位置 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `biz_type` | form | 是 | 业务分类标签,会作为对象 key 的一部分(如 `app-package`、`avatar`) |
|
||||
| `file` | form file | 是 | 待上传文件二进制 |
|
||||
|
||||
#### 文件约束(来自 `etc/ppanel.yaml` → `S3`,可调整)
|
||||
|
||||
| 项 | 默认值 |
|
||||
|---|---|
|
||||
| 单文件最大 | **104857600 字节(100 MiB)** |
|
||||
| 允许的 Content-Type | `application/zip, application/x-zip-compressed, application/gzip, application/x-gzip, application/octet-stream, text/plain, application/json, image/jpeg, image/jpg, image/png, image/webp, image/gif, image/heic, image/heif, image/bmp` |
|
||||
|
||||
> Content-Type 判定优先级:multipart 文件头里的 `Content-Type` → 文件嗅探(前 512 字节)→ 兜底 `application/octet-stream`。
|
||||
> **前端 form 上传时尽量带上 `Content-Type`**,否则被嗅探成 `application/octet-stream` 可能不在白名单里。
|
||||
|
||||
### 1.3 请求示例
|
||||
|
||||
```bash
|
||||
curl -X POST 'https://tapi.hifast.biz/v1/public/file/upload' \
|
||||
-H 'Authorization: <JWT>' \
|
||||
-H 'Accept: application/json' \
|
||||
-F 'biz_type=app-package' \
|
||||
-F 'file=@"/Users/Apple/Documents/avatar.jpg";type=image/jpeg'
|
||||
```
|
||||
|
||||
### 1.4 响应
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `data.url` | string | 上传完成后的可访问 URL,规则:`{S3.PublicBaseURL or S3.Endpoint}/{bucket}/{prefix}/{YYYY}/{MM}/{DD}/{userId}/{safeFileName}__{fileId}` |
|
||||
|
||||
成功示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"url": "http://107.173.50.22:5016/hifastvpn/app-upload/2026/05/28/510/2026-05-27_20.03.55.jpg__226ad097c2ee4e3546e729c5"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 1.5 错误码
|
||||
|
||||
| 业务码 | 触发场景 |
|
||||
|---|---|
|
||||
| `InvalidAccess` | 未登录 / JWT 无效 |
|
||||
| `ParamError` | 缺少 `biz_type` 或 `file` |
|
||||
| `InvalidParams` | `biz_type` 为空、文件名为空、size <= 0、超过 `MaxUploadSize`、`Content-Type` 不在白名单 |
|
||||
| `ERROR` | S3 未启用(`S3.Enable=false`) / S3 写入失败 |
|
||||
|
||||
### 1.6 前端易踩坑
|
||||
|
||||
1. `file` 字段名必须是 `file`,写 `image` / `upload` 都不行。
|
||||
2. `biz_type` 走 form 字段(`form:"biz_type"`),不要塞 query 里。
|
||||
3. 上传成功只返回 `url`,不返回 `file_id` / 大小等元数据;如需附加元数据,请走分片协议 `POST /upload/init` + `POST /upload/complete`。
|
||||
4. 想上传 PDF / DOC 不会成功——白名单里没有,需要后端调 `S3.AllowedContentTypes`。
|
||||
|
||||
---
|
||||
|
||||
## 2. 订阅列表(含促销 promo)
|
||||
|
||||
### 2.1 Endpoint
|
||||
|
||||
```
|
||||
GET /v1/public/subscribe/list
|
||||
```
|
||||
|
||||
- Handler: `internal/handler/public/subscribe/querySubscribeListHandler.go`
|
||||
- Logic: `internal/logic/public/subscribe/querySubscribeListLogic.go:32`
|
||||
- Promo 合并逻辑: `internal/logic/public/subscribe/promo.go`
|
||||
- 路由: `internal/handler/routes.go:1026`
|
||||
- 中间件: **`OptionalAuthMiddleware` + `DeviceMiddleware`**(**未登录也能请求**,但未登录时只能拿到 `rule_type = campaign` 的促销)
|
||||
|
||||
### 2.2 请求
|
||||
|
||||
#### Query 参数
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `language` | string | 否 | 语言筛选,传值后返回该语言版本;不传则按系统默认语言返回 |
|
||||
|
||||
#### 头部说明(影响返回内容)
|
||||
|
||||
| Header | 影响 |
|
||||
|---|---|
|
||||
| `Authorization` | 传则识别为登录态,能拿到 `new_user` / `inactive_user` 类型的个性化促销;不传只返回 `campaign` 类型 |
|
||||
| `X-App-Id` | **不传**会被识别为"老版本客户端",每个套餐的 `discount` 列表会被**截掉最后一个元素**。新版 App 必须带 `X-App-Id` |
|
||||
|
||||
### 2.3 请求示例
|
||||
|
||||
```bash
|
||||
curl -X GET 'https://tapi.hifast.biz/v1/public/subscribe/list?language=zh-CN' \
|
||||
-H 'Authorization: <JWT>' \
|
||||
-H 'X-App-Id: hifast-ios' \
|
||||
-H 'Accept: application/json'
|
||||
```
|
||||
|
||||
### 2.4 响应
|
||||
|
||||
#### 顶层
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `data.total` | int64 | 返回的套餐数量(= `len(list)`,不是数据库总数) |
|
||||
| `data.list` | Subscribe[] | 套餐列表 |
|
||||
|
||||
#### `Subscribe` 关键字段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `id` | int64 | 套餐 ID |
|
||||
| `name` | string | 套餐名 |
|
||||
| `language` | string | 当前返回的语言版本 |
|
||||
| `description` | string | 套餐描述(可能是富文本/Markdown) |
|
||||
| `unit_price` | int64 | 单时间单位**原价**,单位:**分** |
|
||||
| `unit_time` | string | 时间单位,枚举:`Day` / `Month` / `Year`(注意首字母大写) |
|
||||
| `discount` | SubscribeDiscount[] | 量级折扣 + 促销,按 `quantity` 升序 |
|
||||
| `node_count` | int64 | 节点数 |
|
||||
| `traffic` | int64 | 套餐总流量,单位:字节 |
|
||||
| `speed_limit` | int64 | 限速,单位见后端约定 |
|
||||
| `device_limit` | int64 | 同时在线设备数限制 |
|
||||
| `quota` | int64 | 总配额 |
|
||||
| `show` | bool | 是否在前端展示 |
|
||||
| `sell` | bool | 是否可售卖(本接口只返回 `sell=true`) |
|
||||
| `show_original_price` | bool | 是否展示划线原价 |
|
||||
| `reset_cycle` | int64 | 流量重置周期 |
|
||||
| `renewal_reset` | bool | 续费时是否重置流量 |
|
||||
| `created_at` / `updated_at` | int64 | 秒级 Unix 时间戳 |
|
||||
|
||||
#### `SubscribeDiscount` 字段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `quantity` | int64 | 购买的时间单位数量(如 1 = 1 个月,3 = 3 个月) |
|
||||
| `discount` | float64 | 量级折扣比例,0 表示无折扣,0.05 表示再优惠 5% |
|
||||
| `map_apple` | string | 对应 Apple IAP 商品 ID |
|
||||
| `promo` | SubscribePromo \| null | **#77 新增的促销对象**,命中促销规则时下发,否则为 `null` |
|
||||
|
||||
#### `SubscribePromo` 字段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `rule_name` | string | 促销规则名(运营在后台填写,可直接给用户展示,如"新人首单 8 折") |
|
||||
| `rule_type` | string | 规则类型枚举(见下表) |
|
||||
| `promo_price` | int64 | **促销价**,单位:**分**。优先级高于 `unit_price * discount`,前端命中促销时按此价显示 |
|
||||
| `expires_at` | int64 | 该促销对当前用户的失效时间(**秒级 Unix**),`0` 表示无明确截止 |
|
||||
|
||||
#### `rule_type` 枚举
|
||||
|
||||
| 值 | 含义 | 资格判定 |
|
||||
|---|---|---|
|
||||
| `campaign` | 全员/限时活动 | 仅看 `start_time` / `end_time` 是否在窗口内;**未登录也会下发** |
|
||||
| `new_user` | 新用户首单 | 登录用户,且 `now < user.created_at + params.window_hours`;`expires_at = user.created_at + window_hours` |
|
||||
| `inactive_user` | 老用户唤回 | 登录用户,且距离最近一个订阅过期已超过 `params.inactive_months` 个月;`expires_at = 规则 end_time` |
|
||||
|
||||
> 多条促销规则命中同一 `(subscribe_id, quantity)` 时,按 `priority DESC, id ASC` 取**首条**,不是合并。
|
||||
|
||||
### 2.5 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"total": 1,
|
||||
"list": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "月付套餐",
|
||||
"language": "zh-CN",
|
||||
"description": "...",
|
||||
"unit_price": 1000,
|
||||
"unit_time": "Month",
|
||||
"show_original_price": true,
|
||||
"node_count": 30,
|
||||
"traffic": 107374182400,
|
||||
"device_limit": 3,
|
||||
"discount": [
|
||||
{
|
||||
"quantity": 1,
|
||||
"discount": 0,
|
||||
"map_apple": "ios.month1",
|
||||
"promo": {
|
||||
"rule_name": "新人首单 8 折",
|
||||
"rule_type": "new_user",
|
||||
"promo_price": 800,
|
||||
"expires_at": 1780500000
|
||||
}
|
||||
},
|
||||
{
|
||||
"quantity": 3,
|
||||
"discount": 0.05,
|
||||
"map_apple": "ios.month3",
|
||||
"promo": null
|
||||
}
|
||||
],
|
||||
"show": true,
|
||||
"sell": true,
|
||||
"created_at": 1764547200,
|
||||
"updated_at": 1779934580
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.6 价格计算建议(前端)
|
||||
|
||||
对每个 `discount` 元素:
|
||||
|
||||
```
|
||||
原价 = unit_price * quantity
|
||||
量级折后价 = round(原价 * (1 - discount))
|
||||
|
||||
if promo != null:
|
||||
实付 = promo.promo_price * quantity // 注意:promo_price 是「单价」,乘以 quantity
|
||||
划线价 = 原价 // 用于展示「省 XX」
|
||||
else:
|
||||
实付 = 量级折后价
|
||||
划线价 = 原价(show_original_price=true 时展示)
|
||||
```
|
||||
|
||||
> 注意:`promo_price` 设计为**单价**(与 `unit_price` 同级),不是总价。
|
||||
> 命中促销时建议同时显示 `rule_name`("新人首单 8 折")和倒计时(基于 `expires_at`)。
|
||||
|
||||
### 2.7 前端易踩坑
|
||||
|
||||
1. **必带 `X-App-Id`**——否则 `discount` 数组最后一个会被砍掉。
|
||||
2. **促销分登录态**:未登录时只能拿到 `campaign`;未拿到 `new_user`/`inactive_user` 时先检查是否传了 `Authorization`。
|
||||
3. **`unit_time` 是 PascalCase**:`Day` / `Month` / `Year`,别小写匹配。
|
||||
4. **金额单位都是分**(`unit_price`、`promo_price`),展示时除以 100。
|
||||
5. **`expires_at = 0`** 表示无截止,不要展示成 1970 年。
|
||||
6. `total` 是当前返回的条数,不是数据库总数(接口在 logic 里强制 `Size: 9999`,相当于不分页)。
|
||||
|
||||
---
|
||||
|
||||
## 3. 邀请赠送记录
|
||||
|
||||
> 当前用户的"邀请赠送天数"流水。包含两类:
|
||||
> - 当前用户作为**邀请人**,被邀请的朋友下单触发的赠送;
|
||||
> - 当前用户作为**被邀请人**,自己下单触发的对应赠送(双向赠送)。
|
||||
>
|
||||
> 数据源:`system_logs` 表,`type = 33 (TypeGift)` 且 `content.remark = "邀请赠送"`。
|
||||
> 这里**只是赠送天数**,不包含邀请佣金(请走 affiliate 系列接口)。
|
||||
|
||||
### 3.1 Endpoint
|
||||
|
||||
```
|
||||
GET /v1/public/user/invite_records
|
||||
```
|
||||
|
||||
- Handler: `internal/handler/public/user/getInviteRecordsHandler.go`
|
||||
- Logic: `internal/logic/public/user/getInviteRecordsLogic.go:61`
|
||||
- 路由: `internal/handler/routes.go:1122`
|
||||
- 中间件: `AuthMiddleware` + `DeviceMiddleware`(必须登录)
|
||||
|
||||
### 3.2 请求
|
||||
|
||||
#### Query 参数
|
||||
|
||||
| 字段 | 类型 | 必填 | 默认 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| `page` | int | 否 | `1` | 页码,<1 自动归一为 1 |
|
||||
| `size` | int | 否 | `10` | 每页条数,<1 归一为 10,**>100 截断为 100** |
|
||||
| `start_time` | int64 | 否 | `0` | 起始时间(**秒级 Unix**),`0` 表示不过滤下界 |
|
||||
| `end_time` | int64 | 否 | `0` | 截止时间(**秒级 Unix**),`0` 表示不过滤上界 |
|
||||
|
||||
> ⚠️ `start_time` / `end_time` 单位是**秒**(后端用 `FROM_UNIXTIME(?)`)。传毫秒会过滤掉所有记录。
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```bash
|
||||
# 不带时间过滤
|
||||
curl -X GET 'https://tapi.hifast.biz/v1/public/user/invite_records?page=1&size=20' \
|
||||
-H 'Authorization: <JWT>' \
|
||||
-H 'Accept: application/json'
|
||||
|
||||
# 带时间过滤
|
||||
curl -X GET 'https://tapi.hifast.biz/v1/public/user/invite_records?page=1&size=20&start_time=1764547200&end_time=1780099200' \
|
||||
-H 'Authorization: <JWT>'
|
||||
```
|
||||
|
||||
> 旧 curl 模板里的 `--data-urlencode 'page=1'` 等对 GET 是 form body,不会被读取,请用 query string。
|
||||
|
||||
### 3.3 响应
|
||||
|
||||
#### 顶层
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `data.total` | int64 | 当前过滤条件下的**记录总数**(用于分页) |
|
||||
| `data.list` | InviteRecord[] | 当前页列表,可能为空数组 `[]` |
|
||||
|
||||
#### `InviteRecord` 字段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `role` | string | 当前用户在该条记录中的角色:`inviter` 或 `invitee`(详见下表) |
|
||||
| `peer_hash` | string | 对端用户的脱敏哈希(10 位定长数字字符串),用于"匿名展示朋友"。订单已删 / 对端 id 缺失时为 `""` |
|
||||
| `gift_days` | int64 | 本次赠送天数(来源 `system_logs.content.amount`) |
|
||||
| `order_no` | string | 触发本次赠送的订单号 |
|
||||
| `created_at` | int64 | 赠送时间,**毫秒级 Unix**(SQL 端 `UNIX_TIMESTAMP(created_at) * 1000`) |
|
||||
|
||||
> ⚠️ **时间戳单位不一致**:请求里的 `start_time/end_time` 是**秒**,响应里的 `created_at` 是**毫秒**。前端请区分对待。
|
||||
> (与项目其它接口"统一秒级"约定不同,是该接口的当前实现。)
|
||||
|
||||
#### `role` 取值
|
||||
|
||||
| 值 | 含义 | `peer_hash` 来源 |
|
||||
|---|---|---|
|
||||
| `inviter` | 当前用户是**邀请人**,朋友下单触发的赠送 | 被邀请人(即订单的 `user_id`)的脱敏 hash |
|
||||
| `invitee` | 当前用户是**被邀请人**,自己下单触发的赠送 | 邀请人(`user.referer_id`)的脱敏 hash |
|
||||
|
||||
判定规则:默认 `inviter`;若 `order.user_id == 当前用户 id`,切换为 `invitee` 并改用 `referer_id` 计算 hash。
|
||||
|
||||
#### 排序与分页
|
||||
|
||||
- 排序:`created_at DESC, id DESC`(最近一条在最前)
|
||||
- 分页:`LIMIT size OFFSET (page-1)*size`
|
||||
- `total` **不**受 `LIMIT/OFFSET` 影响
|
||||
|
||||
### 3.4 响应示例
|
||||
|
||||
非空:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"total": 2,
|
||||
"list": [
|
||||
{
|
||||
"role": "inviter",
|
||||
"peer_hash": "0382716459",
|
||||
"gift_days": 30,
|
||||
"order_no": "20260527123456789",
|
||||
"created_at": 1779934580000
|
||||
},
|
||||
{
|
||||
"role": "invitee",
|
||||
"peer_hash": "1745920031",
|
||||
"gift_days": 30,
|
||||
"order_no": "20260520112233445",
|
||||
"created_at": 1779329780000
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
空:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"total": 0,
|
||||
"list": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.5 错误码
|
||||
|
||||
| 业务码 | 触发场景 |
|
||||
|---|---|
|
||||
| `InvalidAccess` | 未登录 / JWT 无效 |
|
||||
| `ParamError` | 参数绑定失败 |
|
||||
| `DatabaseQueryError` | DB 查询失败(count / 日志 / 订单任一) |
|
||||
|
||||
### 3.6 前端易踩坑
|
||||
|
||||
1. 传**毫秒**给 `start_time/end_time` → 永远拿到空集。请传**秒**。
|
||||
2. 拿到的 `created_at` 是**毫秒**,**不要再 `*1000`**,直接 `new Date(created_at)` 即可。
|
||||
3. 空列表是 `[]` 不是 `null`,可直接 `.map`。
|
||||
4. `peer_hash` 可能为 `""`,UI 兜底展示"未知朋友"。
|
||||
5. `size` 上限 100,传 1000 会被截断。
|
||||
6. 本接口**只含赠送天数**,不含邀请佣金(佣金 → affiliate 接口)。
|
||||
|
||||
---
|
||||
|
||||
## 附录:业务码常量速查
|
||||
|
||||
| 名称 | HTTP 含义 | 出现场景 |
|
||||
|---|---|---|
|
||||
| `200` | 成功 | `{"code":200,"msg":"success",...}` |
|
||||
| `InvalidAccess` | 未授权 | 未登录 / JWT 无效 / 设备未绑定 |
|
||||
| `ParamError` | 参数错误 | 请求绑定失败、缺必填项 |
|
||||
| `InvalidParams` | 参数校验不通过 | 业务规则校验失败(文件超限、Content-Type 不合法等) |
|
||||
| `DatabaseQueryError` | DB 错 | SQL 查询失败 |
|
||||
| `ERROR` | 通用错 | 第三方/中间件失败(S3 未启用、S3 写入失败等) |
|
||||
@@ -0,0 +1,254 @@
|
||||
# 开发流程(迁移到 GitHub 后)
|
||||
|
||||
> 适用:`github.com/TawCorp/hifast-server`(canonical 仓库)及配套的 Multica agent 工作区。
|
||||
> 历史背景:本仓库于 2026-06-03 从 `git.kxsw.us/HI-VPN/hi-server` 迁移到 GitHub。**`git.kxsw.us` 已废弃**,所有新开发只走本仓库。
|
||||
|
||||
---
|
||||
|
||||
## 0. TL;DR
|
||||
|
||||
```
|
||||
Multica issue → 分支 fix/<num>-中文简述 → 推 github → 开 PR → CI 绿 → 架构师 approve
|
||||
→ GitHub UI Squash and merge 进 internal → 测试环境部署 (deploy-staging.yml) 自动跑
|
||||
→ QA 在 staging 验收 → Multica issue → done
|
||||
|
||||
发版时:架构师把 internal squash 进 main → 对外正式版本
|
||||
```
|
||||
|
||||
不要在 GitHub UI 之外 squash 后直接 push `internal` / `main`。不要往 `git.kxsw.us` 推。
|
||||
|
||||
---
|
||||
|
||||
## 1. 分支模型
|
||||
|
||||
| 分支 | 角色 | 写入方式 |
|
||||
|---|---|---|
|
||||
| `main` | 对外正式版本(生产) | **只**由架构师 squash merge `internal` → `main` |
|
||||
| `internal` | 日常开发主干,staging 触发分支 | **只**由架构师在 GitHub UI 上 Squash and merge PR |
|
||||
| `fix/<num>-…` | bug 修复分支 | 工程师自己开自己删(PR 合并后 GitHub 自动删) |
|
||||
| `feat/<num>-…` | 新功能分支 | 同上 |
|
||||
| `chore/…` `refactor/…` `hotfix/…` | 杂项 / 重构 / 紧急修复 | 同上 |
|
||||
|
||||
**分支命名约定**(严格遵守,便于 CI / CODEOWNERS / 历史追溯):
|
||||
|
||||
| 前缀 | 用途 | 示例 |
|
||||
|---|---|---|
|
||||
| `fix/<num>-` | 关联 Multica issue 编号的 bug 修复 | `fix/143-邀请权益单测flake` |
|
||||
| `feat/<num>-` | 关联 Multica issue 编号的新功能 | `feat/77-套餐列表促销信息` |
|
||||
| `chore/` | 配置 / 文档 / 工具链(无关联 issue 可不带编号) | `chore/dev-workflow-docs` |
|
||||
| `hotfix/<num>-` | 生产紧急修复 | `hotfix/151-payment-callback-503` |
|
||||
| `refactor/<num>-` | 重构(行为不变) | `refactor/138-promo-eligibility` |
|
||||
|
||||
**单分支存活上限:3 天**(架构师 CLAUDE.md 约定)。超时未合并的分支由架构师在 issue 上 ping 持有者收尾或重切。
|
||||
|
||||
---
|
||||
|
||||
## 2. 标准 PR 生命周期
|
||||
|
||||
### 2.1 立项
|
||||
|
||||
- Multica 上有对应 issue(HIF-XXX)。
|
||||
- issue 已 assigned,状态 `todo` 或 `in_progress`。
|
||||
|
||||
### 2.2 写代码
|
||||
|
||||
```bash
|
||||
# 在 worktree 里
|
||||
git fetch origin
|
||||
git checkout -B fix/<num>-中文简述 origin/internal
|
||||
|
||||
# 编码 + 写测试
|
||||
# lefthook pre-commit 会自动跑 fmt / imports / lint / vet / test
|
||||
git commit -m "修复(#<num>): 一句话说清楚做了什么"
|
||||
|
||||
# 推到 github(不再推 git.kxsw.us)
|
||||
git push origin fix/<num>-中文简述
|
||||
```
|
||||
|
||||
**commit message**(commitlint 强制):
|
||||
|
||||
```
|
||||
<类型>(#<num>): <一句话描述,<= 72 字符>
|
||||
|
||||
<可选正文,说明 why>
|
||||
```
|
||||
|
||||
类型只有这五个:**`修复` / `新功能` / `重构` / `文档` / `配置`**。其它一律不允许。
|
||||
|
||||
### 2.3 开 PR
|
||||
|
||||
```bash
|
||||
gh pr create --base internal --title '修复(#<num>): ...' --body-file <path>
|
||||
```
|
||||
|
||||
或 GitHub UI 开。PR 模板(`.github/PULL_REQUEST_TEMPLATE.md`)会自动填入,按指引补内容。
|
||||
|
||||
**PR 标题必须等于 squash 后的合入 commit 标题**(用 `<类型>(#<num>): ...` 形式)。
|
||||
|
||||
### 2.4 CI 自动跑
|
||||
|
||||
`.github/workflows/ci.yml` 在 PR 上触发:
|
||||
|
||||
| Job | 执行 |
|
||||
|---|---|
|
||||
| `build-and-test` | `go build ./...` + `go vet ./...` + `go test -race -count=1 ./...` |
|
||||
| `lint` | `golangci-lint run` |
|
||||
|
||||
红 → 工程师本地复现 + 修,反复直到全绿。
|
||||
|
||||
### 2.5 Code review
|
||||
|
||||
- `CODEOWNERS` 自动 request review。
|
||||
- 架构师(或被指派的 reviewer)逐 hunk 看,特别关注:
|
||||
- scope 与 issue 一致性(无 scope creep——架构师红线之一)
|
||||
- 错误处理 / SQL 注入 / N+1
|
||||
- 测试覆盖关键边界
|
||||
- 是否引入了无关的代码改动(架构师红线:"发现成员修改了无关代码 → 立即打回重做")
|
||||
- review 通过即在 PR 上点 Approve。
|
||||
|
||||
### 2.6 Merge to `internal`
|
||||
|
||||
- **必须用 GitHub UI 的 "Squash and merge"**。
|
||||
- squash 后 commit message 由架构师编辑确认:保持 `<类型>(#<num>): ...` 形式 + 必要正文。
|
||||
- 合并按钮按下后 PR 自动 close,分支由 GitHub 自动删("Automatically delete head branches" 应开启)。
|
||||
- 架构师在 Multica issue 上 `@运维工程师` 附 commit hash,通知可以部署(虽然测试环境部署 workflow 已自动跑,但运维需要确认部署状态)。
|
||||
|
||||
**禁止做的事**:
|
||||
- ❌ 在本地 squash 再 `git push` 到 `internal` / `main`
|
||||
- ❌ Rebase merge / Create a merge commit(保持 linear history)
|
||||
- ❌ Force push 到 `internal` / `main`
|
||||
- ❌ Bypass CI(CI 红时不要 merge)
|
||||
- ❌ 自己 approve 自己的 PR
|
||||
- ❌ 未经测试工程师验收的分支合并(架构师红线)
|
||||
|
||||
### 2.7 测试环境部署自动触发 (`.github/workflows/deploy-staging.yml`)
|
||||
|
||||
合并到 `internal` 后,`.github/workflows/deploy-staging.yml` 自动触发:
|
||||
|
||||
1. `go test ./...`(已被 CI 保证过,理论上不会再红)
|
||||
2. Build Docker image `registry.kxsw.us/vpn-server:<sha>` + `:staging`
|
||||
3. scp `docker-compose.cloud.yml` 到 staging host
|
||||
4. ssh 重启 `ppanel-server` 服务
|
||||
5. healthcheck + Telegram 通知
|
||||
|
||||
部署失败 → Telegram 告警 → 运维介入回滚。Deploy 不阻塞下一个 PR,但**修复 deploy 是当前 deploy 失败者的责任**。
|
||||
|
||||
### 2.8 QA 验收
|
||||
|
||||
- 测试工程师在 staging(`tapi.hifast.biz` / 配套前端)按 issue 描述的 acceptance criteria 验收。
|
||||
- 验收通过 → Multica issue 推 `done`。
|
||||
- 任何一项失败 → 单独开子 issue 指派对应工程师,**不要**回退 PR / revert(除非生产数据安全风险)。
|
||||
|
||||
### 2.9 发版到 `main`(对外正式版本)
|
||||
|
||||
由架构师在合适的时机(feature 集齐、staging 跑稳一段时间后)执行:
|
||||
|
||||
```bash
|
||||
gh pr create --base main --head internal --title '发版: <版本号>'
|
||||
```
|
||||
|
||||
走和普通 PR 一样的流程(CI 绿 + review + Squash and merge)。`main` 的 push 也可以触发后续生产部署 workflow(如未来增加 `deploy-production.yml`)。
|
||||
|
||||
---
|
||||
|
||||
## 3. Multica issue 状态映射
|
||||
|
||||
| Multica 状态 | 对应阶段 |
|
||||
|---|---|
|
||||
| `backlog` | 还没排期 |
|
||||
| `todo` | 已排期,待 assigned agent 开始 |
|
||||
| `in_progress` | 分支已开始写,未 push |
|
||||
| `in_review` | PR 已开,等 review / CI / merge |
|
||||
| `done` | PR merged + 测试环境部署通过 + QA 验收通过 |
|
||||
|
||||
**三个条件没全满足就不要标 done**——否则验收链路看不到真问题。
|
||||
|
||||
Issue metadata 建议字段(与现有 agent CLAUDE.md 推荐一致):
|
||||
|
||||
| 字段 | 内容 |
|
||||
|---|---|
|
||||
| `pr_url` | https://github.com/TawCorp/hifast-server/pull/XX |
|
||||
| `merge_commit` | merge 后的 squash commit SHA |
|
||||
| `deploy_url` | `tapi.hifast.biz` 或对应前端域名 |
|
||||
| `pipeline_status` | `coding` / `pr_open` / `merged` / `deployed` / `qa_passed` |
|
||||
| `waiting_on` | 当前阻塞点(如 `qa_e2e_access` / `architect_review`) |
|
||||
|
||||
---
|
||||
|
||||
## 4. Agent 边界
|
||||
|
||||
| Agent | 可以做 | 不可以做 |
|
||||
|---|---|---|
|
||||
| 后端 / 前端 / 运维工程师 | push 自己的 fix/feat 分支;开 PR;回评 issue;本地 squash 自己分支 | 合 PR;push `internal` / `main`;改 CODEOWNERS;自己 approve 自己 PR |
|
||||
| 架构师(Squad Leader) | review;approve;GitHub UI squash merge;在 issue 上拆解需求 + 分派子任务;维护 doc/ 和 CLAUDE.md | 在本地 merge 后 push `internal`(绕过 CI gate);直接编写业务逻辑或 UI 代码 |
|
||||
| 测试工程师 | 在 staging 验收;回评 issue;开子 issue 报 bug | 改 prod 代码;改 CI workflow;自己合 PR |
|
||||
| 仓库 owner(人类) | 任何越界操作 + 流程治理决策(如 branch protection 升级) | — |
|
||||
|
||||
任何越界都需要在 issue 上声明 + 走另一个 PR。
|
||||
|
||||
---
|
||||
|
||||
## 5. 平台层约束的现状(重要)
|
||||
|
||||
> 私有仓库 + 当前 GitHub plan 不支持 branch protection / rulesets API(HTTP 403)。
|
||||
|
||||
这意味着 "禁止直接 push internal" / "require CI pass" / "require approval" 这些**在 GitHub 平台层面没法硬强制**。当前依赖三层软约束:
|
||||
|
||||
1. **客户端层**:`lefthook.yml` 的 `pre-push` 钩子会在尝试直接 push `internal` / `main` 时报错。绕过去要明确加 `--no-verify`,会留 git trailers。
|
||||
2. **流程层**:本文档 + `CODEOWNERS` 自动 request review + 架构师作为单一合并执行人。
|
||||
3. **审计层**:所有 merge 都对应 Multica issue,事后任何"非 PR 路径上去的 commit"都能在 git log + Multica 对照查出来。
|
||||
|
||||
如果未来组织规模扩大、人类协作者增多,建议升级 GitHub Team($4/user/月)拿到 branch protection 平台保障。**目前规模下软约束足够。**
|
||||
|
||||
---
|
||||
|
||||
## 6. 常见场景
|
||||
|
||||
### Hotfix(生产紧急修复)
|
||||
|
||||
走和 fix 同样的流程,只是分支前缀 `hotfix/`,PR 标题前加 `[HOTFIX]`。架构师可酌情把 review 等待时间压缩到 30 分钟以内。Hotfix 通常直接合 `internal` 后立即由架构师再开 `internal → main` 的 PR 一起发版。
|
||||
|
||||
### Revert
|
||||
|
||||
- 在 GitHub UI 上找到要 revert 的 PR,点 "Revert"。
|
||||
- GitHub 会生成 revert PR,按正常流程过 review + merge。
|
||||
- Revert PR 标题用 `修复(#<原 issue 编号>): revert <原 PR 标题>`。
|
||||
|
||||
### 文档 / 配置改动
|
||||
|
||||
走 `chore/` 分支。CI 一样跑(保险),review 可走 fast-track。
|
||||
|
||||
### 跨多个 issue 的大改动
|
||||
|
||||
- 优先拆成多个独立 PR,每个 PR 对应一个 issue。
|
||||
- 如果实在拆不开,PR title 用 `<类型>(#XXX/#YYY/#ZZZ): ...`,PR body 里 `Closes` 三个。
|
||||
|
||||
---
|
||||
|
||||
## 7. FAQ
|
||||
|
||||
**Q: 为什么不允许在本地 squash 再推 internal?**
|
||||
A: 绕过 GitHub PR review 流程 + CI gate + 审计记录。即使你确定改动 100% 正确,也走 PR——这是文化约束,避免架构师红线被逐渐侵蚀。
|
||||
|
||||
**Q: CI 跑得慢,PR 一直 yellow,可以先 merge 吗?**
|
||||
A: 不可以。CI 通常 5 分钟内出结果;如果超过 15 分钟仍 pending,检查是否有 stuck job,必要时 re-run。
|
||||
|
||||
**Q: 如果架构师不在,怎么办?**
|
||||
A: 备份 reviewer = 仓库 owner(@shanshanzhong147)。CODEOWNERS 已经把 owner 列为兜底 reviewer。
|
||||
|
||||
**Q: lefthook pre-push 不让我 push internal,但我确实需要紧急修一行小字?**
|
||||
A: 没有"紧急修一行小字"这种例外。开 hotfix 分支 + 开 PR + 走 fast-track review。
|
||||
|
||||
**Q: Multica issue 状态没流转到 done,是不是要手动推?**
|
||||
A: 不要直接推。先确认 PR merged + deploy 绿 + QA 通过三个条件全满足。任一项没满足就维持 `in_review` 并加评论说卡在哪。
|
||||
|
||||
**Q: 我能不能用 Rebase / Merge commit 模式合 PR?**
|
||||
A: 不行。必须用 Squash and merge。`internal` 必须保持 linear history(一个 PR = 一个 commit),方便回滚和审计。
|
||||
|
||||
---
|
||||
|
||||
## 8. 紧急联系
|
||||
|
||||
- Deploy 红 / staging 挂:运维工程师(Multica agent `071d94d9-38bb-43cc-8c58-29e3b52d7bd4`)
|
||||
- 流程问题 / branch protection 决策:仓库 owner @shanshanzhong147
|
||||
- 架构 / API 设计:架构师(Multica agent `0de10589-f101-49ef-8dfa-0e9e992fe27c`)
|
||||
@@ -0,0 +1,862 @@
|
||||
# 邀请赠送与购买订阅逻辑说明
|
||||
|
||||
本文档说明当前代码中的购买订阅、订单激活、邀请佣金、邀请赠送时间、家庭组归属逻辑。重点覆盖每个主要分支,方便排查“重复订单/重复订阅/邀请未赠时/赠时落点错误”等问题。
|
||||
|
||||
涉及核心文件:
|
||||
|
||||
- `internal/logic/public/order/purchaseLogic.go`
|
||||
- `queue/logic/order/activateOrderLogic.go`
|
||||
- `internal/logic/common/familyEntitlement.go`
|
||||
- `internal/model/user/model.go`
|
||||
|
||||
## 1. 核心概念
|
||||
|
||||
### 1.1 订单状态
|
||||
|
||||
| 状态 | 含义 |
|
||||
| --- | --- |
|
||||
| `1` | pending,已创建未支付 |
|
||||
| `2` | paid,已支付待激活 |
|
||||
| `3` | close,已关闭 |
|
||||
| `4` | failed/claimed,代码里同时用于失败和 worker 临时领取 |
|
||||
| `5` | finished,激活完成 |
|
||||
|
||||
### 1.2 订单类型
|
||||
|
||||
| 类型 | 含义 |
|
||||
| --- | --- |
|
||||
| `1` | 新购套餐 |
|
||||
| `2` | 续费/换套餐 |
|
||||
| `3` | 重置流量 |
|
||||
| `4` | 余额充值 |
|
||||
| `5` | 兑换码激活 |
|
||||
|
||||
### 1.3 用户 ID 与订阅归属
|
||||
|
||||
订单有两个重要用户字段:
|
||||
|
||||
| 字段 | 含义 |
|
||||
| --- | --- |
|
||||
| `user_id` | 发起订单/付款的用户 |
|
||||
| `subscription_user_id` | 订阅权益归属用户;`0` 表示同 `user_id` |
|
||||
|
||||
家庭组规则:
|
||||
|
||||
- 普通用户下单:`subscription_user_id = user_id`。
|
||||
- 家庭组成员下单:`subscription_user_id = 家主用户 ID`。
|
||||
- 家庭组主账号下单:`subscription_user_id = 家主用户 ID`。
|
||||
|
||||
当前代码使用 `ResolveEntitlementUser` 判断家庭归属:
|
||||
|
||||
- 只有有效家庭组、有效成员关系、角色为 member 时,权益归家主。
|
||||
- 家主本人不会被改写到别人名下。
|
||||
|
||||
## 2. 购买订阅下单逻辑
|
||||
|
||||
入口:`Purchase(req *types.PurchaseOrderRequest)`
|
||||
|
||||
这里只是创建订单和安排关闭任务,不直接发放订阅。真正发放订阅在订单支付后由队列激活处理。
|
||||
|
||||
### 2.1 登录用户检查
|
||||
|
||||
分支:
|
||||
|
||||
- 上下文没有当前用户:返回 `InvalidAccess`。
|
||||
- 当前用户存在:继续。
|
||||
|
||||
### 2.2 解析订阅权益归属
|
||||
|
||||
调用 `ResolveEntitlementUser`:
|
||||
|
||||
| 场景 | `effective_user_id` | 结果 |
|
||||
| --- | --- | --- |
|
||||
| 普通用户 | 本人 ID | 订阅归本人 |
|
||||
| 家庭组主账号 | 本人 ID | 订阅归本人 |
|
||||
| 家庭组成员 | 家主 ID | 订阅归家主 |
|
||||
|
||||
后续查询已有订阅、quota、创建订单里的 `subscription_user_id` 都会使用这个归属结果。
|
||||
|
||||
### 2.3 数量校验
|
||||
|
||||
分支:
|
||||
|
||||
- `quantity <= 0`:自动改成 `1`。
|
||||
- `quantity > MaxQuantity`:返回参数错误。
|
||||
- 合法:继续。
|
||||
|
||||
### 2.4 单订阅模式路由
|
||||
|
||||
先默认:
|
||||
|
||||
```text
|
||||
order_type = 1
|
||||
target_subscribe_id = req.subscribe_id
|
||||
parent_order_id = 0
|
||||
subscribe_token = ""
|
||||
```
|
||||
|
||||
如果开启 `Subscribe.SingleModel`:
|
||||
|
||||
| 查询结果 | 行为 |
|
||||
| --- | --- |
|
||||
| 找到已有 anchor 订阅 | 下单路由为续费:`order_type=2`,保留新请求套餐 ID,设置 parent/order token |
|
||||
| 没找到已有订阅 | 保持新购:`order_type=1` |
|
||||
| 查询异常 | 返回数据库错误 |
|
||||
|
||||
说明:即使是换套餐,只要单订阅模式已有订阅,也走续费语义,后续激活会更新套餐 ID 和流量配置。
|
||||
|
||||
### 2.5 非 SingleModel 的全局单订阅兜底
|
||||
|
||||
如果未开启 `SingleModel`,且当前还是新购 `order_type=1`:
|
||||
|
||||
- 查询 `effective_user_id` 名下已有付费订阅:
|
||||
|
||||
```sql
|
||||
user_id = effective_user_id
|
||||
AND token != ''
|
||||
AND (order_id > 0 OR token LIKE 'iap:%')
|
||||
```
|
||||
|
||||
| 查询结果 | 行为 |
|
||||
| --- | --- |
|
||||
| 找到已有订阅 | 路由为续费:`order_type=2`,用已有订阅 token |
|
||||
| 没找到 | 仍然新购 |
|
||||
|
||||
目的:避免同一个权益归属用户购买不同套餐后出现多条订阅权益。
|
||||
|
||||
### 2.6 pending 订单处理
|
||||
|
||||
当前只有一个分支会主动关闭旧 pending 单:
|
||||
|
||||
```text
|
||||
SingleModel = true
|
||||
AND order_type = 1
|
||||
AND 存在同 user_id + subscribe_id + status=1 的订单
|
||||
```
|
||||
|
||||
行为:
|
||||
|
||||
- 关闭旧 pending 订单。
|
||||
- 继续创建新订单。
|
||||
|
||||
注意:
|
||||
|
||||
- 如果订单已被路由为 `order_type=2`,这里不会关闭旧 pending。
|
||||
- 非 `SingleModel` 下也不会走这段 pending 关闭逻辑。
|
||||
|
||||
### 2.7 套餐校验
|
||||
|
||||
分支:
|
||||
|
||||
| 条件 | 行为 |
|
||||
| --- | --- |
|
||||
| 套餐不存在 | 返回数据库错误 |
|
||||
| `sell=false` | 返回套餐不可售 |
|
||||
| 新购且库存为 `0` | 返回库存不足 |
|
||||
| 续费/换套餐 | 不检查库存为 0 的拦截分支 |
|
||||
|
||||
### 2.8 新用户优惠与新用户限定
|
||||
|
||||
调用 `resolveNewUserDiscountEligibility`:
|
||||
|
||||
| 分支 | 行为 |
|
||||
| --- | --- |
|
||||
| 解析失败 | 返回错误 |
|
||||
| 有折扣且符合条件 | 按折扣计算金额 |
|
||||
| 有折扣但不符合条件 | 按原价 |
|
||||
| 新用户限定且不是新用户窗口 | 新购事务内再次校验,不通过则失败 |
|
||||
|
||||
### 2.9 优惠券逻辑
|
||||
|
||||
如果 `req.coupon` 为空:跳过。
|
||||
|
||||
如果不为空:
|
||||
|
||||
| 校验 | 不通过行为 |
|
||||
| --- | --- |
|
||||
| 优惠券存在 | 返回 `CouponNotExist` |
|
||||
| 总使用次数未超限 | 返回使用次数不足 |
|
||||
| 用户使用次数未超限 | 返回用户次数不足 |
|
||||
| 套餐适用 | 返回不适用 |
|
||||
|
||||
通过后计算 `coupon_discount`,从订单金额中扣除。
|
||||
|
||||
### 2.10 支付手续费与礼品余额抵扣
|
||||
|
||||
流程:
|
||||
|
||||
1. 找支付方式。
|
||||
2. 如果金额大于 0,计算手续费并加到订单金额。
|
||||
3. 如果用户 `gift_amount > 0`,继续抵扣订单金额。
|
||||
4. 抵扣金额记录到订单 `gift_amount`。
|
||||
|
||||
事务内如果有礼品余额抵扣:
|
||||
|
||||
- 扣减用户 `gift_amount`。
|
||||
- 写 `system_logs` 的 gift reduce 日志。
|
||||
|
||||
### 2.11 `is_new` 首单标记
|
||||
|
||||
创建订单前调用:
|
||||
|
||||
```sql
|
||||
SELECT COUNT(*)
|
||||
FROM `order`
|
||||
WHERE user_id = 当前付款用户
|
||||
AND status IN (2, 5)
|
||||
```
|
||||
|
||||
| 结果 | `is_new` |
|
||||
| --- | --- |
|
||||
| count = 0 | `true` |
|
||||
| count > 0 | `false` |
|
||||
|
||||
注意:
|
||||
|
||||
- 判断口径是付款用户 `user_id`,不是 `subscription_user_id`。
|
||||
- pending/closed 订单不影响 `is_new`。
|
||||
- 续费订单也可能是 `is_new=true`,例如单订阅模式下首次购买被路由为续费。
|
||||
|
||||
### 2.12 事务内创建订单
|
||||
|
||||
事务内执行:
|
||||
|
||||
1. 新购且套餐有 quota 时,再查一次 `effective_user_id` 名下订阅数量防并发。
|
||||
2. 新购且新用户限定时,再查一次新用户资格。
|
||||
3. 如有礼品余额抵扣,扣减余额并写日志。
|
||||
4. 新购且库存不是 `-1` 时扣库存。
|
||||
5. 插入订单。
|
||||
|
||||
订单核心字段:
|
||||
|
||||
| 字段 | 值 |
|
||||
| --- | --- |
|
||||
| `user_id` | 当前付款用户 |
|
||||
| `subscription_user_id` | 权益归属用户 |
|
||||
| `type` | 新购 `1` 或续费 `2` |
|
||||
| `subscribe_id` | 本次购买的套餐 ID |
|
||||
| `subscribe_token` | 续费时已有订阅 token |
|
||||
| `is_new` | 首单标记 |
|
||||
| `status` | `1` pending |
|
||||
|
||||
### 2.13 延迟关单任务
|
||||
|
||||
订单创建成功后,发送 `DeferCloseOrder` 任务:
|
||||
|
||||
- 延迟时间:15 分钟。
|
||||
- 作用:未支付订单自动关闭。
|
||||
|
||||
## 3. 订单支付后的激活逻辑
|
||||
|
||||
入口:`ActivateOrderLogic.ProcessTask`
|
||||
|
||||
### 3.1 任务解析和订单领取
|
||||
|
||||
分支:
|
||||
|
||||
| 分支 | 行为 |
|
||||
| --- | --- |
|
||||
| payload 解析失败 | 记录错误,不重试 |
|
||||
| 订单不存在 | 返回错误,允许重试 |
|
||||
| 订单已 finished | 幂等跳过 |
|
||||
| 订单不是 paid | 跳过 |
|
||||
| paid 订单 | 原子更新为 claimed 状态后处理 |
|
||||
|
||||
### 3.2 按订单类型分发
|
||||
|
||||
| 订单类型 | 处理函数 |
|
||||
| --- | --- |
|
||||
| 新购 `1` | `NewPurchase` |
|
||||
| 续费 `2` | `Renewal` |
|
||||
| 重置流量 `3` | `ResetTraffic` |
|
||||
| 充值 `4` | `Recharge` |
|
||||
| 兑换码 `5` | `RedemptionActivate` |
|
||||
|
||||
处理成功后:
|
||||
|
||||
1. 执行订阅合并兜底 `reconcilePostOrderSubscriptions`。
|
||||
2. 更新优惠券使用次数。
|
||||
3. 更新订单为 `finished`。
|
||||
|
||||
如果处理失败:
|
||||
|
||||
- 把订单从 claimed 释放回 paid。
|
||||
- 返回错误给队列重试。
|
||||
|
||||
## 4. 新购订单激活与订阅发放
|
||||
|
||||
入口:`NewPurchase`
|
||||
|
||||
### 4.1 获取用户
|
||||
|
||||
分支:
|
||||
|
||||
| 订单 user_id | 行为 |
|
||||
| --- | --- |
|
||||
| 不为 0 | 查询已有用户 |
|
||||
| 为 0 | 从 Redis 临时订单创建游客用户 |
|
||||
|
||||
游客订单创建用户时:
|
||||
|
||||
- 创建用户和 auth method。
|
||||
- 生成 refer code。
|
||||
- 把订单 `user_id` 更新为新用户 ID。
|
||||
- 如果临时订单有邀请码,绑定 `referer_id`。
|
||||
|
||||
### 4.2 新用户限定激活时复查
|
||||
|
||||
如果套餐是新用户限定,激活时再次校验:
|
||||
|
||||
- 不符合则激活失败,订单回到 paid 等待重试/处理。
|
||||
- 符合继续。
|
||||
|
||||
### 4.3 SingleModel 下复用 anchor 订阅
|
||||
|
||||
如果 `Subscribe.SingleModel=true`:
|
||||
|
||||
| 分支 | 行为 |
|
||||
| --- | --- |
|
||||
| 找到 anchor 订阅 | 更新订单 parent_id;用续费逻辑延长/换套餐 |
|
||||
| 找不到 | 继续后续分支 |
|
||||
| 查询异常 | 记录错误,继续后续分支 |
|
||||
|
||||
家庭组场景下查 anchor 的用户 ID:
|
||||
|
||||
- 优先 `subscription_user_id`。
|
||||
- 没有则用 `user_id`。
|
||||
|
||||
### 4.4 复用赠送订阅
|
||||
|
||||
如果还没有可复用订阅:
|
||||
|
||||
- 查找权益归属用户名下 `order_id=0` 的赠送订阅。
|
||||
- 找到后将它升级为付费订阅:
|
||||
- `order_id` 改为当前订单 ID。
|
||||
- 延长到期时间。
|
||||
- 状态改为 active。
|
||||
- 如果套餐变更,更新套餐 ID、流量额度,并清空已用流量。
|
||||
|
||||
### 4.5 兜底复用已有订阅
|
||||
|
||||
如果仍未复用到订阅:
|
||||
|
||||
- 候选用户 ID:
|
||||
- `order.user_id`
|
||||
- 如果 `subscription_user_id` 存在且不同,也加入候选。
|
||||
- 查找这些用户名下 `token != ''` 的订阅。
|
||||
|
||||
找到后:
|
||||
|
||||
- 如果订阅 owner 不是当前 `subscription_user_id`,先把 `user_id` 修正为权益归属用户。
|
||||
- 用续费逻辑延长/换套餐。
|
||||
|
||||
目的:家庭组绑定前后 owner 变化时,也尽量复用旧记录,避免创建重复订阅。
|
||||
|
||||
### 4.6 创建新订阅
|
||||
|
||||
如果以上都没有复用成功,才创建新 `user_subscribe`:
|
||||
|
||||
| 字段 | 值 |
|
||||
| --- | --- |
|
||||
| `user_id` | `subscription_user_id`,没有则 `order.user_id` |
|
||||
| `order_id` | 当前订单 ID |
|
||||
| `subscribe_id` | 当前订单套餐 ID |
|
||||
| `start_time` | 当前时间 |
|
||||
| `expire_time` | 按套餐时间单位和数量计算 |
|
||||
| `traffic` | 套餐流量 |
|
||||
| `token` | 基于订单号生成 |
|
||||
| `uuid` | 新 UUID |
|
||||
| `status` | `1` active |
|
||||
|
||||
创建前如果套餐有 quota,会再按订阅 owner 统计数量。
|
||||
|
||||
### 4.7 新购激活后的异步逻辑
|
||||
|
||||
订阅发放后:
|
||||
|
||||
1. 后台触发用户分组重算。
|
||||
2. 后台异步处理邀请佣金和赠送时间。
|
||||
3. 清套餐缓存。
|
||||
|
||||
注意:邀请逻辑在 goroutine 中执行,不阻塞订单激活。
|
||||
|
||||
## 5. 续费/换套餐激活逻辑
|
||||
|
||||
入口:`Renewal`
|
||||
|
||||
### 5.1 获取用户和订阅
|
||||
|
||||
- 查询订单 `user_id` 对应用户。
|
||||
- 通过 `subscribe_token` 查订阅。
|
||||
- 查询订单 `subscribe_id` 对应套餐。
|
||||
|
||||
### 5.2 Apple IAP 与普通续费分支
|
||||
|
||||
| 分支 | 行为 |
|
||||
| --- | --- |
|
||||
| `iap_expire_at > 0` | 使用 IAP 到期时间兜底,但仍按累计加时语义 |
|
||||
| 普通续费 | `updateSubscriptionForRenewal` |
|
||||
|
||||
### 5.3 普通续费/换套餐规则
|
||||
|
||||
`updateSubscriptionForRenewal`:
|
||||
|
||||
- 如果当前订阅已过期,先把基准时间改为现在。
|
||||
- 如果套餐 ID 变化:
|
||||
- 更新订阅套餐 ID。
|
||||
- 更新流量额度。
|
||||
- 清空已用流量。
|
||||
- 如果套餐没变:
|
||||
- 如果套餐设置 renewal reset,或今天是重置日,则清空已用流量。
|
||||
- 清理 `finished_at`。
|
||||
- `order_id` 改为当前订单 ID。
|
||||
- 按套餐时间单位和数量延长到期时间。
|
||||
- 状态改为 active。
|
||||
- 清空过期流量字段。
|
||||
|
||||
### 5.4 续费后的邀请逻辑
|
||||
|
||||
续费成功后也会调用 `handleCommission`:
|
||||
|
||||
- 是否发佣金由邀请配置和 `order.is_new` 决定。
|
||||
- 是否赠时同样由邀请配置和 `order.is_new` 决定。
|
||||
|
||||
注意:如果 `OnlyFirstPurchase=true`,非首单续费通常不会发佣金,也不会赠首单时间。
|
||||
|
||||
## 6. 邀请关系绑定逻辑
|
||||
|
||||
### 6.1 注册/登录时的邀请码
|
||||
|
||||
新用户注册、游客订单创建用户时,如果带邀请码:
|
||||
|
||||
- 根据邀请码查邀请人。
|
||||
- 设置新用户 `referer_id = 邀请人 ID`。
|
||||
|
||||
### 6.2 用户后绑邀请码
|
||||
|
||||
入口:`BindInviteCode`
|
||||
|
||||
分支:
|
||||
|
||||
| 分支 | 行为 |
|
||||
| --- | --- |
|
||||
| 当前用户不存在 | 返回无权限 |
|
||||
| 当前用户已有 `referer_id` | 返回已绑定 |
|
||||
| 邀请码不存在 | 返回邀请码错误 |
|
||||
| 邀请码属于自己 | 返回不允许绑定自己 |
|
||||
| 通过 | 更新当前用户 `referer_id` |
|
||||
|
||||
注意:
|
||||
|
||||
- `referer_id` 始终记录实际邀请码所有者。
|
||||
- 邀请人是家庭成员时,`referer_id` 仍然是该成员 ID,不自动改为家主 ID。
|
||||
|
||||
## 7. 邀请佣金与赠送时间逻辑
|
||||
|
||||
入口:`handleCommission(userInfo, orderInfo)`
|
||||
|
||||
这里的 `userInfo` 是订单付款用户,也就是被邀请人。
|
||||
|
||||
### 7.1 总入口分支
|
||||
|
||||
先调用 `shouldProcessCommission(userInfo, orderInfo.IsNew)`。
|
||||
|
||||
| 结果 | 行为 |
|
||||
| --- | --- |
|
||||
| `false` | 不发佣金;如果 `is_new=true`,走双方赠时 |
|
||||
| `true` | 发佣金;如果 `is_new=true`,被邀请人赠时 |
|
||||
|
||||
### 7.2 什么时候发佣金
|
||||
|
||||
`shouldProcessCommission` 规则:
|
||||
|
||||
| 条件 | 结果 |
|
||||
| --- | --- |
|
||||
| 被邀请人为空 | 不发 |
|
||||
| 被邀请人 `referer_id=0` | 不发 |
|
||||
| 查不到邀请人 | 不发 |
|
||||
| 邀请人自定义 `referral_percentage > 0`,且只首购但不是首单 | 不发 |
|
||||
| 邀请人自定义 `referral_percentage > 0`,且通过首购限制 | 发佣金 |
|
||||
| 邀请人无自定义比例,系统 `ReferralPercentage=0` | 不发 |
|
||||
| 系统 `OnlyFirstPurchase=true` 且不是首单 | 不发 |
|
||||
| 系统有比例且通过首购限制 | 发佣金 |
|
||||
|
||||
### 7.3 发佣金路径
|
||||
|
||||
如果 `shouldProcessCommission=true`:
|
||||
|
||||
1. 查询邀请人,也就是 `userInfo.referer_id` 对应用户。
|
||||
2. 佣金比例:
|
||||
- 邀请人自定义比例优先。
|
||||
- 否则用系统配置 `Invite.ReferralPercentage`。
|
||||
3. 佣金金额:
|
||||
|
||||
```text
|
||||
(order.amount - order.fee_amount) * referral_percentage / 100
|
||||
```
|
||||
|
||||
4. 事务内幂等检查:
|
||||
- 如果已有同订单佣金日志,则跳过。
|
||||
- 否则增加邀请人的 `commission`。
|
||||
- 写 `system_logs type=33` 佣金日志。
|
||||
5. 更新邀请人缓存。
|
||||
6. 如果 `order.is_new=true`:
|
||||
- 给被邀请人赠送订阅时间。
|
||||
|
||||
当前保持不变的行为:
|
||||
|
||||
- 邀请人是家庭成员时,佣金仍然给实际邀请人成员本人。
|
||||
- 佣金不归并到家主。
|
||||
- 有佣金路径下,邀请人不额外赠送订阅时间。
|
||||
|
||||
### 7.4 不发佣金路径
|
||||
|
||||
如果 `shouldProcessCommission=false`:
|
||||
|
||||
| `order.is_new` | 行为 |
|
||||
| --- | --- |
|
||||
| `true` | 被邀请人和邀请人双方赠送订阅时间 |
|
||||
| `false` | 不赠送时间 |
|
||||
|
||||
双方赠时具体为:
|
||||
|
||||
1. 被邀请人赠时:
|
||||
- 如果被邀请人是家庭成员,加到被邀请人家主套餐。
|
||||
- 否则加到被邀请人本人套餐。
|
||||
2. 邀请人赠时:
|
||||
- 如果邀请人是家庭成员,加到邀请人家主套餐。
|
||||
- 否则加到邀请人本人套餐。
|
||||
|
||||
## 8. 赠送时间目标解析
|
||||
|
||||
入口:`resolveGiftTargetUser(source, forcedOwnerID)`
|
||||
|
||||
### 8.1 强制 owner 分支
|
||||
|
||||
如果 `forcedOwnerID > 0`:
|
||||
|
||||
- 赠送目标直接使用 `forcedOwnerID`。
|
||||
- 典型场景:订单里已有 `subscription_user_id`。
|
||||
- 这保证了家庭成员购买时,被邀请人的赠时落到家主。
|
||||
|
||||
### 8.2 自动家庭组解析分支
|
||||
|
||||
如果没有强制 owner:
|
||||
|
||||
- 调用 `ResolveEntitlementUser(source.Id)`。
|
||||
- 如果 source 是有效家庭成员,目标改为家主。
|
||||
- 否则目标为本人。
|
||||
|
||||
典型场景:
|
||||
|
||||
- 无佣金路径下,邀请人也赠时。
|
||||
- 邀请人如果是家庭成员,赠时会加到邀请人家主套餐。
|
||||
|
||||
### 8.3 目标用户查询失败
|
||||
|
||||
如果解析出来的目标用户查不到:
|
||||
|
||||
- 记录错误日志。
|
||||
- 回退为 source 本人。
|
||||
|
||||
## 9. 赠送时间落库逻辑
|
||||
|
||||
入口:`grantGiftDays(u, days, orderNo, remark)`
|
||||
|
||||
### 9.1 空值和配置分支
|
||||
|
||||
| 条件 | 行为 |
|
||||
| --- | --- |
|
||||
| 目标用户为空 | 直接返回,不写日志 |
|
||||
| `days <= 0` | 直接返回,不写日志 |
|
||||
|
||||
### 9.2 幂等检查
|
||||
|
||||
按下面条件查 gift 日志:
|
||||
|
||||
```sql
|
||||
type = 34
|
||||
AND object_id = 目标用户 ID
|
||||
AND content LIKE '%订单号%'
|
||||
```
|
||||
|
||||
| 结果 | 行为 |
|
||||
| --- | --- |
|
||||
| 已存在 | 跳过,不重复赠时 |
|
||||
| 不存在 | 继续 |
|
||||
|
||||
### 9.3 查目标用户活跃订阅
|
||||
|
||||
调用 `FindActiveSubscribe`。
|
||||
|
||||
当前活跃口径:
|
||||
|
||||
```sql
|
||||
user_id = 目标用户 ID
|
||||
AND status IN (0, 1)
|
||||
AND (
|
||||
expire_time > NOW()
|
||||
OR expire_time = FROM_UNIXTIME(0)
|
||||
)
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `expire_time > NOW()` 是普通未过期订阅。
|
||||
- `expire_time = FROM_UNIXTIME(0)` 是永久/不限时订阅。
|
||||
|
||||
### 9.4 没有活跃订阅
|
||||
|
||||
如果查不到活跃订阅:
|
||||
|
||||
- 不创建新订阅。
|
||||
- 写一条 `system_logs type=34` 日志。
|
||||
- 日志 remark 为:
|
||||
|
||||
```text
|
||||
邀请赠送 skipped: no active subscription
|
||||
```
|
||||
|
||||
这表示邀请赠时触发过,但目标用户当时没有可加时的套餐。
|
||||
|
||||
### 9.5 找到普通活跃订阅
|
||||
|
||||
如果目标订阅不是永久订阅:
|
||||
|
||||
- `expire_time += days * 24h`
|
||||
- 更新订阅。
|
||||
- 写 `system_logs type=34` gift increase 日志。
|
||||
|
||||
### 9.6 找到永久订阅
|
||||
|
||||
如果目标订阅 `expire_time = FROM_UNIXTIME(0)`:
|
||||
|
||||
- 不改变 `expire_time`,因为永久订阅没有可延长的到期时间。
|
||||
- 仍写 `system_logs type=34` gift increase 日志,表示赠送逻辑已识别并处理。
|
||||
|
||||
### 9.7 赠时失败日志
|
||||
|
||||
发佣金路径和无佣金路径都会检查 `grantGiftDays` 返回错误。
|
||||
|
||||
如果出错,会写应用日志:
|
||||
|
||||
```text
|
||||
Grant invite gift days failed
|
||||
```
|
||||
|
||||
附带字段:
|
||||
|
||||
- `stage`
|
||||
- `target_user_id`
|
||||
- `order_no`
|
||||
- `error`
|
||||
|
||||
## 10. 家庭组下的完整分支示例
|
||||
|
||||
### 10.1 被邀请人是普通用户,邀请人普通用户,有佣金
|
||||
|
||||
条件:
|
||||
|
||||
- 被邀请人 `referer_id != 0`
|
||||
- 系统或邀请人佣金比例大于 0
|
||||
- `order.is_new=true`
|
||||
|
||||
结果:
|
||||
|
||||
- 佣金给邀请人本人。
|
||||
- 被邀请人本人套餐加赠送时间。
|
||||
- 邀请人不加赠送时间。
|
||||
|
||||
### 10.2 被邀请人是家庭成员,邀请人普通用户,有佣金
|
||||
|
||||
结果:
|
||||
|
||||
- 佣金给邀请人本人。
|
||||
- 被邀请人的赠送时间加到被邀请人家主套餐。
|
||||
- 被邀请人成员本人不单独加订阅时间。
|
||||
|
||||
### 10.3 被邀请人普通用户,邀请人是家庭成员,有佣金
|
||||
|
||||
结果:
|
||||
|
||||
- 佣金给邀请人成员本人。
|
||||
- 被邀请人本人套餐加赠送时间。
|
||||
- 邀请人不加赠送时间。
|
||||
- 邀请人家主不拿佣金,也不因该佣金路径加赠时。
|
||||
|
||||
### 10.4 被邀请人是家庭成员,邀请人也是家庭成员,有佣金
|
||||
|
||||
结果:
|
||||
|
||||
- 佣金给邀请人成员本人。
|
||||
- 被邀请人的赠送时间加到被邀请人家主套餐。
|
||||
- 邀请人不加赠送时间。
|
||||
- 邀请人家主不拿佣金。
|
||||
|
||||
### 10.5 无佣金路径,被邀请人普通用户,邀请人普通用户
|
||||
|
||||
触发条件示例:
|
||||
|
||||
- `ReferralPercentage=0`
|
||||
- 或因首购限制导致不发佣金
|
||||
- 且 `order.is_new=true`
|
||||
|
||||
结果:
|
||||
|
||||
- 被邀请人本人套餐加赠送时间。
|
||||
- 邀请人本人套餐加赠送时间。
|
||||
|
||||
### 10.6 无佣金路径,被邀请人是家庭成员
|
||||
|
||||
结果:
|
||||
|
||||
- 被邀请人的赠送时间加到被邀请人家主套餐。
|
||||
- 邀请人的赠时按邀请人自己的家庭归属解析。
|
||||
|
||||
### 10.7 无佣金路径,邀请人是家庭成员
|
||||
|
||||
结果:
|
||||
|
||||
- 被邀请人的赠时按被邀请人的家庭归属解析。
|
||||
- 邀请人的赠送时间加到邀请人家主套餐。
|
||||
- 邀请人成员本人不单独加订阅时间。
|
||||
|
||||
### 10.8 被邀请人没有活跃订阅
|
||||
|
||||
结果:
|
||||
|
||||
- 不创建新订阅。
|
||||
- 写 skipped gift 日志。
|
||||
- 后续即使用户后来有订阅,也不会自动补赠,除非另行补偿。
|
||||
|
||||
### 10.9 被邀请人或目标家主是永久订阅
|
||||
|
||||
结果:
|
||||
|
||||
- 识别为活跃订阅。
|
||||
- 不改变到期时间。
|
||||
- 写 gift increase 日志。
|
||||
|
||||
## 11. 排查 SQL
|
||||
|
||||
### 11.1 查邀请配置
|
||||
|
||||
```sql
|
||||
SELECT `key`, `value`, `updated_at`
|
||||
FROM system
|
||||
WHERE category = 'invite'
|
||||
AND `key` IN ('GiftDays', 'OnlyFirstPurchase', 'ReferralPercentage');
|
||||
```
|
||||
|
||||
### 11.2 查某邀请人的被邀请用户
|
||||
|
||||
```sql
|
||||
SELECT id, referer_id, created_at
|
||||
FROM `user`
|
||||
WHERE referer_id = 23944
|
||||
ORDER BY id DESC
|
||||
LIMIT 100;
|
||||
```
|
||||
|
||||
### 11.3 查被邀请人的订单和首单标记
|
||||
|
||||
```sql
|
||||
SELECT u.id AS invited_user_id,
|
||||
o.id AS order_id,
|
||||
o.order_no,
|
||||
o.type,
|
||||
o.status,
|
||||
o.amount,
|
||||
o.is_new,
|
||||
o.subscribe_id,
|
||||
o.subscription_user_id,
|
||||
o.created_at
|
||||
FROM `user` u
|
||||
LEFT JOIN `order` o
|
||||
ON o.user_id = u.id
|
||||
AND o.type IN (1, 2)
|
||||
WHERE u.referer_id = 23944
|
||||
ORDER BY u.id DESC, o.id ASC
|
||||
LIMIT 200;
|
||||
```
|
||||
|
||||
### 11.4 查某订单佣金和赠时日志
|
||||
|
||||
```sql
|
||||
SELECT id, type, object_id, content, created_at
|
||||
FROM system_logs
|
||||
WHERE content LIKE '%202604281812556044982351822%'
|
||||
ORDER BY id DESC;
|
||||
```
|
||||
|
||||
### 11.5 查某用户订阅
|
||||
|
||||
```sql
|
||||
SELECT id, user_id, order_id, subscribe_id, status,
|
||||
expire_time, finished_at, token, created_at, updated_at
|
||||
FROM user_subscribe
|
||||
WHERE user_id = 24425
|
||||
ORDER BY id DESC;
|
||||
```
|
||||
|
||||
### 11.6 查首单但没有赠时日志的被邀请人
|
||||
|
||||
```sql
|
||||
SELECT first_orders.user_id AS invited_user_id,
|
||||
first_orders.order_no,
|
||||
first_orders.is_new,
|
||||
first_orders.status,
|
||||
first_orders.subscription_user_id,
|
||||
first_orders.created_at,
|
||||
(
|
||||
SELECT COUNT(*)
|
||||
FROM system_logs sl
|
||||
WHERE sl.type = 34
|
||||
AND sl.content LIKE CONCAT('%', first_orders.order_no, '%')
|
||||
) AS gift_log_count,
|
||||
(
|
||||
SELECT COUNT(*)
|
||||
FROM system_logs sl
|
||||
WHERE sl.type = 33
|
||||
AND sl.content LIKE CONCAT('%', first_orders.order_no, '%')
|
||||
) AS commission_log_count
|
||||
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)
|
||||
GROUP BY user_id
|
||||
) fo ON fo.first_order_id = o.id
|
||||
) first_orders
|
||||
JOIN `user` u ON u.id = first_orders.user_id
|
||||
WHERE u.referer_id = 23944
|
||||
ORDER BY first_orders.created_at DESC
|
||||
LIMIT 100;
|
||||
```
|
||||
|
||||
## 12. 部署注意事项
|
||||
|
||||
邀请配置存在两层状态:
|
||||
|
||||
1. Redis 缓存:`system:invite_config`
|
||||
2. 服务进程内存:`svc.Config.Invite`
|
||||
|
||||
如果直接修改数据库或 Redis,已经运行的 `ppanel-server` 进程不会自动刷新内存配置。订单激活和赠时发生在服务进程/队列 worker 内,所以修改邀请配置或部署赠时逻辑后,需要重启服务。
|
||||
|
||||
推荐步骤:
|
||||
|
||||
```bash
|
||||
docker exec ppanel-redis redis-cli DEL system:invite_config system:global_config
|
||||
docker restart ppanel-server
|
||||
```
|
||||
|
||||
确认启动时间:
|
||||
|
||||
```bash
|
||||
docker inspect --format '{{.Name}} {{.State.StartedAt}} {{.Config.Image}}' ppanel-server
|
||||
docker ps --filter name=ppanel-server
|
||||
```
|
||||
|
||||
@@ -0,0 +1,796 @@
|
||||
# 促销优惠价系统设计文档
|
||||
|
||||
## 1. 背景与目标
|
||||
|
||||
### 1.1 业务需求
|
||||
|
||||
为套餐规格提供可配置的优惠价格能力,支持多种促销场景:
|
||||
|
||||
- **新客优惠**:注册 N 天内的用户享受优惠价
|
||||
- **回归用户**:N 个月未活跃的用户享受优惠价
|
||||
- **活动促销**:指定时间段内所有用户享受优惠价
|
||||
- **未来可扩展**:首充优惠、邀请用户专属价、指定地区优惠等
|
||||
|
||||
### 1.2 设计原则
|
||||
|
||||
1. **纯新增,不改老代码**:现有的 `new_user_only` + `discount.NewUserOnly` + 24h 窗口逻辑全部保留不动
|
||||
2. **固定价格,非百分比**:运营直接设定优惠价(如 $5.99),不再需要反算折扣百分比
|
||||
3. **后台可配置**:规则类型、参数、时间窗口、优先级均可在管理后台配置
|
||||
4. **促销价不叠加批量折扣**:促销价命中时即为最终基础单价,跳过 `getDiscount()` 的百分比折扣
|
||||
|
||||
### 1.3 与现有体系的关系
|
||||
|
||||
```
|
||||
现有体系(保留不动):
|
||||
subscribe.NewUserOnly → 套餐级新客限制
|
||||
discount[].NewUserOnly → 折扣档位级新客限制
|
||||
newUserEligibility.go → 24h 窗口 + 家庭组判定
|
||||
newUserDiscountEligibility.go → 新客折扣资格组装
|
||||
getDiscount() → 百分比折扣选择
|
||||
order.IsNew → 订单首购标记(统计/佣金用)
|
||||
|
||||
新增体系(本次设计):
|
||||
promo_rule 表 → 可配置的促销规则
|
||||
subscribe_promo 表 → 规格×规则 的优惠价
|
||||
promo_usage 表 → 使用记录(运营分析用)
|
||||
EvaluatePromo() → 促销资格判定
|
||||
```
|
||||
|
||||
**互斥规则**:促销价命中时,跳过老的百分比折扣逻辑(`getDiscount()`)。
|
||||
两套体系不叠加 — 用户要么走促销价,要么走原价+百分比折扣,不会同时生效。
|
||||
|
||||
---
|
||||
|
||||
## 2. 数据模型
|
||||
|
||||
### 2.1 新增表:`promo_rule`(促销规则)
|
||||
|
||||
```sql
|
||||
CREATE TABLE `promo_rule` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`name` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '规则名称,如"新客7天优惠"',
|
||||
`type` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '规则类型:new_user / inactive_user / campaign',
|
||||
`params` JSON NOT NULL COMMENT '类型专属参数',
|
||||
`priority` INT NOT NULL DEFAULT 0 COMMENT '优先级,数值越大越优先匹配',
|
||||
`enabled` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否启用',
|
||||
`start_time` DATETIME DEFAULT NULL COMMENT '生效开始时间,NULL=立即生效',
|
||||
`end_time` DATETIME DEFAULT NULL COMMENT '生效结束时间,NULL=永不过期',
|
||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`deleted_at` DATETIME DEFAULT NULL COMMENT '软删除时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_enabled_priority` (`enabled`, `priority` DESC)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='促销规则表';
|
||||
```
|
||||
|
||||
### 2.2 新增表:`subscribe_promo`(规格优惠价)
|
||||
|
||||
```sql
|
||||
CREATE TABLE `subscribe_promo` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`subscribe_id` BIGINT UNSIGNED NOT NULL COMMENT '套餐规格 ID',
|
||||
`promo_rule_id` BIGINT UNSIGNED NOT NULL COMMENT '促销规则 ID',
|
||||
`promo_price` BIGINT NOT NULL DEFAULT 0 COMMENT '该规格在此规则下的优惠价(分)',
|
||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_subscribe_rule` (`subscribe_id`, `promo_rule_id`),
|
||||
KEY `idx_promo_rule_id` (`promo_rule_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='规格促销价表';
|
||||
```
|
||||
|
||||
### 2.3 新增表:`promo_usage`(促销使用记录)
|
||||
|
||||
用于运营分析,不做强制去重约束。
|
||||
|
||||
```sql
|
||||
CREATE TABLE `promo_usage` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`user_id` BIGINT UNSIGNED NOT NULL COMMENT '用户 ID',
|
||||
`promo_rule_id` BIGINT UNSIGNED NOT NULL COMMENT '使用的规则 ID',
|
||||
`subscribe_id` BIGINT UNSIGNED NOT NULL COMMENT '购买的规格 ID',
|
||||
`order_no` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '关联订单号',
|
||||
`promo_price` BIGINT NOT NULL DEFAULT 0 COMMENT '使用时的促销单价(分)',
|
||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_user_rule` (`user_id`, `promo_rule_id`),
|
||||
KEY `idx_order_no` (`order_no`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='促销使用记录表';
|
||||
```
|
||||
|
||||
### 2.4 `order` 表新增字段
|
||||
|
||||
```sql
|
||||
ALTER TABLE `order`
|
||||
ADD COLUMN `promo_rule_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '促销规则ID, 0=未使用促销',
|
||||
ADD COLUMN `promo_discount` BIGINT NOT NULL DEFAULT 0 COMMENT '促销优惠金额(分)';
|
||||
```
|
||||
|
||||
**字段说明**:
|
||||
|
||||
| 订单字段 | 含义 | 促销命中时 | 未命中时 |
|
||||
|---------|------|-----------|---------|
|
||||
| `Price` | 原始总价 = `UnitPrice × Quantity` | 不变,始终记录原价 | 不变 |
|
||||
| `promo_rule_id` | 使用的促销规则 | 规则 ID | 0 |
|
||||
| `promo_discount` | 促销优惠金额 | `(UnitPrice - PromoPrice) × Quantity` | 0 |
|
||||
| `Discount` | 百分比折扣金额 | **0**(不叠加) | 正常计算 |
|
||||
| `Amount` | 最终支付金额 | 基于促销价计算 | 基于原价+折扣计算 |
|
||||
|
||||
**订单自证**:任何一笔订单都能独立还原其价格构成,不需要回查促销规则表:
|
||||
```
|
||||
Amount = Price - promo_discount - Discount - CouponDiscount + FeeAmount - GiftAmount
|
||||
```
|
||||
|
||||
### 2.5 ER 关系
|
||||
|
||||
```
|
||||
subscribe (1) ──── (*) subscribe_promo (*) ──── (1) promo_rule
|
||||
│
|
||||
│
|
||||
user (1) ──────── (*) promo_usage (*) ─────────── (1) promo_rule
|
||||
│
|
||||
(*) order ← 新增 promo_rule_id, promo_discount
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 规则类型定义
|
||||
|
||||
### 3.1 `new_user` — 新客优惠
|
||||
|
||||
**含义**:用户注册后 N 小时内可享受优惠价
|
||||
|
||||
**params 结构**:
|
||||
|
||||
```json
|
||||
{
|
||||
"window_hours": 168
|
||||
}
|
||||
```
|
||||
|
||||
**判定逻辑**:
|
||||
|
||||
```
|
||||
eligible = (当前时间 - 用户注册时间) < window_hours
|
||||
expires_at = 用户注册时间 + window_hours
|
||||
```
|
||||
|
||||
**与老逻辑的区别**:
|
||||
|
||||
| | 老逻辑 | 新逻辑 |
|
||||
|--|--------|--------|
|
||||
| 窗口期 | 硬编码 24h | 配置化,后台可改 |
|
||||
| 判定基准 | 首台设备注册时间 + 家庭组 | 用户注册时间(`user.created_at`) |
|
||||
| 价格方式 | 百分比折扣 | 固定价格 |
|
||||
| 与折扣叠加 | 是(百分比折扣本身) | 否(替代原价,跳过折扣) |
|
||||
|
||||
### 3.2 `inactive_user` — 回归用户优惠
|
||||
|
||||
**含义**:最近 N 个月没有活跃订阅的用户可享受优惠价
|
||||
|
||||
**params 结构**:
|
||||
|
||||
```json
|
||||
{
|
||||
"inactive_months": 3
|
||||
}
|
||||
```
|
||||
|
||||
**判定逻辑**:
|
||||
|
||||
```
|
||||
last_active = 用户最后一个订阅的 expire_time
|
||||
eligible = last_active 为空(从未购买过)
|
||||
OR (当前时间 - last_active) >= inactive_months 个月
|
||||
expires_at = 规则的 end_time(如有),否则无过期
|
||||
```
|
||||
|
||||
**查询依据**:`user_subscribe` 表中该用户最近一条记录的 `expire_time`
|
||||
|
||||
**注意**:「从未购买过」的用户同时满足 `new_user` 和 `inactive_user`,靠 `priority` 排序选择高优先级的那条。
|
||||
|
||||
### 3.3 `campaign` — 活动促销
|
||||
|
||||
**含义**:在指定时间段内,所有用户均可享受优惠价
|
||||
|
||||
**params 结构**:
|
||||
|
||||
```json
|
||||
{}
|
||||
```
|
||||
|
||||
活动促销不需要额外参数,完全靠 `promo_rule.start_time` 和 `end_time` 控制。
|
||||
|
||||
**判定逻辑**:
|
||||
|
||||
```
|
||||
eligible = start_time <= 当前时间 <= end_time
|
||||
expires_at = end_time
|
||||
```
|
||||
|
||||
### 3.4 扩展预留
|
||||
|
||||
未来新增规则类型只需:
|
||||
1. 定义新的 `type` 字符串(如 `first_purchase`、`referral`、`region`)
|
||||
2. 定义对应的 `params` 结构
|
||||
3. 在判定逻辑中增加一个 `case` 分支
|
||||
|
||||
不需要改表结构,不需要改 API 格式。
|
||||
|
||||
---
|
||||
|
||||
## 4. 核心逻辑
|
||||
|
||||
### 4.1 促销资格判定
|
||||
|
||||
新增文件:`internal/logic/common/promoEligibility.go`
|
||||
|
||||
```go
|
||||
type PromoResult struct {
|
||||
Eligible bool
|
||||
RuleID int64
|
||||
RuleName string
|
||||
RuleType string
|
||||
PromoPrice int64 // 促销单价(分)
|
||||
ExpiresAt time.Time
|
||||
}
|
||||
|
||||
func EvaluatePromo(ctx context.Context, svcCtx *svc.ServiceContext, userID int64, subscribeID int64) (*PromoResult, error) {
|
||||
// 1. 查询该规格关联的所有已启用规则,按 priority DESC
|
||||
// 2. 遍历规则,按类型判定
|
||||
// 3. 首条命中即返回
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 各类型判定函数
|
||||
|
||||
```go
|
||||
func evaluateNewUser(user *User, params RuleParams) (bool, time.Time) {
|
||||
windowHours := params.WindowHours
|
||||
if windowHours <= 0 {
|
||||
return false, time.Time{}
|
||||
}
|
||||
expiresAt := user.CreatedAt.Add(time.Duration(windowHours) * time.Hour)
|
||||
eligible := time.Now().Before(expiresAt)
|
||||
return eligible, expiresAt
|
||||
}
|
||||
|
||||
func evaluateInactiveUser(ctx context.Context, userID int64, rule PromoRule) (bool, time.Time) {
|
||||
inactiveMonths := rule.Params.InactiveMonths
|
||||
if inactiveMonths <= 0 {
|
||||
return false, time.Time{}
|
||||
}
|
||||
lastExpire := getLastSubscriptionExpireTime(ctx, userID)
|
||||
if lastExpire.IsZero() {
|
||||
return true, rule.GetExpiresAt()
|
||||
}
|
||||
threshold := time.Now().AddDate(0, -inactiveMonths, 0)
|
||||
eligible := lastExpire.Before(threshold)
|
||||
return eligible, rule.GetExpiresAt()
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 下单流程集成(不叠加方案)
|
||||
|
||||
在 `purchaseLogic.go` 中 `sub.UnitPrice * req.Quantity` 之前,插入促销价判定:
|
||||
|
||||
```go
|
||||
// === 新增:促销价判定 ===
|
||||
promoResult, promoErr := commonLogic.EvaluatePromo(l.ctx, l.svcCtx, u.Id, targetSubscribeID)
|
||||
if promoErr != nil {
|
||||
return nil, promoErr
|
||||
}
|
||||
|
||||
var promoDiscount int64
|
||||
var promoRuleID int64
|
||||
|
||||
if promoResult.Eligible {
|
||||
// 促销命中 → 用促销价,跳过百分比折扣
|
||||
price = promoResult.PromoPrice * req.Quantity
|
||||
promoDiscount = (sub.UnitPrice * req.Quantity) - price
|
||||
promoRuleID = promoResult.RuleID
|
||||
discount = 1 // 不叠加批量折扣
|
||||
discountAmount = 0
|
||||
} else {
|
||||
// 未命中 → 走原有逻辑(不动)
|
||||
price = sub.UnitPrice * req.Quantity
|
||||
discount = getDiscount(newUserDiscount.Discounts, req.Quantity, newUserDiscount.EligibleForDiscount)
|
||||
discountAmount = price - int64(math.Round(float64(price)*discount))
|
||||
}
|
||||
// === 新增结束 ===
|
||||
|
||||
// 后续 coupon / fee / gift 逻辑完全不动
|
||||
```
|
||||
|
||||
**订单创建时记录**:
|
||||
|
||||
```go
|
||||
orderInfo := &order.Order{
|
||||
// ... 原有字段不动 ...
|
||||
Price: sub.UnitPrice * req.Quantity, // 始终记录原价
|
||||
PromoRuleID: promoRuleID, // 新增
|
||||
PromoDiscount: promoDiscount, // 新增
|
||||
Discount: discountAmount, // 促销命中时为 0
|
||||
Amount: amount,
|
||||
}
|
||||
```
|
||||
|
||||
**激活时写 usage**(`activateOrderLogic.go` 追加):
|
||||
|
||||
```go
|
||||
if orderInfo.PromoRuleID > 0 {
|
||||
insertPromoUsage(ctx, orderInfo.UserId, orderInfo.PromoRuleID, orderInfo.SubscribeId, orderInfo.OrderNo, promoPrice)
|
||||
}
|
||||
```
|
||||
|
||||
### 4.4 价格计算完整流程
|
||||
|
||||
```
|
||||
┌───────────────────────────────────────────────────┐
|
||||
│ 1. 判定促销 │
|
||||
│ EvaluatePromo(userId, subscribeId) │
|
||||
├──────────────┬────────────────────────────────────┤
|
||||
│ 促销命中 │ 促销未命中 │
|
||||
├──────────────┼────────────────────────────────────┤
|
||||
│ basePrice │ basePrice │
|
||||
│ = promoPrice│ = unitPrice │
|
||||
│ │ │
|
||||
│ discount = 0 │ discount = getDiscount(...) │
|
||||
│ (跳过折扣) │ (百分比折扣正常生效) │
|
||||
├──────────────┴────────────────────────────────────┤
|
||||
│ 2. price = basePrice × quantity │
|
||||
│ amount = price - discountAmount │
|
||||
├───────────────────────────────────────────────────┤
|
||||
│ 3. 优惠券(原有逻辑,不动) │
|
||||
│ amount -= couponDiscount │
|
||||
├───────────────────────────────────────────────────┤
|
||||
│ 4. 手续费(原有逻辑,不动) │
|
||||
│ amount += feeAmount │
|
||||
├───────────────────────────────────────────────────┤
|
||||
│ 5. 余额抵扣(原有逻辑,不动) │
|
||||
│ amount -= giftAmount │
|
||||
└───────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 退款影响分析
|
||||
|
||||
### 5.1 结论:退款逻辑无需改动
|
||||
|
||||
当前退款流程(`refundOrderLogic.go`)基于**订单上已存储的字段**运作,不回查价格体系:
|
||||
|
||||
| 退款动作 | 数据来源 | 是否受促销影响 |
|
||||
|---------|---------|--------------|
|
||||
| 退款金额 | `order.Amount`(支付时已锁定) | 否 — Amount 已反映促销价 |
|
||||
| 佣金回退 | `system_log` 表中的 commission 记录 | 否 — 佣金是基于 Amount 计算的 |
|
||||
| 订阅终止 | `user_subscribe.status → 3` | 否 — 和价格无关 |
|
||||
| 审计日志 | `buildRefundAuditLog()` 读订单快照 | 否 — 记录的就是实际值 |
|
||||
|
||||
**原因**:订单创建时所有金额字段(Price、Amount、Discount、PromoDiscount、FeeAmount 等)都已写入 `order` 表。退款只读这些已存储的值,不会重新计算价格。
|
||||
|
||||
### 5.2 退款后的促销资格
|
||||
|
||||
退款后用户的订阅被终止(`expire_time = now - 1s`)。如果用户再次购买:
|
||||
|
||||
| 场景 | 促销资格 | 说明 |
|
||||
|------|---------|------|
|
||||
| 新客退款后重新购买 | 如仍在窗口期内 → 仍然可以享受促销价 | 正常行为,`promo_usage` 只是记录不做去重 |
|
||||
| 回归用户退款后重新购买 | 需重新判定 `inactive_months` | 退款后订阅 expire_time 被设为过去时间 |
|
||||
| 活动促销退款后重新购买 | 如活动仍在进行 → 可以继续购买 | 活动促销不限次数 |
|
||||
|
||||
这些都是合理的业务行为,不需要额外处理。
|
||||
|
||||
### 5.3 佣金影响
|
||||
|
||||
佣金计算公式(`activateOrderLogic.go:1104`):
|
||||
|
||||
```go
|
||||
amount := l.calculateCommission(orderInfo.Amount - orderInfo.FeeAmount, referralPercentage)
|
||||
```
|
||||
|
||||
- `Amount` 在促销命中时已反映促销价(更低的金额)
|
||||
- 所以佣金会相应减少 — **这是正确的行为**
|
||||
- 退款时佣金回退金额从 `system_log` 读取,回退的也是减少后的佣金
|
||||
|
||||
**无需任何改动**。
|
||||
|
||||
---
|
||||
|
||||
## 6. Apple IAP 影响分析
|
||||
|
||||
### 6.1 现状
|
||||
|
||||
- Apple IAP 价格在 App Store Connect 中配置,不支持后端动态定价
|
||||
- 当前通过 `discount[].MapApple` 字段映射 Apple Product ID
|
||||
- IAP 订单在 `appleIAPNotifyLogic.go` 中处理,走独立的价格逻辑
|
||||
|
||||
### 6.2 设计决策
|
||||
|
||||
**促销价不适用于 IAP 订单**。原因:
|
||||
- IAP 价格由 Apple 控制,后端无法干预
|
||||
- IAP 通知回调(`appleIAPNotifyLogic.go`)有独立的价格处理流程
|
||||
- IAP 审计订单设 `IsNew: false`,不走常规购买逻辑
|
||||
|
||||
**实现方式**:`EvaluatePromo()` 不需要特殊处理 — IAP 订单根本不经过 `purchaseLogic.go`,自然不会触发促销判定。
|
||||
|
||||
---
|
||||
|
||||
## 7. 各购买场景适配
|
||||
|
||||
### 7.1 需要集成促销的场景
|
||||
|
||||
| 文件 | 场景 | 集成方式 |
|
||||
|------|------|---------|
|
||||
| `purchaseLogic.go` | 新购 | 完整促销判定 + 不叠加逻辑 |
|
||||
| `preCreateOrderLogic.go` | 价格预览 | 同上(返回 promo_discount 字段) |
|
||||
|
||||
### 7.2 不需要改动的场景
|
||||
|
||||
| 文件 | 场景 | 原因 |
|
||||
|------|------|------|
|
||||
| `renewalLogic.go` | 续费 | 促销价仅限首购,续费走原价+折扣 |
|
||||
| `rechargeLogic.go` | 余额充值 | 充值不涉及套餐价格 |
|
||||
| `redeemCodeLogic.go` | 兑换码 | 兑换码有自己的固定逻辑 |
|
||||
| `recoverOrderLogic.go` | 历史导入 | 导入的是已完成订单 |
|
||||
| `appleIAPNotifyLogic.go` | IAP 续订 | Apple 控制价格 |
|
||||
| `portal/purchaseLogic.go` | 游客购买 | 游客无 user_id,无法判定促销资格 |
|
||||
| `refundOrderLogic.go` | 退款 | 读取订单已存储的金额,不重新计算 |
|
||||
| `activateOrderLogic.go` | 订单激活 | 只追加 promo_usage 写入,价格不重算 |
|
||||
|
||||
### 7.3 统计报表
|
||||
|
||||
现有统计 SQL(`order/model.go` 中 8 处)按 `is_new` 拆分收入,**不需要改动**。
|
||||
|
||||
未来如需促销维度报表,可通过 `order.promo_rule_id` 字段扩展:
|
||||
```sql
|
||||
SUM(CASE WHEN promo_rule_id > 0 THEN amount ELSE 0 END) AS promo_order_amount,
|
||||
SUM(CASE WHEN promo_rule_id = 0 THEN amount ELSE 0 END) AS normal_order_amount
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. API 设计
|
||||
|
||||
### 8.1 套餐列表 API(改造)
|
||||
|
||||
**接口**:`GET /v1/public/subscribe/list`
|
||||
|
||||
**响应变更**:在原有 `Subscribe` 结构体中追加 `promo` 字段。
|
||||
|
||||
```json
|
||||
{
|
||||
"list": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "基础套餐",
|
||||
"unit_price": 288,
|
||||
"discount": [...],
|
||||
"promo": {
|
||||
"rule_name": "新客7天优惠",
|
||||
"rule_type": "new_user",
|
||||
"promo_price": 279,
|
||||
"expires_at": 1748870400
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "标准套餐",
|
||||
"unit_price": 688,
|
||||
"promo": null
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**`promo` 字段说明**:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `rule_name` | string | 规则名称,前端展示用 |
|
||||
| `rule_type` | string | 规则类型,前端可据此展示不同样式 |
|
||||
| `promo_price` | int64 | 优惠单价(分),注意是单价不是总价 |
|
||||
| `expires_at` | int64 | 优惠过期时间戳(秒),0 = 无过期 |
|
||||
|
||||
- 用户未登录时:仅展示 `campaign` 类型促销(不需要用户信息)
|
||||
- 用户已登录:展示所有命中的促销
|
||||
- 未命中任何规则时,`promo` 为 `null`
|
||||
|
||||
### 8.2 预算订单 API(改造)
|
||||
|
||||
**接口**:`POST /v1/public/order/pre`
|
||||
|
||||
**响应追加字段**:
|
||||
|
||||
```json
|
||||
{
|
||||
"price": 688,
|
||||
"amount": 499,
|
||||
"discount": 0,
|
||||
"promo_discount": 189,
|
||||
"coupon_discount": 0,
|
||||
"fee_amount": 0,
|
||||
"gift_amount": 0
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 含义 |
|
||||
|------|------|
|
||||
| `price` | 原始总价 = `UnitPrice × Quantity` |
|
||||
| `promo_discount` | 促销优惠 = `(UnitPrice - PromoPrice) × Quantity` |
|
||||
| `discount` | 百分比折扣优惠(促销命中时为 0) |
|
||||
| `amount` | 最终支付金额 |
|
||||
|
||||
前端可展示:~~原价 ¥6.88~~ → 促销价 ¥4.99
|
||||
|
||||
### 8.3 管理后台 API(新增)
|
||||
|
||||
#### 8.3.1 促销规则 CRUD
|
||||
|
||||
```
|
||||
POST /v1/admin/promo/rule 创建规则
|
||||
GET /v1/admin/promo/rule/list 规则列表
|
||||
GET /v1/admin/promo/rule/:id 规则详情
|
||||
PUT /v1/admin/promo/rule/:id 更新规则
|
||||
DELETE /v1/admin/promo/rule/:id 删除规则(软删除)
|
||||
```
|
||||
|
||||
**创建/更新请求体**:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "新客7天优惠",
|
||||
"type": "new_user",
|
||||
"params": {
|
||||
"window_hours": 168
|
||||
},
|
||||
"priority": 10,
|
||||
"enabled": true,
|
||||
"start_time": null,
|
||||
"end_time": null
|
||||
}
|
||||
```
|
||||
|
||||
**校验规则**:
|
||||
- `type` 必须是已支持的类型
|
||||
- `params` 按 `type` 做结构校验(如 `new_user` 必须有 `window_hours > 0`)
|
||||
- `priority` >= 0
|
||||
- `start_time` < `end_time`(如果两者都提供)
|
||||
|
||||
#### 8.3.2 规格优惠价配置
|
||||
|
||||
```
|
||||
POST /v1/admin/promo/price 批量设置优惠价
|
||||
GET /v1/admin/promo/price/list 查询某规则下的所有优惠价
|
||||
DELETE /v1/admin/promo/price/:id 删除某条优惠价
|
||||
```
|
||||
|
||||
**批量设置请求体**:
|
||||
|
||||
```json
|
||||
{
|
||||
"promo_rule_id": 1,
|
||||
"items": [
|
||||
{"subscribe_id": 1, "promo_price": 279},
|
||||
{"subscribe_id": 2, "promo_price": 599}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**校验**:`promo_price` 必须 < 对应规格的 `unit_price`(防止配置错误)。
|
||||
|
||||
#### 8.3.3 使用记录查询
|
||||
|
||||
```
|
||||
GET /v1/admin/promo/usage/list?rule_id=1&page=1&size=20
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 缓存策略
|
||||
|
||||
### 9.1 规则缓存
|
||||
|
||||
```
|
||||
Key: promo:rules:enabled
|
||||
Value: JSON 数组(所有启用的规则,按 priority DESC)
|
||||
TTL: 300 秒(5 分钟)
|
||||
清除: 管理后台修改规则时主动删除
|
||||
```
|
||||
|
||||
### 9.2 规格优惠价缓存
|
||||
|
||||
```
|
||||
Key: promo:subscribe:{subscribe_id}
|
||||
Value: JSON 数组(该规格关联的所有 rule_id → promo_price)
|
||||
TTL: 300 秒
|
||||
清除: 管理后台修改优惠价时主动删除
|
||||
```
|
||||
|
||||
### 9.3 注意事项
|
||||
|
||||
- 缓存 TTL 300 秒意味着活动 `end_time` 到期后最多 5 分钟延迟,可接受
|
||||
- 管理后台操作后主动 DEL 缓存 key,确保配置变更及时生效
|
||||
- `EvaluatePromo()` 缓存未命中时回查 DB
|
||||
|
||||
---
|
||||
|
||||
## 10. 确定的决策项
|
||||
|
||||
| 编号 | 问题 | 结论 | 原因 |
|
||||
|------|------|------|------|
|
||||
| D-01 | 促销价与批量折扣叠加 | **不叠加** | 促销价即最终单价,跳过 `getDiscount()` |
|
||||
| D-02 | 未登录用户展示促销价 | 仅展示 `campaign` 类型 | `new_user`/`inactive_user` 需要用户信息 |
|
||||
| D-03 | 续费订单适用促销价 | **仅首购** | 促销价用于拉新/回归,续费走原价 |
|
||||
| D-04 | 回归用户判定方式 | 订阅过期时间 | `user_subscribe.expire_time`,数据最可靠 |
|
||||
| D-05 | 多规则命中 | 按 `priority` DESC 取第一条 | 运营可控 |
|
||||
| D-07 | Portal(游客)购买走促销 | **不走** | 游客无 user_id,无法判定资格 |
|
||||
|
||||
---
|
||||
|
||||
## 11. 新增文件清单
|
||||
|
||||
| 层级 | 新增文件 | 说明 |
|
||||
|------|----------|------|
|
||||
| **Model** | `internal/model/promo_rule/promo_rule.go` | 促销规则模型 |
|
||||
| **Model** | `internal/model/subscribe_promo/subscribe_promo.go` | 规格优惠价模型 |
|
||||
| **Model** | `internal/model/promo_usage/promo_usage.go` | 使用记录模型 |
|
||||
| **Logic** | `internal/logic/common/promoEligibility.go` | 促销资格判定核心逻辑 |
|
||||
| **Logic** | `internal/logic/admin/promo/` 目录(CRUD) | 管理后台逻辑 |
|
||||
| **Handler** | `internal/handler/admin/promo/` 目录 | 管理后台 Handler |
|
||||
| **Types** | `internal/types/types.go` 追加 | 新增结构体 |
|
||||
| **Migration** | `initialize/migrate/database/02153_promo_rule.up.sql` | 建表 + order 加字段 |
|
||||
| **Migration** | `initialize/migrate/database/02153_promo_rule.down.sql` | 回滚 |
|
||||
|
||||
### 需改动的已有文件(仅追加)
|
||||
|
||||
| 文件 | 改动方式 |
|
||||
|------|----------|
|
||||
| `internal/logic/public/subscribe/querySubscribeListLogic.go` | 追加:查促销信息,填充 `promo` |
|
||||
| `internal/logic/public/order/purchaseLogic.go` | 追加:促销判定 + 不叠加分支 |
|
||||
| `internal/logic/public/order/preCreateOrderLogic.go` | 追加:预算时考虑促销价 |
|
||||
| `queue/logic/order/activateOrderLogic.go` | 追加:激活后写 `promo_usage` |
|
||||
| `internal/model/order/order.go` | 追加:`PromoRuleID`、`PromoDiscount` 字段 |
|
||||
| `internal/model/order/model.go` | 追加:`Details` 同步字段 |
|
||||
| `internal/types/types.go` | 追加:新增结构体、响应字段 |
|
||||
| `internal/svc/serviceContext.go` | 追加:注入新 Model |
|
||||
| 路由配置 | 追加:管理后台路由 |
|
||||
|
||||
---
|
||||
|
||||
## 12. 运营配置示例
|
||||
|
||||
### 场景 1:新客 7 天优惠
|
||||
|
||||
```
|
||||
promo_rule:
|
||||
name = "新客7天优惠"
|
||||
type = "new_user"
|
||||
params = {"window_hours": 168}
|
||||
priority = 10
|
||||
enabled = true
|
||||
start_time = NULL(永久生效)
|
||||
end_time = NULL
|
||||
|
||||
subscribe_promo:
|
||||
规格"7天" → promo_price = 279
|
||||
规格"30天" → promo_price = 599
|
||||
规格"90天" → promo_price = 1299
|
||||
规格"365天" → promo_price = 4499
|
||||
```
|
||||
|
||||
### 场景 2:回归用户优惠
|
||||
|
||||
```
|
||||
promo_rule:
|
||||
name = "回归用户专属价"
|
||||
type = "inactive_user"
|
||||
params = {"inactive_months": 3}
|
||||
priority = 5
|
||||
enabled = true
|
||||
|
||||
subscribe_promo:
|
||||
规格"30天" → promo_price = 499
|
||||
规格"90天" → promo_price = 999
|
||||
```
|
||||
|
||||
### 场景 3:双十一全站活动
|
||||
|
||||
```
|
||||
promo_rule:
|
||||
name = "双十一特惠"
|
||||
type = "campaign"
|
||||
params = {}
|
||||
priority = 20(优先级高于新客和回归)
|
||||
enabled = true
|
||||
start_time = "2026-11-01 00:00:00"
|
||||
end_time = "2026-11-12 00:00:00"
|
||||
|
||||
subscribe_promo:
|
||||
规格"90天" → promo_price = 999
|
||||
规格"365天" → promo_price = 3999
|
||||
```
|
||||
|
||||
**优先级效果**:双十一期间(priority=20),即使用户是新客(priority=10),也走双十一价格。双十一结束后,新客仍可享受新客优惠。
|
||||
|
||||
---
|
||||
|
||||
## 13. 迁移脚本
|
||||
|
||||
### 02153_promo_system.up.sql
|
||||
|
||||
```sql
|
||||
-- 促销规则表
|
||||
CREATE TABLE IF NOT EXISTS `promo_rule` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`name` VARCHAR(100) NOT NULL DEFAULT '',
|
||||
`type` VARCHAR(32) NOT NULL DEFAULT '',
|
||||
`params` JSON NOT NULL,
|
||||
`priority` INT NOT NULL DEFAULT 0,
|
||||
`enabled` TINYINT(1) NOT NULL DEFAULT 1,
|
||||
`start_time` DATETIME DEFAULT NULL,
|
||||
`end_time` DATETIME DEFAULT NULL,
|
||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`deleted_at` DATETIME DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_enabled_priority` (`enabled`, `priority` DESC)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='促销规则表';
|
||||
|
||||
-- 规格促销价表
|
||||
CREATE TABLE IF NOT EXISTS `subscribe_promo` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`subscribe_id` BIGINT UNSIGNED NOT NULL,
|
||||
`promo_rule_id` BIGINT UNSIGNED NOT NULL,
|
||||
`promo_price` BIGINT NOT NULL DEFAULT 0,
|
||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_subscribe_rule` (`subscribe_id`, `promo_rule_id`),
|
||||
KEY `idx_promo_rule_id` (`promo_rule_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='规格促销价表';
|
||||
|
||||
-- 促销使用记录表
|
||||
CREATE TABLE IF NOT EXISTS `promo_usage` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`user_id` BIGINT UNSIGNED NOT NULL,
|
||||
`promo_rule_id` BIGINT UNSIGNED NOT NULL,
|
||||
`subscribe_id` BIGINT UNSIGNED NOT NULL,
|
||||
`order_no` VARCHAR(255) NOT NULL DEFAULT '',
|
||||
`promo_price` BIGINT NOT NULL DEFAULT 0,
|
||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_user_rule` (`user_id`, `promo_rule_id`),
|
||||
KEY `idx_order_no` (`order_no`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='促销使用记录表';
|
||||
|
||||
-- order 表新增促销字段
|
||||
ALTER TABLE `order`
|
||||
ADD COLUMN `promo_rule_id` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '促销规则ID, 0=未使用促销',
|
||||
ADD COLUMN `promo_discount` BIGINT NOT NULL DEFAULT 0 COMMENT '促销优惠金额(分)';
|
||||
```
|
||||
|
||||
### 02153_promo_system.down.sql
|
||||
|
||||
```sql
|
||||
ALTER TABLE `order`
|
||||
DROP COLUMN IF EXISTS `promo_discount`,
|
||||
DROP COLUMN IF EXISTS `promo_rule_id`;
|
||||
|
||||
DROP TABLE IF EXISTS `promo_usage`;
|
||||
DROP TABLE IF EXISTS `subscribe_promo`;
|
||||
DROP TABLE IF EXISTS `promo_rule`;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 14. 风险与注意事项
|
||||
|
||||
| 风险 | 应对 |
|
||||
|------|------|
|
||||
| 促销价 > 原价(配置错误) | 管理后台校验:`promo_price` 必须 < `unit_price` |
|
||||
| 规则删除后已有订单受影响 | 软删除(`deleted_at`),订单上已存储 `promo_rule_id` 和 `promo_discount`,不依赖规则表 |
|
||||
| 缓存与数据库不一致 | 管理后台修改时主动清缓存,判定逻辑以 DB 为准 |
|
||||
| 新促销和老 NewUserOnly 折扣共存 | **互斥**:促销命中时跳过 `getDiscount()` 的百分比折扣 |
|
||||
| 活动到期后 5 分钟内仍可下单 | 缓存 TTL=300s 的延迟,可接受;下单时可选择实时查 DB 校验 |
|
||||
| 退款后重新购买仍享促销 | 正常行为 — `promo_usage` 只做记录不做去重 |
|
||||
@@ -0,0 +1,190 @@
|
||||
# TAPI 文件上传接入说明
|
||||
|
||||
本文档说明 `https://tapi.hifast.biz/v1/public/file/upload` 相关上传接口的推荐接入方式、签名规则与常见排查方式。
|
||||
|
||||
## 总览
|
||||
|
||||
上传能力包含两类接入方式:
|
||||
|
||||
- 推荐方式:`init -> S3 PUT -> complete`
|
||||
- 兼容方式:`/upload` multipart 直传
|
||||
|
||||
推荐优先使用预签名三段式,因为:
|
||||
|
||||
- 现有签名串包含 `BODY_SHA256`
|
||||
- `/upload` 是 `multipart/form-data`
|
||||
- multipart 原始 body 的签名和调试成本更高
|
||||
- `init` 和 `complete` 是 JSON,更适合客户端和 Apifox 调试
|
||||
|
||||
## 签名生效逻辑
|
||||
|
||||
项目保持现有旧逻辑,不做强制签名改造:
|
||||
|
||||
- `Signature.EnableSignature = false` 时:不校验签名
|
||||
- `Signature.EnableSignature = true` 且未携带 `X-App-Id` 时:不校验签名,兼容老客户端
|
||||
- `Signature.EnableSignature = true` 且携带 `X-App-Id` 时:必须同时携带并校验
|
||||
- `X-Timestamp`
|
||||
- `X-Nonce`
|
||||
- `X-Signature`
|
||||
|
||||
这意味着:
|
||||
|
||||
- 新客户端建议始终带完整签名头
|
||||
- 老客户端如果没有 `X-App-Id`,仍可按旧逻辑访问
|
||||
|
||||
## 签名头定义
|
||||
|
||||
- `X-App-Id`: 客户端标识,例如 `ios-client`
|
||||
- `X-Timestamp`: Unix 秒级时间戳
|
||||
- `X-Nonce`: 每次请求唯一随机串
|
||||
- `X-Signature`: `HMAC-SHA256` 结果的十六进制小写字符串
|
||||
|
||||
## StringToSign 规则
|
||||
|
||||
StringToSign 由下面 7 段按换行符 `\n` 拼接:
|
||||
|
||||
```text
|
||||
METHOD
|
||||
PATH
|
||||
CANONICAL_QUERY
|
||||
BODY_SHA256
|
||||
X-App-Id
|
||||
X-Timestamp
|
||||
X-Nonce
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `METHOD`:HTTP 方法大写,例如 `POST`
|
||||
- `PATH`:请求路径,例如 `/v1/public/file/upload/init`
|
||||
- `CANONICAL_QUERY`:按 key 排序后的 query string,没有 query 则为空字符串
|
||||
- `BODY_SHA256`:请求体原始字节的 SHA-256 十六进制小写
|
||||
- 其余三项直接使用请求头值
|
||||
|
||||
签名计算方式:
|
||||
|
||||
```text
|
||||
signature = hex_lower(HMAC_SHA256(app_secret, string_to_sign))
|
||||
```
|
||||
|
||||
时间窗与防重放:
|
||||
|
||||
- `X-Timestamp` 默认有效时间窗是 300 秒
|
||||
- `X-Nonce` 在有效时间窗内不能重复使用
|
||||
|
||||
## 推荐接入:预签名三段式
|
||||
|
||||
### 1. 初始化上传
|
||||
|
||||
请求:
|
||||
|
||||
```bash
|
||||
curl -X POST 'https://tapi.hifast.biz/v1/public/file/upload/init' \
|
||||
-H 'Accept: application/json, text/plain, */*' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'authorization: your-token' \
|
||||
-H 'X-App-Id: ios-client' \
|
||||
-H 'X-Timestamp: 1778776400' \
|
||||
-H 'X-Nonce: nonce-001' \
|
||||
-H 'X-Signature: your-signature' \
|
||||
-d '{
|
||||
"biz_type": "app-package",
|
||||
"file_name": "demo.zip",
|
||||
"content_type": "application/zip",
|
||||
"size": 123456,
|
||||
"sha256": ""
|
||||
}'
|
||||
```
|
||||
|
||||
典型返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"file_id": "c29274ee26ab5aa211e0396e",
|
||||
"object_key": "app-upload/app-package/519/2026/05/c29274ee26ab5aa211e0396e_demo.zip",
|
||||
"upload_url": "https://bucket.s3.ap-east-1.amazonaws.com/...",
|
||||
"method": "PUT",
|
||||
"headers": {
|
||||
"Content-Type": "application/zip"
|
||||
},
|
||||
"expired_at": 1778776715
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 直传 S3
|
||||
|
||||
这一步是直接上传二进制文件到 S3,不走业务签名中间件。
|
||||
|
||||
```bash
|
||||
curl -X PUT 'https://bucket.s3.ap-east-1.amazonaws.com/...' \
|
||||
-H 'Content-Type: application/zip' \
|
||||
--upload-file '/tmp/demo.zip'
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `Content-Type` 需和 `init` 返回的 `headers.Content-Type` 一致
|
||||
- 允许的 `Content-Type` 由服务端 `S3.AllowedContentTypes` 配置控制,默认包含:
|
||||
- 压缩包:`application/zip`、`application/x-zip-compressed`、`application/gzip`、`application/x-gzip`
|
||||
- 通用文件:`application/octet-stream`、`text/plain`、`application/json`
|
||||
- 图片:`image/jpeg`、`image/jpg`、`image/png`、`image/webp`、`image/gif`、`image/heic`、`image/heif`、`image/bmp`
|
||||
- `upload_url` 有过期时间,通常 300 秒
|
||||
- 成功时 S3 常见返回 `200` 或 `204`
|
||||
|
||||
### 3. 完成上传
|
||||
|
||||
```bash
|
||||
curl -X POST 'https://tapi.hifast.biz/v1/public/file/upload/complete' \
|
||||
-H 'Accept: application/json, text/plain, */*' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'authorization: your-token' \
|
||||
-H 'X-App-Id: ios-client' \
|
||||
-H 'X-Timestamp: 1778776405' \
|
||||
-H 'X-Nonce: nonce-002' \
|
||||
-H 'X-Signature: your-signature' \
|
||||
-d '{
|
||||
"file_id": "c29274ee26ab5aa211e0396e"
|
||||
}'
|
||||
```
|
||||
|
||||
## 兼容接入:单接口 multipart 直传
|
||||
|
||||
接口:
|
||||
|
||||
- `POST /v1/public/file/upload`
|
||||
|
||||
表单字段:
|
||||
|
||||
- `biz_type`
|
||||
- `file`
|
||||
|
||||
说明:
|
||||
|
||||
- 该接口继续保留,兼容旧客户端
|
||||
- 如果请求带了 `X-App-Id`,就按现有逻辑验签
|
||||
- 如果没有 `X-App-Id`,仍按旧逻辑放行
|
||||
- 如果要给该接口加签,签名时必须对原始 multipart body 计算 `BODY_SHA256`
|
||||
- 允许的 `Content-Type` 与预签名三段式一致;multipart 文件字段未显式携带 `Content-Type` 时,服务端会基于文件内容嗅探常见类型。
|
||||
|
||||
## 常见错误码
|
||||
|
||||
- `200`: 成功
|
||||
- `400`: 参数错误
|
||||
- `400 content_type is not allowed`: 文件 `Content-Type` 不在 `S3.AllowedContentTypes` 白名单内
|
||||
- `40008`: 缺少签名头
|
||||
- `40009`: 签名已过期
|
||||
- `40010`: 签名无效
|
||||
- `40011`: nonce 重放
|
||||
- `10001`: 上传元数据不存在或对象不存在
|
||||
|
||||
## 排查建议
|
||||
|
||||
- `40008`:确认带了 `X-App-Id` 后,也同时带上 `X-Timestamp / X-Nonce / X-Signature`
|
||||
- `40009`:检查客户端时间是否偏差过大
|
||||
- `40010`:确认 `PATH`、query 排序、body 原始字节、secret 是否完全一致
|
||||
- `40011`:确保每次请求都生成新的 `X-Nonce`
|
||||
- `complete` 失败:确认 S3 `PUT` 已成功,且上传大小与 `init.size` 一致
|
||||
@@ -0,0 +1,144 @@
|
||||
# 提现接口文档
|
||||
|
||||
## 基础信息
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|-----|
|
||||
| Base URL | `/v1/public/user` |
|
||||
| 认证方式 | JWT Token(`AuthMiddleware` + `DeviceMiddleware`) |
|
||||
| 数据表 | `user_withdrawal` |
|
||||
|
||||
## 数据模型
|
||||
|
||||
### user_withdrawal 表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `id` | int64 | 主键 |
|
||||
| `user_id` | int64 | 用户 ID |
|
||||
| `amount` | int64 | 提现金额(单位:分) |
|
||||
| `content` | text | 收款信息(账号、姓名等) |
|
||||
| `status` | tinyint | 0=待审核, 1=已通过, 2=已拒绝 |
|
||||
| `reason` | varchar(500) | 拒绝原因(通过时为空) |
|
||||
| `created_at` | datetime | 创建时间 |
|
||||
| `updated_at` | datetime | 更新时间 |
|
||||
|
||||
### status 枚举
|
||||
|
||||
| 值 | 含义 | 说明 |
|
||||
|----|------|------|
|
||||
| 0 | Pending(待审核) | 用户提交申请后的初始状态 |
|
||||
| 1 | Approved(已通过) | 管理员审核通过,佣金已扣减 |
|
||||
| 2 | Rejected(已拒绝) | 管理员拒绝,无需退款(申请时未扣款) |
|
||||
|
||||
---
|
||||
|
||||
## 用户端接口
|
||||
|
||||
### 1. 申请提现
|
||||
|
||||
申请佣金提现,创建一条待审核记录。申请时**不扣余额**,管理员审核通过后才扣。
|
||||
|
||||
```
|
||||
POST /v1/public/user/commission_withdraw
|
||||
```
|
||||
|
||||
#### 请求体
|
||||
|
||||
```json
|
||||
{
|
||||
"amount": 1000,
|
||||
"content": "支付宝:138xxxx1234 / 张三"
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `amount` | int64 | 是 | 提现金额(单位:分) |
|
||||
| `content` | string | 是 | 收款信息(支付宝/银行卡等) |
|
||||
|
||||
#### 成功响应
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"id": 1,
|
||||
"user_id": 10001,
|
||||
"amount": 1000,
|
||||
"content": "支付宝:138xxxx1234 / 张三",
|
||||
"status": 0,
|
||||
"reason": "",
|
||||
"created_at": 1716624000000,
|
||||
"updated_at": 1716624000000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **注意**:此接口的 `created_at` / `updated_at` 返回**毫秒级**时间戳(`.UnixMilli()`),与项目其他接口的秒级时间戳不一致。
|
||||
|
||||
#### 业务逻辑
|
||||
|
||||
1. 查询该用户所有 status=0(待审核)的提现记录,求和得 `pendingTotal`
|
||||
2. 校验可用余额:`commission >= amount + pendingTotal`
|
||||
3. 创建 `user_withdrawal` 记录,status=0
|
||||
4. **不扣减** `user.commission`,等审核通过才扣
|
||||
|
||||
#### 错误码
|
||||
|
||||
| 错误码 | 常量 | 说明 |
|
||||
|--------|------|------|
|
||||
| 20010 | `UserCommissionNotEnough` | 可用余额不足(余额 = commission - 所有 pending 提现总额) |
|
||||
| 40005 | `InvalidAccess` | 未登录 / Token 无效 |
|
||||
|
||||
#### 源码位置
|
||||
|
||||
- Handler: `internal/handler/public/user/commissionWithdrawHandler.go`
|
||||
- Logic: `internal/logic/public/user/commissionWithdrawLogic.go`
|
||||
|
||||
---
|
||||
|
||||
### 2. 查询提现记录
|
||||
|
||||
分页查询当前用户的提现记录。
|
||||
|
||||
```
|
||||
GET /v1/public/user/withdrawal_log
|
||||
```
|
||||
|
||||
#### 请求参数(Query)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `page` | int | 否 | 页码 |
|
||||
| `size` | int | 否 | 每页数量 |
|
||||
|
||||
#### 成功响应
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"list": [
|
||||
{
|
||||
"id": 1,
|
||||
"user_id": 10001,
|
||||
"amount": 1000,
|
||||
"content": "支付宝:138xxxx1234",
|
||||
"status": 0,
|
||||
"reason": "",
|
||||
"created_at": 1716624000,
|
||||
"updated_at": 1716624000
|
||||
}
|
||||
],
|
||||
"total": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 源码位置
|
||||
|
||||
- Handler: `internal/handler/public/user/queryWithdrawalLogHandler.go`
|
||||
- Logic: `internal/logic/public/user/queryWithdrawalLogLogic.go`
|
||||
|
||||
> **Warning**: Logic 层尚未实现(仍为 TODO),调用会返回空响应。
|
||||
|
||||
---
|
||||
@@ -0,0 +1,153 @@
|
||||
# 用户端提现列表 API — 订阅字段现状调研
|
||||
|
||||
> 调研日期:2026-05-27
|
||||
> 调研范围:用户端「提现记录列表」接口当前返回字段,重点关注是否包含订阅相关信息
|
||||
|
||||
## 一、接口信息
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|-----|
|
||||
| 方法 | `GET` |
|
||||
| 路径 | `/v1/public/user/withdrawal_log` |
|
||||
| 认证 | JWT Token(`AuthMiddleware` + `DeviceMiddleware`) |
|
||||
| 分组 | `apis/public/user.api` |
|
||||
|
||||
## 二、文件定位
|
||||
|
||||
| 层 | 路径 |
|
||||
|----|------|
|
||||
| API DSL | `apis/public/user.api:118` (`WithdrawalLog`) / `apis/public/user.api:368` (路由) |
|
||||
| Handler | `internal/handler/public/user/queryWithdrawalLogHandler.go` |
|
||||
| Logic | `internal/logic/public/user/queryWithdrawalLogLogic.go:30` |
|
||||
| 类型生成 | `internal/types/types.go`(`WithdrawalLog`、`QueryWithdrawalLogListRequest`、`QueryWithdrawalLogListResponse`) |
|
||||
| 数据模型 | `internal/model/user/user.go:167` (`Withdrawal`,表名 `withdrawals`) |
|
||||
|
||||
## 三、请求参数
|
||||
|
||||
```go
|
||||
QueryWithdrawalLogListRequest {
|
||||
Page int `form:"page"`
|
||||
Size int `form:"size"`
|
||||
}
|
||||
```
|
||||
|
||||
- 默认值:`page=1`、`size=10`(在 logic 内兜底)
|
||||
|
||||
## 四、响应结构
|
||||
|
||||
### 4.1 顶层响应
|
||||
|
||||
```go
|
||||
QueryWithdrawalLogListResponse {
|
||||
List []WithdrawalLog `json:"list"`
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 列表项 `WithdrawalLog`
|
||||
|
||||
```go
|
||||
WithdrawalLog {
|
||||
Id int64 `json:"id"`
|
||||
UserId int64 `json:"user_id"`
|
||||
Amount int64 `json:"amount"` // 单位:分
|
||||
Content string `json:"content"` // 收款附加信息
|
||||
Status uint8 `json:"status"` // 0:Pending 1:Approved 2:Rejected 3:Cancelled
|
||||
Reason string `json:"reason,omitempty"` // 拒绝原因
|
||||
Method uint8 `json:"method"` // 0:其他 1:支付宝 2:微信 3:USDT
|
||||
Account string `json:"account"` // 收款账号
|
||||
QrCodeUrl string `json:"qr_code_url"` // 收款码图片 URL
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
}
|
||||
```
|
||||
|
||||
## 五、底层数据模型 `Withdrawal`
|
||||
|
||||
```go
|
||||
type Withdrawal struct {
|
||||
Id int64
|
||||
UserId int64 // index:idx_user_id
|
||||
Amount int64
|
||||
Content string // type:text
|
||||
Status uint8 // 0:Pending 1:Approved 2:Rejected 3:Cancelled
|
||||
Reason string // varchar(500)
|
||||
Method uint8 // 0:其他 1:支付宝 2:微信 3:USDT
|
||||
Account string // varchar(255)
|
||||
QrCodeUrl string // varchar(500)
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
```
|
||||
|
||||
> 表名:`withdrawals`,与用户关联仅靠 `user_id` 外键,**无任何订阅 ID / 订阅快照字段**。
|
||||
|
||||
## 六、订阅字段现状(核心结论)
|
||||
|
||||
### 6.1 当前结论
|
||||
|
||||
| 维度 | 是否包含订阅信息 |
|
||||
|------|------------------|
|
||||
| API 响应(`WithdrawalLog`) | ❌ 无 |
|
||||
| 数据库表(`withdrawals`) | ❌ 无 |
|
||||
| Logic 查询逻辑 | ❌ 无 JOIN、无附加查询 `user_subscribe` |
|
||||
|
||||
提现记录与订阅之间**完全没有关联**。原因:佣金来源于多次订单累计,提现是从「佣金余额(`user.commission`)」整体扣减,不绑定到任何具体订阅。
|
||||
|
||||
### 6.2 Logic 当前实现要点
|
||||
|
||||
```go
|
||||
// internal/logic/public/user/queryWithdrawalLogLogic.go:46-72
|
||||
query := l.svcCtx.DB.WithContext(l.ctx).
|
||||
Model(&user.Withdrawal{}).
|
||||
Where("user_id = ?", u.Id)
|
||||
|
||||
// 仅按 user_id 过滤 + 分页 + 倒序,无任何 Preload / Join
|
||||
```
|
||||
|
||||
## 七、已发现的隐患(与本次需求关联)
|
||||
|
||||
### 7.1 时间戳违反项目约定 ⚠️
|
||||
|
||||
`queryWithdrawalLogLogic.go:70-71`:
|
||||
|
||||
```go
|
||||
CreatedAt: row.CreatedAt.UnixMilli(),
|
||||
UpdatedAt: row.UpdatedAt.UnixMilli(),
|
||||
```
|
||||
|
||||
- 项目约定:**后端统一返回秒级 Unix 时间戳**(前端 `formatDate` 已按 `数字 × 1000` 处理)
|
||||
- 当前实现返回毫秒级,前端会解析为约公元 +55000 年的日期,**展示必然异常**
|
||||
- 修复方式:改为 `.Unix()`
|
||||
|
||||
> 该问题独立于「订阅字段」需求,但属于同一接口,建议同批修复。
|
||||
|
||||
## 八、可选扩展方向(待业务确认)
|
||||
|
||||
若产品希望在提现列表中展示订阅相关信息,可选方案如下:
|
||||
|
||||
| 方案 | 字段示意 | 实现成本 | 适用场景 |
|
||||
|------|----------|----------|----------|
|
||||
| A. 当前生效订阅摘要 | `current_subscribe: { id, name, expire_at }` | 中(每行额外查 `user_subscribe`) | 想让用户看到「我提的是哪个订阅产生的佣金对应的余额」 |
|
||||
| B. 用户全部订阅列表 | `subscribes: [{ id, name, expire_at }]` | 高(N+1 风险) | 极少场景,需评估必要性 |
|
||||
| C. 仅订阅 ID 数组 | `subscribe_ids: [int64]` | 低 | 仅前端跳详情用 |
|
||||
| D. 不加,保持现状 | — | 0 | 若业务上提现与订阅本就无关 |
|
||||
|
||||
> **推荐先与产品确认动机**:提现是佣金余额提现,与订阅本身没有直接业务关系,加字段前需明确「让用户看到订阅信息要解决什么问题」。
|
||||
|
||||
## 九、相关接口(一并列出,便于对照)
|
||||
|
||||
| 接口 | 方法 | 路径 | 说明 |
|
||||
|------|------|------|------|
|
||||
| 提交提现 | POST | `/v1/public/user/commission_withdraw` | 入参 `CommissionWithdrawRequest`,返回 `WithdrawalLog` |
|
||||
| 取消提现 | POST | `/v1/public/user/withdrawal_cancel` | 入参 `CancelWithdrawalRequest`,返回 `WithdrawalLog` |
|
||||
| 提现记录列表 | GET | `/v1/public/user/withdrawal_log` | 本文主角 |
|
||||
|
||||
> 三个接口共用 `WithdrawalLog` 类型,**任何字段变更需统一同步**,否则前端类型会错位。
|
||||
|
||||
## 十、后续动作建议
|
||||
|
||||
1. **产品确认**:是否真的需要在提现列表里返回订阅字段?目的是什么?
|
||||
2. **若需新增**:在 `apis/public/user.api` 修改 `WithdrawalLog`,运行 goctl 重新生成,再补 Logic 查询。
|
||||
3. **顺手修复**:将 `UnixMilli()` 改为 `Unix()`(独立小 PR 即可)。
|
||||
4. **如新增订阅字段**:注意三个接口(list / cancel / withdraw)的返回结构同步,避免前端类型联动断裂。
|
||||
+2
-343
@@ -1,33 +1,12 @@
|
||||
# PPanel 服务部署 (云端/无源码版)
|
||||
# 使用方法:
|
||||
# 1. 确保已将 docker-compose.cloud.yml, configs/, loki/, grafana/, prometheus/, tempo/ 目录上传到服务器同一目录
|
||||
# 2. 确保 configs/ 目录下有 ppanel.yaml 配置文件(参考 etc/ppanel.yaml)
|
||||
# 3. 确保 logs/ cache/ tempo_data/ 目录存在 (mkdir -p logs cache tempo_data)
|
||||
# 4. 运行: docker-compose -f docker-compose.cloud.yml up -d
|
||||
#
|
||||
# 网络说明:
|
||||
# ppanel-server 使用 host 网络(可出外网,访问 MySQL/Redis/Tempo 用 127.0.0.1)
|
||||
# 监控服务(MySQL/Redis/Loki/Tempo/Grafana/Prometheus)在 ppanel_net bridge 网络中
|
||||
# MySQL(3306)/Redis(6379)/Tempo(4317) 将端口映射到 127.0.0.1,ppanel-server 通过 host 网络访问
|
||||
# 监控端口绑定 127.0.0.1,需通过 SSH 隧道或 Nginx 反代访问
|
||||
#
|
||||
# 未来多开 ppanel-server 时:
|
||||
# 修复宿主机 iptables bridge 出网规则后,可将 ppanel-server 切回 bridge 网络
|
||||
# 多实例用不同端口: ports: ["8081:8080"] + container_name: ppanel-server-2
|
||||
|
||||
services:
|
||||
# ----------------------------------------------------
|
||||
# 1. 业务后端 (PPanel Server)
|
||||
# host 网络:可出外网,通过 127.0.0.1 访问 MySQL/Redis/Tempo
|
||||
# ----------------------------------------------------
|
||||
ppanel-server:
|
||||
image: registry.kxsw.us/vpn-server:${PPANEL_SERVER_TAG:-latest}
|
||||
image: ${PPANEL_SERVER_IMAGE:-registry.kxsw.us/vpn-server}:${PPANEL_SERVER_TAG:?please set PPANEL_SERVER_TAG to an immutable image tag}
|
||||
container_name: ppanel-server
|
||||
restart: always
|
||||
volumes:
|
||||
- ./configs:/app/etc
|
||||
- ./logs:/app/logs
|
||||
- ./cache:/app/cache # GeoLite2-City.mmdb IP 地理位置数据库
|
||||
- ./cache:/app/cache
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
network_mode: host
|
||||
@@ -36,328 +15,8 @@ services:
|
||||
nofile:
|
||||
soft: 65535
|
||||
hard: 65535
|
||||
depends_on:
|
||||
mysql:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
tempo:
|
||||
condition: service_started
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 2. MySQL Database
|
||||
# ----------------------------------------------------
|
||||
mysql:
|
||||
image: mysql:8.0
|
||||
container_name: ppanel-mysql
|
||||
restart: always
|
||||
ports:
|
||||
- "3306:3306" # 仅宿主机可访问,ppanel-server(host网络)通过127.0.0.1连接
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD:?请在 .env 文件中设置 MYSQL_ROOT_PASSWORD}"
|
||||
MYSQL_DATABASE: "ppanel"
|
||||
TZ: Asia/Shanghai
|
||||
command:
|
||||
- --default-authentication-plugin=mysql_native_password
|
||||
- --innodb_buffer_pool_size=16G
|
||||
- --innodb_buffer_pool_instances=16
|
||||
- --innodb_log_file_size=2G
|
||||
- --innodb_flush_log_at_trx_commit=2
|
||||
- --innodb_io_capacity=5000
|
||||
- --max_connections=5000
|
||||
volumes:
|
||||
- mysql_data:/var/lib/mysql
|
||||
ulimits:
|
||||
nproc: 65535
|
||||
nofile:
|
||||
soft: 65535
|
||||
hard: 65535
|
||||
networks:
|
||||
- ppanel_net
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-p${MYSQL_ROOT_PASSWORD}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 3. Redis
|
||||
# ----------------------------------------------------
|
||||
redis:
|
||||
image: redis:8.2.1
|
||||
container_name: ppanel-redis
|
||||
restart: always
|
||||
ports:
|
||||
- "127.0.0.1:6379:6379" # 仅宿主机可访问,ppanel-server(host网络)通过127.0.0.1连接
|
||||
command:
|
||||
- redis-server
|
||||
- --tcp-backlog 65535
|
||||
- --maxmemory-policy allkeys-lru
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
ulimits:
|
||||
nproc: 65535
|
||||
nofile:
|
||||
soft: 65535
|
||||
hard: 65535
|
||||
networks:
|
||||
- ppanel_net
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 4. Tempo (链路追踪存储)
|
||||
# ----------------------------------------------------
|
||||
tempo:
|
||||
image: grafana/tempo:2.4.1
|
||||
container_name: ppanel-tempo
|
||||
user: root
|
||||
restart: always
|
||||
command:
|
||||
- "-config.file=/etc/tempo.yaml"
|
||||
- "-target=all"
|
||||
volumes:
|
||||
- ./tempo/tempo-config.yaml:/etc/tempo.yaml
|
||||
- ./tempo_data:/var/tempo
|
||||
ports:
|
||||
- "127.0.0.1:4317:4317" # OTLP gRPC,ppanel-server(host网络)通过127.0.0.1:4317发送trace
|
||||
networks:
|
||||
- ppanel_net
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 5. Loki (日志存储)
|
||||
# ----------------------------------------------------
|
||||
loki:
|
||||
image: grafana/loki:3.0.0
|
||||
container_name: ppanel-loki
|
||||
restart: always
|
||||
volumes:
|
||||
- ./loki/loki-config.yaml:/etc/loki/local-config.yaml
|
||||
- loki_data:/loki
|
||||
command: -config.file=/etc/loki/local-config.yaml
|
||||
# 不对外暴露端口,仅内网访问
|
||||
networks:
|
||||
- ppanel_net
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 6. Promtail (日志采集)
|
||||
# ----------------------------------------------------
|
||||
promtail:
|
||||
image: grafana/promtail:3.0.0
|
||||
container_name: ppanel-promtail
|
||||
restart: always
|
||||
volumes:
|
||||
- ./loki/promtail-config.yaml:/etc/promtail/config.yaml
|
||||
- /var/lib/docker/containers:/var/lib/docker/containers:ro
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ./logs:/var/log/ppanel-server:ro
|
||||
- /var/log/nginx:/var/log/nginx:ro
|
||||
command: -config.file=/etc/promtail/config.yaml
|
||||
networks:
|
||||
- ppanel_net
|
||||
depends_on:
|
||||
- loki
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 7. Grafana (可观测面板)
|
||||
# 访问: ssh -L 3333:localhost:3333 your-server 后浏览器打开 http://localhost:3333
|
||||
# 或配置 Nginx 反代(建议加认证)
|
||||
# ----------------------------------------------------
|
||||
grafana:
|
||||
image: grafana/grafana:latest
|
||||
container_name: ppanel-grafana
|
||||
restart: always
|
||||
ports:
|
||||
- "127.0.0.1:3333:3000" # 仅本机可访问,需 SSH 隧道或 Nginx 反代
|
||||
environment:
|
||||
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD:?请在 .env 文件中设置 GRAFANA_PASSWORD}
|
||||
- GF_USERS_ALLOW_SIGN_UP=false
|
||||
- GF_FEATURE_TOGGLES_ENABLE=appObservability
|
||||
volumes:
|
||||
- grafana_data:/var/lib/grafana
|
||||
- ./grafana/provisioning:/etc/grafana/provisioning
|
||||
networks:
|
||||
- ppanel_net
|
||||
depends_on:
|
||||
- loki
|
||||
- tempo
|
||||
- prometheus
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 8. Prometheus (指标采集)
|
||||
# ----------------------------------------------------
|
||||
prometheus:
|
||||
image: prom/prometheus:latest
|
||||
container_name: ppanel-prometheus
|
||||
restart: always
|
||||
ports:
|
||||
- "127.0.0.1:9090:9090" # 仅本机可访问
|
||||
volumes:
|
||||
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
|
||||
- prometheus_data:/prometheus
|
||||
command:
|
||||
- '--config.file=/etc/prometheus/prometheus.yml'
|
||||
- '--storage.tsdb.path=/prometheus'
|
||||
- '--web.enable-lifecycle'
|
||||
- '--web.enable-remote-write-receiver'
|
||||
networks:
|
||||
- ppanel_net
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 9. Redis Exporter
|
||||
# ----------------------------------------------------
|
||||
redis-exporter:
|
||||
image: oliver006/redis_exporter:latest
|
||||
container_name: ppanel-redis-exporter
|
||||
restart: always
|
||||
environment:
|
||||
- REDIS_ADDR=redis://redis:6379
|
||||
networks:
|
||||
- ppanel_net
|
||||
depends_on:
|
||||
- redis
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 10. Nginx Exporter (监控宿主机 Nginx)
|
||||
# ----------------------------------------------------
|
||||
nginx-exporter:
|
||||
image: nginx/nginx-prometheus-exporter:latest
|
||||
container_name: ppanel-nginx-exporter
|
||||
restart: always
|
||||
command:
|
||||
- -nginx.scrape-uri=http://host.docker.internal:8090/nginx_status
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
networks:
|
||||
- ppanel_net
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 11. MySQL Exporter
|
||||
# ----------------------------------------------------
|
||||
mysql-exporter:
|
||||
image: prom/mysqld-exporter:latest
|
||||
container_name: ppanel-mysql-exporter
|
||||
restart: always
|
||||
command:
|
||||
- --config.my-cnf=/etc/.my.cnf
|
||||
volumes:
|
||||
- ./mysql/.my.cnf:/etc/.my.cnf:ro
|
||||
networks:
|
||||
- ppanel_net
|
||||
depends_on:
|
||||
- mysql
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 12. Node Exporter (宿主机监控)
|
||||
# ----------------------------------------------------
|
||||
node-exporter:
|
||||
image: prom/node-exporter:latest
|
||||
container_name: ppanel-node-exporter
|
||||
restart: always
|
||||
volumes:
|
||||
- /proc:/host/proc:ro
|
||||
- /sys:/host/sys:ro
|
||||
- /:/rootfs:ro
|
||||
command:
|
||||
- '--path.procfs=/host/proc'
|
||||
- '--path.sysfs=/host/sys'
|
||||
- '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
|
||||
networks:
|
||||
- ppanel_net
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 13. cAdvisor (容器监控)
|
||||
# ----------------------------------------------------
|
||||
cadvisor:
|
||||
image: gcr.io/cadvisor/cadvisor:latest
|
||||
container_name: ppanel-cadvisor
|
||||
restart: always
|
||||
volumes:
|
||||
- /:/rootfs:ro
|
||||
- /var/run:/var/run:ro
|
||||
- /sys:/sys:ro
|
||||
- /var/lib/docker/:/var/lib/docker:ro
|
||||
- /dev/disk/:/dev/disk:ro
|
||||
networks:
|
||||
- ppanel_net
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
volumes:
|
||||
mysql_data:
|
||||
redis_data:
|
||||
loki_data:
|
||||
grafana_data:
|
||||
prometheus_data:
|
||||
tempo_data:
|
||||
|
||||
networks:
|
||||
ppanel_net:
|
||||
name: ppanel_net
|
||||
driver: bridge
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:8.0
|
||||
container_name: ppanel-mysql
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: ppanel_dev
|
||||
MYSQL_DATABASE: ppanel
|
||||
MYSQL_USER: ppanel
|
||||
MYSQL_PASSWORD: ppanel_dev
|
||||
ports:
|
||||
- "3306:3306"
|
||||
volumes:
|
||||
- ppanel_mysql_data:/var/lib/mysql
|
||||
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-pppanel_dev"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: ppanel-redis
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- ppanel_redis_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
|
||||
volumes:
|
||||
ppanel_mysql_data:
|
||||
ppanel_redis_data:
|
||||
@@ -1,5 +1,3 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
ppanel:
|
||||
container_name: ppanel-server
|
||||
|
||||
+24
-9
@@ -15,10 +15,10 @@ Logger: # 日志配置
|
||||
Level: debug # 日志级别: debug, info, warn, error, panic, fatal
|
||||
|
||||
MySQL:
|
||||
Addr: 103.150.215.44:3306 # host 网络模式; bridge 模式改为 mysql:3306
|
||||
Addr: 127.0.0.1:3306 # 本地开发默认;Docker bridge 模式可改为 mysql:3306
|
||||
Username: root # MySQL用户名
|
||||
Password: jpcV41ppanel # MySQL密码,与 .env MYSQL_ROOT_PASSWORD 一致
|
||||
Dbname: hifast # MySQL数据库名
|
||||
Password: CHANGE_ME_TO_DB_PASSWORD # MySQL密码
|
||||
Dbname: ppanel # MySQL数据库名
|
||||
Config: charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai
|
||||
MaxIdleConns: 10
|
||||
MaxOpenConns: 100
|
||||
@@ -42,9 +42,9 @@ Redis:
|
||||
|
||||
AppSignature:
|
||||
AppSecrets:
|
||||
android-client: uB4G,XxL2{7b # Android 客户端签名密钥
|
||||
ios-client: uB4G,XxL2{7b # iOS 客户端签名密钥
|
||||
web-client: uB4G,XxL2{7b # Web 客户端签名密钥
|
||||
android-client: CHANGE_ME_ANDROID_APP_SECRET # Android 客户端签名密钥
|
||||
ios-client: CHANGE_ME_IOS_APP_SECRET # iOS 客户端签名密钥
|
||||
web-client: CHANGE_ME_WEB_APP_SECRET # Web 客户端签名密钥
|
||||
ValidWindowSeconds: 300 # 签名时间窗口(秒)
|
||||
SkipPrefixes:
|
||||
- /v1/notify/ # 支付回调不验签
|
||||
@@ -58,8 +58,23 @@ Signature:
|
||||
Trace: # 链路追踪配置 (OpenTelemetry)
|
||||
Name: ppanel # 服务名
|
||||
Sampler: 1.0 # 采样率 0.0-1.0,生产建议 0.1
|
||||
Batcher: otlpgrpc # 本地开发留空""; 生产填 otlpgrpc
|
||||
Endpoint: "127.0.0.1:4317" # host 网络模式; bridge 模式改为 tempo:4317
|
||||
Batcher: "" # 本地开发留空;生产如需链路追踪再配置 exporter
|
||||
Endpoint: ""
|
||||
|
||||
S3:
|
||||
Enable: false
|
||||
Region: ""
|
||||
Bucket: ""
|
||||
Endpoint: ""
|
||||
AccessKey: ""
|
||||
SecretKey: ""
|
||||
SessionToken: ""
|
||||
Prefix: "app-upload"
|
||||
PublicBaseURL: ""
|
||||
UsePathStyle: false
|
||||
PresignExpireSeconds: 300
|
||||
MaxUploadSize: 104857600
|
||||
AllowedContentTypes: "application/zip,application/x-zip-compressed,application/gzip,application/x-gzip,application/octet-stream,text/plain,application/json,image/jpeg,image/jpg,image/png,image/webp,image/gif,image/heic,image/heif,image/bmp"
|
||||
|
||||
device:
|
||||
enable: true # 开启设备加密通信
|
||||
@@ -72,4 +87,4 @@ Administrator:
|
||||
Register:
|
||||
EnableTrial: true
|
||||
EnableTrialEmailWhitelist: true
|
||||
TrialEmailDomainWhitelist: "facebook.com,yahoo.com,qq.com,live.com,outlook.com,msn.com,example.com,go.com,aol.com,free.fr,aliyun.com,163.com,yandex.ru,indiatimes.com,alibaba.com,geocities.com,about.com,naver.com,netscape.com,yahoo.co.jp,earthlink.net,zoho.com,sky.com,mail.ru,angelfire.com,uol.com.br,spb.ru,yandex.com,globo.com,gmail.com,medscape.com,space.com,discovery.com,t-online.de,mac.com,icloud.com,homestead.com,lycos.com,web.id,nus.edu.sg,altavista.com,berlin.de,rambler.ru,pp.ua,comcast.net,sapo.pt,msk.ru,ancestry.com,daum.net,proton.me,law.com,bt.com,techspot.com,icq.com,sina.cn,libero.it,test.com,yourdomain.com,docomo.ne.jp,wp.pl,hotmail.com,orange.fr,onet.pl,me.com,india.com,kansascity.com,wanadoo.fr,fortunecity.com,web.de,terra.com.br,att.net,canada.com,skynet.be,ya.ru,excite.com,detik.com,compuserve.com,ig.com.br,zp.ua,gmx.net,xoom.com,mindspring.com,freeserve.co.uk,interia.pl,excite.co.jp,test.de,shaw.ca,virgilio.it,chez.com,rr.com,freenet.de,ntlworld.com,seznam.cz,arcor.de,tiscali.it,sympatico.ca,sina.com,gazeta.pl,care2.com,yam.com,r7.com,telenet.be,rcn.com,geek.com,sfr.fr,hotbot.com,cox.net,blueyonder.co.uk,tom.com,virginmedia.com,btinternet.com,iinet.net.au,rogers.com,ireland.com,pochta.ru,ozemail.com.au,catholic.org,bluewin.ch,chat.ru,virgin.net,verizon.net,erols.com,lycos.de,lycos.co.uk,protonmail.com,doityourself.com,home.nl,nate.com,casino.com,o2.co.uk,terra.es,mail.com,albawaba.com,126.com,www.com,planet.nl,sanook.com,21cn.com,online.de,name.com,i.ua,centrum.cz,rin.ru,aol.co.uk,voila.fr,walla.co.il,poste.it,netcom.com,parrot.com,charter.net,mydomain.com,mail-tester.com,myway.com,chello.nl,club-internet.fr,sdf.org,tiscali.co.uk,freeuk.com,unican.es,sci.fi,anonymize.com,sify.com,metacrawler.com,go.ro,ivillage.com,telus.net,dailypioneer.com,iespana.es,lycos.es,hey.com,sweb.cz,optusnet.com.au,alice.it,tpg.com.au,hamptonroads.com,saudia.com,lycos.nl,blackplanet.com,frontier.com,looksmart.com,pobox.com,prodigy.net,i.am,freeyellow.com,gmx.com,bigpond.com,crosswinds.net,dejanews.com,wanadoo.es,foxmail.com,eircom.net,islamonline.net,webindia123.com,oath.com,frontiernet.net,hetnet.nl,onmilwaukee.com,ukr.net,bugmenot.com,neuf.fr,kiwibox.com,za.com,iol.it,zonnet.nl,newmail.ru,pacbell.net,cogeco.ca,depechemode.com,concentric.net,aim.com,f5.si,yahoo.jp,terra.com,hot.ee,netzero.net,netins.net,sprynet.com,mailbox.org,mail2web.com,o2.pl,idirect.com,bigfoot.com,netspace.net.au,masrawy.com,supereva.it,yahoo.de,lycos.it,yeah.net,montevideo.com.uy,gmx.de,yahoo.co.uk,yahoofs.com,scubadiving.com,hushmail.com,iprimus.com.au,gportal.hu,swissinfo.org,inbox.com,bolt.com,telstra.com,bellsouth.net,spray.se,c3.hu,attbi.com,talktalk.co.uk,dynu.net,juno.com,yahoo.fr,msn.co.uk,fr.nf,pe.hu,bigpond.net.au,incredimail.com,adelphia.net,elvis.com,interfree.it,starmedia.com,seanet.com,yahoo.com.tw,zip.net,tds.net,she.com,forthnet.gr,land.ru,wow.com,dnsmadeeasy.com,webjump.com,singnet.com.sg,spacewar.com,tin.it,4mg.com,sp.nl,wowway.com,dmv.com,bangkok.com,fastmail.fm,sbcglobal.net,bright.net,usa.com,37.com,aeiou.pt,terra.cl,thirdage.com,btconnect.com,optimum.net,cableone.net,talkcity.com,blogos.com,c2i.net,iwon.com,aver.com,barcelona.com,ddnsfree.com,oi.com.br,lex.bg,roadrunner.com,airmail.net,lawyer.com,yahoo.com.cn,cu.cc,ananzi.co.za,au.ru,pipeline.com,cs.com,3ammagazine.com,gmx.at,qwest.net,btopenworld.com,easypost.com,westnet.com.au,nyc.com,korea.com,front.ru,inbox.lv,yahoo.com.br,ny.com,hispavista.com,abv.bg,mchsi.com,apollo.lv,everyone.net,terra.com.ar,singpost.com,doctor.com,garbage.com,bizhosting.com,go2net.com,clerk.com,games.com,charm.net,onlinehome.de,laposte.net" # 填你的白名单域名,逗号分隔
|
||||
TrialEmailDomainWhitelist: "facebook.com,yahoo.com,qq.com,live.com,outlook.com,msn.com,example.com,go.com,aol.com,free.fr,aliyun.com,163.com,yandex.ru,indiatimes.com,alibaba.com,geocities.com,about.com,naver.com,netscape.com,yahoo.co.jp,earthlink.net,zoho.com,sky.com,mail.ru,angelfire.com,uol.com.br,spb.ru,yandex.com,globo.com,gmail.com,medscape.com,space.com,discovery.com,t-online.de,mac.com,icloud.com,homestead.com,lycos.com,web.id,nus.edu.sg,altavista.com,berlin.de,rambler.ru,pp.ua,comcast.net,sapo.pt,msk.ru,ancestry.com,daum.net,proton.me,law.com,bt.com,techspot.com,icq.com,sina.cn,libero.it,test.com,yourdomain.com,docomo.ne.jp,wp.pl,hotmail.com,orange.fr,onet.pl,me.com,india.com,kansascity.com,wanadoo.fr,fortunecity.com,web.de,terra.com.br,att.net,canada.com,skynet.be,ya.ru,excite.com,detik.com,compuserve.com,ig.com.br,zp.ua,gmx.net,xoom.com,mindspring.com,freeserve.co.uk,interia.pl,excite.co.jp,test.de,shaw.ca,virgilio.it,chez.com,rr.com,freenet.de,ntlworld.com,seznam.cz,arcor.de,tiscali.it,sympatico.ca,sina.com,gazeta.pl,care2.com,yam.com,r7.com,telenet.be,rcn.com,geek.com,sfr.fr,hotbot.com,cox.net,blueyonder.co.uk,tom.com,virginmedia.com,btinternet.com,iinet.net.au,rogers.com,ireland.com,pochta.ru,ozemail.com.au,catholic.org,bluewin.ch,chat.ru,virgin.net,verizon.net,erols.com,lycos.de,lycos.co.uk,protonmail.com,doityourself.com,home.nl,nate.com,casino.com,o2.co.uk,terra.es,mail.com,albawaba.com,126.com,www.com,planet.nl,sanook.com,21cn.com,online.de,name.com,i.ua,centrum.cz,rin.ru,aol.co.uk,voila.fr,walla.co.il,poste.it,netcom.com,parrot.com,charter.net,mydomain.com,mail-tester.com,myway.com,chello.nl,club-internet.fr,sdf.org,tiscali.co.uk,freeuk.com,unican.es,sci.fi,anonymize.com,sify.com,metacrawler.com,go.ro,ivillage.com,telus.net,dailypioneer.com,iespana.es,lycos.es,hey.com,sweb.cz,optusnet.com.au,alice.it,tpg.com.au,hamptonroads.com,saudia.com,lycos.nl,blackplanet.com,frontier.com,looksmart.com,pobox.com,prodigy.net,i.am,freeyellow.com,gmx.com,bigpond.com,crosswinds.net,dejanews.com,wanadoo.es,foxmail.com,eircom.net,islamonline.net,webindia123.com,oath.com,frontiernet.net,hetnet.nl,onmilwaukee.com,ukr.net,bugmenot.com,neuf.fr,kiwibox.com,za.com,iol.it,zonnet.nl,newmail.ru,pacbell.net,cogeco.ca,depechemode.com,concentric.net,aim.com,f5.si,yahoo.jp,terra.com,hot.ee,netzero.net,netins.net,sprynet.com,mailbox.org,mail2web.com,o2.pl,idirect.com,bigfoot.com,netspace.net.au,masrawy.com,supereva.it,yahoo.de,lycos.it,yeah.net,montevideo.com.uy,gmx.de,yahoo.co.uk,yahoofs.com,scubadiving.com,hushmail.com,iprimus.com.au,gportal.hu,swissinfo.org,inbox.com,bolt.com,telstra.com,bellsouth.net,spray.se,c3.hu,attbi.com,talktalk.co.uk,dynu.net,juno.com,yahoo.fr,msn.co.uk,fr.nf,pe.hu,bigpond.net.au,incredimail.com,adelphia.net,elvis.com,interfree.it,starmedia.com,seanet.com,yahoo.com.tw,zip.net,tds.net,she.com,forthnet.gr,land.ru,wow.com,dnsmadeeasy.com,webjump.com,singnet.com.sg,spacewar.com,tin.it,4mg.com,sp.nl,wowway.com,dmv.com,bangkok.com,fastmail.fm,sbcglobal.net,bright.net,usa.com,37.com,aeiou.pt,terra.cl,thirdage.com,btconnect.com,optimum.net,cableone.net,talkcity.com,blogos.com,c2i.net,iwon.com,aver.com,barcelona.com,ddnsfree.com,oi.com.br,lex.bg,roadrunner.com,airmail.net,lawyer.com,yahoo.com.cn,cu.cc,ananzi.co.za,au.ru,pipeline.com,cs.com,3ammagazine.com,gmx.at,qwest.net,btopenworld.com,easypost.com,westnet.com.au,nyc.com,korea.com,front.ru,inbox.lv,yahoo.com.br,ny.com,hispavista.com,abv.bg,mchsi.com,apollo.lv,everyone.net,terra.com.ar,singpost.com,doctor.com,garbage.com,bizhosting.com,go2net.com,clerk.com,games.com,charm.net,onlinehome.de,laposte.net" # 填你的白名单域名,逗号分隔
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
module github.com/perfect-panel/server
|
||||
|
||||
go 1.23.3
|
||||
go 1.24
|
||||
|
||||
require (
|
||||
github.com/GUAIK-ORG/go-snowflake v0.0.0-20200116064823-220c4260e85f
|
||||
github.com/alibabacloud-go/darabonba-openapi v0.1.18
|
||||
github.com/alibabacloud-go/dysmsapi-20170525/v2 v2.0.18
|
||||
github.com/alibabacloud-go/tea v1.2.2
|
||||
github.com/alicebob/miniredis/v2 v2.34.0
|
||||
github.com/anaskhan96/go-password-encoder v0.0.0-20201010210601-c765b799fd72
|
||||
github.com/andybalholm/brotli v1.1.1
|
||||
github.com/forgoer/openssl v1.6.0
|
||||
@@ -32,7 +31,6 @@ require (
|
||||
github.com/smartwalle/alipay/v3 v3.2.23
|
||||
github.com/spf13/cast v1.7.0 // indirect
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/stripe/stripe-go/v81 v81.1.0
|
||||
github.com/twilio/twilio-go v1.23.11
|
||||
go.opentelemetry.io/otel v1.29.0
|
||||
@@ -51,15 +49,20 @@ require (
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/driver/mysql v1.5.7
|
||||
gorm.io/gorm v1.30.0
|
||||
gorm.io/plugin/soft_delete v1.2.1
|
||||
k8s.io/apimachinery v0.31.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2
|
||||
github.com/Masterminds/sprig/v3 v3.3.0
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.7
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.17
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.16
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.101.0
|
||||
github.com/fatih/color v1.18.0
|
||||
github.com/goccy/go-json v0.10.4
|
||||
github.com/golang-migrate/migrate/v4 v4.18.2
|
||||
github.com/mojocn/base64Captcha v1.3.8
|
||||
github.com/oschwald/geoip2-golang v1.13.0
|
||||
github.com/spaolacci/murmur3 v1.1.0
|
||||
google.golang.org/grpc v1.64.1
|
||||
@@ -79,8 +82,22 @@ require (
|
||||
github.com/alibabacloud-go/tea-utils v1.4.5 // indirect
|
||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.7 // indirect
|
||||
github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
|
||||
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 // indirect
|
||||
github.com/alicebob/miniredis/v2 v2.35.0 // indirect
|
||||
github.com/aliyun/credentials-go v1.3.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.42.1 // indirect
|
||||
github.com/aws/smithy-go v1.25.1 // indirect
|
||||
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff // indirect
|
||||
github.com/bytedance/sonic v1.12.7 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.3 // indirect
|
||||
@@ -88,7 +105,6 @@ require (
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/clbanning/mxj/v2 v2.5.6 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/gin-contrib/sse v1.0.0 // indirect
|
||||
@@ -114,23 +130,19 @@ require (
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/mojocn/base64Captcha v1.3.8 // indirect
|
||||
github.com/openzipkin/zipkin-go v0.4.2 // indirect
|
||||
github.com/oschwald/maxminddb-golang v1.13.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/smartwalle/ncrypto v1.0.4 // indirect
|
||||
github.com/smartwalle/ngx v1.0.9 // indirect
|
||||
github.com/smartwalle/nsign v1.0.9 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
@@ -150,5 +162,4 @@ require (
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gorm.io/driver/sqlite v1.6.0 // indirect
|
||||
)
|
||||
|
||||
@@ -8,6 +8,8 @@ filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
|
||||
github.com/GUAIK-ORG/go-snowflake v0.0.0-20200116064823-220c4260e85f h1:RDkg3pyE1qGbBpRWmvSN9RNZC5nUrOaEPiEpEb8y2f0=
|
||||
github.com/GUAIK-ORG/go-snowflake v0.0.0-20200116064823-220c4260e85f/go.mod h1:zA7AF9RTfpluCfz0omI4t5KCMaWHUMicsZoMccnaT44=
|
||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
@@ -52,10 +54,8 @@ github.com/alibabacloud-go/tea-utils/v2 v2.0.7/go.mod h1:qxn986l+q33J5VkialKMqT/
|
||||
github.com/alibabacloud-go/tea-xml v1.1.2/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
|
||||
github.com/alibabacloud-go/tea-xml v1.1.3 h1:7LYnm+JbOq2B+T/B0fHC4Ies4/FofC4zHzYtqw7dgt0=
|
||||
github.com/alibabacloud-go/tea-xml v1.1.3/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
|
||||
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE=
|
||||
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||
github.com/alicebob/miniredis/v2 v2.34.0 h1:mBFWMaJSNL9RwdGRyEDoAAv8OQc5UlEhLDQggTglU/0=
|
||||
github.com/alicebob/miniredis/v2 v2.34.0/go.mod h1:kWShP4b58T1CW0Y5dViCd5ztzrDqRWqM3nksiyXk5s8=
|
||||
github.com/alicebob/miniredis/v2 v2.35.0 h1:QwLphYqCEAo1eu1TqPRN2jgVMPBweeQcR21jeqDCONI=
|
||||
github.com/alicebob/miniredis/v2 v2.35.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM=
|
||||
github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw=
|
||||
github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM=
|
||||
github.com/aliyun/credentials-go v1.3.10 h1:45Xxrae/evfzQL9V10zL3xX31eqgLWEaIdCoPipOEQA=
|
||||
@@ -64,6 +64,42 @@ github.com/anaskhan96/go-password-encoder v0.0.0-20201010210601-c765b799fd72 h1:
|
||||
github.com/anaskhan96/go-password-encoder v0.0.0-20201010210601-c765b799fd72/go.mod h1:PsJICrlruG9QcJDYuZ0dO/2KtMDALzRbony8NkxZ2nE=
|
||||
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.7 h1:DWpAJt66FmnnaRIOT/8ASTucrvuDPZASqhhLey6tLY8=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.7/go.mod h1:4LAfZOPHNVNQEckOACQx60Y8pSRjIkNZQz1w92xpMJc=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10 h1:gx1AwW1Iyk9Z9dD9F4akX5gnN3QZwUB20GGKH/I+Rho=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10/go.mod h1:qqY157uZoqm5OXq/amuaBJyC9hgBCBQnsaWnPe905GY=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.17 h1:FpL4/758/diKwqbytU0prpuiu60fgXKUWCpDJtApclU=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.17/go.mod h1:OXqUMzgXytfoF9JaKkhrOYsyh72t9G+MJH8mMRaexOE=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.16 h1:r3RJBuU7X9ibt8RHbMjWE6y60QbKBiII6wSrXnapxSU=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.16/go.mod h1:6cx7zqDENJDbBIIWX6P8s0h6hqHC8Avbjh9Dseo27ug=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23 h1:UuSfcORqNSz/ey3VPRS8TcVH2Ikf0/sC+Hdj400QI6U=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23/go.mod h1:+G/OSGiOFnSOkYloKj/9M35s74LgVAdJBSD5lsFfqKg=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 h1:GpT/TrnBYuE5gan2cZbTtvP+JlHsutdmlV2YfEyNde0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23/go.mod h1:xYWD6BS9ywC5bS3sz9Xh04whO/hzK2plt2Zkyrp4JuA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 h1:bpd8vxhlQi2r1hiueOw02f/duEPTMK59Q4QMAoTTtTo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23/go.mod h1:15DfR2nw+CRHIk0tqNyifu3G1YdAOy68RftkhMDDwYk=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 h1:OQqn11BtaYv1WLUowvcA30MpzIu8Ti4pcLPIIyoKZrA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24/go.mod h1:X5ZJyfwVrWA96GzPmUCWFQaEARPR7gCrpq2E92PJwAE=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 h1:FLudkZLt5ci0ozzgkVo8BJGwvqNaZbTWb3UcucAateA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9/go.mod h1:w7wZ/s9qK7c8g4al+UyoF1Sp/Z45UwMGcqIzLWVQHWk=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15 h1:ieLCO1JxUWuxTZ1cRd0GAaeX7O6cIxnwk7tc1LsQhC4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15/go.mod h1:e3IzZvQ3kAWNykvE0Tr0RDZCMFInMvhku3qNpcIQXhM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23 h1:pbrxO/kuIwgEsOPLkaHu0O+m4fNgLU8B3vxQ+72jTPw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23/go.mod h1:/CMNUqoj46HpS3MNRDEDIwcgEnrtZlKRaHNaHxIFpNA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23 h1:03xatSQO4+AM1lTAbnRg5OK528EUg744nW7F73U8DKw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23/go.mod h1:M8l3mwgx5ToK7wot2sBBce/ojzgnPzZXUV445gTSyE8=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.101.0 h1:etqBTKY581iwLL/H/S2sVgk3C9lAsTJFeXWFDsDcWOU=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.101.0/go.mod h1:L2dcoOgS2VSgbPLvpak2NyUPsO1TBN7M45Z4H7DlRc4=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.11 h1:TdJ+HdzOBhU8+iVAOGUTU63VXopcumCOF1paFulHWZc=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.11/go.mod h1:R82ZRExE/nheo0N+T8zHPcLRTcH8MGsnR3BiVGX0TwI=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.17 h1:7byT8HUWrgoRp6sXjxtZwgOKfhss5fW6SkLBtqzgRoE=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.17/go.mod h1:xNWknVi4Ezm1vg1QsB/5EWpAJURq22uqd38U8qKvOJc=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21 h1:+1Kl1zx6bWi4X7cKi3VYh29h8BvsCoHQEQ6ST9X8w7w=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21/go.mod h1:4vIRDq+CJB2xFAXZ+YgGUTiEft7oAQlhIs71xcSeuVg=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.42.1 h1:F/M5Y9I3nwr2IEpshZgh1GeHpOItExNM9L1euNuh/fk=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.42.1/go.mod h1:mTNxImtovCOEEuD65mKW7DCsL+2gjEH+RPEAexAzAio=
|
||||
github.com/aws/smithy-go v1.25.1 h1:J8ERsGSU7d+aCmdQur5Txg6bVoYelvQJgtZehD12GkI=
|
||||
github.com/aws/smithy-go v1.25.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
||||
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
|
||||
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff h1:RmdPFa+slIr4SCBg4st/l/vZWVe9QJKMXGO60Bxbe04=
|
||||
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw=
|
||||
@@ -226,14 +262,13 @@ github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
||||
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
|
||||
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
|
||||
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
@@ -258,9 +293,6 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
@@ -578,20 +610,9 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
|
||||
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
|
||||
gorm.io/driver/sqlite v1.1.3/go.mod h1:AKDgRWk8lcSQSw+9kxCJnX/yySj8G3rdwYlU57cB45c=
|
||||
gorm.io/driver/sqlite v1.4.4 h1:gIufGoR0dQzjkyqDyYSCvsYR6fba1Gw5YKDqKeChxFc=
|
||||
gorm.io/driver/sqlite v1.4.4/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
|
||||
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
|
||||
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
|
||||
gorm.io/gorm v1.20.1/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
|
||||
gorm.io/gorm v1.23.0/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
||||
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
|
||||
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||
gorm.io/plugin/soft_delete v1.2.1 h1:qx9D/c4Xu6w5KT8LviX8DgLcB9hkKl6JC9f44Tj7cGU=
|
||||
gorm.io/plugin/soft_delete v1.2.1/go.mod h1:Zv7vQctOJTGOsJ/bWgrN1n3od0GBAZgnLjEx+cApLGk=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
k8s.io/apimachinery v0.31.1 h1:mhcUBbj7KUjaVhyXILglcVjuS4nYXiwC+KKFBgIVy7U=
|
||||
|
||||
@@ -449,7 +449,7 @@ CREATE TABLE IF NOT EXISTS `user_device`
|
||||
`subscribe_id` bigint DEFAULT NULL COMMENT 'Subscribe ID',
|
||||
`ip` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Device Ip.',
|
||||
`Identifier` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Device Identifier.',
|
||||
`user_agent` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Device User Agent.',
|
||||
`user_agent` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Device User Agent.',
|
||||
`online` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Online',
|
||||
`enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'EnableDeviceNumber',
|
||||
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
-- Remove app_account_token column from order table if it exists
|
||||
SET @col_exists = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'order' AND COLUMN_NAME = 'app_account_token');
|
||||
SET @sql = IF(@col_exists = 1, 'ALTER TABLE `order` DROP COLUMN `app_account_token`', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- Remove subscription_user_id column from order table if it exists
|
||||
SET @col_exists2 = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'order' AND COLUMN_NAME = 'subscription_user_id');
|
||||
SET @sql2 = IF(@col_exists2 = 1, 'ALTER TABLE `order` DROP COLUMN `subscription_user_id`', 'SELECT 1');
|
||||
PREPARE stmt2 FROM @sql2;
|
||||
EXECUTE stmt2;
|
||||
DEALLOCATE PREPARE stmt2;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE `user_device` DROP COLUMN `base_payload`;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE `user_device` ADD COLUMN `base_payload` TEXT DEFAULT NULL COMMENT 'Base Payload' AFTER `short_code`;
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE IF EXISTS `order_recovery_claims`;
|
||||
@@ -0,0 +1,15 @@
|
||||
CREATE TABLE IF NOT EXISTS `order_recovery_claims` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`order_no` VARCHAR(255) NOT NULL COMMENT 'Recovered Order No',
|
||||
`email` VARCHAR(255) NOT NULL COMMENT 'Claim Email',
|
||||
`user_id` BIGINT NOT NULL DEFAULT 0 COMMENT 'User ID',
|
||||
`subscribe_id` BIGINT NOT NULL DEFAULT 0 COMMENT 'Subscribe ID',
|
||||
`order_id` BIGINT NOT NULL DEFAULT 0 COMMENT 'Recovered Order ID',
|
||||
`user_subscribe_id` BIGINT NOT NULL DEFAULT 0 COMMENT 'User Subscribe ID',
|
||||
`claimed_at` DATETIME(3) NOT NULL COMMENT 'Claimed Time',
|
||||
`created_at` DATETIME(3) DEFAULT NULL COMMENT 'Creation Time',
|
||||
`updated_at` DATETIME(3) DEFAULT NULL COMMENT 'Update Time',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `idx_order_recovery_claim_order_no` (`order_no`),
|
||||
KEY `idx_order_recovery_claim_email` (`email`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
@@ -0,0 +1,7 @@
|
||||
ALTER TABLE `log_message`
|
||||
MODIFY COLUMN `app_version` VARCHAR(32) NULL,
|
||||
MODIFY COLUMN `os_name` VARCHAR(32) NULL,
|
||||
MODIFY COLUMN `os_version` VARCHAR(32) NULL,
|
||||
MODIFY COLUMN `device_id` VARCHAR(64) NULL,
|
||||
MODIFY COLUMN `session_id` VARCHAR(64) NULL,
|
||||
MODIFY COLUMN `error_code` VARCHAR(64) NULL;
|
||||
@@ -0,0 +1,7 @@
|
||||
ALTER TABLE `log_message`
|
||||
MODIFY COLUMN `app_version` VARCHAR(64) NULL,
|
||||
MODIFY COLUMN `os_name` VARCHAR(64) NULL,
|
||||
MODIFY COLUMN `os_version` VARCHAR(64) NULL,
|
||||
MODIFY COLUMN `device_id` VARCHAR(255) NULL,
|
||||
MODIFY COLUMN `session_id` VARCHAR(255) NULL,
|
||||
MODIFY COLUMN `error_code` VARCHAR(128) NULL;
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE `user_device`
|
||||
MODIFY COLUMN `user_agent` VARCHAR(64) NULL COMMENT 'Device User Agent.';
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE `user_device`
|
||||
MODIFY COLUMN `user_agent` VARCHAR(255) NULL COMMENT 'Device User Agent.';
|
||||
@@ -0,0 +1,19 @@
|
||||
-- Rollback: re-deduct commission for users with pending (status=0) withdrawals.
|
||||
-- This re-applies the OLD behaviour where commission is deducted on application.
|
||||
-- Only run this if you are rolling back to the old code; do NOT run against
|
||||
-- the new code or commission will be double-deducted on approval.
|
||||
|
||||
UPDATE `user` u
|
||||
JOIN (
|
||||
SELECT user_id, COALESCE(SUM(amount), 0) AS pending_total
|
||||
FROM withdrawals
|
||||
WHERE status = 0
|
||||
GROUP BY user_id
|
||||
) p ON u.id = p.user_id
|
||||
SET u.commission = u.commission - p.pending_total
|
||||
WHERE p.pending_total > 0;
|
||||
|
||||
-- Remove the migration log entries written by the up migration.
|
||||
DELETE FROM system_logs
|
||||
WHERE type = 33
|
||||
AND content LIKE '%migration: refund pending withdrawal commission (HIF-22)%';
|
||||
@@ -0,0 +1,65 @@
|
||||
-- Migration: refund commission for existing pending (status=0) withdrawals
|
||||
--
|
||||
-- Under the old logic, commission was deducted when a withdrawal was submitted.
|
||||
-- Under the new logic, commission is only deducted on approval.
|
||||
-- This migration refunds the deducted amounts back to each user so that
|
||||
-- the system is in a consistent state before the new code is deployed.
|
||||
--
|
||||
-- Idempotency: the UPDATE only touches rows whose commission would need
|
||||
-- to increase, and each execution produces the same result because
|
||||
-- COALESCE(SUM(amount),0) is deterministic given the same pending set.
|
||||
-- Running this script multiple times is safe only if no new pending
|
||||
-- withdrawals are created between runs; deploy new code immediately after.
|
||||
|
||||
-- Compatibility: some historical databases missed migration 02122, so the
|
||||
-- withdrawals table may not exist yet. Create it idempotently before the
|
||||
-- refund logic so this migration can self-heal older installations.
|
||||
CREATE TABLE IF NOT EXISTS `withdrawals` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT 'Primary Key',
|
||||
`user_id` BIGINT NOT NULL COMMENT 'User ID',
|
||||
`amount` BIGINT NOT NULL COMMENT 'Withdrawal Amount',
|
||||
`content` TEXT COMMENT 'Withdrawal Content',
|
||||
`status` TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'Withdrawal Status',
|
||||
`reason` VARCHAR(500) NOT NULL DEFAULT '' COMMENT 'Rejection Reason',
|
||||
`created_at` DATETIME NOT NULL COMMENT 'Creation Time',
|
||||
`updated_at` DATETIME NOT NULL COMMENT 'Update Time',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_user_id` (`user_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
INSERT IGNORE INTO `system` (`category`, `key`, `value`, `type`, `desc`, `created_at`, `updated_at`)
|
||||
VALUES
|
||||
('invite', 'WithdrawalMethod', '', 'string', 'withdrawal method', '2025-04-22 14:25:16.637', '2025-04-22 14:25:16.637');
|
||||
|
||||
-- Step 1: refund commission for all users with pending withdrawals.
|
||||
UPDATE `user` u
|
||||
JOIN (
|
||||
SELECT user_id, COALESCE(SUM(amount), 0) AS pending_total
|
||||
FROM withdrawals
|
||||
WHERE status = 0
|
||||
GROUP BY user_id
|
||||
) p ON u.id = p.user_id
|
||||
SET u.commission = u.commission + p.pending_total
|
||||
WHERE p.pending_total > 0;
|
||||
|
||||
-- Step 2: write a migration log entry for each refunded user.
|
||||
INSERT INTO system_logs (type, date, object_id, content, created_at)
|
||||
SELECT
|
||||
33 AS type,
|
||||
DATE(NOW()) AS date,
|
||||
p.user_id AS object_id,
|
||||
JSON_OBJECT(
|
||||
'type', 99,
|
||||
'amount', p.pending_total,
|
||||
'order_no', '',
|
||||
'timestamp', UNIX_TIMESTAMP(NOW()) * 1000,
|
||||
'note', 'migration: refund pending withdrawal commission (HIF-22)'
|
||||
) AS content,
|
||||
NOW() AS created_at
|
||||
FROM (
|
||||
SELECT user_id, COALESCE(SUM(amount), 0) AS pending_total
|
||||
FROM withdrawals
|
||||
WHERE status = 0
|
||||
GROUP BY user_id
|
||||
HAVING pending_total > 0
|
||||
) p;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Remove activation_context column from order table
|
||||
ALTER TABLE `order` DROP COLUMN IF EXISTS `activation_context`;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user