feat: 初始化 SCALE OS 工程框架
- 添加 SCALE Engine 配置 (.scale/) - 添加 OpenClaw Agent 配置 (.openclaw/) - 添加知识文档 (AGENTS.md, TOOLS.md) - 添加质量契约和工作流配置 - 添加 22 个工作流模板 - 添加验证脚本和门控脚本 - 添加 skills-registry 技能注册表
This commit is contained in:
54
scripts/gates/all.sh
Executable file
54
scripts/gates/all.sh
Executable file
@@ -0,0 +1,54 @@
|
||||
#!/bin/bash
|
||||
# scripts/gates/all.sh — 运行所有门控检查
|
||||
set -e
|
||||
source ~/.cargo/env 2>/dev/null || true
|
||||
cd "$(dirname "$0")/../.."
|
||||
|
||||
DRY_RUN=false
|
||||
if [ "$1" = "--dry-run" ]; then
|
||||
DRY_RUN=true
|
||||
echo "🔒 门控检查 (dry-run 模式)"
|
||||
else
|
||||
echo "🔒 运行门控检查..."
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
run_gate() {
|
||||
local name="$1"
|
||||
local cmd="$2"
|
||||
|
||||
if [ "$DRY_RUN" = "true" ]; then
|
||||
echo " ⏭️ $name (跳过, dry-run)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo " 🔍 $name..."
|
||||
if eval "$cmd" > /dev/null 2>&1; then
|
||||
echo " ✅ $name 通过"
|
||||
return 0
|
||||
else
|
||||
echo " ❌ $name 失败"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
FAILED=0
|
||||
|
||||
run_gate "类型检查 (cargo check)" "cargo check" || FAILED=$((FAILED + 1))
|
||||
run_gate "Lint (cargo clippy)" "cargo clippy -- -D warnings" || FAILED=$((FAILED + 1))
|
||||
run_gate "测试 (cargo test)" "cargo test" || FAILED=$((FAILED + 1))
|
||||
|
||||
if [ "$DRY_RUN" = "false" ]; then
|
||||
run_gate "Release 构建" "cargo build --release" || FAILED=$((FAILED + 1))
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "────────────────────────────────────"
|
||||
if [ $FAILED -gt 0 ]; then
|
||||
echo "❌ $FAILED 个门控失败"
|
||||
exit 1
|
||||
else
|
||||
echo "✅ 所有门控通过"
|
||||
exit 0
|
||||
fi
|
||||
107
scripts/qa/product-smoke.ps1
Normal file
107
scripts/qa/product-smoke.ps1
Normal file
@@ -0,0 +1,107 @@
|
||||
# Product smoke probe runner generated by scale-engine.
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$Root = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).Path
|
||||
$ConfigPath = Join-Path $Root ".scale\product-smoke.json"
|
||||
$LogDir = Join-Path $Root ".agent\logs"
|
||||
$LogPath = Join-Path $LogDir "product-smoke.json"
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $LogDir | Out-Null
|
||||
|
||||
$NodeProgram = @'
|
||||
const fs = require('fs');
|
||||
const cp = require('child_process');
|
||||
const path = require('path');
|
||||
|
||||
const configPath = process.argv[2];
|
||||
const logPath = process.argv[3];
|
||||
|
||||
function writeReport(report) {
|
||||
fs.mkdirSync(path.dirname(logPath), { recursive: true });
|
||||
fs.writeFileSync(logPath, JSON.stringify(report, null, 2) + '\n', 'utf8');
|
||||
process.stdout.write(JSON.stringify(report, null, 2) + '\n');
|
||||
}
|
||||
|
||||
if (!fs.existsSync(configPath)) {
|
||||
writeReport({
|
||||
version: 1,
|
||||
status: 'failed',
|
||||
verifiedAt: new Date().toISOString(),
|
||||
message: 'Missing .scale/product-smoke.json',
|
||||
results: []
|
||||
});
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const config = JSON.parse(fs.readFileSync(configPath, 'utf8').replace(/^\uFEFF/, ''));
|
||||
const probes = Array.isArray(config.probes) ? config.probes.filter(probe => probe && probe.enabled === true) : [];
|
||||
|
||||
if (probes.length === 0) {
|
||||
const status = config.emptyProbeBehavior === 'block' ? 'failed' : 'skipped';
|
||||
writeReport({
|
||||
version: 1,
|
||||
status,
|
||||
verifiedAt: new Date().toISOString(),
|
||||
message: 'No enabled product smoke probes. Enable probes in .scale/product-smoke.json after defining the real product path.',
|
||||
results: []
|
||||
});
|
||||
process.exit(status === 'failed' ? 1 : 0);
|
||||
}
|
||||
|
||||
const results = probes.map((probe) => {
|
||||
const startedAt = new Date().toISOString();
|
||||
const expectedExitCode = Number.isInteger(probe.expected && probe.expected.exitCode) ? probe.expected.exitCode : 0;
|
||||
const command = String(probe.command || '');
|
||||
if (!command.trim()) {
|
||||
return {
|
||||
id: String(probe.id || 'unnamed-probe'),
|
||||
description: String(probe.description || ''),
|
||||
command,
|
||||
expectedExitCode,
|
||||
exitCode: 1,
|
||||
status: 'failed',
|
||||
startedAt,
|
||||
endedAt: new Date().toISOString(),
|
||||
outputTail: 'Probe command is empty'
|
||||
};
|
||||
}
|
||||
const result = cp.spawnSync(command, {
|
||||
cwd: process.cwd(),
|
||||
shell: true,
|
||||
encoding: 'utf8',
|
||||
timeout: Number(config.timeoutMs || 180000)
|
||||
});
|
||||
const output = String(result.stdout || '') + String(result.stderr || '') + String(result.error ? result.error.message : '');
|
||||
const exitCode = typeof result.status === 'number' ? result.status : 1;
|
||||
return {
|
||||
id: String(probe.id || 'unnamed-probe'),
|
||||
description: String(probe.description || ''),
|
||||
command,
|
||||
expectedExitCode,
|
||||
exitCode,
|
||||
status: exitCode === expectedExitCode ? 'passed' : 'failed',
|
||||
startedAt,
|
||||
endedAt: new Date().toISOString(),
|
||||
outputTail: output.length > 2000 ? output.slice(-2000) : output
|
||||
};
|
||||
});
|
||||
|
||||
const failed = results.filter(result => result.status !== 'passed');
|
||||
writeReport({
|
||||
version: 1,
|
||||
status: failed.length === 0 ? 'passed' : 'failed',
|
||||
verifiedAt: new Date().toISOString(),
|
||||
results
|
||||
});
|
||||
process.exit(failed.length === 0 ? 0 : 1);
|
||||
|
||||
'@
|
||||
|
||||
$TempFile = [System.IO.Path]::GetTempFileName() + ".js"
|
||||
Set-Content -Path $TempFile -Value $NodeProgram -Encoding UTF8
|
||||
try {
|
||||
node $TempFile $ConfigPath $LogPath
|
||||
exit $LASTEXITCODE
|
||||
} finally {
|
||||
Remove-Item -Force $TempFile -ErrorAction SilentlyContinue
|
||||
}
|
||||
98
scripts/qa/product-smoke.sh
Executable file
98
scripts/qa/product-smoke.sh
Executable file
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
CONFIG_PATH="$ROOT/.scale/product-smoke.json"
|
||||
LOG_DIR="$ROOT/.agent/logs"
|
||||
LOG_PATH="$LOG_DIR/product-smoke.json"
|
||||
|
||||
mkdir -p "$LOG_DIR"
|
||||
|
||||
node - "$CONFIG_PATH" "$LOG_PATH" <<'NODE'
|
||||
const fs = require('fs');
|
||||
const cp = require('child_process');
|
||||
const path = require('path');
|
||||
|
||||
const configPath = process.argv[2];
|
||||
const logPath = process.argv[3];
|
||||
|
||||
function writeReport(report) {
|
||||
fs.mkdirSync(path.dirname(logPath), { recursive: true });
|
||||
fs.writeFileSync(logPath, JSON.stringify(report, null, 2) + '\n', 'utf8');
|
||||
process.stdout.write(JSON.stringify(report, null, 2) + '\n');
|
||||
}
|
||||
|
||||
if (!fs.existsSync(configPath)) {
|
||||
writeReport({
|
||||
version: 1,
|
||||
status: 'failed',
|
||||
verifiedAt: new Date().toISOString(),
|
||||
message: 'Missing .scale/product-smoke.json',
|
||||
results: []
|
||||
});
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const config = JSON.parse(fs.readFileSync(configPath, 'utf8').replace(/^\uFEFF/, ''));
|
||||
const probes = Array.isArray(config.probes) ? config.probes.filter(probe => probe && probe.enabled === true) : [];
|
||||
|
||||
if (probes.length === 0) {
|
||||
const status = config.emptyProbeBehavior === 'block' ? 'failed' : 'skipped';
|
||||
writeReport({
|
||||
version: 1,
|
||||
status,
|
||||
verifiedAt: new Date().toISOString(),
|
||||
message: 'No enabled product smoke probes. Enable probes in .scale/product-smoke.json after defining the real product path.',
|
||||
results: []
|
||||
});
|
||||
process.exit(status === 'failed' ? 1 : 0);
|
||||
}
|
||||
|
||||
const results = probes.map((probe) => {
|
||||
const startedAt = new Date().toISOString();
|
||||
const expectedExitCode = Number.isInteger(probe.expected && probe.expected.exitCode) ? probe.expected.exitCode : 0;
|
||||
const command = String(probe.command || '');
|
||||
if (!command.trim()) {
|
||||
return {
|
||||
id: String(probe.id || 'unnamed-probe'),
|
||||
description: String(probe.description || ''),
|
||||
command,
|
||||
expectedExitCode,
|
||||
exitCode: 1,
|
||||
status: 'failed',
|
||||
startedAt,
|
||||
endedAt: new Date().toISOString(),
|
||||
outputTail: 'Probe command is empty'
|
||||
};
|
||||
}
|
||||
const result = cp.spawnSync(command, {
|
||||
cwd: process.cwd(),
|
||||
shell: true,
|
||||
encoding: 'utf8',
|
||||
timeout: Number(config.timeoutMs || 180000)
|
||||
});
|
||||
const output = String(result.stdout || '') + String(result.stderr || '') + String(result.error ? result.error.message : '');
|
||||
const exitCode = typeof result.status === 'number' ? result.status : 1;
|
||||
return {
|
||||
id: String(probe.id || 'unnamed-probe'),
|
||||
description: String(probe.description || ''),
|
||||
command,
|
||||
expectedExitCode,
|
||||
exitCode,
|
||||
status: exitCode === expectedExitCode ? 'passed' : 'failed',
|
||||
startedAt,
|
||||
endedAt: new Date().toISOString(),
|
||||
outputTail: output.length > 2000 ? output.slice(-2000) : output
|
||||
};
|
||||
});
|
||||
|
||||
const failed = results.filter(result => result.status !== 'passed');
|
||||
writeReport({
|
||||
version: 1,
|
||||
status: failed.length === 0 ? 'passed' : 'failed',
|
||||
verifiedAt: new Date().toISOString(),
|
||||
results
|
||||
});
|
||||
process.exit(failed.length === 0 ? 0 : 1);
|
||||
|
||||
NODE
|
||||
8
scripts/tests/run.sh
Executable file
8
scripts/tests/run.sh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
# scripts/tests/run.sh — 运行测试
|
||||
set -e
|
||||
echo "🧪 运行测试..."
|
||||
source ~/.cargo/env 2>/dev/null || true
|
||||
cd "$(dirname "$0")/../.."
|
||||
cargo test 2>&1
|
||||
echo "✅ 测试完成"
|
||||
65
scripts/validate-config.sh
Executable file
65
scripts/validate-config.sh
Executable file
@@ -0,0 +1,65 @@
|
||||
#!/bin/bash
|
||||
# validate-config.sh — 验证 SCALE OS 配置完整性
|
||||
set -e
|
||||
|
||||
echo "🔍 验证 SCALE OS 配置..."
|
||||
echo ""
|
||||
|
||||
PASS=0
|
||||
FAIL=0
|
||||
WARN=0
|
||||
|
||||
check() {
|
||||
local name="$1"
|
||||
local path="$2"
|
||||
local required="$3"
|
||||
|
||||
if [ -e "$path" ]; then
|
||||
echo " ✅ $name"
|
||||
PASS=$((PASS + 1))
|
||||
elif [ "$required" = "true" ]; then
|
||||
echo " ❌ $name (缺失: $path)"
|
||||
FAIL=$((FAIL + 1))
|
||||
else
|
||||
echo " ⚠️ $name (可选, 缺失: $path)"
|
||||
WARN=$((WARN + 1))
|
||||
fi
|
||||
}
|
||||
|
||||
echo "📁 项目级文件:"
|
||||
check "AGENTS.md" "AGENTS.md" "true"
|
||||
check "TOOLS.md" "TOOLS.md" "true"
|
||||
check ".openclaw/settings.json" ".openclaw/settings.json" "true"
|
||||
|
||||
echo ""
|
||||
echo "📁 SCALE 配置:"
|
||||
check ".scale/ 目录" ".scale" "true"
|
||||
check "workflow.json" ".scale/workflow.json" "true"
|
||||
check "quality-contract.json" ".scale/quality-contract.json" "true"
|
||||
check "skills-registry.json" ".scale/skills-registry.json" "true"
|
||||
check "verification.json" ".scale/verification.json" "true"
|
||||
check "skills.json" ".scale/skills.json" "true"
|
||||
check "tools.json" ".scale/tools.json" "true"
|
||||
check ".gitignore" ".scale/.gitignore" "true"
|
||||
|
||||
echo ""
|
||||
echo "📁 工作流模板:"
|
||||
check "docs/workflow/ 目录" "docs/workflow" "true"
|
||||
check "QUALITY_CONTRACT.md" "docs/workflow/QUALITY_CONTRACT.md" "true"
|
||||
|
||||
echo ""
|
||||
echo "📁 验证脚本:"
|
||||
check "scripts/validate-config.sh" "scripts/validate-config.sh" "true"
|
||||
check "scripts/qa/product-smoke.sh" "scripts/qa/product-smoke.sh" "true"
|
||||
|
||||
echo ""
|
||||
echo "────────────────────────────────────"
|
||||
echo "结果: ✅ $PASS 通过 | ❌ $FAIL 失败 | ⚠️ $WARN 警告"
|
||||
|
||||
if [ $FAIL -gt 0 ]; then
|
||||
echo "❌ 配置验证失败"
|
||||
exit 1
|
||||
else
|
||||
echo "✅ 配置验证通过"
|
||||
exit 0
|
||||
fi
|
||||
29
scripts/workflow/verify.sh
Executable file
29
scripts/workflow/verify.sh
Executable file
@@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
# scripts/workflow/verify.sh — 验证工作流配置
|
||||
set -e
|
||||
cd "$(dirname "$0")/../.."
|
||||
|
||||
PROFILE="${1:-default}"
|
||||
echo "📋 验证工作流 (profile: $PROFILE)..."
|
||||
|
||||
# 检查 workflow.json
|
||||
if [ ! -f ".scale/workflow.json" ]; then
|
||||
echo "❌ 缺少 .scale/workflow.json"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查 quality-contract.json
|
||||
if [ ! -f ".scale/quality-contract.json" ]; then
|
||||
echo "❌ 缺少 .scale/quality-contract.json"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查模板文件
|
||||
TEMPLATE_COUNT=$(ls docs/workflow/templates/*.md 2>/dev/null | wc -l)
|
||||
echo " 📄 工作流模板: $TEMPLATE_COUNT 个"
|
||||
|
||||
# 验证 JSON 格式
|
||||
python3 -c "import json; json.load(open('.scale/workflow.json')); print(' ✅ workflow.json 格式正确')"
|
||||
python3 -c "import json; json.load(open('.scale/quality-contract.json')); print(' ✅ quality-contract.json 格式正确')"
|
||||
|
||||
echo "✅ 工作流验证通过"
|
||||
Reference in New Issue
Block a user