chore: import upstream snapshot with attribution
ADR-166 MCP Bridge Security Lock / Static-source security lock (push) Failing after 0s
ADR-166 MCP Bridge Security Lock / Compose default binds loopback + Mongo has auth (push) Failing after 2s
CodeQL Advanced / Analyze (rust) (push) Failing after 0s
ADR-166 MCP Bridge Security Lock / plugin-agent-federation bindHost default (push) Failing after 1s
ADR-166 MCP Bridge Security Lock / Runtime behavior — 401 + terminal gate + fail-closed (push) Failing after 4s
business-pods-smoke / smoke (push) Failing after 1s
all-plugins-smoke / smoke-all (push) Failing after 2s
CI/CD Pipeline / Security & Code Quality (push) Failing after 1s
CI/CD Pipeline / Test Suite (ubuntu-latest) (push) Failing after 1s
CI/CD Pipeline / Build & Package (macos-latest) (push) Has been skipped
CI/CD Pipeline / Build & Package (ubuntu-latest) (push) Has been skipped
CI/CD Pipeline / Build & Package (windows-latest) (push) Has been skipped
CI/CD Pipeline / Documentation & Examples (push) Failing after 1s
Clone Tracker (14-day rolling) / Snapshot clones for ruflo ecosystem (push) Failing after 1s
CodeQL Advanced / Analyze (actions) (push) Failing after 1s
CodeQL Advanced / Analyze (javascript-typescript) (push) Failing after 1s
federation-peer-rust / stable-noop (push) Failing after 1s
metaharness-ci / score (push) Failing after 1s
metaharness-ci / router-compat (push) Failing after 0s
metaharness-ci / similarity-tests (push) Failing after 0s
no-agentbbs-smoke / smoke-without-agentbbs (push) Failing after 1s
V3 CI/CD Pipeline / Build V3 (windows-latest) (push) Has been skipped
codex-integration-audit / Codex integration audit (push) Failing after 1s
helpers-manifest-guard / guard (push) Failing after 1s
🔗 Cross-Agent Integration Tests / 🤝 Agent Coordination Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / 🧠 Memory Sharing Integration (push) Has been skipped
🔗 Cross-Agent Integration Tests / 🛡️ Fault Tolerance Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / ⚡ Performance Integration Tests (push) Has been skipped
metaharness-ci / mcp-scan (push) Failing after 1s
metaharness-ci / eject-dryrun (push) Failing after 1s
metaharness-ci / metaharness-real-data (push) Failing after 0s
no-cli-optdep-bloat-2561 / guard (push) Failing after 1s
no-metaharness-smoke / smoke-without-metaharness (push) Failing after 1s
no-phantom-agentic-flow-subpath / guard (push) Failing after 1s
🔄 Automated Rollback Manager / 🚨 Failure Detection (push) Failing after 1s
V3 CI/CD Pipeline / Plugin hooks smoke / ubuntu-latest / Node 22 (push) Failing after 1s
V3 CI/CD Pipeline / ruflo-graph-intelligence build + test smoke (#2044, ADR-123) (push) Failing after 1s
CVE Audit Gate / Audit root (critical-blocking) (push) Failing after 2s
cost-tracker-smoke / smoke (push) Failing after 3s
oia-audit-weekly / audit (push) Failing after 2s
ruflo-agent-smoke / ruflo-agent structural smoke (push) Failing after 1s
📊 Status Badges Update / 📊 Update Status Badges (push) Failing after 1s
V3 CI/CD Pipeline / Static regression guards (#2267 YAML + (push) Failing after 1s
V3 CI/CD Pipeline / Test V3 Packages (push) Failing after 0s
V3 CI/CD Pipeline / agent_execute provider routing smoke (#2042) (push) Failing after 0s
CVE Audit Gate / Audit v3 (critical-blocking) (push) Failing after 1s
federation-peer-rust / stable-native (push) Failing after 2s
🔗 Cross-Agent Integration Tests / 🚀 Integration Test Setup (push) Failing after 2s
neural-trader-smoke / runtime-smoke (push) Failing after 1s
V3 CI/CD Pipeline / Build V3 (macos-latest) (push) Has been skipped
V3 CI/CD Pipeline / Build V3 (ubuntu-latest) (push) Has been skipped
V3 CI/CD Pipeline / Type Check V3 (push) Failing after 1s
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / ubuntu-latest / Node 24 (push) Failing after 1s
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / ubuntu-latest / Node 22 (push) Failing after 2s
V3 CI/CD Pipeline / browser rvf create flag smoke (#2015) (push) Failing after 0s
V3 CI/CD Pipeline / Dependency review (#2046) (push) Has been skipped
V3 CI/CD Pipeline / Supply-chain audit (#2046) (push) Failing after 0s
V3 CI/CD Pipeline / witness marker drift smoke (#2021) (push) Failing after 1s
V3 CI/CD Pipeline / neural-trader portfolio CG smoke (#2068, ADR-126 Phase 3) (push) Failing after 1s
V3 CI/CD Pipeline / neural-trader backtest signing smoke (#2068, ADR-126 Phase 4) (push) Failing after 1s
V3 CI/CD Pipeline / kg-extract type-import classification smoke (#2049) (push) Failing after 0s
V3 CI/CD Pipeline / witness verify precondition smoke (#1880) (push) Failing after 2s
V3 CI/CD Pipeline / neural-trader pipeline risk-gate smoke (#2068, ADR-126 Phase 5) (push) Failing after 0s
V3 CI/CD Pipeline / neural-trader feature attribution smoke (#2068, ADR-126 Phase 6) (push) Failing after 0s
V3 CI/CD Pipeline / plugin-registry signature verification smoke (#1922, CWE-347) (push) Failing after 4s
V3 CI/CD Pipeline / memory stats legacy-DB smoke (#2120) (push) Failing after 4s
V3 CI/CD Pipeline / github deprecated actions smoke (#2089, ADR-127 Phase 3) (push) Failing after 1s
V3 CI/CD Pipeline / graph query + pathfinder smoke (ADR-130 P2+P5) (push) Has been skipped
V3 CI/CD Pipeline / graph trajectory hooks smoke (ADR-130 P3) (push) Has been skipped
V3 CI/CD Pipeline / graph plugin adapter smoke (ADR-130 P4) (push) Has been skipped
V3 CI/CD Pipeline / graph benchmark (ADR-130 P6) (push) Has been skipped
V3 CI/CD Pipeline / statusline generator delegation smoke (#2195) (push) Failing after 1s
V3 CI/CD Pipeline / wizard init regression guard (#2206 (push) Failing after 1s
V3 CI/CD Pipeline / memory no-stray-db smoke (ADR-125 P7) (push) Failing after 1s
V3 CI/CD Pipeline / github-safe injection smoke (#2089, ADR-127 Phase 1) (push) Failing after 1s
V3 CI/CD Pipeline / github actions pin smoke (#2089, ADR-127 Phase 1) (push) Failing after 1s
V3 CI/CD Pipeline / github attribution opt-in smoke (#2089, ADR-127 Phase 4) (push) Failing after 1s
V3 CI/CD Pipeline / pre-bash hook safety smoke (#2017) (push) Failing after 1s
V3 CI/CD Pipeline / Memory import smoke / ubuntu-latest (push) Failing after 0s
V3 CI/CD Pipeline / MCP protocol smoke / ubuntu-latest (push) Failing after 2s
V3 CI/CD Pipeline / ruvllm WASM auto-init smoke (#2086) (push) Failing after 4s
V3 CI/CD Pipeline / MCP paired-tool round-trip smoke (#1889) (push) Failing after 1s
V3 CI/CD Pipeline / Plugin package install-safety (#1902/#1903/#1904) (push) Failing after 1s
V3 CI/CD Pipeline / Tool description discoverability (ADR-112) (push) Failing after 3s
V3 CI/CD Pipeline / CLI npx-install smoke (#1147 / (22) (push) Failing after 1s
V3 CI/CD Pipeline / CLI npx-install smoke (#1147 / (24) (push) Failing after 1s
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / ubuntu-latest (push) Failing after 2s
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / ubuntu-latest (push) Failing after 1s
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / ubuntu-latest (push) Failing after 1s
V3 CI/CD Pipeline / Vector-index dimension audit (#1947) (push) Failing after 0s
V3 CI/CD Pipeline / Hook-command install safety (#1921) (push) Failing after 1s
V3 CI/CD Pipeline / ToolOutputGuardrail smoke (ADR-131, (push) Failing after 1s
V3 CI/CD Pipeline / init-bundle invariants smoke (#2095, ADR-128 Phase 5) (push) Failing after 1s
V3 CI/CD Pipeline / wasm provider bridge smoke (ADR-129 P1) (push) Failing after 2s
V3 CI/CD Pipeline / wasm gallery CRUD smoke (ADR-129 P3) (push) Failing after 1s
V3 CI/CD Pipeline / wasm plugin bridge smoke (ADR-129 P4) (push) Failing after 0s
V3 CI/CD Pipeline / wasm compose smoke (ADR-129 P2) (push) Failing after 4s
V3 CI/CD Pipeline / graph schema smoke (ADR-130 P1) (push) Failing after 0s
Validate Marketplace / validate (push) Failing after 1s
🔍 Verification Pipeline / 🚀 Setup Verification (push) Failing after 1s
🔍 Verification Pipeline / 🛡️ Security Verification (push) Has been skipped
🔍 Verification Pipeline / 📝 Code Quality (push) Has been skipped
🔍 Verification Pipeline / 🧪 Test Verification (${{ matrix.os }}, Node ${{ matrix.node }}) (push) Has been skipped
🔍 Verification Pipeline / 🏗️ Build Verification (push) Has been skipped
🔍 Verification Pipeline / 📚 Documentation Verification (push) Has been skipped
CVE Audit Gate / High-severity report (warn only) (push) Has been cancelled
🔄 Automated Rollback Manager / 🔄 Execute Rollback (push) Has been cancelled
🔄 Automated Rollback Manager / ✅ Post-Rollback Verification (push) Has been cancelled
🔄 Automated Rollback Manager / 📊 Rollback Monitoring (push) Has been cancelled
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / windows-latest (push) Has been cancelled
🔄 Automated Rollback Manager / ⏳ Manual Rollback Approval (push) Has been cancelled
V3 CI/CD Pipeline / MCP protocol smoke / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Memory import smoke / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / ubuntu-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Publish to npm (alpha) (push) Has been cancelled
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / macos-latest / Node 22 (push) Has been cancelled
V3 CI/CD Pipeline / Plugin hooks smoke / macos-latest / Node 22 (push) Has been cancelled
CI/CD Pipeline / Deploy & Release (push) Has been cancelled
CI/CD Pipeline / CI Status (push) Has been cancelled
🔗 Cross-Agent Integration Tests / 📊 Integration Test Report (push) Has been cancelled
🔄 Automated Rollback Manager / 🔍 Pre-Rollback Validation (push) Has been cancelled
🔍 Verification Pipeline / ⚡ Performance Verification (push) Has been cancelled
🔍 Verification Pipeline / 📊 Verification Report (push) Has been cancelled
ADR-166 MCP Bridge Security Lock / Static-source security lock (push) Failing after 0s
ADR-166 MCP Bridge Security Lock / Compose default binds loopback + Mongo has auth (push) Failing after 2s
CodeQL Advanced / Analyze (rust) (push) Failing after 0s
ADR-166 MCP Bridge Security Lock / plugin-agent-federation bindHost default (push) Failing after 1s
ADR-166 MCP Bridge Security Lock / Runtime behavior — 401 + terminal gate + fail-closed (push) Failing after 4s
business-pods-smoke / smoke (push) Failing after 1s
all-plugins-smoke / smoke-all (push) Failing after 2s
CI/CD Pipeline / Security & Code Quality (push) Failing after 1s
CI/CD Pipeline / Test Suite (ubuntu-latest) (push) Failing after 1s
CI/CD Pipeline / Build & Package (macos-latest) (push) Has been skipped
CI/CD Pipeline / Build & Package (ubuntu-latest) (push) Has been skipped
CI/CD Pipeline / Build & Package (windows-latest) (push) Has been skipped
CI/CD Pipeline / Documentation & Examples (push) Failing after 1s
Clone Tracker (14-day rolling) / Snapshot clones for ruflo ecosystem (push) Failing after 1s
CodeQL Advanced / Analyze (actions) (push) Failing after 1s
CodeQL Advanced / Analyze (javascript-typescript) (push) Failing after 1s
federation-peer-rust / stable-noop (push) Failing after 1s
metaharness-ci / score (push) Failing after 1s
metaharness-ci / router-compat (push) Failing after 0s
metaharness-ci / similarity-tests (push) Failing after 0s
no-agentbbs-smoke / smoke-without-agentbbs (push) Failing after 1s
V3 CI/CD Pipeline / Build V3 (windows-latest) (push) Has been skipped
codex-integration-audit / Codex integration audit (push) Failing after 1s
helpers-manifest-guard / guard (push) Failing after 1s
🔗 Cross-Agent Integration Tests / 🤝 Agent Coordination Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / 🧠 Memory Sharing Integration (push) Has been skipped
🔗 Cross-Agent Integration Tests / 🛡️ Fault Tolerance Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / ⚡ Performance Integration Tests (push) Has been skipped
metaharness-ci / mcp-scan (push) Failing after 1s
metaharness-ci / eject-dryrun (push) Failing after 1s
metaharness-ci / metaharness-real-data (push) Failing after 0s
no-cli-optdep-bloat-2561 / guard (push) Failing after 1s
no-metaharness-smoke / smoke-without-metaharness (push) Failing after 1s
no-phantom-agentic-flow-subpath / guard (push) Failing after 1s
🔄 Automated Rollback Manager / 🚨 Failure Detection (push) Failing after 1s
V3 CI/CD Pipeline / Plugin hooks smoke / ubuntu-latest / Node 22 (push) Failing after 1s
V3 CI/CD Pipeline / ruflo-graph-intelligence build + test smoke (#2044, ADR-123) (push) Failing after 1s
CVE Audit Gate / Audit root (critical-blocking) (push) Failing after 2s
cost-tracker-smoke / smoke (push) Failing after 3s
oia-audit-weekly / audit (push) Failing after 2s
ruflo-agent-smoke / ruflo-agent structural smoke (push) Failing after 1s
📊 Status Badges Update / 📊 Update Status Badges (push) Failing after 1s
V3 CI/CD Pipeline / Static regression guards (#2267 YAML + (push) Failing after 1s
V3 CI/CD Pipeline / Test V3 Packages (push) Failing after 0s
V3 CI/CD Pipeline / agent_execute provider routing smoke (#2042) (push) Failing after 0s
CVE Audit Gate / Audit v3 (critical-blocking) (push) Failing after 1s
federation-peer-rust / stable-native (push) Failing after 2s
🔗 Cross-Agent Integration Tests / 🚀 Integration Test Setup (push) Failing after 2s
neural-trader-smoke / runtime-smoke (push) Failing after 1s
V3 CI/CD Pipeline / Build V3 (macos-latest) (push) Has been skipped
V3 CI/CD Pipeline / Build V3 (ubuntu-latest) (push) Has been skipped
V3 CI/CD Pipeline / Type Check V3 (push) Failing after 1s
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / ubuntu-latest / Node 24 (push) Failing after 1s
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / ubuntu-latest / Node 22 (push) Failing after 2s
V3 CI/CD Pipeline / browser rvf create flag smoke (#2015) (push) Failing after 0s
V3 CI/CD Pipeline / Dependency review (#2046) (push) Has been skipped
V3 CI/CD Pipeline / Supply-chain audit (#2046) (push) Failing after 0s
V3 CI/CD Pipeline / witness marker drift smoke (#2021) (push) Failing after 1s
V3 CI/CD Pipeline / neural-trader portfolio CG smoke (#2068, ADR-126 Phase 3) (push) Failing after 1s
V3 CI/CD Pipeline / neural-trader backtest signing smoke (#2068, ADR-126 Phase 4) (push) Failing after 1s
V3 CI/CD Pipeline / kg-extract type-import classification smoke (#2049) (push) Failing after 0s
V3 CI/CD Pipeline / witness verify precondition smoke (#1880) (push) Failing after 2s
V3 CI/CD Pipeline / neural-trader pipeline risk-gate smoke (#2068, ADR-126 Phase 5) (push) Failing after 0s
V3 CI/CD Pipeline / neural-trader feature attribution smoke (#2068, ADR-126 Phase 6) (push) Failing after 0s
V3 CI/CD Pipeline / plugin-registry signature verification smoke (#1922, CWE-347) (push) Failing after 4s
V3 CI/CD Pipeline / memory stats legacy-DB smoke (#2120) (push) Failing after 4s
V3 CI/CD Pipeline / github deprecated actions smoke (#2089, ADR-127 Phase 3) (push) Failing after 1s
V3 CI/CD Pipeline / graph query + pathfinder smoke (ADR-130 P2+P5) (push) Has been skipped
V3 CI/CD Pipeline / graph trajectory hooks smoke (ADR-130 P3) (push) Has been skipped
V3 CI/CD Pipeline / graph plugin adapter smoke (ADR-130 P4) (push) Has been skipped
V3 CI/CD Pipeline / graph benchmark (ADR-130 P6) (push) Has been skipped
V3 CI/CD Pipeline / statusline generator delegation smoke (#2195) (push) Failing after 1s
V3 CI/CD Pipeline / wizard init regression guard (#2206 (push) Failing after 1s
V3 CI/CD Pipeline / memory no-stray-db smoke (ADR-125 P7) (push) Failing after 1s
V3 CI/CD Pipeline / github-safe injection smoke (#2089, ADR-127 Phase 1) (push) Failing after 1s
V3 CI/CD Pipeline / github actions pin smoke (#2089, ADR-127 Phase 1) (push) Failing after 1s
V3 CI/CD Pipeline / github attribution opt-in smoke (#2089, ADR-127 Phase 4) (push) Failing after 1s
V3 CI/CD Pipeline / pre-bash hook safety smoke (#2017) (push) Failing after 1s
V3 CI/CD Pipeline / Memory import smoke / ubuntu-latest (push) Failing after 0s
V3 CI/CD Pipeline / MCP protocol smoke / ubuntu-latest (push) Failing after 2s
V3 CI/CD Pipeline / ruvllm WASM auto-init smoke (#2086) (push) Failing after 4s
V3 CI/CD Pipeline / MCP paired-tool round-trip smoke (#1889) (push) Failing after 1s
V3 CI/CD Pipeline / Plugin package install-safety (#1902/#1903/#1904) (push) Failing after 1s
V3 CI/CD Pipeline / Tool description discoverability (ADR-112) (push) Failing after 3s
V3 CI/CD Pipeline / CLI npx-install smoke (#1147 / (22) (push) Failing after 1s
V3 CI/CD Pipeline / CLI npx-install smoke (#1147 / (24) (push) Failing after 1s
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / ubuntu-latest (push) Failing after 2s
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / ubuntu-latest (push) Failing after 1s
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / ubuntu-latest (push) Failing after 1s
V3 CI/CD Pipeline / Vector-index dimension audit (#1947) (push) Failing after 0s
V3 CI/CD Pipeline / Hook-command install safety (#1921) (push) Failing after 1s
V3 CI/CD Pipeline / ToolOutputGuardrail smoke (ADR-131, (push) Failing after 1s
V3 CI/CD Pipeline / init-bundle invariants smoke (#2095, ADR-128 Phase 5) (push) Failing after 1s
V3 CI/CD Pipeline / wasm provider bridge smoke (ADR-129 P1) (push) Failing after 2s
V3 CI/CD Pipeline / wasm gallery CRUD smoke (ADR-129 P3) (push) Failing after 1s
V3 CI/CD Pipeline / wasm plugin bridge smoke (ADR-129 P4) (push) Failing after 0s
V3 CI/CD Pipeline / wasm compose smoke (ADR-129 P2) (push) Failing after 4s
V3 CI/CD Pipeline / graph schema smoke (ADR-130 P1) (push) Failing after 0s
Validate Marketplace / validate (push) Failing after 1s
🔍 Verification Pipeline / 🚀 Setup Verification (push) Failing after 1s
🔍 Verification Pipeline / 🛡️ Security Verification (push) Has been skipped
🔍 Verification Pipeline / 📝 Code Quality (push) Has been skipped
🔍 Verification Pipeline / 🧪 Test Verification (${{ matrix.os }}, Node ${{ matrix.node }}) (push) Has been skipped
🔍 Verification Pipeline / 🏗️ Build Verification (push) Has been skipped
🔍 Verification Pipeline / 📚 Documentation Verification (push) Has been skipped
CVE Audit Gate / High-severity report (warn only) (push) Has been cancelled
🔄 Automated Rollback Manager / 🔄 Execute Rollback (push) Has been cancelled
🔄 Automated Rollback Manager / ✅ Post-Rollback Verification (push) Has been cancelled
🔄 Automated Rollback Manager / 📊 Rollback Monitoring (push) Has been cancelled
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / windows-latest (push) Has been cancelled
🔄 Automated Rollback Manager / ⏳ Manual Rollback Approval (push) Has been cancelled
V3 CI/CD Pipeline / MCP protocol smoke / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Memory import smoke / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / ubuntu-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Publish to npm (alpha) (push) Has been cancelled
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / macos-latest / Node 22 (push) Has been cancelled
V3 CI/CD Pipeline / Plugin hooks smoke / macos-latest / Node 22 (push) Has been cancelled
CI/CD Pipeline / Deploy & Release (push) Has been cancelled
CI/CD Pipeline / CI Status (push) Has been cancelled
🔗 Cross-Agent Integration Tests / 📊 Integration Test Report (push) Has been cancelled
🔄 Automated Rollback Manager / 🔍 Pre-Rollback Validation (push) Has been cancelled
🔍 Verification Pipeline / ⚡ Performance Verification (push) Has been cancelled
🔍 Verification Pipeline / 📊 Verification Report (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
name: ADR-166 MCP Bridge Security Lock
|
||||
|
||||
# Anti-regression gate for the ADR-166 remediation. Runs on every PR touching
|
||||
# either bridge file, the shared docker-compose, or the security tests. Fails
|
||||
# closed if any load-bearing control drifts.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'ruflo/src/mcp-bridge/**'
|
||||
- 'ruflo/src/ruvocal/mcp-bridge/**'
|
||||
- 'ruflo/docker-compose.yml'
|
||||
- 'ruflo/docker-compose.public.yml'
|
||||
- 'v3/@claude-flow/plugin-agent-federation/src/bin.ts'
|
||||
- '.github/workflows/adr-166-mcp-bridge-security.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'ruflo/src/mcp-bridge/**'
|
||||
- 'ruflo/src/ruvocal/mcp-bridge/**'
|
||||
- 'ruflo/docker-compose.yml'
|
||||
- 'ruflo/docker-compose.public.yml'
|
||||
- 'v3/@claude-flow/plugin-agent-federation/src/bin.ts'
|
||||
- '.github/workflows/adr-166-mcp-bridge-security.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
static-source-lock:
|
||||
name: Static-source security lock
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
- name: ADR-166 §6 #9 — anti-regression lock (both bridges)
|
||||
run: node ruflo/src/mcp-bridge/test-security-lock.js
|
||||
|
||||
runtime-behavior:
|
||||
name: Runtime behavior — 401 + terminal gate + fail-closed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
- name: Install both bridges
|
||||
run: |
|
||||
(cd ruflo/src/mcp-bridge && npm install --no-audit --no-fund)
|
||||
(cd ruflo/src/ruvocal/mcp-bridge && npm install --no-audit --no-fund)
|
||||
- name: ADR-166 runtime verification
|
||||
run: node ruflo/src/mcp-bridge/test-runtime-security.mjs
|
||||
|
||||
compose-loopback-only:
|
||||
name: Compose default binds loopback + Mongo has auth
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Assert docker-compose binds ONLY to 127.0.0.1
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# No line may map a container port to a public host port.
|
||||
# Look for "N.N.N.N:PORT:PORT" or bare "PORT:PORT" patterns under `ports:`.
|
||||
if grep -nE '^\s*-\s+"(?!127\.0\.0\.1|0\.0\.0\.0)?[^"]*3001:3001"' ruflo/docker-compose.yml \
|
||||
| grep -v '127.0.0.1:3001'; then
|
||||
echo "::error::docker-compose.yml exposes 3001 on non-loopback interface"
|
||||
exit 1
|
||||
fi
|
||||
if grep -nE '^\s*-\s+"(?!127\.0\.0\.1|0\.0\.0\.0)?[^"]*27017:27017"' ruflo/docker-compose.yml \
|
||||
| grep -v '127.0.0.1:27017'; then
|
||||
echo "::error::docker-compose.yml exposes 27017 on non-loopback interface"
|
||||
exit 1
|
||||
fi
|
||||
# Mongo must run with --auth AND require MONGO_INITDB_ROOT_PASSWORD to boot.
|
||||
grep -q '"--auth"' ruflo/docker-compose.yml \
|
||||
|| { echo "::error::mongodb service missing --auth (Phase 2b)"; exit 1; }
|
||||
grep -q 'MONGO_INITDB_ROOT_PASSWORD:\?' ruflo/docker-compose.yml \
|
||||
|| { echo "::error::MONGO_INITDB_ROOT_PASSWORD must be a required variable (:? syntax)"; exit 1; }
|
||||
grep -q 'read_only: true' ruflo/docker-compose.yml \
|
||||
|| { echo "::error::bridge service must be read_only: true (Phase 2c)"; exit 1; }
|
||||
echo "compose defaults: OK"
|
||||
|
||||
federation-loopback-default:
|
||||
name: plugin-agent-federation bindHost default
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Assert bindHost defaults to 127.0.0.1
|
||||
run: |
|
||||
set -euo pipefail
|
||||
grep -q "bindHost: process.env.FEDERATION_BIND_HOST ?? '127.0.0.1'" \
|
||||
v3/@claude-flow/plugin-agent-federation/src/bin.ts \
|
||||
|| { echo "::error::plugin-agent-federation bindHost must default to 127.0.0.1 (Phase 3d)"; exit 1; }
|
||||
echo "federation default: OK"
|
||||
@@ -0,0 +1,86 @@
|
||||
# Meta-smoke: run every plugins/*/scripts/smoke.sh in parallel and fail the
|
||||
# build if any plugin's structural contract regresses.
|
||||
#
|
||||
# Before iter 74, only ruflo-cost-tracker and ruflo-agent had dedicated CI
|
||||
# gates — the other 30 plugins shipped smoke.sh files that nobody enforced.
|
||||
# This workflow turns the 32 unrelated smoke scripts into a single
|
||||
# CI-gateable check. New plugins authored with the canonical scripts/smoke.sh
|
||||
# layout are automatically covered.
|
||||
#
|
||||
# Triggers on any plugin change (paths-filter) and is fast enough (~8s wall
|
||||
# on the iter-74 baseline) that it can be a required check on PRs.
|
||||
name: all-plugins-smoke
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'plugins/**'
|
||||
- 'scripts/smoke-all-plugins.mjs'
|
||||
- '.github/workflows/all-plugins-smoke.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'plugins/**'
|
||||
- 'scripts/smoke-all-plugins.mjs'
|
||||
- '.github/workflows/all-plugins-smoke.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
smoke-all:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Discover and run every plugin smoke contract (parallel)
|
||||
# --timeout 300 caps each individual plugin smoke at 5 minutes.
|
||||
# iter 119 — bumped from 60s because ruflo-metaharness has grown
|
||||
# to 118 steps with many npx invocations and routinely exceeds
|
||||
# 60s (was timing out at 151s before SIGKILL). Most plugins
|
||||
# still complete in <5s; 300s protects against a genuinely-hung
|
||||
# smoke while letting the metaharness fleet finish.
|
||||
run: node scripts/smoke-all-plugins.mjs --timeout 300
|
||||
|
||||
- name: Fleet-wide exit-bypass antipattern lint (iter-75 bug class)
|
||||
run: node scripts/audit-exit-bypass-antipattern.mjs
|
||||
# Static analyzer: scans every plugins/*/scripts/*.mjs for the
|
||||
# iter-75 antipattern — `return console.log(JSON.stringify(...))`
|
||||
# placed BEFORE a `process.exit(N>0)` in the same function (which
|
||||
# silently swallows the exit signal). Use the inline marker
|
||||
# `// audit-allow: exit-bypass — <reason>` to suppress known-safe
|
||||
# cases (e.g. early returns on no-config paths that can't reach
|
||||
# the exit).
|
||||
|
||||
- name: Fleet-wide SKILL.md frontmatter audit (iter 87)
|
||||
run: node scripts/audit-skill-frontmatter.mjs
|
||||
# Scans every plugins/*/skills/*/SKILL.md for required frontmatter:
|
||||
# name / description / allowed-tools all present and non-empty,
|
||||
# no wildcard allowed-tools (security), name matches directory.
|
||||
# Each plugin's own smoke checks its own skills; this catches
|
||||
# violations that escape per-plugin coverage (new plugin without
|
||||
# smoke, new skill without smoke-list update, etc.).
|
||||
|
||||
- name: Fleet-wide plugin.json manifest audit (iter 88)
|
||||
run: node scripts/audit-plugin-manifest.mjs
|
||||
# Scans every plugins/*/.claude-plugin/plugin.json for: valid JSON,
|
||||
# required fields (name/version/description/keywords[]) present
|
||||
# and non-empty, version matches semver X.Y.Z, name matches
|
||||
# enclosing directory. Each plugin's smoke step 1 pins its own
|
||||
# expected version literal; this catches structural violations
|
||||
# (non-semver, name drift, missing fields) that the per-plugin
|
||||
# grep can't see.
|
||||
|
||||
- name: Upload machine-readable report
|
||||
if: always()
|
||||
run: node scripts/smoke-all-plugins.mjs --format json > /tmp/smoke-all-plugins.json
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: all-plugins-smoke-report
|
||||
path: /tmp/smoke-all-plugins.json
|
||||
retention-days: 30
|
||||
@@ -0,0 +1,61 @@
|
||||
# ADR-164 Phase 2 — sales-pod smoke contract.
|
||||
#
|
||||
# Asserts that the business-pod template surface (sales.json + pod-schema
|
||||
# validator + business_pod_validate MCP tool + pod-tick.mjs dry-run runner)
|
||||
# stays green on every PR that touches the relevant files.
|
||||
name: business-pods-smoke
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'plugins/ruflo-business-pods/**'
|
||||
- 'v3/@claude-flow/cli/src/business-pods/**'
|
||||
- 'v3/@claude-flow/cli/src/mcp-tools/business-pod-tools.ts'
|
||||
- 'v3/@claude-flow/cli/src/mcp-tools/index.ts'
|
||||
- 'v3/@claude-flow/cli/src/mcp-client.ts'
|
||||
- '.github/workflows/business-pods-smoke.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'plugins/ruflo-business-pods/**'
|
||||
- 'v3/@claude-flow/cli/src/business-pods/**'
|
||||
- 'v3/@claude-flow/cli/src/mcp-tools/business-pod-tools.ts'
|
||||
- 'v3/@claude-flow/cli/src/mcp-tools/index.ts'
|
||||
- 'v3/@claude-flow/cli/src/mcp-client.ts'
|
||||
- '.github/workflows/business-pods-smoke.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
smoke:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 8
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: v3/pnpm-lock.yaml
|
||||
- name: install + build CLI (pnpm, v3 workspace)
|
||||
# The root package.json uses npm/workspaces but the v3/ tree is a
|
||||
# pnpm workspace (v3/pnpm-workspace.yaml). npm chokes on
|
||||
# `workspace:*` here — use pnpm in the v3 dir instead.
|
||||
working-directory: v3
|
||||
run: |
|
||||
pnpm install --frozen-lockfile
|
||||
# Build ALL workspace packages in topological order — the cli has
|
||||
# cross-package deps (e.g. imports @claude-flow/swarm/dist/...)
|
||||
# that pnpm's dependency-aware filter can't discover from
|
||||
# package.json alone. `pnpm -r build` runs the build script across
|
||||
# the whole workspace, dep-first.
|
||||
pnpm -r --no-bail build || pnpm --filter @claude-flow/cli build
|
||||
- name: vitest — business-pod-tools
|
||||
working-directory: v3/@claude-flow/cli
|
||||
run: pnpm vitest run __tests__/business-pod-tools.test.ts
|
||||
- name: node --test — pod-tick
|
||||
run: node --test plugins/ruflo-business-pods/scripts/pod-tick.test.mjs
|
||||
- name: smoke contract
|
||||
run: bash plugins/ruflo-business-pods/scripts/smoke.sh
|
||||
@@ -0,0 +1,307 @@
|
||||
name: CI/CD Pipeline
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, develop ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
schedule:
|
||||
# Run tests daily at 2 AM UTC
|
||||
- cron: '0 2 * * *'
|
||||
|
||||
env:
|
||||
NODE_VERSION: '20'
|
||||
|
||||
jobs:
|
||||
# Code quality and security checks
|
||||
security:
|
||||
name: Security & Code Quality
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci --legacy-peer-deps
|
||||
|
||||
- name: Run security audit
|
||||
run: |
|
||||
npm audit --audit-level=high || echo "⚠️ Security vulnerabilities found (non-blocking)"
|
||||
npm audit --production --audit-level=moderate || echo "⚠️ Production vulnerabilities found (non-blocking)"
|
||||
continue-on-error: true
|
||||
|
||||
- name: Lint code
|
||||
run: npm run lint
|
||||
|
||||
- name: Type check
|
||||
run: npm run typecheck || echo "⚠️ Type checking skipped (TypeScript compiler crash)"
|
||||
continue-on-error: true
|
||||
|
||||
- name: Check for outdated dependencies
|
||||
run: npm outdated || true
|
||||
continue-on-error: true
|
||||
|
||||
- name: License compliance check
|
||||
run: npx license-checker --onlyAllow 'MIT;Apache-2.0;BSD-2-Clause;BSD-3-Clause;ISC;CC0-1.0' || true
|
||||
continue-on-error: true
|
||||
|
||||
# All tests
|
||||
test:
|
||||
name: Test Suite
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci --legacy-peer-deps
|
||||
|
||||
- name: Run all tests
|
||||
run: npm test || echo "⚠️ Some tests failed (Jest teardown issues - non-blocking)"
|
||||
continue-on-error: true
|
||||
|
||||
- name: Generate coverage report
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: npm run test:coverage || echo "⚠️ Coverage generation failed (non-blocking)"
|
||||
continue-on-error: true
|
||||
|
||||
- name: Upload test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-results-${{ matrix.os }}
|
||||
path: coverage/
|
||||
|
||||
# Documentation generation
|
||||
docs:
|
||||
name: Documentation & Examples
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
|
||||
- name: Check documentation
|
||||
run: |
|
||||
echo "✅ Documentation check passed"
|
||||
ls -la README.md CHANGELOG.md
|
||||
|
||||
# Build and package
|
||||
build:
|
||||
name: Build & Package (${{ matrix.os }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: [security, test]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
platform: linux
|
||||
- os: macos-latest
|
||||
platform: darwin
|
||||
- os: windows-latest
|
||||
platform: win32
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
if [ "${{ runner.os }}" == "Linux" ]; then
|
||||
npm ci --legacy-peer-deps
|
||||
else
|
||||
npm ci --legacy-peer-deps --omit=optional || npm ci --legacy-peer-deps --force
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
- name: Build project
|
||||
run: |
|
||||
echo "Building project for ${{ matrix.platform }}..."
|
||||
npm run build:ts
|
||||
|
||||
- name: Test CLI binary (Unix)
|
||||
if: runner.os != 'Windows'
|
||||
run: |
|
||||
chmod +x ./v3/@claude-flow/cli/bin/cli.js
|
||||
node ./v3/@claude-flow/cli/bin/cli.js --version
|
||||
continue-on-error: true
|
||||
|
||||
- name: Test CLI binary (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
run: |
|
||||
node ./v3/@claude-flow/cli/bin/cli.js --version
|
||||
continue-on-error: true
|
||||
|
||||
- name: Daemon survives parent exit (Windows, regression #1766)
|
||||
if: runner.os == 'Windows'
|
||||
timeout-minutes: 15
|
||||
shell: pwsh
|
||||
run: |
|
||||
# We install the published `@claude-flow/cli@alpha` inside an isolated
|
||||
# temp dir rather than running the source-tree CLI directly, because
|
||||
# the source-tree CLI imports the sibling workspace package
|
||||
# `@claude-flow/cli-core` (split out in #1764) which isn't always
|
||||
# resolvable from a source checkout. Skipping the `ruflo` umbrella
|
||||
# (just a thin wrapper) keeps the install footprint smaller on the
|
||||
# cold-cache Windows runner.
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$tmp = Join-Path $env:RUNNER_TEMP 'daemon-1766'
|
||||
if (Test-Path $tmp) { Remove-Item -Recurse -Force $tmp }
|
||||
New-Item -ItemType Directory -Path $tmp | Out-Null
|
||||
Set-Location $tmp
|
||||
|
||||
Write-Host "::group::Phase 1: install @claude-flow/cli@alpha"
|
||||
$installSw = [System.Diagnostics.Stopwatch]::StartNew()
|
||||
npm init -y | Out-Null
|
||||
npm install '@claude-flow/cli@alpha' --no-audit --no-fund --omit=optional --omit=dev
|
||||
$installSw.Stop()
|
||||
Write-Host "Install took $($installSw.Elapsed.TotalSeconds)s"
|
||||
$cli = Join-Path $tmp 'node_modules/@claude-flow/cli/bin/cli.js'
|
||||
if (-not (Test-Path $cli)) { throw "CLI not found at $cli after npm install" }
|
||||
$cliVer = (Get-Content (Join-Path $tmp 'node_modules/@claude-flow/cli/package.json') | ConvertFrom-Json).version
|
||||
Write-Host "Testing daemon survival for @claude-flow/cli@$cliVer"
|
||||
Write-Host "::endgroup::"
|
||||
|
||||
Write-Host "::group::Phase 2: spawn daemon via cmd.exe (parent exits when node returns)"
|
||||
# Use raw .NET Process.Start with cmd.exe /c as the spawn parent.
|
||||
# cmd.exe is a clean Windows parent that exits the moment node exits,
|
||||
# giving us a deterministic "parent gone" signal — without the PS7
|
||||
# `Start-Process -Wait -NoNewWindow -RedirectStandard*` hang where
|
||||
# the parent waits indefinitely on output streams it inherited.
|
||||
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
||||
$psi.FileName = 'cmd.exe'
|
||||
$psi.Arguments = "/c node `"$cli`" daemon start"
|
||||
$psi.UseShellExecute = $false
|
||||
$psi.CreateNoWindow = $true
|
||||
$psi.WorkingDirectory = $tmp
|
||||
# No stdio redirect — let cmd inherit nothing meaningful, daemon's
|
||||
# fork() opts use stdio:'ignore' anyway so nothing leaks back here.
|
||||
$spawnSw = [System.Diagnostics.Stopwatch]::StartNew()
|
||||
$proc = [System.Diagnostics.Process]::Start($psi)
|
||||
if (-not $proc.WaitForExit(120000)) {
|
||||
$proc.Kill($true)
|
||||
throw "FAIL: cmd.exe parent did not exit in 120s — daemon start hung"
|
||||
}
|
||||
$spawnSw.Stop()
|
||||
Write-Host "cmd.exe parent exited in $($spawnSw.Elapsed.TotalSeconds)s with code $($proc.ExitCode)"
|
||||
Write-Host "::endgroup::"
|
||||
|
||||
Write-Host "::group::Phase 3: verify daemon survives parent exit"
|
||||
$pidFile = Join-Path $tmp '.claude-flow/daemon.pid'
|
||||
if (-not (Test-Path $pidFile)) {
|
||||
throw "FAIL: pid file $pidFile was never written — daemon failed to start"
|
||||
}
|
||||
$daemonPid = (Get-Content $pidFile).Trim()
|
||||
Write-Host "Daemon recorded PID = $daemonPid"
|
||||
|
||||
# Original #1766 symptom: daemon died within ~1s of parent exit.
|
||||
# Wait 5s — well past any plausible delayed-teardown race.
|
||||
Start-Sleep -Seconds 5
|
||||
$alive = Get-Process -Id $daemonPid -ErrorAction SilentlyContinue
|
||||
if ($null -eq $alive) {
|
||||
throw "FAIL: daemon PID $daemonPid is no longer running 5s after parent exit (regression of #1766 in @claude-flow/cli@$cliVer)"
|
||||
}
|
||||
Write-Host "PASS: daemon PID $daemonPid alive 5s after parent exit (@claude-flow/cli@$cliVer)"
|
||||
Write-Host "::endgroup::"
|
||||
|
||||
Write-Host "::group::Phase 4: cleanup"
|
||||
# Force-kill is fine here (CI ephemeral runner) — saves time vs the
|
||||
# interactive `daemon stop` path which on Windows shells out to ps/grep.
|
||||
Stop-Process -Id $daemonPid -Force -ErrorAction SilentlyContinue
|
||||
Write-Host "::endgroup::"
|
||||
|
||||
- name: Package build
|
||||
run: |
|
||||
npm pack
|
||||
ls -la *.tgz
|
||||
shell: bash
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-artifacts-${{ matrix.platform }}
|
||||
path: |
|
||||
dist/
|
||||
bin/
|
||||
*.tgz
|
||||
|
||||
# Deployment (only on main branch)
|
||||
deploy:
|
||||
name: Deploy & Release
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build]
|
||||
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download Linux build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: build-artifacts-linux
|
||||
path: dist-linux/
|
||||
|
||||
- name: Download macOS build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: build-artifacts-darwin
|
||||
path: dist-darwin/
|
||||
|
||||
- name: Download Windows build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: build-artifacts-win32
|
||||
path: dist-windows/
|
||||
|
||||
- name: Prepare for deployment
|
||||
run: |
|
||||
echo "✅ Ready for deployment"
|
||||
echo "Version: $(node -p "require('./package.json').version")"
|
||||
echo "Platform builds:"
|
||||
ls -la dist-*/
|
||||
|
||||
# Final status check
|
||||
status:
|
||||
name: CI Status
|
||||
runs-on: ubuntu-latest
|
||||
needs: [security, test, build]
|
||||
if: always()
|
||||
|
||||
steps:
|
||||
- name: Check overall status
|
||||
run: |
|
||||
echo "✅ CI Pipeline completed"
|
||||
echo "Security: ${{ needs.security.result }}"
|
||||
echo "Test: ${{ needs.test.result }}"
|
||||
echo "Build: ${{ needs.build.result }}"
|
||||
@@ -0,0 +1,84 @@
|
||||
name: Clone Tracker (14-day rolling)
|
||||
|
||||
# GitHub's clone API only retains 14 days. We run every 13 days so we always
|
||||
# catch the full window with a 24h safety margin. The job appends a snapshot
|
||||
# to `data/clone-data.rvf` (RuVector vector store) + `data/clone-data.ledger.json`
|
||||
# (chronological JSON), regenerates `data/clone-data.proof.json` with a fresh
|
||||
# SHA-256 over the ledger, and commits the result back to main.
|
||||
#
|
||||
# Schedule: every 13 days at 06:17 UTC (off-peak; avoids the :00 mark to
|
||||
# spread cron load).
|
||||
#
|
||||
# Why we own this rather than rely on a third-party traffic-tracker: GitHub's
|
||||
# clone API requires push access, so we'd have to give a stranger our token.
|
||||
# Running it ourselves keeps the data path inside the repo and signed.
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '17 6 */13 * *'
|
||||
workflow_dispatch:
|
||||
# Allow manual runs (e.g., after a release announcement to capture the spike).
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'scripts/track-clones.mjs'
|
||||
- '.github/workflows/clone-tracker.yml'
|
||||
|
||||
permissions:
|
||||
contents: write # to push the snapshot commit
|
||||
actions: read
|
||||
|
||||
concurrency:
|
||||
group: clone-tracker
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
track:
|
||||
name: Snapshot clones for ruflo ecosystem
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout main
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: main
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
|
||||
- name: Install root deps (for @ruvector/rvf binding)
|
||||
run: npm install --legacy-peer-deps --no-audit --no-fund --ignore-scripts
|
||||
|
||||
- name: Run clone tracker
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: node scripts/track-clones.mjs
|
||||
|
||||
- name: Show resulting artifacts
|
||||
run: |
|
||||
ls -la data/clone-data.* 2>/dev/null || echo "(no artifacts produced)"
|
||||
echo ""
|
||||
echo "--- proof ---"
|
||||
cat data/clone-data.proof.json
|
||||
|
||||
- name: Commit + push snapshot
|
||||
run: |
|
||||
git config user.name "ruflo-bot"
|
||||
git config user.email "ruflo-bot@users.noreply.github.com"
|
||||
git add data/clone-data.rvf data/clone-data.ledger.json data/clone-data.proof.json
|
||||
if git diff --cached --quiet; then
|
||||
echo "No changes to commit (snapshot identical to previous?)"
|
||||
exit 0
|
||||
fi
|
||||
SNAP_COUNT=$(node -e "console.log(JSON.parse(require('fs').readFileSync('data/clone-data.proof.json','utf8')).ledger_snapshot_count)")
|
||||
CLONES=$(node -e "console.log(JSON.parse(require('fs').readFileSync('data/clone-data.proof.json','utf8')).latest_snapshot.clones_14d.toLocaleString())")
|
||||
git commit -m "chore(data): clone snapshot #${SNAP_COUNT} — ${CLONES} clones (14d)
|
||||
|
||||
Auto-generated by .github/workflows/clone-tracker.yml.
|
||||
Source: GitHub Traffic API (14-day rolling window).
|
||||
|
||||
Co-Authored-By: ruflo-bot <ruflo-bot@users.noreply.github.com>"
|
||||
git push origin main
|
||||
@@ -0,0 +1,103 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL Advanced"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
schedule:
|
||||
- cron: '42 22 * * 5'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze (${{ matrix.language }})
|
||||
# Runner size impacts CodeQL analysis time. To learn more, please see:
|
||||
# - https://gh.io/recommended-hardware-resources-for-running-codeql
|
||||
# - https://gh.io/supported-runners-and-hardware-resources
|
||||
# - https://gh.io/using-larger-runners (GitHub.com only)
|
||||
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
|
||||
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
# required for all workflows
|
||||
security-events: write
|
||||
|
||||
# required to fetch internal or private CodeQL packs
|
||||
packages: read
|
||||
|
||||
# only required for workflows in private repositories
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- language: actions
|
||||
build-mode: none
|
||||
- language: javascript-typescript
|
||||
build-mode: none
|
||||
- language: rust
|
||||
build-mode: none
|
||||
# CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift'
|
||||
# Use `c-cpp` to analyze code written in C, C++ or both
|
||||
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
|
||||
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
|
||||
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
|
||||
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
|
||||
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
|
||||
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Add any setup steps before running the `github/codeql-action/init` action.
|
||||
# This includes steps like installing compilers or runtimes (`actions/setup-node`
|
||||
# or others). This is typically only required for manual builds.
|
||||
# - name: Setup runtime (example)
|
||||
# uses: actions/setup-example@v1
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
# If the analyze step fails for one of the languages you are analyzing with
|
||||
# "We were unable to automatically build your code", modify the matrix above
|
||||
# to set the build mode to "manual" for that language. Then modify this step
|
||||
# to build your code.
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
- name: Run manual build steps
|
||||
if: matrix.build-mode == 'manual'
|
||||
shell: bash
|
||||
run: |
|
||||
echo 'If you are using a "manual" build mode for one or more of the' \
|
||||
'languages you are analyzing, replace this with the commands to build' \
|
||||
'your code, for example:'
|
||||
echo ' make bootstrap'
|
||||
echo ' make release'
|
||||
exit 1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v4
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
@@ -0,0 +1,41 @@
|
||||
name: codex-integration-audit
|
||||
|
||||
# Guards the Codex ↔ Ruflo integration invariants (issue #1909):
|
||||
# - the `codex` MCP backend uses the real `mcp-server` subcommand
|
||||
# - @claude-flow/codex VERSION const tracks its package.json
|
||||
# - the dual-mode orchestrator / agent defs drive real `codex exec`
|
||||
# - CLI refs are standardized to `ruflo`, `dual run` exposes `--worker`,
|
||||
# generated SKILL.md frontmatter is complete, config.toml emits a
|
||||
# working `ruflo` MCP server.
|
||||
# Pure-Node static checks — no install needed. Build + unit tests are
|
||||
# covered by the main CI workflow.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'v3/@claude-flow/codex/**'
|
||||
- 'ruflo/src/mcp-bridge/**'
|
||||
- 'ruflo/src/ruvocal/mcp-bridge/**'
|
||||
- '.claude/agents/dual-mode/**'
|
||||
- 'scripts/audit-codex-integration.mjs'
|
||||
- '.github/workflows/codex-integration-audit.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'v3/@claude-flow/codex/**'
|
||||
- 'ruflo/src/mcp-bridge/**'
|
||||
- 'ruflo/src/ruvocal/mcp-bridge/**'
|
||||
- '.claude/agents/dual-mode/**'
|
||||
- 'scripts/audit-codex-integration.mjs'
|
||||
- '.github/workflows/codex-integration-audit.yml'
|
||||
|
||||
jobs:
|
||||
audit:
|
||||
name: Codex integration audit
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
- run: node scripts/audit-codex-integration.mjs
|
||||
@@ -0,0 +1,98 @@
|
||||
# Smoke + booster-only bench for plugins/ruflo-cost-tracker.
|
||||
#
|
||||
# Triggers on changes to the plugin or its corpus. Smoke is fast (~100 ms,
|
||||
# pure bash + node --check) so it always runs. The booster-only bench runs
|
||||
# locally — installs `agent-booster` in a sibling temp dir then invokes
|
||||
# bench.mjs from there so node-resolve picks up the package. The LLM and
|
||||
# Anthropic baselines are intentionally OMITTED: they cost real money per
|
||||
# run and require Secret Manager keys; they belong in a manual-trigger or
|
||||
# scheduled workflow with a budget guard, not on every PR.
|
||||
name: cost-tracker-smoke
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'plugins/ruflo-cost-tracker/**'
|
||||
- '.github/workflows/cost-tracker-smoke.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'plugins/ruflo-cost-tracker/**'
|
||||
- '.github/workflows/cost-tracker-smoke.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
smoke:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 8
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Run smoke (39+ structural checks)
|
||||
run: bash plugins/ruflo-cost-tracker/scripts/smoke.sh
|
||||
|
||||
- name: Install agent-booster for the bench
|
||||
run: |
|
||||
mkdir -p .ci-bench
|
||||
cd .ci-bench
|
||||
# npm 11 rejects a package name starting with `.` (the dir name), so
|
||||
# write the manifest explicitly instead of `npm init -y`.
|
||||
printf '{"name":"ci-bench","version":"0.0.0","private":true}\n' > package.json
|
||||
# Pin to the same major as v3/node_modules to keep results comparable.
|
||||
npm install --no-audit --no-fund --silent agent-booster@^0.2
|
||||
|
||||
- name: Run booster-only bench (no LLM cost)
|
||||
run: |
|
||||
cd .ci-bench
|
||||
node ../plugins/ruflo-cost-tracker/scripts/bench.mjs
|
||||
|
||||
- name: Trend report (drift across runs in this checkout)
|
||||
run: node plugins/ruflo-cost-tracker/scripts/trend.mjs
|
||||
# The checkout only contains the runs that were committed — useful
|
||||
# as a sanity check that trend.mjs runs cleanly on real data.
|
||||
|
||||
- name: cost-health composite gate (smoke — no sessions in CI)
|
||||
run: |
|
||||
# In CI there's no cost-tracking namespace, so every subcheck
|
||||
# returns "insufficient data" / "no budget" — the composite must
|
||||
# still exit 0. This guards against regressions where a subcheck
|
||||
# mis-handles empty input and bubbles up a false alert.
|
||||
node plugins/ruflo-cost-tracker/scripts/health.mjs --format json > /tmp/cost-health.json
|
||||
node -e "
|
||||
const r = JSON.parse(require('fs').readFileSync('/tmp/cost-health.json'));
|
||||
if (!r.overall.ok) {
|
||||
console.error('cost-health failed on empty CI input:', JSON.stringify(r, null, 2));
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('cost-health: ' + r.checks.length + ' subchecks, all OK on empty fixture');
|
||||
"
|
||||
|
||||
- name: cost-health integration test (synthetic fixtures incl. iter-75 regression)
|
||||
run: node plugins/ruflo-cost-tracker/scripts/test-health-integration.mjs
|
||||
# 7 end-to-end assertions including the EXACT iter-75 regression
|
||||
# target: budget HARD_STOP via BUDGET_QUIET=1 must propagate as
|
||||
# exit 1 to cost-health's composite gate. Catches cross-script
|
||||
# contract violations that per-script smoke can't see.
|
||||
|
||||
- name: Verify Tier 1 win rate ≥ 0.80 (regression gate)
|
||||
run: |
|
||||
node -e "
|
||||
const d = JSON.parse(require('fs').readFileSync('plugins/ruflo-cost-tracker/docs/benchmarks/runs/latest.json'));
|
||||
if (d.summary.winRate < 0.80) {
|
||||
console.error('REGRESSION: Tier 1 win rate', d.summary.winRate, '< 0.80');
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('Tier 1 win rate:', (d.summary.winRate * 100).toFixed(1) + '%');
|
||||
"
|
||||
|
||||
- name: Upload bench artifact
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: cost-tracker-bench-result
|
||||
path: plugins/ruflo-cost-tracker/docs/benchmarks/runs/latest.json
|
||||
retention-days: 30
|
||||
@@ -0,0 +1,114 @@
|
||||
name: CVE Audit Gate
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
push:
|
||||
branches: [main]
|
||||
schedule:
|
||||
# Daily at 03:47 UTC (stagger from other crons)
|
||||
- cron: '47 3 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: cve-audit-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
# ───────────────────────────────────────────────────────────
|
||||
# Job 1: Root workspace — BLOCKING on critical
|
||||
# Phase 1 target: 0 criticals (ADR-165)
|
||||
# ───────────────────────────────────────────────────────────
|
||||
audit-root:
|
||||
name: Audit root (critical-blocking)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install root dependencies (lockfile only)
|
||||
run: npm install --package-lock-only --ignore-scripts
|
||||
|
||||
- name: npm audit — critical gate (must be 0)
|
||||
run: npm audit --audit-level=critical
|
||||
# Exit 1 if any critical advisory is found.
|
||||
# High/moderate/low are reported but do not block.
|
||||
|
||||
- name: npm audit — high summary (warn only)
|
||||
run: |
|
||||
# Use jq (preinstalled on ubuntu-latest) instead of embedded python
|
||||
# so the static YAML guard doesn't mistake an `if x > 0:` for a
|
||||
# YAML block-mapping key indicator.
|
||||
summary=$(npm audit --json 2>/dev/null | jq -r '.metadata.vulnerabilities | "critical:\(.critical) high:\(.high) moderate:\(.moderate) total:\(.total)"' || echo "audit-failed")
|
||||
echo "::notice::Root audit summary — $summary"
|
||||
high=$(npm audit --json 2>/dev/null | jq -r '.metadata.vulnerabilities.high // 0' || echo 0)
|
||||
if [ "$high" -gt 0 ]; then
|
||||
echo "::warning::Root workspace has $high high-severity advisories (non-blocking — target Phase 5)"
|
||||
fi
|
||||
# Non-blocking: highs are surfaced as warnings in the Actions log
|
||||
|
||||
# ───────────────────────────────────────────────────────────
|
||||
# Job 2: v3 workspace — BLOCKING on critical
|
||||
# Phase 1 target: 0 criticals (ADR-165)
|
||||
# ───────────────────────────────────────────────────────────
|
||||
audit-v3:
|
||||
name: Audit v3 (critical-blocking)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
# v3 uses pnpm for runtime but npm for audit; no npm cache for v3
|
||||
cache: 'npm'
|
||||
|
||||
- name: npm audit v3 — critical gate (must be 0)
|
||||
working-directory: v3
|
||||
run: npm audit --audit-level=critical
|
||||
# Reads v3/package-lock.json generated by npm.
|
||||
# v3/pnpm-lock.yaml is used by pnpm at runtime; this job validates the
|
||||
# npm-readable lockfile kept in sync by the remediation workflow.
|
||||
|
||||
- name: npm audit v3 — high summary (warn only)
|
||||
working-directory: v3
|
||||
run: |
|
||||
# jq, not python — avoids static YAML guard tripping on `if x > 0:`
|
||||
summary=$(npm audit --json 2>/dev/null | jq -r '.metadata.vulnerabilities | "critical:\(.critical) high:\(.high) moderate:\(.moderate) total:\(.total)"' || echo "audit-failed")
|
||||
echo "::notice::v3 audit summary — $summary"
|
||||
high=$(npm audit --json 2>/dev/null | jq -r '.metadata.vulnerabilities.high // 0' || echo 0)
|
||||
if [ "$high" -gt 0 ]; then
|
||||
echo "::warning::v3 workspace has $high high-severity advisories (non-blocking — target Phase 5)"
|
||||
fi
|
||||
|
||||
# ───────────────────────────────────────────────────────────
|
||||
# Job 3: Combined high-severity report (warn only, never blocks)
|
||||
# ───────────────────────────────────────────────────────────
|
||||
audit-high-report:
|
||||
name: High-severity report (warn only)
|
||||
runs-on: ubuntu-latest
|
||||
needs: [audit-root, audit-v3]
|
||||
if: always()
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Combined high/moderate summary
|
||||
run: |
|
||||
# jq instead of inline python so the static YAML guard doesn't
|
||||
# trip on `if x > 0:`-style colons.
|
||||
echo "=== Root workspace ==="
|
||||
npm audit --json 2>/dev/null | jq -r '.metadata.vulnerabilities | " critical:\(.critical) high:\(.high) moderate:\(.moderate) total:\(.total)"' || true
|
||||
echo ""
|
||||
echo "=== v3 workspace ==="
|
||||
(cd v3 && npm audit --json 2>/dev/null | jq -r '.metadata.vulnerabilities | " critical:\(.critical) high:\(.high) moderate:\(.moderate) total:\(.total)"') || true
|
||||
# This job always runs and surfaces a combined summary.
|
||||
# It never sets exit code > 0 so it cannot block merges.
|
||||
@@ -0,0 +1,80 @@
|
||||
name: federation-peer-rust
|
||||
|
||||
# Builds + tests the v3/crates/ruflo-federation-peer crate (ADR-120
|
||||
# Step 3). Triggers only on changes to the crate or this workflow.
|
||||
# Two jobs:
|
||||
#
|
||||
# stable-noop — cargo build + cargo test without --features native.
|
||||
# Verifies the trait surface compiles in a tree that
|
||||
# doesn't have the upstream crate deps materialized.
|
||||
#
|
||||
# stable-native — cargo check --features native. Pulls in
|
||||
# midstreamer-quic@0.2.1 + aimds-*@0.1.1 from
|
||||
# crates.io. Type-checks only (the placeholder impls
|
||||
# don't yet exercise the upstream APIs).
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'v3/crates/ruflo-federation-peer/**'
|
||||
- '.github/workflows/federation-peer-rust.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'v3/crates/ruflo-federation-peer/**'
|
||||
- '.github/workflows/federation-peer-rust.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
stable-noop:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Cache cargo
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
v3/crates/ruflo-federation-peer/target
|
||||
key: federation-peer-${{ runner.os }}-${{ hashFiles('v3/crates/ruflo-federation-peer/Cargo.toml') }}
|
||||
|
||||
- name: cargo build (no native features)
|
||||
working-directory: v3/crates/ruflo-federation-peer
|
||||
run: cargo build --verbose
|
||||
|
||||
- name: cargo test (no native features)
|
||||
working-directory: v3/crates/ruflo-federation-peer
|
||||
run: cargo test --verbose
|
||||
|
||||
- name: cargo clippy (no native features)
|
||||
working-directory: v3/crates/ruflo-federation-peer
|
||||
run: cargo clippy --all-targets -- -D warnings
|
||||
continue-on-error: true
|
||||
|
||||
stable-native:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Cache cargo
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
v3/crates/ruflo-federation-peer/target
|
||||
key: federation-peer-native-${{ runner.os }}-${{ hashFiles('v3/crates/ruflo-federation-peer/Cargo.toml') }}
|
||||
|
||||
- name: cargo check --features native (resolves midstreamer-quic + aimds-*)
|
||||
working-directory: v3/crates/ruflo-federation-peer
|
||||
run: cargo check --features native --verbose
|
||||
@@ -0,0 +1,74 @@
|
||||
name: helpers-manifest-guard
|
||||
|
||||
# Regression guard for issue #2593.
|
||||
#
|
||||
# Root cause: `scripts/sign-helpers.mjs` existed but was NOT wired into
|
||||
# `prepublishOnly`, so intelligence.cjs shipped in 3.24/3.25 with a stale
|
||||
# 3.23.0 manifest hash. writeCriticalHelpers then fail-closed on every CLI
|
||||
# run in stamped projects with a tamper warning.
|
||||
#
|
||||
# The fix (commit b6f4750fa) wired `sign-helpers.mjs` + `verify-helpers.mjs`
|
||||
# into prepublishOnly. This guard fails if either script gets dropped from
|
||||
# prepublishOnly again, or if either script file goes missing.
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'v3/@claude-flow/cli/.claude/helpers/auto-memory-hook.mjs'
|
||||
- 'v3/@claude-flow/cli/.claude/helpers/hook-handler.cjs'
|
||||
- 'v3/@claude-flow/cli/.claude/helpers/intelligence.cjs'
|
||||
- 'v3/@claude-flow/cli/.claude/helpers/helpers.manifest.json'
|
||||
- 'v3/@claude-flow/cli/scripts/sign-helpers.mjs'
|
||||
- 'v3/@claude-flow/cli/scripts/verify-helpers.mjs'
|
||||
- 'v3/@claude-flow/cli/package.json'
|
||||
- '.github/workflows/helpers-manifest-guard.yml'
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'v3/@claude-flow/cli/.claude/helpers/**'
|
||||
- 'v3/@claude-flow/cli/scripts/sign-helpers.mjs'
|
||||
- 'v3/@claude-flow/cli/scripts/verify-helpers.mjs'
|
||||
- 'v3/@claude-flow/cli/package.json'
|
||||
|
||||
jobs:
|
||||
guard:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
# 1. The scripts themselves must exist. If either is deleted, the
|
||||
# prepublishOnly command below silently no-ops on that step and
|
||||
# manifest drift ships again.
|
||||
- name: sign-helpers.mjs and verify-helpers.mjs exist
|
||||
run: |
|
||||
set -euo pipefail
|
||||
test -f v3/@claude-flow/cli/scripts/sign-helpers.mjs || { echo '::error::scripts/sign-helpers.mjs missing (#2593)'; exit 1; }
|
||||
test -f v3/@claude-flow/cli/scripts/verify-helpers.mjs || { echo '::error::scripts/verify-helpers.mjs missing (#2593)'; exit 1; }
|
||||
|
||||
# 2. prepublishOnly MUST invoke BOTH sign-helpers and verify-helpers.
|
||||
# This is the exact regression from #2593 — sign existed but was
|
||||
# never called by publish. Reading scripts.prepublishOnly directly
|
||||
# from the parsed JSON avoids false positives from comments/strings.
|
||||
- name: prepublishOnly wires sign-helpers + verify-helpers
|
||||
run: |
|
||||
set -euo pipefail
|
||||
pp=$(node -e "process.stdout.write(require('./v3/@claude-flow/cli/package.json').scripts.prepublishOnly || '')")
|
||||
echo "prepublishOnly: $pp"
|
||||
echo "$pp" | grep -q 'sign-helpers.mjs' || { echo '::error::prepublishOnly must call scripts/sign-helpers.mjs (#2593)'; exit 1; }
|
||||
echo "$pp" | grep -q 'verify-helpers.mjs' || { echo '::error::prepublishOnly must call scripts/verify-helpers.mjs (#2593)'; exit 1; }
|
||||
|
||||
# 3. verify-helpers must run sign FIRST then verify — verify has to
|
||||
# run AFTER sign or a stale manifest would fail-close the release.
|
||||
- name: sign runs before verify in prepublishOnly
|
||||
run: |
|
||||
set -euo pipefail
|
||||
pp=$(node -e "process.stdout.write(require('./v3/@claude-flow/cli/package.json').scripts.prepublishOnly || '')")
|
||||
sign_pos=$(echo "$pp" | grep -bo 'sign-helpers.mjs' | head -1 | cut -d: -f1)
|
||||
ver_pos=$(echo "$pp" | grep -bo 'verify-helpers.mjs' | head -1 | cut -d: -f1)
|
||||
if [ "$sign_pos" -ge "$ver_pos" ]; then
|
||||
echo "::error::sign-helpers.mjs must run BEFORE verify-helpers.mjs in prepublishOnly (#2593)"
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,886 @@
|
||||
name: 🔗 Cross-Agent Integration Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop, alpha-*]
|
||||
pull_request:
|
||||
branches: [main, develop]
|
||||
schedule:
|
||||
# Run integration tests daily at 3 AM UTC
|
||||
- cron: '0 3 * * *'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
integration_scope:
|
||||
description: 'Integration test scope'
|
||||
required: false
|
||||
default: 'full'
|
||||
type: choice
|
||||
options:
|
||||
- smoke
|
||||
- core
|
||||
- full
|
||||
- stress
|
||||
agent_count:
|
||||
description: 'Maximum agent count for testing'
|
||||
required: false
|
||||
default: '8'
|
||||
test_duration:
|
||||
description: 'Test duration in minutes'
|
||||
required: false
|
||||
default: '10'
|
||||
|
||||
env:
|
||||
NODE_VERSION: '20'
|
||||
MAX_PARALLEL_AGENTS: 8
|
||||
DEFAULT_TIMEOUT: 300000
|
||||
INTEGRATION_DB_PATH: './integration-test.db'
|
||||
|
||||
jobs:
|
||||
# Setup integration test environment
|
||||
integration-setup:
|
||||
name: 🚀 Integration Test Setup
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
test-session-id: ${{ steps.setup.outputs.test-session-id }}
|
||||
agent-matrix: ${{ steps.setup.outputs.agent-matrix }}
|
||||
test-scenarios: ${{ steps.setup.outputs.test-scenarios }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci --legacy-peer-deps
|
||||
|
||||
- name: Install SQLite3
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y sqlite3
|
||||
sqlite3 --version
|
||||
|
||||
- name: Initialize integration test session
|
||||
id: setup
|
||||
run: |
|
||||
TEST_SESSION="integration-$(date +%Y%m%d-%H%M%S)-${{ github.sha }}"
|
||||
echo "test-session-id=$TEST_SESSION" >> $GITHUB_OUTPUT
|
||||
|
||||
# Define agent test matrix based on input scope
|
||||
SCOPE="${{ github.event.inputs.integration_scope || 'full' }}"
|
||||
|
||||
if [ "$SCOPE" = "smoke" ]; then
|
||||
AGENT_MATRIX='{"include":[{"type":"coder","count":2},{"type":"tester","count":1}]}'
|
||||
elif [ "$SCOPE" = "core" ]; then
|
||||
AGENT_MATRIX='{"include":[{"type":"coder","count":3},{"type":"tester","count":2},{"type":"reviewer","count":1},{"type":"planner","count":1}]}'
|
||||
elif [ "$SCOPE" = "stress" ]; then
|
||||
AGENT_MATRIX='{"include":[{"type":"coder","count":5},{"type":"tester","count":3},{"type":"reviewer","count":2},{"type":"planner","count":2},{"type":"researcher","count":1}]}'
|
||||
else
|
||||
# Full scope
|
||||
AGENT_MATRIX='{"include":[{"type":"coder","count":4},{"type":"tester","count":3},{"type":"reviewer","count":2},{"type":"planner","count":2},{"type":"researcher","count":1},{"type":"backend-dev","count":1},{"type":"performance-benchmarker","count":1}]}'
|
||||
fi
|
||||
|
||||
echo "agent-matrix=$AGENT_MATRIX" >> $GITHUB_OUTPUT
|
||||
|
||||
# Define test scenarios
|
||||
TEST_SCENARIOS='["coordination","memory-sharing","task-orchestration","fault-tolerance","performance"]'
|
||||
echo "test-scenarios=$TEST_SCENARIOS" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create integration test database
|
||||
run: |
|
||||
echo "🗄️ Creating integration test database..."
|
||||
|
||||
mkdir -p integration-test-data
|
||||
|
||||
# Initialize SQLite database for integration tests
|
||||
sqlite3 ${{ env.INTEGRATION_DB_PATH }} << 'EOF'
|
||||
CREATE TABLE IF NOT EXISTS test_sessions (
|
||||
id TEXT PRIMARY KEY,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
status TEXT DEFAULT 'pending',
|
||||
metadata TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS agent_tests (
|
||||
id TEXT PRIMARY KEY,
|
||||
session_id TEXT,
|
||||
agent_type TEXT,
|
||||
agent_count INTEGER,
|
||||
status TEXT DEFAULT 'pending',
|
||||
started_at DATETIME,
|
||||
completed_at DATETIME,
|
||||
results TEXT,
|
||||
FOREIGN KEY (session_id) REFERENCES test_sessions (id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS integration_scenarios (
|
||||
id TEXT PRIMARY KEY,
|
||||
session_id TEXT,
|
||||
scenario_name TEXT,
|
||||
status TEXT DEFAULT 'pending',
|
||||
agents_involved TEXT,
|
||||
execution_time_ms INTEGER,
|
||||
success_rate REAL,
|
||||
error_details TEXT,
|
||||
FOREIGN KEY (session_id) REFERENCES test_sessions (id)
|
||||
);
|
||||
|
||||
INSERT INTO test_sessions (id, metadata) VALUES
|
||||
('${{ steps.setup.outputs.test-session-id }}', '{"scope": "${{ github.event.inputs.integration_scope || 'full' }}", "agent_count": "${{ github.event.inputs.agent_count || '8' }}"}');
|
||||
EOF
|
||||
|
||||
cp ${{ env.INTEGRATION_DB_PATH }} integration-test-data/
|
||||
|
||||
- name: Upload integration test setup
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: integration-setup-${{ steps.setup.outputs.test-session-id }}
|
||||
path: integration-test-data/
|
||||
retention-days: 30
|
||||
|
||||
# Test agent coordination
|
||||
test-agent-coordination:
|
||||
name: 🤝 Agent Coordination Tests
|
||||
runs-on: ubuntu-latest
|
||||
needs: integration-setup
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.integration-setup.outputs.agent-matrix) }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci --legacy-peer-deps
|
||||
|
||||
- name: Download integration setup
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: integration-setup-${{ needs.integration-setup.outputs.test-session-id }}
|
||||
path: integration-test-data/
|
||||
|
||||
- name: Initialize swarm for agent testing
|
||||
run: |
|
||||
echo "🚀 Initializing swarm for ${{ matrix.type }} agents (count: ${{ matrix.count }})"
|
||||
|
||||
# Start background swarm process
|
||||
timeout 300s node -e "
|
||||
const { spawn } = require('child_process');
|
||||
|
||||
async function testAgentCoordination() {
|
||||
console.log('Starting agent coordination test...');
|
||||
|
||||
// Simulate swarm initialization
|
||||
console.log('Swarm initialized with topology: mesh');
|
||||
|
||||
// Spawn agents
|
||||
for (let i = 0; i < ${{ matrix.count }}; i++) {
|
||||
console.log(\`Agent \${i + 1} (${{ matrix.type }}): Spawned and ready\`);
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
}
|
||||
|
||||
// Test coordination
|
||||
console.log('Testing agent coordination...');
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
|
||||
console.log('Coordination test completed successfully');
|
||||
return true;
|
||||
}
|
||||
|
||||
testAgentCoordination().catch(console.error);
|
||||
" > coordination-test-${{ matrix.type }}.log 2>&1 || true
|
||||
|
||||
- name: Test inter-agent communication
|
||||
run: |
|
||||
echo "📡 Testing inter-agent communication for ${{ matrix.type }}"
|
||||
|
||||
node -e "
|
||||
async function testCommunication() {
|
||||
const results = {
|
||||
agentType: '${{ matrix.type }}',
|
||||
agentCount: ${{ matrix.count }},
|
||||
communicationTests: [],
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
// Simulate communication tests
|
||||
for (let i = 0; i < ${{ matrix.count }}; i++) {
|
||||
const test = {
|
||||
agentId: \`\${{ matrix.type }}-\${i + 1}\`,
|
||||
messagesSent: Math.floor(Math.random() * 50) + 10,
|
||||
messagesReceived: Math.floor(Math.random() * 50) + 10,
|
||||
averageLatency: Math.floor(Math.random() * 100) + 20,
|
||||
successRate: 0.95 + Math.random() * 0.05
|
||||
};
|
||||
results.communicationTests.push(test);
|
||||
}
|
||||
|
||||
console.log('Communication test results:', JSON.stringify(results, null, 2));
|
||||
require('fs').writeFileSync('communication-results-${{ matrix.type }}.json', JSON.stringify(results, null, 2));
|
||||
}
|
||||
|
||||
testCommunication().catch(console.error);
|
||||
"
|
||||
|
||||
- name: Test task distribution
|
||||
run: |
|
||||
echo "📋 Testing task distribution for ${{ matrix.type }}"
|
||||
|
||||
node -e "
|
||||
async function testTaskDistribution() {
|
||||
const results = {
|
||||
agentType: '${{ matrix.type }}',
|
||||
agentCount: ${{ matrix.count }},
|
||||
taskDistribution: {
|
||||
totalTasks: 50,
|
||||
tasksPerAgent: [],
|
||||
loadBalance: 0,
|
||||
completionRate: 0
|
||||
},
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
let totalAssigned = 0;
|
||||
let totalCompleted = 0;
|
||||
|
||||
for (let i = 0; i < ${{ matrix.count }}; i++) {
|
||||
const tasksAssigned = Math.floor(Math.random() * 15) + 5;
|
||||
const tasksCompleted = Math.floor(tasksAssigned * (0.8 + Math.random() * 0.2));
|
||||
|
||||
results.taskDistribution.tasksPerAgent.push({
|
||||
agentId: \`\${{ matrix.type }}-\${i + 1}\`,
|
||||
assigned: tasksAssigned,
|
||||
completed: tasksCompleted,
|
||||
efficiency: tasksCompleted / tasksAssigned
|
||||
});
|
||||
|
||||
totalAssigned += tasksAssigned;
|
||||
totalCompleted += tasksCompleted;
|
||||
}
|
||||
|
||||
results.taskDistribution.completionRate = totalCompleted / totalAssigned;
|
||||
|
||||
// Calculate load balance (standard deviation of task distribution)
|
||||
const avgTasks = totalAssigned / ${{ matrix.count }};
|
||||
const variance = results.taskDistribution.tasksPerAgent.reduce((sum, agent) => {
|
||||
return sum + Math.pow(agent.assigned - avgTasks, 2);
|
||||
}, 0) / ${{ matrix.count }};
|
||||
results.taskDistribution.loadBalance = 1 - (Math.sqrt(variance) / avgTasks);
|
||||
|
||||
console.log('Task distribution results:', JSON.stringify(results, null, 2));
|
||||
require('fs').writeFileSync('task-distribution-${{ matrix.type }}.json', JSON.stringify(results, null, 2));
|
||||
}
|
||||
|
||||
testTaskDistribution().catch(console.error);
|
||||
"
|
||||
|
||||
- name: Upload agent coordination results
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coordination-results-${{ matrix.type }}-${{ needs.integration-setup.outputs.test-session-id }}
|
||||
path: |
|
||||
coordination-test-${{ matrix.type }}.log
|
||||
communication-results-${{ matrix.type }}.json
|
||||
task-distribution-${{ matrix.type }}.json
|
||||
retention-days: 30
|
||||
|
||||
# Test memory sharing integration
|
||||
test-memory-integration:
|
||||
name: 🧠 Memory Sharing Integration
|
||||
runs-on: ubuntu-latest
|
||||
needs: integration-setup
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci --legacy-peer-deps
|
||||
|
||||
- name: Download integration setup
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: integration-setup-${{ needs.integration-setup.outputs.test-session-id }}
|
||||
path: integration-test-data/
|
||||
|
||||
- name: Test shared memory operations
|
||||
run: |
|
||||
echo "🧠 Testing shared memory operations..."
|
||||
|
||||
node -e "
|
||||
async function testSharedMemory() {
|
||||
const results = {
|
||||
sessionId: '${{ needs.integration-setup.outputs.test-session-id }}',
|
||||
memoryTests: [],
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
// Test memory store operations
|
||||
const operations = ['store', 'retrieve', 'update', 'delete', 'search'];
|
||||
|
||||
for (const operation of operations) {
|
||||
const test = {
|
||||
operation: operation,
|
||||
testCases: [],
|
||||
averageLatency: 0,
|
||||
successRate: 0
|
||||
};
|
||||
|
||||
// Simulate test cases for each operation
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const latency = Math.floor(Math.random() * 50) + 10;
|
||||
const success = Math.random() > 0.05; // 95% success rate
|
||||
|
||||
test.testCases.push({
|
||||
caseId: i + 1,
|
||||
latency: latency,
|
||||
success: success,
|
||||
memoryKey: \`test-key-\${operation}-\${i}\`,
|
||||
dataSize: Math.floor(Math.random() * 1000) + 100
|
||||
});
|
||||
}
|
||||
|
||||
test.averageLatency = test.testCases.reduce((sum, tc) => sum + tc.latency, 0) / test.testCases.length;
|
||||
test.successRate = test.testCases.filter(tc => tc.success).length / test.testCases.length;
|
||||
|
||||
results.memoryTests.push(test);
|
||||
}
|
||||
|
||||
console.log('Memory integration results:', JSON.stringify(results, null, 2));
|
||||
require('fs').writeFileSync('memory-integration-results.json', JSON.stringify(results, null, 2));
|
||||
}
|
||||
|
||||
testSharedMemory().catch(console.error);
|
||||
"
|
||||
|
||||
- name: Test cross-agent memory synchronization
|
||||
run: |
|
||||
echo "🔄 Testing cross-agent memory synchronization..."
|
||||
|
||||
node -e "
|
||||
async function testMemorySync() {
|
||||
const results = {
|
||||
syncTests: [],
|
||||
conflictResolution: [],
|
||||
consistencyCheck: {
|
||||
passed: true,
|
||||
inconsistencies: []
|
||||
}
|
||||
};
|
||||
|
||||
// Simulate multiple agents accessing shared memory
|
||||
const agents = ['coder-1', 'tester-1', 'reviewer-1', 'planner-1'];
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const syncTest = {
|
||||
testId: i + 1,
|
||||
participants: agents.slice(0, Math.floor(Math.random() * 3) + 2),
|
||||
syncLatency: Math.floor(Math.random() * 200) + 50,
|
||||
conflictsDetected: Math.floor(Math.random() * 3),
|
||||
conflictsResolved: 0,
|
||||
dataConsistency: true
|
||||
};
|
||||
|
||||
syncTest.conflictsResolved = syncTest.conflictsDetected;
|
||||
results.syncTests.push(syncTest);
|
||||
}
|
||||
|
||||
console.log('Memory synchronization results:', JSON.stringify(results, null, 2));
|
||||
require('fs').writeFileSync('memory-sync-results.json', JSON.stringify(results, null, 2));
|
||||
}
|
||||
|
||||
testMemorySync().catch(console.error);
|
||||
"
|
||||
|
||||
- name: Upload memory integration results
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: memory-integration-${{ needs.integration-setup.outputs.test-session-id }}
|
||||
path: |
|
||||
memory-integration-results.json
|
||||
memory-sync-results.json
|
||||
retention-days: 30
|
||||
|
||||
# Test fault tolerance
|
||||
test-fault-tolerance:
|
||||
name: 🛡️ Fault Tolerance Tests
|
||||
runs-on: ubuntu-latest
|
||||
needs: integration-setup
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci --legacy-peer-deps
|
||||
|
||||
- name: Test agent failure recovery
|
||||
run: |
|
||||
echo "🔧 Testing agent failure recovery..."
|
||||
|
||||
node -e "
|
||||
async function testFailureRecovery() {
|
||||
const results = {
|
||||
failureTests: [],
|
||||
recoveryMetrics: {
|
||||
averageRecoveryTime: 0,
|
||||
successfulRecoveries: 0,
|
||||
totalFailures: 0
|
||||
}
|
||||
};
|
||||
|
||||
// Simulate agent failures and recovery
|
||||
const failureScenarios = [
|
||||
'agent-crash',
|
||||
'network-timeout',
|
||||
'memory-overflow',
|
||||
'task-timeout',
|
||||
'communication-failure'
|
||||
];
|
||||
|
||||
for (const scenario of failureScenarios) {
|
||||
const test = {
|
||||
scenario: scenario,
|
||||
agentId: \`test-agent-\${Math.floor(Math.random() * 5) + 1}\`,
|
||||
failureTime: new Date().toISOString(),
|
||||
detectionTime: Math.floor(Math.random() * 5000) + 1000,
|
||||
recoveryTime: Math.floor(Math.random() * 10000) + 3000,
|
||||
recoverySuccess: Math.random() > 0.1, // 90% recovery success
|
||||
impact: {
|
||||
tasksLost: Math.floor(Math.random() * 5),
|
||||
downtime: Math.floor(Math.random() * 30000) + 5000
|
||||
}
|
||||
};
|
||||
|
||||
results.failureTests.push(test);
|
||||
results.recoveryMetrics.totalFailures++;
|
||||
if (test.recoverySuccess) {
|
||||
results.recoveryMetrics.successfulRecoveries++;
|
||||
}
|
||||
}
|
||||
|
||||
if (results.recoveryMetrics.successfulRecoveries > 0) {
|
||||
const successfulTests = results.failureTests.filter(t => t.recoverySuccess);
|
||||
results.recoveryMetrics.averageRecoveryTime =
|
||||
successfulTests.reduce((sum, t) => sum + t.recoveryTime, 0) / successfulTests.length;
|
||||
}
|
||||
|
||||
console.log('Fault tolerance results:', JSON.stringify(results, null, 2));
|
||||
require('fs').writeFileSync('fault-tolerance-results.json', JSON.stringify(results, null, 2));
|
||||
}
|
||||
|
||||
testFailureRecovery().catch(console.error);
|
||||
"
|
||||
|
||||
- name: Test system resilience
|
||||
run: |
|
||||
echo "🏋️ Testing system resilience under load..."
|
||||
|
||||
node -e "
|
||||
async function testResilience() {
|
||||
const results = {
|
||||
loadTests: [],
|
||||
systemMetrics: {
|
||||
maxConcurrentAgents: 0,
|
||||
memoryUsage: [],
|
||||
responseTime: [],
|
||||
errorRate: 0
|
||||
}
|
||||
};
|
||||
|
||||
// Simulate increasing load
|
||||
for (let agentCount = 2; agentCount <= 10; agentCount += 2) {
|
||||
const loadTest = {
|
||||
agentCount: agentCount,
|
||||
duration: 30000, // 30 seconds
|
||||
requestsPerSecond: agentCount * 5,
|
||||
averageResponseTime: Math.floor(Math.random() * 500) + 100,
|
||||
errorCount: Math.floor(Math.random() * agentCount),
|
||||
memoryUsageMB: Math.floor(Math.random() * 200) + agentCount * 10,
|
||||
systemStable: true
|
||||
};
|
||||
|
||||
loadTest.systemStable = loadTest.errorCount < agentCount * 0.1;
|
||||
results.loadTests.push(loadTest);
|
||||
|
||||
if (loadTest.systemStable) {
|
||||
results.systemMetrics.maxConcurrentAgents = agentCount;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Resilience test results:', JSON.stringify(results, null, 2));
|
||||
require('fs').writeFileSync('resilience-results.json', JSON.stringify(results, null, 2));
|
||||
}
|
||||
|
||||
testResilience().catch(console.error);
|
||||
"
|
||||
|
||||
- name: Upload fault tolerance results
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: fault-tolerance-${{ needs.integration-setup.outputs.test-session-id }}
|
||||
path: |
|
||||
fault-tolerance-results.json
|
||||
resilience-results.json
|
||||
retention-days: 30
|
||||
|
||||
# Test performance under load
|
||||
test-performance-integration:
|
||||
name: ⚡ Performance Integration Tests
|
||||
runs-on: ubuntu-latest
|
||||
needs: integration-setup
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci --legacy-peer-deps
|
||||
|
||||
- name: Test multi-agent performance
|
||||
run: |
|
||||
echo "⚡ Testing multi-agent performance..."
|
||||
|
||||
node -e "
|
||||
async function testPerformance() {
|
||||
const results = {
|
||||
performanceTests: [],
|
||||
benchmarks: {
|
||||
taskThroughput: 0,
|
||||
averageLatency: 0,
|
||||
resourceUtilization: {}
|
||||
}
|
||||
};
|
||||
|
||||
// Test different agent configurations
|
||||
const configurations = [
|
||||
{ agents: 2, tasks: 10 },
|
||||
{ agents: 4, tasks: 25 },
|
||||
{ agents: 6, tasks: 40 },
|
||||
{ agents: 8, tasks: 60 }
|
||||
];
|
||||
|
||||
for (const config of configurations) {
|
||||
const perfTest = {
|
||||
configuration: config,
|
||||
executionTime: Math.floor(Math.random() * 10000) + 5000,
|
||||
tasksCompleted: Math.floor(config.tasks * (0.9 + Math.random() * 0.1)),
|
||||
averageTaskTime: 0,
|
||||
throughput: 0,
|
||||
resourceUsage: {
|
||||
cpu: Math.floor(Math.random() * 80) + 20,
|
||||
memory: Math.floor(Math.random() * 512) + 128,
|
||||
network: Math.floor(Math.random() * 100) + 50
|
||||
}
|
||||
};
|
||||
|
||||
perfTest.averageTaskTime = perfTest.executionTime / perfTest.tasksCompleted;
|
||||
perfTest.throughput = (perfTest.tasksCompleted / perfTest.executionTime) * 1000;
|
||||
|
||||
results.performanceTests.push(perfTest);
|
||||
}
|
||||
|
||||
// Calculate overall benchmarks
|
||||
results.benchmarks.taskThroughput =
|
||||
results.performanceTests.reduce((sum, t) => sum + t.throughput, 0) / results.performanceTests.length;
|
||||
results.benchmarks.averageLatency =
|
||||
results.performanceTests.reduce((sum, t) => sum + t.averageTaskTime, 0) / results.performanceTests.length;
|
||||
|
||||
console.log('Performance integration results:', JSON.stringify(results, null, 2));
|
||||
require('fs').writeFileSync('performance-integration-results.json', JSON.stringify(results, null, 2));
|
||||
}
|
||||
|
||||
testPerformance().catch(console.error);
|
||||
"
|
||||
|
||||
- name: Test scalability limits
|
||||
run: |
|
||||
echo "📈 Testing scalability limits..."
|
||||
|
||||
node -e "
|
||||
async function testScalability() {
|
||||
const results = {
|
||||
scalabilityTests: [],
|
||||
limits: {
|
||||
maxAgents: 0,
|
||||
optimalAgentCount: 0,
|
||||
performanceDegradationPoint: 0
|
||||
}
|
||||
};
|
||||
|
||||
let bestPerformance = 0;
|
||||
let degradationDetected = false;
|
||||
|
||||
// Test scaling from 1 to 15 agents
|
||||
for (let agentCount = 1; agentCount <= 15; agentCount++) {
|
||||
const throughput = Math.max(0, 100 - (agentCount > 8 ? Math.pow(agentCount - 8, 2) * 2 : 0)) + Math.random() * 10;
|
||||
const latency = 50 + (agentCount > 6 ? Math.pow(agentCount - 6, 1.5) * 10 : 0) + Math.random() * 20;
|
||||
|
||||
const scalTest = {
|
||||
agentCount: agentCount,
|
||||
throughput: Math.round(throughput * 100) / 100,
|
||||
latency: Math.round(latency * 100) / 100,
|
||||
stability: agentCount <= 10,
|
||||
efficiency: Math.round((throughput / agentCount) * 100) / 100
|
||||
};
|
||||
|
||||
results.scalabilityTests.push(scalTest);
|
||||
|
||||
if (scalTest.throughput > bestPerformance) {
|
||||
bestPerformance = scalTest.throughput;
|
||||
results.limits.optimalAgentCount = agentCount;
|
||||
}
|
||||
|
||||
if (!degradationDetected && agentCount > 1) {
|
||||
const prevTest = results.scalabilityTests[agentCount - 2];
|
||||
if (scalTest.throughput < prevTest.throughput * 0.95) {
|
||||
results.limits.performanceDegradationPoint = agentCount;
|
||||
degradationDetected = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (scalTest.stability) {
|
||||
results.limits.maxAgents = agentCount;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Scalability test results:', JSON.stringify(results, null, 2));
|
||||
require('fs').writeFileSync('scalability-results.json', JSON.stringify(results, null, 2));
|
||||
}
|
||||
|
||||
testScalability().catch(console.error);
|
||||
"
|
||||
|
||||
- name: Upload performance integration results
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: performance-integration-${{ needs.integration-setup.outputs.test-session-id }}
|
||||
path: |
|
||||
performance-integration-results.json
|
||||
scalability-results.json
|
||||
retention-days: 30
|
||||
|
||||
# Generate integration test report
|
||||
integration-test-report:
|
||||
name: 📊 Integration Test Report
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- integration-setup
|
||||
- test-agent-coordination
|
||||
- test-memory-integration
|
||||
- test-fault-tolerance
|
||||
- test-performance-integration
|
||||
if: always()
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download all test artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: integration-test-results/
|
||||
|
||||
- name: Generate comprehensive report
|
||||
run: |
|
||||
echo "📊 Generating integration test report..."
|
||||
|
||||
mkdir -p final-report
|
||||
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
async function generateReport() {
|
||||
const report = {
|
||||
sessionId: '${{ needs.integration-setup.outputs.test-session-id }}',
|
||||
timestamp: new Date().toISOString(),
|
||||
summary: {
|
||||
totalTests: 0,
|
||||
passedTests: 0,
|
||||
failedTests: 0,
|
||||
overallSuccess: false
|
||||
},
|
||||
testResults: {
|
||||
coordination: { status: 'unknown', details: {} },
|
||||
memory: { status: 'unknown', details: {} },
|
||||
faultTolerance: { status: 'unknown', details: {} },
|
||||
performance: { status: 'unknown', details: {} }
|
||||
},
|
||||
recommendations: []
|
||||
};
|
||||
|
||||
try {
|
||||
// Parse coordination results
|
||||
const coordFiles = fs.readdirSync('integration-test-results').filter(f => f.includes('coordination-results'));
|
||||
if (coordFiles.length > 0) {
|
||||
report.testResults.coordination.status = 'passed';
|
||||
report.testResults.coordination.details = { agentTypes: coordFiles.length };
|
||||
report.summary.passedTests++;
|
||||
}
|
||||
report.summary.totalTests++;
|
||||
|
||||
// Parse memory results
|
||||
const memoryFiles = fs.readdirSync('integration-test-results').filter(f => f.includes('memory-integration'));
|
||||
if (memoryFiles.length > 0) {
|
||||
report.testResults.memory.status = 'passed';
|
||||
report.summary.passedTests++;
|
||||
}
|
||||
report.summary.totalTests++;
|
||||
|
||||
// Parse fault tolerance results
|
||||
const faultFiles = fs.readdirSync('integration-test-results').filter(f => f.includes('fault-tolerance'));
|
||||
if (faultFiles.length > 0) {
|
||||
report.testResults.faultTolerance.status = 'passed';
|
||||
report.summary.passedTests++;
|
||||
}
|
||||
report.summary.totalTests++;
|
||||
|
||||
// Parse performance results
|
||||
const perfFiles = fs.readdirSync('integration-test-results').filter(f => f.includes('performance-integration'));
|
||||
if (perfFiles.length > 0) {
|
||||
report.testResults.performance.status = 'passed';
|
||||
report.summary.passedTests++;
|
||||
}
|
||||
report.summary.totalTests++;
|
||||
|
||||
} catch (e) {
|
||||
console.error('Error parsing test results:', e);
|
||||
}
|
||||
|
||||
report.summary.failedTests = report.summary.totalTests - report.summary.passedTests;
|
||||
report.summary.overallSuccess = report.summary.failedTests === 0;
|
||||
|
||||
// Generate recommendations
|
||||
if (report.testResults.coordination.status !== 'passed') {
|
||||
report.recommendations.push('Review agent coordination mechanisms');
|
||||
}
|
||||
if (report.testResults.memory.status !== 'passed') {
|
||||
report.recommendations.push('Improve shared memory synchronization');
|
||||
}
|
||||
if (report.testResults.faultTolerance.status !== 'passed') {
|
||||
report.recommendations.push('Enhance fault tolerance and recovery procedures');
|
||||
}
|
||||
if (report.testResults.performance.status !== 'passed') {
|
||||
report.recommendations.push('Optimize performance for multi-agent scenarios');
|
||||
}
|
||||
|
||||
fs.writeFileSync('final-report/integration-test-report.json', JSON.stringify(report, null, 2));
|
||||
|
||||
// Generate markdown report
|
||||
const markdown = \`
|
||||
# 🔗 Cross-Agent Integration Test Report
|
||||
|
||||
**Session ID:** \${report.sessionId}
|
||||
**Timestamp:** \${report.timestamp}
|
||||
**Overall Status:** \${report.summary.overallSuccess ? '✅ PASSED' : '❌ FAILED'}
|
||||
|
||||
## Summary
|
||||
|
||||
- **Total Tests:** \${report.summary.totalTests}
|
||||
- **Passed:** \${report.summary.passedTests}
|
||||
- **Failed:** \${report.summary.failedTests}
|
||||
- **Success Rate:** \${((report.summary.passedTests / report.summary.totalTests) * 100).toFixed(1)}%
|
||||
|
||||
## Test Results
|
||||
|
||||
| Component | Status | Details |
|
||||
|-----------|--------|---------|
|
||||
| Agent Coordination | \${report.testResults.coordination.status === 'passed' ? '✅' : '❌'} | Multi-agent communication and task distribution |
|
||||
| Memory Integration | \${report.testResults.memory.status === 'passed' ? '✅' : '❌'} | Shared memory operations and synchronization |
|
||||
| Fault Tolerance | \${report.testResults.faultTolerance.status === 'passed' ? '✅' : '❌'} | Failure recovery and system resilience |
|
||||
| Performance | \${report.testResults.performance.status === 'passed' ? '✅' : '❌'} | Multi-agent performance and scalability |
|
||||
|
||||
## Recommendations
|
||||
|
||||
\${report.recommendations.length > 0 ? report.recommendations.map(r => \`- \${r}\`).join('\\n') : '- All integration tests passed successfully!'}
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Review detailed test artifacts
|
||||
2. Address any failed test scenarios
|
||||
3. Monitor integration performance in production
|
||||
|
||||
---
|
||||
*Generated by Cross-Agent Integration Test Pipeline*
|
||||
\`;
|
||||
|
||||
fs.writeFileSync('final-report/integration-test-report.md', markdown);
|
||||
|
||||
console.log('Integration test report generated');
|
||||
console.log(\`Overall status: \${report.summary.overallSuccess ? 'SUCCESS' : 'FAILURE'}\`);
|
||||
console.log(\`Tests passed: \${report.summary.passedTests}/\${report.summary.totalTests}\`);
|
||||
}
|
||||
|
||||
generateReport().catch(console.error);
|
||||
"
|
||||
|
||||
- name: Upload final integration report
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: integration-test-final-report-${{ needs.integration-setup.outputs.test-session-id }}
|
||||
path: final-report/
|
||||
retention-days: 90
|
||||
|
||||
- name: Comment PR with integration results
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: actions/github-script@v7
|
||||
continue-on-error: true
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
|
||||
try {
|
||||
const report = fs.readFileSync('final-report/integration-test-report.md', 'utf8');
|
||||
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: `## 🔗 Integration Test Results\n\n${report}`
|
||||
});
|
||||
} catch (e) {
|
||||
console.log('Could not post integration test results to PR');
|
||||
}
|
||||
|
||||
- name: Set integration test status
|
||||
run: |
|
||||
echo "🎯 Integration test pipeline completed"
|
||||
|
||||
# Check if critical tests passed
|
||||
if [ -f "final-report/integration-test-report.json" ]; then
|
||||
OVERALL_SUCCESS=$(node -e "console.log(JSON.parse(require('fs').readFileSync('final-report/integration-test-report.json', 'utf8')).summary.overallSuccess)")
|
||||
|
||||
if [ "$OVERALL_SUCCESS" = "false" ]; then
|
||||
echo "❌ Integration tests failed"
|
||||
exit 1
|
||||
else
|
||||
echo "✅ Integration tests passed"
|
||||
fi
|
||||
else
|
||||
echo "⚠️ Could not determine integration test status"
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,640 @@
|
||||
# MetaHarness integration gates — ADR-150 Phase 1 MVP.
|
||||
#
|
||||
# Three jobs, all fast (subprocess invocations of `npx metaharness`):
|
||||
# 1. score — fail if ruflo's own harnessFit drops below 70
|
||||
# 2. mcp-scan — fail on any HIGH-severity MCP finding in ruflo
|
||||
# 3. router-compat — exercise `@metaharness/router.Router` constructor;
|
||||
# catches breaking API changes before publish
|
||||
#
|
||||
# Triggers on changes that could move any of these signals:
|
||||
# - the ruflo-metaharness plugin
|
||||
# - the @metaharness/router optional dep version in v3/@claude-flow/cli
|
||||
# - the v3 ruvector router-trajectory + neural-router source
|
||||
# - this workflow file
|
||||
name: metaharness-ci
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'plugins/ruflo-metaharness/**'
|
||||
- 'v3/@claude-flow/cli/package.json'
|
||||
- 'v3/@claude-flow/cli/src/ruvector/neural-router.ts'
|
||||
- 'v3/@claude-flow/cli/src/ruvector/router-trajectory.ts'
|
||||
- 'scripts/check-metaharness-compat.mjs'
|
||||
- '.github/workflows/metaharness-ci.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'plugins/ruflo-metaharness/**'
|
||||
- 'v3/@claude-flow/cli/package.json'
|
||||
- 'v3/@claude-flow/cli/src/ruvector/neural-router.ts'
|
||||
- 'v3/@claude-flow/cli/src/ruvector/router-trajectory.ts'
|
||||
- 'scripts/check-metaharness-compat.mjs'
|
||||
- '.github/workflows/metaharness-ci.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
score:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Plugin structural smoke
|
||||
run: bash plugins/ruflo-metaharness/scripts/smoke.sh
|
||||
|
||||
- name: harness-score against ruflo (alert on harnessFit < 70)
|
||||
run: |
|
||||
node plugins/ruflo-metaharness/scripts/score.mjs \
|
||||
--path . \
|
||||
--alert-on-fit-below 70 \
|
||||
--format json > /tmp/metaharness-score.json
|
||||
cat /tmp/metaharness-score.json
|
||||
|
||||
- name: Upload score artifact
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: metaharness-score
|
||||
path: /tmp/metaharness-score.json
|
||||
retention-days: 30
|
||||
|
||||
- name: harness-genome against ruflo (alert on risk_score > 0.5)
|
||||
run: |
|
||||
node plugins/ruflo-metaharness/scripts/genome.mjs \
|
||||
--path . \
|
||||
--alert-on-risk-above 0.5 \
|
||||
--format json > /tmp/metaharness-genome.json
|
||||
cat /tmp/metaharness-genome.json
|
||||
|
||||
- name: Upload genome artifact
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: metaharness-genome
|
||||
path: /tmp/metaharness-genome.json
|
||||
retention-days: 30
|
||||
|
||||
mcp-scan:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: harness mcp-scan against ruflo (fail on HIGH findings)
|
||||
run: |
|
||||
# Exit 0 on no/low-severity findings; exit 1 on HIGH.
|
||||
# Exit 0 also when metaharness is unavailable — ADR-150
|
||||
# graceful-degradation rule #3 lets ruflo continue without it.
|
||||
node plugins/ruflo-metaharness/scripts/mcp-scan.mjs \
|
||||
--path . \
|
||||
--fail-on high \
|
||||
--format json > /tmp/metaharness-mcp-scan.json
|
||||
cat /tmp/metaharness-mcp-scan.json
|
||||
|
||||
- name: Upload mcp-scan artifact
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: metaharness-mcp-scan
|
||||
path: /tmp/metaharness-mcp-scan.json
|
||||
retention-days: 30
|
||||
|
||||
router-compat:
|
||||
runs-on: ubuntu-latest
|
||||
# iter 136: 5m → 12m — parallel-pipeline e2e + bench-overhead step
|
||||
# consistently exceeds 5m on shared runners (#2405 PR run + manual re-run
|
||||
# both canceled at exactly 5m0s).
|
||||
timeout-minutes: 12
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Compat check against current @metaharness/router pin
|
||||
# Exercises the Router constructor, fromExamples factory, and
|
||||
# routedBy field shape. If any of these change in @metaharness/
|
||||
# router@0.4.x, this fails BEFORE we publish a ruflo release
|
||||
# that would break neural-router.ts at runtime.
|
||||
run: node scripts/check-metaharness-compat.mjs
|
||||
|
||||
- name: Parallel-pipeline e2e integration test (ADR-150 iter 13)
|
||||
# Exercises recorder TS module ↔ JSONL ↔ analyzer composition.
|
||||
# 25 assertions including the exact 3 thresholds from
|
||||
# ADR-150 review-round-1, plus --strict semantics for both
|
||||
# promotable and non-promotable paths.
|
||||
run: node plugins/ruflo-metaharness/scripts/test-parallel-pipeline.mjs
|
||||
|
||||
- name: MCP tool runtime contract test (ADR-150 iter 23)
|
||||
# Builds the CLI dist, imports metaharnessTools, invokes every
|
||||
# handler with minimal input, asserts each returns the
|
||||
# {success, data, degraded, exitCode} contract without throwing.
|
||||
# 65 assertions across 7 tools. Slow path (~50s) due to npx
|
||||
# warmup; runs only on PRs that touched the MCP wiring.
|
||||
run: |
|
||||
# iter 117 — `npm install` in a single v3 workspace pkg fails with
|
||||
# EUNSUPPORTEDPROTOCOL "workspace:*" because the workspace uses pnpm
|
||||
# (v3/pnpm-workspace.yaml). Install at workspace root with pnpm.
|
||||
# iter 119 — cli imports from workspace siblings; build them first.
|
||||
# iter 127 — switched from `-F "@claude-flow/cli..."` to `-r` because
|
||||
# cli imports from `../../../swarm/src/...` (CROSS-PACKAGE relative
|
||||
# path, NOT via the package dep graph). pnpm filter follows
|
||||
# package.json deps and missed swarm, producing TS6305 "Output file
|
||||
# has not been built from source file" errors. `-r --no-bail` builds
|
||||
# all workspace packages in topological order, tolerating unrelated
|
||||
# failures (which the cli build doesn't need to succeed).
|
||||
# pnpm@8.15.0 matches v3/package.json's packageManager pin.
|
||||
cd v3
|
||||
# iter 139 — npm/pnpm fetch retries via env. Several postinstalls
|
||||
# (onnxruntime-node, sharp) download CDN binaries; GH runners
|
||||
# observed ECONNRESET mid-fetch with no auto-retry. These env
|
||||
# vars push npm/pnpm to retry 5x with 1s→60s backoff.
|
||||
export npm_config_fetch_retries=5
|
||||
export npm_config_fetch_retry_mintimeout=1000
|
||||
export npm_config_fetch_retry_maxtimeout=60000
|
||||
export npm_config_fetch_retry_factor=2
|
||||
npx -y pnpm@8.15.0 install --frozen-lockfile --reporter=append-only
|
||||
# iter 138 — pnpm rebuild sharp (iter 134) didn't fix the missing
|
||||
# linux-x64 .node binary because pnpm 8.15's store doesn't honor
|
||||
# sharp's vendored download script in some CI configs. Force the
|
||||
# platform-specific install directly into the workspace's sharp
|
||||
# node_modules path. The CLI's transitive agentic-flow chain
|
||||
# require()s sharp at bootstrap; without this binary the CLI
|
||||
# crashes with "Cannot find module '../build/Release/
|
||||
# sharp-linux-x64.node'" before eject's action even runs.
|
||||
SHARP_DIR=$(find node_modules/.pnpm -maxdepth 4 -type d -name sharp 2>/dev/null | head -1)
|
||||
if [ -n "$SHARP_DIR" ]; then
|
||||
( cd "$SHARP_DIR" && npm install --no-audit --no-fund --ignore-scripts=false 2>&1 | tail -3 || true )
|
||||
ls "$SHARP_DIR/build/Release/" 2>&1 | grep -E '\.node$' || echo "sharp prebuild missing — runtime may fall back to optional path"
|
||||
fi
|
||||
# iter 127b — `|| true` because cli's tsc emits TS6305 ("Output
|
||||
# file has not been built from source file") for cross-package
|
||||
# relative imports from @claude-flow/swarm/src/..., even though
|
||||
# the JS gets emitted correctly. Mirror the existing v3-ci.yml
|
||||
# pattern: keep building, then assert the critical dist exists.
|
||||
npx -y pnpm@8.15.0 -r --no-bail run build || true
|
||||
test -f @claude-flow/cli/dist/src/index.js \
|
||||
|| { echo "::error::cli build did not produce dist/src/index.js"; exit 1; }
|
||||
cd @claude-flow/cli
|
||||
cd ../../..
|
||||
node plugins/ruflo-metaharness/scripts/test-mcp-tools.mjs
|
||||
|
||||
- name: Regression gate — iter-12 dispatch overhead < 500ns (iter 24/25)
|
||||
# Micro-benchmark proving the iter-12 "zero default-path overhead"
|
||||
# claim with measured numbers. Threshold 500ns chosen as ~3.5x
|
||||
# headroom over the iter-24 baseline of ~147ns on Apple Silicon
|
||||
# /Node 22. Fails the PR if someone accidentally inflates the
|
||||
# route() hot path on the default path.
|
||||
run: |
|
||||
node plugins/ruflo-metaharness/scripts/bench-recordpair-overhead.mjs \
|
||||
--max-overhead-ns 500 --format json > /tmp/bench-overhead.json
|
||||
# Pretty-print the verdict
|
||||
node -e "
|
||||
const r = JSON.parse(require('fs').readFileSync('/tmp/bench-overhead.json'));
|
||||
const baseline = r.results.find(x => x.label.startsWith('baseline'));
|
||||
const env = r.results.find(x => x.label.includes('FLAG OFF'));
|
||||
const overhead = env.meanNsPerCall - baseline.meanNsPerCall;
|
||||
console.log('Measured overhead:', Math.round(overhead) + 'ns per route() call');
|
||||
console.log('Threshold: 500ns. Headroom: ' + Math.round(500 - overhead) + 'ns');
|
||||
"
|
||||
|
||||
- name: Upload benchmark artifact (90-day retention for trend tracking)
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: metaharness-bench-overhead-${{ github.run_id }}
|
||||
path: /tmp/bench-overhead.json
|
||||
retention-days: 90
|
||||
|
||||
eject-dryrun:
|
||||
# ADR-150 Phase 2 — verify `ruflo eject` dry-run produces a valid
|
||||
# plan against the ruflo repo itself, AND that the safety gate
|
||||
# rejects --target paths inside the repo. The actual eject is
|
||||
# never executed in CI; only the dry-run path + safety gates.
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 3
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Build CLI to dist
|
||||
# The eject command lives in v3/@claude-flow/cli/src/commands/.
|
||||
# CI doesn't have the bundled dist; build it here.
|
||||
run: |
|
||||
# iter 117 — `npm install` in a single v3 workspace pkg fails with
|
||||
# EUNSUPPORTEDPROTOCOL "workspace:*" because the workspace uses pnpm
|
||||
# (v3/pnpm-workspace.yaml). Install at workspace root with pnpm.
|
||||
# iter 119 — cli imports from workspace siblings; build them first.
|
||||
# iter 127 — switched from `-F "@claude-flow/cli..."` to `-r` because
|
||||
# cli imports from `../../../swarm/src/...` (CROSS-PACKAGE relative
|
||||
# path, NOT via the package dep graph). pnpm filter follows
|
||||
# package.json deps and missed swarm, producing TS6305 "Output file
|
||||
# has not been built from source file" errors. `-r --no-bail` builds
|
||||
# all workspace packages in topological order, tolerating unrelated
|
||||
# failures (which the cli build doesn't need to succeed).
|
||||
# pnpm@8.15.0 matches v3/package.json's packageManager pin.
|
||||
cd v3
|
||||
# iter 139 — npm/pnpm fetch retries via env. Several postinstalls
|
||||
# (onnxruntime-node, sharp) download CDN binaries; GH runners
|
||||
# observed ECONNRESET mid-fetch with no auto-retry. These env
|
||||
# vars push npm/pnpm to retry 5x with 1s→60s backoff.
|
||||
export npm_config_fetch_retries=5
|
||||
export npm_config_fetch_retry_mintimeout=1000
|
||||
export npm_config_fetch_retry_maxtimeout=60000
|
||||
export npm_config_fetch_retry_factor=2
|
||||
npx -y pnpm@8.15.0 install --frozen-lockfile --reporter=append-only
|
||||
# iter 138 — pnpm rebuild sharp (iter 134) didn't fix the missing
|
||||
# linux-x64 .node binary because pnpm 8.15's store doesn't honor
|
||||
# sharp's vendored download script in some CI configs. Force the
|
||||
# platform-specific install directly into the workspace's sharp
|
||||
# node_modules path. The CLI's transitive agentic-flow chain
|
||||
# require()s sharp at bootstrap; without this binary the CLI
|
||||
# crashes with "Cannot find module '../build/Release/
|
||||
# sharp-linux-x64.node'" before eject's action even runs.
|
||||
SHARP_DIR=$(find node_modules/.pnpm -maxdepth 4 -type d -name sharp 2>/dev/null | head -1)
|
||||
if [ -n "$SHARP_DIR" ]; then
|
||||
( cd "$SHARP_DIR" && npm install --no-audit --no-fund --ignore-scripts=false 2>&1 | tail -3 || true )
|
||||
ls "$SHARP_DIR/build/Release/" 2>&1 | grep -E '\.node$' || echo "sharp prebuild missing — runtime may fall back to optional path"
|
||||
fi
|
||||
# iter 127b — `|| true` because cli's tsc emits TS6305 ("Output
|
||||
# file has not been built from source file") for cross-package
|
||||
# relative imports from @claude-flow/swarm/src/..., even though
|
||||
# the JS gets emitted correctly. Mirror the existing v3-ci.yml
|
||||
# pattern: keep building, then assert the critical dist exists.
|
||||
npx -y pnpm@8.15.0 -r --no-bail run build || true
|
||||
test -f @claude-flow/cli/dist/src/index.js \
|
||||
|| { echo "::error::cli build did not produce dist/src/index.js"; exit 1; }
|
||||
cd @claude-flow/cli
|
||||
|
||||
- name: eject dry-run produces a valid plan
|
||||
run: |
|
||||
set -e
|
||||
# Run via the built bin
|
||||
OUT=$(node v3/@claude-flow/cli/bin/cli.js eject --name my-test-harness --format json 2>&1 || true)
|
||||
echo "$OUT" | head -20
|
||||
# Must contain the dry-run plan + dryRun:true
|
||||
echo "$OUT" | grep -q '"dryRun"' || { echo "FAIL: eject dry-run did not emit dryRun:true"; exit 1; }
|
||||
echo "$OUT" | grep -q '"name": "my-test-harness"' || { echo "FAIL: name not in plan"; exit 1; }
|
||||
echo "✓ eject dry-run plan validates"
|
||||
|
||||
- name: eject refuses --target inside the repo (safety gate)
|
||||
run: |
|
||||
set -e
|
||||
OUT=$(node v3/@claude-flow/cli/bin/cli.js eject --name foo --target "$PWD/eject-test" --confirm 2>&1 || true)
|
||||
echo "$OUT" | head -10
|
||||
echo "$OUT" | grep -qi "refusing to write\|target-inside-repo" || {
|
||||
echo "FAIL: eject should refuse --target inside the repo"
|
||||
exit 1
|
||||
}
|
||||
echo "✓ eject correctly refused in-repo target"
|
||||
|
||||
similarity-tests:
|
||||
# iter 40 — direct CI gate on the ADR-152 §3.1 contract.
|
||||
# Runs without any @metaharness/* installed — the production module is
|
||||
# pure-TS and must work in that environment (architectural constraint #4).
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Unit tests — _similarity.mjs (iter 39, 53 assertions)
|
||||
run: node plugins/ruflo-metaharness/scripts/test-similarity.mjs
|
||||
|
||||
- name: Spike invariants still hold (iter 35 regression anchor)
|
||||
run: node plugins/ruflo-metaharness/scripts/_spike-similarity.mjs
|
||||
|
||||
- name: CLI skill — file-input round-trip
|
||||
run: |
|
||||
set -e
|
||||
cat > /tmp/a.json <<'JSON'
|
||||
{"score":{"harnessFit":78,"compileConfidence":92,"taskCoverage":65,"toolSafety":88,"memoryUsefulness":70,"estCostPerRunUsd":0.04,"recommendedMode":"CLI + MCP","archetype":"compliance-harness","template":"vertical:legal"},"genome":{"repo_type":"node_mcp_ci","agent_topology":["a1","a2","a3","a4"],"risk_score":0.45,"test_confidence":0.7,"publish_readiness":0.6}}
|
||||
JSON
|
||||
cat > /tmp/b.json <<'JSON'
|
||||
{"score":{"harnessFit":75,"compileConfidence":90,"taskCoverage":70,"toolSafety":90,"memoryUsefulness":72,"estCostPerRunUsd":0.05,"recommendedMode":"CLI + MCP","archetype":"compliance-harness","template":"vertical:support"},"genome":{"repo_type":"node_mcp_ci","agent_topology":["b1","b2","b3","a3","a4"],"risk_score":0.40,"test_confidence":0.75,"publish_readiness":0.65}}
|
||||
JSON
|
||||
OUT=$(node plugins/ruflo-metaharness/scripts/similarity.mjs --a /tmp/a.json --b /tmp/b.json --format json)
|
||||
echo "$OUT"
|
||||
echo "$OUT" | grep -q '"overall"' || { echo "FAIL: no overall field"; exit 1; }
|
||||
echo "✓ similarity skill emits valid JSON"
|
||||
|
||||
- name: audit-trend structural-distance integration (iter 38)
|
||||
run: |
|
||||
set -e
|
||||
cat > /tmp/audit-baseline.json <<'JSON'
|
||||
{"startedAt":"2026-06-15T00:00:00Z","composite":{"worst":"clean"},"components":{"oiaManifest":{},"threatModel":{},"mcpScan":{"json":{"findings":[]}}},"fingerprint":{"score":{"harnessFit":82,"recommendedMode":"CLI + MCP","archetype":"typescript-sdk-harness","template":"vertical:coding"},"genome":{"repo_type":"node_mcp_ci","agent_topology":["maintainer","tester","security","release"],"risk_score":0.3,"test_confidence":0.85,"publish_readiness":0.9}}}
|
||||
JSON
|
||||
cat > /tmp/audit-current.json <<'JSON'
|
||||
{"startedAt":"2026-06-16T00:00:00Z","composite":{"worst":"low"},"components":{"oiaManifest":{},"threatModel":{},"mcpScan":{"json":{"findings":[]}}},"fingerprint":{"score":{"harnessFit":75,"recommendedMode":"CLI + MCP","archetype":"typescript-sdk-harness","template":"vertical:coding"},"genome":{"repo_type":"node_mcp_ci","agent_topology":["maintainer","tester","security","release","experimental"],"risk_score":0.35,"test_confidence":0.78,"publish_readiness":0.85}}}
|
||||
JSON
|
||||
OUT=$(node plugins/ruflo-metaharness/scripts/audit-trend.mjs --baseline /tmp/audit-baseline.json --current /tmp/audit-current.json --format json)
|
||||
echo "$OUT" | python3 -c "import json,sys; d=json.load(sys.stdin); sd=d['delta']['structuralDistance']; assert sd['verdict'] in ('near-identical','minor-drift'), sd; assert 0 < sd['distance'] < 0.2, sd; print('✓ structural-distance:', sd['verdict'], 'distance=' + str(sd['distance']))"
|
||||
|
||||
- name: Graceful fallback when fingerprint missing
|
||||
run: |
|
||||
set -e
|
||||
cat > /tmp/audit-old.json <<'JSON'
|
||||
{"startedAt":"2026-06-01T00:00:00Z","composite":{"worst":"clean"},"components":{"oiaManifest":{},"threatModel":{},"mcpScan":{"json":{"findings":[]}}}}
|
||||
JSON
|
||||
cat > /tmp/audit-new.json <<'JSON'
|
||||
{"startedAt":"2026-06-16T00:00:00Z","composite":{"worst":"clean"},"components":{"oiaManifest":{},"threatModel":{},"mcpScan":{"json":{"findings":[]}}},"fingerprint":{"score":{"harnessFit":82},"genome":{"repo_type":"node_mcp_ci","agent_topology":["x"]}}}
|
||||
JSON
|
||||
OUT=$(node plugins/ruflo-metaharness/scripts/audit-trend.mjs --baseline /tmp/audit-old.json --current /tmp/audit-new.json --format json)
|
||||
echo "$OUT" | python3 -c "import json,sys; d=json.load(sys.stdin); assert d['delta']['structuralDistance']['verdict']=='unavailable', d['delta']['structuralDistance']; print('✓ graceful fallback when fingerprint absent')"
|
||||
|
||||
- name: Distance alert gate exits 1 below threshold
|
||||
run: |
|
||||
set -e
|
||||
# The fixtures from the previous step produce overall ~0.97;
|
||||
# threshold 0.99 must trigger exit 1.
|
||||
if node plugins/ruflo-metaharness/scripts/audit-trend.mjs \
|
||||
--baseline /tmp/audit-baseline.json \
|
||||
--current /tmp/audit-current.json \
|
||||
--alert-on-distance-below 0.99 > /tmp/trend-alert.txt 2>&1; then
|
||||
echo "FAIL: --alert-on-distance-below should have exited 1"
|
||||
cat /tmp/trend-alert.txt
|
||||
exit 1
|
||||
else
|
||||
echo "✓ structural-distance alert correctly exited non-zero (got $?)"
|
||||
fi
|
||||
|
||||
- name: Performance gate — sub-10μs mean per similarity() call (iter 41)
|
||||
# CI runners are slower than Apple-Silicon baseline (~0.4μs); 10μs
|
||||
# ceiling gives ~25× headroom while still catching ~10× regressions.
|
||||
# 100k iters keeps the job fast (~50ms total work).
|
||||
# iter 82 — also capture JSON output as artifact so historical perf
|
||||
# data accumulates. Future regression analysis can diff across runs
|
||||
# without re-running the bench.
|
||||
run: |
|
||||
node plugins/ruflo-metaharness/scripts/bench-similarity.mjs \
|
||||
--iters 100000 \
|
||||
--max-mean-us 10 \
|
||||
--format json > /tmp/bench-similarity.json
|
||||
echo "## Similarity perf (iter 82 — artifact tracking)" >> $GITHUB_STEP_SUMMARY
|
||||
node -e "
|
||||
const j = JSON.parse(require('fs').readFileSync('/tmp/bench-similarity.json'));
|
||||
for (const r of j.results) {
|
||||
console.log('| ' + r.label.padEnd(18) + ' | mean ' + r.meanUs.toFixed(3) + 'μs | p99 ' + r.p99Us.toFixed(3) + 'μs |');
|
||||
}
|
||||
" | tee -a $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Upload bench-similarity artifact (iter 82)
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bench-similarity-${{ github.run_id }}
|
||||
path: /tmp/bench-similarity.json
|
||||
retention-days: 90
|
||||
|
||||
- name: Performance gate — parseMcpScanText sub-5μs (iter 87)
|
||||
# iter 86 measured sub-2μs across all categories on Apple Silicon.
|
||||
# CI runners are slower; 5μs ceiling gives ~3× headroom while
|
||||
# still catching ~10× regressions. 100k iters keeps the job fast.
|
||||
run: |
|
||||
node plugins/ruflo-metaharness/scripts/bench-parse-mcp-scan.mjs \
|
||||
--iters 100000 \
|
||||
--max-mean-us 5 \
|
||||
--format json > /tmp/bench-parse-mcp-scan.json
|
||||
echo "## parseMcpScanText perf (iter 87 — artifact tracking)" >> $GITHUB_STEP_SUMMARY
|
||||
node -e "
|
||||
const j = JSON.parse(require('fs').readFileSync('/tmp/bench-parse-mcp-scan.json'));
|
||||
for (const r of j.results) {
|
||||
console.log('| ' + r.label.padEnd(20) + ' | mean ' + r.meanUs.toFixed(3) + 'μs | p99 ' + r.p99Us.toFixed(3) + 'μs |');
|
||||
}
|
||||
" | tee -a $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Upload bench-parse-mcp-scan artifact (iter 87)
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bench-parse-mcp-scan-${{ github.run_id }}
|
||||
path: /tmp/bench-parse-mcp-scan.json
|
||||
retention-days: 90
|
||||
|
||||
- name: Build CLI dist (for dispatcher round-trip)
|
||||
run: |
|
||||
# iter 117 — `npm install` in a single v3 workspace pkg fails with
|
||||
# EUNSUPPORTEDPROTOCOL "workspace:*" because the workspace uses pnpm
|
||||
# (v3/pnpm-workspace.yaml). Install at workspace root with pnpm.
|
||||
# iter 119 — cli imports from workspace siblings; build them first.
|
||||
# iter 127 — switched from `-F "@claude-flow/cli..."` to `-r` because
|
||||
# cli imports from `../../../swarm/src/...` (CROSS-PACKAGE relative
|
||||
# path, NOT via the package dep graph). pnpm filter follows
|
||||
# package.json deps and missed swarm, producing TS6305 "Output file
|
||||
# has not been built from source file" errors. `-r --no-bail` builds
|
||||
# all workspace packages in topological order, tolerating unrelated
|
||||
# failures (which the cli build doesn't need to succeed).
|
||||
# pnpm@8.15.0 matches v3/package.json's packageManager pin.
|
||||
cd v3
|
||||
# iter 139 — npm/pnpm fetch retries via env. Several postinstalls
|
||||
# (onnxruntime-node, sharp) download CDN binaries; GH runners
|
||||
# observed ECONNRESET mid-fetch with no auto-retry. These env
|
||||
# vars push npm/pnpm to retry 5x with 1s→60s backoff.
|
||||
export npm_config_fetch_retries=5
|
||||
export npm_config_fetch_retry_mintimeout=1000
|
||||
export npm_config_fetch_retry_maxtimeout=60000
|
||||
export npm_config_fetch_retry_factor=2
|
||||
npx -y pnpm@8.15.0 install --frozen-lockfile --reporter=append-only
|
||||
# iter 138 — pnpm rebuild sharp (iter 134) didn't fix the missing
|
||||
# linux-x64 .node binary because pnpm 8.15's store doesn't honor
|
||||
# sharp's vendored download script in some CI configs. Force the
|
||||
# platform-specific install directly into the workspace's sharp
|
||||
# node_modules path. The CLI's transitive agentic-flow chain
|
||||
# require()s sharp at bootstrap; without this binary the CLI
|
||||
# crashes with "Cannot find module '../build/Release/
|
||||
# sharp-linux-x64.node'" before eject's action even runs.
|
||||
SHARP_DIR=$(find node_modules/.pnpm -maxdepth 4 -type d -name sharp 2>/dev/null | head -1)
|
||||
if [ -n "$SHARP_DIR" ]; then
|
||||
( cd "$SHARP_DIR" && npm install --no-audit --no-fund --ignore-scripts=false 2>&1 | tail -3 || true )
|
||||
ls "$SHARP_DIR/build/Release/" 2>&1 | grep -E '\.node$' || echo "sharp prebuild missing — runtime may fall back to optional path"
|
||||
fi
|
||||
# iter 127b — `|| true` because cli's tsc emits TS6305 ("Output
|
||||
# file has not been built from source file") for cross-package
|
||||
# relative imports from @claude-flow/swarm/src/..., even though
|
||||
# the JS gets emitted correctly. Mirror the existing v3-ci.yml
|
||||
# pattern: keep building, then assert the critical dist exists.
|
||||
npx -y pnpm@8.15.0 -r --no-bail run build || true
|
||||
test -f @claude-flow/cli/dist/src/index.js \
|
||||
|| { echo "::error::cli build did not produce dist/src/index.js"; exit 1; }
|
||||
cd @claude-flow/cli
|
||||
|
||||
- name: CLI dispatcher round-trip — `node bin/cli.js metaharness similarity` (iter 36)
|
||||
# Proves the iter-36 SUBCOMMANDS entry actually dispatches at the
|
||||
# CLI surface, not just at the script. Closes the gap between
|
||||
# "the script works in isolation" and "the user-facing command works".
|
||||
run: |
|
||||
set -e
|
||||
OUT=$(node v3/@claude-flow/cli/bin/cli.js metaharness similarity \
|
||||
--a /tmp/a.json --b /tmp/b.json --format json)
|
||||
echo "$OUT" | head -20
|
||||
echo "$OUT" | grep -q '"overall"' || { echo "FAIL: dispatcher did not emit overall"; exit 1; }
|
||||
echo "$OUT" | grep -q '"adr": "ADR-152"' || { echo "FAIL: dispatcher output missing ADR tag"; exit 1; }
|
||||
echo "✓ CLI dispatcher round-trip green"
|
||||
|
||||
- name: CLI help lists similarity subcommand
|
||||
# Anti-regression on the help text (iter 36 added this line).
|
||||
run: |
|
||||
set -e
|
||||
OUT=$(node v3/@claude-flow/cli/bin/cli.js metaharness 2>&1 || true)
|
||||
echo "$OUT" | grep -q "similarity" || { echo "FAIL: help text dropped similarity"; exit 1; }
|
||||
echo "✓ help text references similarity subcommand"
|
||||
|
||||
metaharness-real-data:
|
||||
# iter 48 — the load-bearing integration gate at PR time. Unlike
|
||||
# `similarity-tests` (which runs WITHOUT @metaharness/* to prove
|
||||
# architectural constraint #1), this job INSTALLS the upstream dep
|
||||
# and exercises the real CLI → fingerprint → similarity chain.
|
||||
#
|
||||
# This is the only CI surface that would have caught the iter-38
|
||||
# schema-shape bug fixed in iter 47. Every other test uses hand-built
|
||||
# fixtures that happened to have the correct shape.
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Pre-flight — confirm metaharness CLI is reachable via npx
|
||||
# The roundtrip test uses npx -y metaharness@latest, which fetches
|
||||
# the package on first call. Warm the cache here so the test's
|
||||
# 90s timeout doesn't expire on cold start.
|
||||
run: |
|
||||
npx -y metaharness@latest --version
|
||||
echo "✓ metaharness CLI warm"
|
||||
|
||||
- name: Upstream fingerprint schema invariants (iter 81 — protects _similarity.mjs)
|
||||
# If upstream `metaharness score|genome` renames any of the 14
|
||||
# fields _similarity.mjs::projectToVec reads (harnessFit,
|
||||
# compileConfidence, taskCoverage, toolSafety, memoryUsefulness,
|
||||
# estCostPerRunUsd, recommendedMode, archetype, template, repo_type,
|
||||
# agent_topology, risk_score, test_confidence, publish_readiness),
|
||||
# projectToVec defaults that field to 0 and similarity silently
|
||||
# degrades to categorical+jaccard-only signal. Same class of bug
|
||||
# as iter-47 (CLI binary schema mismatch).
|
||||
run: |
|
||||
node scripts/check-fingerprint-schema.mjs --format json
|
||||
echo "✓ upstream fingerprint schema compatible with _similarity.mjs"
|
||||
|
||||
- name: Upstream mcp-scan format invariants (iter 80 — protects iter-50 parser)
|
||||
# If upstream `harness mcp-scan` ever changes its text format,
|
||||
# iter-50's parseMcpScanText silently returns empty findings and
|
||||
# iter-49's introduced/cleared diff regresses to dead code. This
|
||||
# tripwire fails the workflow if the format invariant drifts.
|
||||
run: |
|
||||
node scripts/check-mcp-scan-format.mjs --format json
|
||||
echo "✓ upstream text format compatible with parseMcpScanText"
|
||||
|
||||
- name: End-to-end pipeline roundtrip (iter 47)
|
||||
# Must exit 0. Exit 2 means metaharness wasn't reachable (we just
|
||||
# warmed it, so that would be infra failure). Exit 1 means the
|
||||
# roundtrip's load-bearing self-match invariant (overall===1)
|
||||
# failed — that's the schema-shape regression iter 47 fixed.
|
||||
run: |
|
||||
node plugins/ruflo-metaharness/scripts/test-pipeline-roundtrip.mjs
|
||||
echo "✓ full ADR-152 §3.1 pipeline works with real metaharness output"
|
||||
|
||||
- name: Cross-check — same path also produces valid score JSON via dispatcher
|
||||
# Hits the iter-36 score subcommand via the iter-42-fixed dispatcher
|
||||
# to confirm the CLI surface stays aligned with what oia-audit
|
||||
# consumes internally.
|
||||
run: |
|
||||
set -e
|
||||
# iter 117 — `npm install` in a single v3 workspace pkg fails with
|
||||
# EUNSUPPORTEDPROTOCOL "workspace:*" because the workspace uses pnpm
|
||||
# (v3/pnpm-workspace.yaml). Install at workspace root with pnpm.
|
||||
# iter 119 — cli imports from workspace siblings; build them first.
|
||||
# iter 127 — switched from `-F "@claude-flow/cli..."` to `-r` because
|
||||
# cli imports from `../../../swarm/src/...` (CROSS-PACKAGE relative
|
||||
# path, NOT via the package dep graph). pnpm filter follows
|
||||
# package.json deps and missed swarm, producing TS6305 "Output file
|
||||
# has not been built from source file" errors. `-r --no-bail` builds
|
||||
# all workspace packages in topological order, tolerating unrelated
|
||||
# failures (which the cli build doesn't need to succeed).
|
||||
# pnpm@8.15.0 matches v3/package.json's packageManager pin.
|
||||
cd v3
|
||||
# iter 139 — npm/pnpm fetch retries via env. Several postinstalls
|
||||
# (onnxruntime-node, sharp) download CDN binaries; GH runners
|
||||
# observed ECONNRESET mid-fetch with no auto-retry. These env
|
||||
# vars push npm/pnpm to retry 5x with 1s→60s backoff.
|
||||
export npm_config_fetch_retries=5
|
||||
export npm_config_fetch_retry_mintimeout=1000
|
||||
export npm_config_fetch_retry_maxtimeout=60000
|
||||
export npm_config_fetch_retry_factor=2
|
||||
npx -y pnpm@8.15.0 install --frozen-lockfile --reporter=append-only
|
||||
# iter 138 — pnpm rebuild sharp (iter 134) didn't fix the missing
|
||||
# linux-x64 .node binary because pnpm 8.15's store doesn't honor
|
||||
# sharp's vendored download script in some CI configs. Force the
|
||||
# platform-specific install directly into the workspace's sharp
|
||||
# node_modules path. The CLI's transitive agentic-flow chain
|
||||
# require()s sharp at bootstrap; without this binary the CLI
|
||||
# crashes with "Cannot find module '../build/Release/
|
||||
# sharp-linux-x64.node'" before eject's action even runs.
|
||||
SHARP_DIR=$(find node_modules/.pnpm -maxdepth 4 -type d -name sharp 2>/dev/null | head -1)
|
||||
if [ -n "$SHARP_DIR" ]; then
|
||||
( cd "$SHARP_DIR" && npm install --no-audit --no-fund --ignore-scripts=false 2>&1 | tail -3 || true )
|
||||
ls "$SHARP_DIR/build/Release/" 2>&1 | grep -E '\.node$' || echo "sharp prebuild missing — runtime may fall back to optional path"
|
||||
fi
|
||||
# iter 127b — `|| true` because cli's tsc emits TS6305 ("Output
|
||||
# file has not been built from source file") for cross-package
|
||||
# relative imports from @claude-flow/swarm/src/..., even though
|
||||
# the JS gets emitted correctly. Mirror the existing v3-ci.yml
|
||||
# pattern: keep building, then assert the critical dist exists.
|
||||
npx -y pnpm@8.15.0 -r --no-bail run build || true
|
||||
test -f @claude-flow/cli/dist/src/index.js \
|
||||
|| { echo "::error::cli build did not produce dist/src/index.js"; exit 1; }
|
||||
cd @claude-flow/cli
|
||||
cd ../../../
|
||||
OUT=$(node v3/@claude-flow/cli/bin/cli.js metaharness score --path . --format json)
|
||||
echo "$OUT" | grep -q '"harnessFit"' || { echo "FAIL: score dispatcher dropped harnessFit"; exit 1; }
|
||||
echo "✓ score dispatcher emits the expected metaharness schema"
|
||||
|
||||
- name: Drift-from-history dispatcher round-trip with --baseline-file (iter 98)
|
||||
# Exercises the iter-66/67 fast-path THROUGH the iter-42-fixed
|
||||
# CLI dispatcher. Catches:
|
||||
# - iter-42 dispatcher flag-drop regression
|
||||
# - iter-67 --baseline-file synth-listResult drift
|
||||
# - iter-95 timing.path derivation drift
|
||||
# The score cross-check above tests the simple path (one arg).
|
||||
# This tests the chained-subprocess path (composes 3 scripts).
|
||||
run: |
|
||||
set -e
|
||||
# Generate a fresh audit as baseline-file input
|
||||
node plugins/ruflo-metaharness/scripts/oia-audit.mjs \
|
||||
--dry-run --format json > /tmp/drift-baseline.json
|
||||
# Dispatch through the CLI — exercises iter-42 flag round-trip
|
||||
OUT=$(node v3/@claude-flow/cli/bin/cli.js metaharness drift-from-history \
|
||||
--baseline-file /tmp/drift-baseline.json \
|
||||
--dry-run --format json)
|
||||
# Verify the iter-95 timing.path field surfaces with 'file'
|
||||
echo "$OUT" | grep -q '"path": "file"' \
|
||||
|| { echo "FAIL: dispatcher did not propagate baseline-file or timing.path missing"; echo "$OUT" | head -30; exit 1; }
|
||||
# Verify the iter-66 skip flag is true (proves the fast-path
|
||||
# synthesizes the listResult correctly through the dispatcher)
|
||||
echo "$OUT" | grep -q '"skippedAuditList": true' \
|
||||
|| { echo "FAIL: dispatcher fast-path bypass not active"; exit 1; }
|
||||
# iter 99 — also verify the fast-path actually delivers the
|
||||
# measured ~1.4s baseline (slow path is ~26s). Allow 30s budget
|
||||
# to tolerate slow CI runners while still catching a ~10x regression.
|
||||
WALL=$(node -e "
|
||||
const j = JSON.parse(\`$OUT\`);
|
||||
console.log(j.timing?.parallelWallMs ?? 0);
|
||||
")
|
||||
if [ "$WALL" -gt 30000 ]; then
|
||||
echo "FAIL: dispatcher fast-path wall ${WALL}ms > 30000ms (regression?)"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ drift-from-history dispatcher round-trip green (fast-path via CLI; wall ${WALL}ms)"
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
name: neural-trader-smoke
|
||||
|
||||
# Runs the ruflo-neural-trader plugin's runtime smoke test against
|
||||
# the currently-pinned neural-trader npm package. Catches regressions
|
||||
# where the published package stops matching what the plugin's skills
|
||||
# document (e.g. another missing-loader bug, another fork-bomb, an
|
||||
# advanced feature flag being silently dropped).
|
||||
#
|
||||
# Structural smoke (smoke.sh) runs separately as part of the broader
|
||||
# validate-marketplace pipeline; this one specifically gates the
|
||||
# *runtime* contract — installs the package fresh, runs the documented
|
||||
# CLI flags, asserts the JSON shape.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'plugins/ruflo-neural-trader/**'
|
||||
- '.github/workflows/neural-trader-smoke.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'plugins/ruflo-neural-trader/**'
|
||||
- '.github/workflows/neural-trader-smoke.yml'
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
# Catch upstream regressions even when nothing in the plugin
|
||||
# changes (e.g. a new `neural-trader` release breaks the contract).
|
||||
- cron: '17 6 * * 1' # Mondays 06:17 UTC
|
||||
|
||||
jobs:
|
||||
runtime-smoke:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Install jq
|
||||
run: sudo apt-get update && sudo apt-get install -y jq
|
||||
|
||||
- name: Run runtime smoke
|
||||
run: bash plugins/ruflo-neural-trader/scripts/runtime-smoke.sh
|
||||
|
||||
- name: Upload smoke output on failure
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: runtime-smoke-output
|
||||
path: |
|
||||
/tmp/runtime-smoke-*.log
|
||||
if-no-files-found: ignore
|
||||
@@ -0,0 +1,173 @@
|
||||
# ADR-164 architectural constraint enforcement.
|
||||
#
|
||||
# "Ruflo remains operational if the agentbbs package is removed."
|
||||
#
|
||||
# This workflow asserts three architectural rules from ADR-164 §5.1.1:
|
||||
# 1. agentbbs lives in `optionalDependencies`, NEVER `dependencies`
|
||||
# 2. Every code path that touches agentbbs in v3/@claude-flow/cli/src/
|
||||
# is preceded by `loadAgentbbs()` (or a dynamic `import('agentbbs')`)
|
||||
# 3. Runtime drill: with `--no-optional` / unreachable registry, the smoke
|
||||
# contract exits 0 (graceful degradation).
|
||||
#
|
||||
# If this job ever fails, an agentbbs API has accidentally been promoted to
|
||||
# a hard runtime requirement — breaking the optional-dep playbook from
|
||||
# ADR-150 / agenticow / metaharness. The fix is either to make the new code
|
||||
# path graceful, or to write a new ADR that supersedes the constraint.
|
||||
name: no-agentbbs-smoke
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'plugins/ruflo-bbs-federation/**'
|
||||
- 'v3/@claude-flow/cli/src/mcp-tools/agentbbs-tools.ts'
|
||||
- 'v3/@claude-flow/cli/src/mcp-client.ts'
|
||||
- 'v3/@claude-flow/cli/src/mcp-tools/index.ts'
|
||||
- 'v3/@claude-flow/cli/package.json'
|
||||
- 'scripts/smoke-agentbbs.sh'
|
||||
- '.github/workflows/no-agentbbs-smoke.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'plugins/ruflo-bbs-federation/**'
|
||||
- 'v3/@claude-flow/cli/src/mcp-tools/agentbbs-tools.ts'
|
||||
- 'v3/@claude-flow/cli/src/mcp-client.ts'
|
||||
- 'v3/@claude-flow/cli/src/mcp-tools/index.ts'
|
||||
- 'v3/@claude-flow/cli/package.json'
|
||||
- 'scripts/smoke-agentbbs.sh'
|
||||
- '.github/workflows/no-agentbbs-smoke.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
smoke-without-agentbbs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Rule 1 — agentbbs must NOT appear in non-optional dependencies anywhere
|
||||
# Static check: every package.json that could carry the dep must list
|
||||
# agentbbs only under optionalDependencies. If it appears in
|
||||
# `dependencies` anywhere, we've broken the architectural constraint.
|
||||
run: |
|
||||
node -e "
|
||||
const { readFileSync, readdirSync, statSync } = require('fs');
|
||||
const { join } = require('path');
|
||||
const candidates = [
|
||||
'package.json',
|
||||
'ruflo/package.json',
|
||||
'v3/@claude-flow/cli/package.json',
|
||||
];
|
||||
try {
|
||||
for (const p of readdirSync('plugins')) {
|
||||
const pj = join('plugins', p, 'package.json');
|
||||
try { statSync(pj); candidates.push(pj); } catch {}
|
||||
}
|
||||
} catch {}
|
||||
const offenders = [];
|
||||
for (const c of candidates) {
|
||||
let json;
|
||||
try { json = JSON.parse(readFileSync(c, 'utf-8')); } catch { continue; }
|
||||
for (const dep of Object.keys(json.dependencies || {})) {
|
||||
if (/^agentbbs$/.test(dep)) {
|
||||
offenders.push({ file: c, dep });
|
||||
}
|
||||
}
|
||||
}
|
||||
if (offenders.length) {
|
||||
console.error('ADR-164 architectural constraint violated:');
|
||||
for (const o of offenders) console.error(' ' + o.file + ' → ' + o.dep + ' in dependencies (must be optionalDependencies)');
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('OK — agentbbs is not in non-optional dependencies anywhere.');
|
||||
"
|
||||
|
||||
- name: Rule 2 — every `agentbbs` usage in cli/src must be guarded by loadAgentbbs / dynamic import
|
||||
# Walk every .ts under v3/@claude-flow/cli/src that mentions agentbbs.
|
||||
# For each match, the SAME file must also reference loadAgentbbs() OR
|
||||
# use a dynamic import('agentbbs'). Static `import ... from 'agentbbs'`
|
||||
# is forbidden — it would make the dep mandatory.
|
||||
run: |
|
||||
node -e "
|
||||
const { readFileSync, readdirSync, statSync } = require('fs');
|
||||
const { join } = require('path');
|
||||
const root = 'v3/@claude-flow/cli/src';
|
||||
const offenders = [];
|
||||
function walk(dir) {
|
||||
for (const e of readdirSync(dir)) {
|
||||
const p = join(dir, e);
|
||||
const st = statSync(p);
|
||||
if (st.isDirectory()) { walk(p); continue; }
|
||||
if (!p.endsWith('.ts')) continue;
|
||||
const src = readFileSync(p, 'utf-8');
|
||||
// Static import — forbidden:
|
||||
if (/^\s*import\s+[^;]*from\s+['\"]agentbbs['\"]/m.test(src)) {
|
||||
offenders.push({ file: p, reason: 'static import of agentbbs' });
|
||||
continue;
|
||||
}
|
||||
// If file mentions agentbbs at all, require a guard — except
|
||||
// for files that ONLY re-export our own agentbbsTools symbol
|
||||
// (the symbol itself contains the loadAgentbbs guard).
|
||||
if (/agentbbs/.test(src)) {
|
||||
// Strip benign re-exports + comment mentions, then check the rest.
|
||||
// Patterns we treat as safe (do not require their own loadAgentbbs guard):
|
||||
// - `export { agentbbsTools } from './agentbbs-tools.js';`
|
||||
// - `import { agentbbsTools } from './mcp-tools/agentbbs-tools.js';`
|
||||
// - `...agentbbsTools,`
|
||||
// - any comment line mentioning agentbbs
|
||||
const stripped = src
|
||||
.replace(/^\s*\/\/.*$/gm, '') // single-line comments
|
||||
.replace(/\/\*[\s\S]*?\*\//g, '') // block comments
|
||||
.replace(/export\s+\{[^}]*agentbbsTools[^}]*\}\s+from\s+['\"][^'\"]+agentbbs-tools[^'\"]*['\"];?/g, '')
|
||||
.replace(/import\s+\{[^}]*agentbbsTools[^}]*\}\s+from\s+['\"][^'\"]+agentbbs-tools[^'\"]*['\"];?/g, '')
|
||||
.replace(/\.\.\.agentbbsTools,?/g, '');
|
||||
if (/agentbbs/.test(stripped)) {
|
||||
const guarded = /loadAgentbbs|import\(['\"]agentbbs['\"]\)/m.test(src);
|
||||
if (!guarded) {
|
||||
offenders.push({ file: p, reason: 'agentbbs reference without loadAgentbbs/dynamic import guard' });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
walk(root);
|
||||
if (offenders.length) {
|
||||
console.error('ADR-164 rule 2 violated:');
|
||||
for (const o of offenders) console.error(' ' + o.file + ': ' + o.reason);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('OK — every agentbbs reference under ' + root + ' is dynamically guarded.');
|
||||
"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 8
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: v3/pnpm-lock.yaml
|
||||
|
||||
- name: Rule 3 — runtime drill (surgically remove agentbbs only, run smoke)
|
||||
# Install everything (so other optional deps like agentdb stay available
|
||||
# for transitive consumers — they have their own degradation paths)
|
||||
# but DELETE node_modules/agentbbs to force loadAgentbbs() to return null.
|
||||
# smoke-agentbbs.sh accepts DEGRADED:agentbbs-not-found as a PASS for
|
||||
# step 8, so the whole script should still exit 0.
|
||||
# Uses pnpm (the v3/ workspace's real package manager) since some root
|
||||
# deps use the `workspace:*` protocol that npm doesn't support.
|
||||
run: |
|
||||
set -e
|
||||
(cd v3 && pnpm install --frozen-lockfile)
|
||||
# Build whole workspace dep-first (cli has cross-package deps the
|
||||
# `...` filter can't discover from package.json alone)
|
||||
(cd v3 && pnpm -r --no-bail build || pnpm --filter @claude-flow/cli build)
|
||||
# Surgically remove agentbbs from every install location it landed.
|
||||
find v3 -type d -name agentbbs -path '*node_modules*' -exec rm -rf {} + 2>/dev/null || true
|
||||
# Sanity check — the package really is gone
|
||||
if find v3 -type d -name agentbbs -path '*node_modules*' 2>/dev/null | grep -q . ; then
|
||||
echo "ERROR: agentbbs still in node_modules"; exit 1
|
||||
fi
|
||||
bash scripts/smoke-agentbbs.sh
|
||||
@@ -0,0 +1,118 @@
|
||||
# Regression guard for #2561 — CLI optionalDependencies bloat causes cold
|
||||
# `npx -y @claude-flow/cli@alpha --version` (and `ruflo@alpha` wrapper) to
|
||||
# time out because npm must resolve/download every optional dep before Node
|
||||
# ever executes bin/cli.js and can hit its in-process --version fast-path.
|
||||
#
|
||||
# The fix (commit 610575ea5) pruned the CLI's optionalDependencies from ~26
|
||||
# entries down to 5 core packages, and emptied ruflo's optionalDependencies.
|
||||
# This guard fails CI if either budget is exceeded, OR if any of the
|
||||
# specific heavy packages that caused the timeout are re-added.
|
||||
#
|
||||
# If this guard trips, either:
|
||||
# (a) prune the dep back out and route the capability through a lazy
|
||||
# runtime install / plugin store install, or
|
||||
# (b) supersede this guard in a follow-up PR with a new cold-npx
|
||||
# benchmark proving the added deps do not re-introduce the timeout.
|
||||
name: no-cli-optdep-bloat-2561
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'v3/@claude-flow/cli/package.json'
|
||||
- 'ruflo/package.json'
|
||||
- '.github/workflows/no-cli-optdep-bloat-2561.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'v3/@claude-flow/cli/package.json'
|
||||
- 'ruflo/package.json'
|
||||
- '.github/workflows/no-cli-optdep-bloat-2561.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
guard:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Enforce CLI + ruflo optionalDependencies budget (#2561)
|
||||
run: |
|
||||
node -e '
|
||||
const fs = require("fs");
|
||||
const cli = JSON.parse(fs.readFileSync("v3/@claude-flow/cli/package.json", "utf8"));
|
||||
const ruflo = JSON.parse(fs.readFileSync("ruflo/package.json", "utf8"));
|
||||
|
||||
const cliOpt = Object.keys(cli.optionalDependencies || {});
|
||||
const rufloOpt = Object.keys(ruflo.optionalDependencies || {});
|
||||
|
||||
// Budgets set by #2561 (610575ea5). Tightened intentionally so the
|
||||
// in-process --version fast-path at bin/cli.js:101-117 is not
|
||||
// starved by a cold npm install of dozens of native/wasm deps.
|
||||
const CLI_MAX = 8;
|
||||
const RUFLO_MAX = 0;
|
||||
|
||||
// Specific packages proven to trigger the cold-npx timeout in
|
||||
// #2561. Re-adding any of these to optionalDependencies re-opens
|
||||
// the regression regardless of the count budget.
|
||||
const FORBIDDEN = [
|
||||
"@claude-flow/aidefence",
|
||||
"@claude-flow/codex",
|
||||
"@claude-flow/embeddings",
|
||||
"@claude-flow/guidance",
|
||||
"@claude-flow/plugin-gastown-bridge",
|
||||
"@metaharness/darwin",
|
||||
"@metaharness/kernel",
|
||||
"@metaharness/redblue",
|
||||
"@metaharness/router",
|
||||
"@metaharness/weight-eft",
|
||||
"metaharness",
|
||||
"@ruvector/attention",
|
||||
"@ruvector/attention-darwin-arm64",
|
||||
"@ruvector/diskann",
|
||||
"@ruvector/learning-wasm",
|
||||
"@ruvector/router",
|
||||
"@ruvector/ruvllm-wasm",
|
||||
"@ruvector/rvagent-wasm",
|
||||
"@ruvector/sona",
|
||||
"@ruvector/tiny-dancer",
|
||||
"agentbbs",
|
||||
"agenticow",
|
||||
"page-agent"
|
||||
];
|
||||
|
||||
let failed = false;
|
||||
|
||||
if (cliOpt.length > CLI_MAX) {
|
||||
console.error(`FAIL (#2561): v3/@claude-flow/cli optionalDependencies=${cliOpt.length} exceeds budget ${CLI_MAX}`);
|
||||
console.error(` entries: ${cliOpt.join(", ")}`);
|
||||
failed = true;
|
||||
} else {
|
||||
console.log(`OK: v3/@claude-flow/cli optionalDependencies=${cliOpt.length} (budget ${CLI_MAX})`);
|
||||
}
|
||||
|
||||
if (rufloOpt.length > RUFLO_MAX) {
|
||||
console.error(`FAIL (#2561): ruflo optionalDependencies=${rufloOpt.length} exceeds budget ${RUFLO_MAX}`);
|
||||
console.error(` entries: ${rufloOpt.join(", ")}`);
|
||||
failed = true;
|
||||
} else {
|
||||
console.log(`OK: ruflo optionalDependencies=${rufloOpt.length} (budget ${RUFLO_MAX})`);
|
||||
}
|
||||
|
||||
const reAdded = FORBIDDEN.filter(p => cliOpt.includes(p) || rufloOpt.includes(p));
|
||||
if (reAdded.length > 0) {
|
||||
console.error(`FAIL (#2561): forbidden heavy optionalDependencies re-added: ${reAdded.join(", ")}`);
|
||||
console.error(` these packages caused the cold npx --version timeout in #2561`);
|
||||
failed = true;
|
||||
} else {
|
||||
console.log(`OK: no forbidden heavy optionalDependencies present`);
|
||||
}
|
||||
|
||||
if (failed) {
|
||||
console.error("");
|
||||
console.error("See #2561 for context. If you truly need one of these deps at the CLI");
|
||||
console.error("layer, prove cold `npx -y @claude-flow/cli@alpha --version` still");
|
||||
console.error("returns under 60s and update this guard in the same PR.");
|
||||
process.exit(1);
|
||||
}
|
||||
'
|
||||
@@ -0,0 +1,154 @@
|
||||
# ADR-150 architectural constraint rule #4 enforcement.
|
||||
#
|
||||
# "Ruflo remains operational if every MetaHarness package is removed."
|
||||
#
|
||||
# This workflow installs ruflo with `--no-optional` (or equivalent) so
|
||||
# every `@metaharness/*` and `metaharness` package is EXCLUDED. It then
|
||||
# runs scripts/smoke-all-plugins.mjs and asserts that ruflo's entire
|
||||
# plugin fleet still passes its structural contract.
|
||||
#
|
||||
# If this job ever fails, a MetaHarness package has accidentally been
|
||||
# promoted to a hard runtime requirement — breaking the architectural
|
||||
# constraint. The fix is either to make the new code path graceful, or
|
||||
# to write a new ADR that supersedes the constraint.
|
||||
name: no-metaharness-smoke
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'plugins/**'
|
||||
- 'scripts/**'
|
||||
- '**/package.json'
|
||||
- '**/package-lock.json'
|
||||
- '.github/workflows/no-metaharness-smoke.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'plugins/**'
|
||||
- 'scripts/**'
|
||||
- '**/package.json'
|
||||
- '**/package-lock.json'
|
||||
- '.github/workflows/no-metaharness-smoke.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
smoke-without-metaharness:
|
||||
runs-on: ubuntu-latest
|
||||
# iter 136: 10m → 20m — consistently hit 10m wall on shared runners
|
||||
# (#2405 PR run + manual re-run both canceled at exactly 10m0s).
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Verify no `@metaharness/*` or `metaharness` appears in non-optional deps anywhere
|
||||
# Static check: every plugins/*/package.json AND v3/*/package.json
|
||||
# AND the root package.json AND ruflo/package.json must NOT list
|
||||
# metaharness/router/kernel in `dependencies` (only `optionalDependencies`
|
||||
# or `peerDependencies` are allowed).
|
||||
run: |
|
||||
node -e "
|
||||
const { readFileSync, readdirSync, statSync } = require('fs');
|
||||
const { join } = require('path');
|
||||
const candidates = [
|
||||
'package.json',
|
||||
'ruflo/package.json',
|
||||
'v3/@claude-flow/cli/package.json',
|
||||
];
|
||||
// Also scan all plugins/*/package.json (if any plugin has one)
|
||||
try {
|
||||
for (const p of readdirSync('plugins')) {
|
||||
const pj = join('plugins', p, 'package.json');
|
||||
try { statSync(pj); candidates.push(pj); } catch {}
|
||||
}
|
||||
} catch {}
|
||||
const offenders = [];
|
||||
for (const c of candidates) {
|
||||
let json;
|
||||
try { json = JSON.parse(readFileSync(c, 'utf-8')); } catch { continue; }
|
||||
for (const dep of Object.keys(json.dependencies || {})) {
|
||||
if (/^metaharness$|^@metaharness\//.test(dep)) {
|
||||
offenders.push({ file: c, dep });
|
||||
}
|
||||
}
|
||||
}
|
||||
if (offenders.length) {
|
||||
console.error('ADR-150 architectural constraint rule #2 violated:');
|
||||
for (const o of offenders) console.error(' ' + o.file + ' → ' + o.dep + ' in dependencies (must be optionalDependencies)');
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('✓ No metaharness/* in non-optional dependencies anywhere.');
|
||||
"
|
||||
|
||||
- name: Run meta-smoke (should pass with current optional deps in place)
|
||||
# iter 119 — 300s timeout to give ruflo-metaharness (118 steps,
|
||||
# many npx invocations) enough wall time. Same change as
|
||||
# all-plugins-smoke.yml; 60s was timing out at 151s+ in CI.
|
||||
run: node scripts/smoke-all-plugins.mjs --timeout 300
|
||||
|
||||
- name: Simulate metaharness-absent runtime by clearing the npm cache fetch
|
||||
# We can't actually un-install optional deps cleanly in this matrix
|
||||
# without breaking other plugins. Instead, the per-skill graceful
|
||||
# degradation path is exercised directly: force `npx` to fail by
|
||||
# pointing the registry at a black hole, then run each skill and
|
||||
# assert exit code 0 + degraded:true JSON.
|
||||
run: |
|
||||
set -e
|
||||
# Use an unresolvable npm registry so `npx metaharness@latest`
|
||||
# cannot fetch the package. The skills should detect this and
|
||||
# emit the degraded payload.
|
||||
export npm_config_registry=https://no-such-registry-9c8c43.example.invalid/
|
||||
# Disable network-cache to ensure the npx miss is fresh
|
||||
export NPX_NO_LOCAL=1
|
||||
# Since the pinned-cache resolution (_invoke.mjs), skills no longer
|
||||
# go through npx: they resolve locally-installed metaharness first,
|
||||
# then a versioned ~/.ruflo cache. Both defeat this drill's absence
|
||||
# simulation (metaharness IS installed in the CI workspace, and the
|
||||
# meta-smoke step above warms the cache). Use the purpose-built test
|
||||
# seams so "absent" means absent again:
|
||||
export RUFLO_METAHARNESS_SKIP_LOCAL=1
|
||||
export RUFLO_METAHARNESS_CACHE_BASE="$(mktemp -d)/empty-cache-root"
|
||||
|
||||
# iter 55 — expanded from 4 to 7 skills. The new entries are:
|
||||
# oia-audit (composite, calls all 5 sub-skills), mint (scaffolds
|
||||
# via `metaharness new`), drift-from-history (composes 3 scripts
|
||||
# — needs oia-audit to gracefully degrade for the chain to too).
|
||||
# Each skill needs a per-skill argv recipe to match its required
|
||||
# flags (e.g. mint needs --name; drift-from-history needs --dry-run).
|
||||
declare -A SKILL_ARGS=(
|
||||
[score]="--format json"
|
||||
[genome]="--format json"
|
||||
[mcp-scan]="--format json"
|
||||
[threat-model]="--format json"
|
||||
[oia-audit]="--dry-run --format json"
|
||||
[mint]="--name no-metaharness-drill --template vertical:coding --format json"
|
||||
[drift-from-history]="--dry-run --threshold 0.5 --format json"
|
||||
)
|
||||
for skill in score genome mcp-scan threat-model oia-audit mint drift-from-history; do
|
||||
echo "--- exercising ${skill}.mjs with unreachable registry ---"
|
||||
ARGS="${SKILL_ARGS[$skill]}"
|
||||
OUT=$(node plugins/ruflo-metaharness/scripts/$skill.mjs $ARGS 2>&1 || echo '__NON_ZERO_EXIT__')
|
||||
EXIT=$(node plugins/ruflo-metaharness/scripts/$skill.mjs $ARGS > /dev/null 2>&1; echo $?)
|
||||
echo "$OUT" | head -10
|
||||
echo "exit: $EXIT"
|
||||
# Assert: graceful path means exit 0 (or 3 for drift-from-history
|
||||
# which intentionally signals "test-cannot-run" via exit 3 per
|
||||
# iter 53's 4-code semantic). Both must emit a degraded payload.
|
||||
ACCEPTABLE_EXITS="0"
|
||||
if [ "$skill" = "drift-from-history" ]; then ACCEPTABLE_EXITS="0 3"; fi
|
||||
MATCHED=0
|
||||
for ok in $ACCEPTABLE_EXITS; do
|
||||
if [ "$EXIT" = "$ok" ]; then MATCHED=1; break; fi
|
||||
done
|
||||
if [ "$MATCHED" = "0" ]; then
|
||||
echo "FAIL: $skill exited $EXIT but graceful-degradation requires one of: $ACCEPTABLE_EXITS"
|
||||
exit 1
|
||||
fi
|
||||
if ! echo "$OUT" | grep -q '"degraded"'; then
|
||||
echo "FAIL: $skill did not emit degraded:true payload"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
echo "✓ All 7 skills gracefully degraded when metaharness was unreachable."
|
||||
@@ -0,0 +1,94 @@
|
||||
# Regression guard for issue #2578.
|
||||
#
|
||||
# Bug: The ADR-104 witness fix marker was the literal string
|
||||
# `agentic-flow/transport/loader`, and plugin-agent-federation's
|
||||
# `src/transport/midstream-aware-loader.ts` performed a static
|
||||
# `import type { … } from 'agentic-flow/transport/loader'`. Upstream
|
||||
# `agentic-flow` does not expose that subpath in its `exports` map —
|
||||
# any external verifier that literally resolves the specifier fails
|
||||
# with `ERR_MODULE_NOT_FOUND`, and strict `moduleResolution: bundler`
|
||||
# builds break too.
|
||||
#
|
||||
# Rules enforced (regression trip-wires):
|
||||
# 1. No file under v3/@claude-flow/plugin-agent-federation/src/ may
|
||||
# static-import `agentic-flow/transport/loader`. Dynamic
|
||||
# `import('agentic-flow/transport/loader')` guarded by try/catch
|
||||
# remains allowed (that is the intentional optional-path).
|
||||
# 2. verification/witness-fixes.json's `ADR-104-transport` entry must
|
||||
# NOT declare `agentic-flow/transport/loader` as its `marker`.
|
||||
# 3. ADR-104's supported smoke import target is the federation plugin
|
||||
# loader, and that loader owns a WebSocket fallback for current
|
||||
# agentic-flow releases that do not export `./transport/loader`.
|
||||
name: no-phantom-agentic-flow-subpath
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'v3/@claude-flow/plugin-agent-federation/src/**'
|
||||
- 'v3/@claude-flow/plugin-agent-federation/dist/**'
|
||||
- 'v3/docs/adr/ADR-104-federation-wire-transport.md'
|
||||
- 'scripts/smoke-adr104-transport.mjs'
|
||||
- 'verification/witness-fixes.json'
|
||||
- '.github/workflows/no-phantom-agentic-flow-subpath.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'v3/@claude-flow/plugin-agent-federation/src/**'
|
||||
- 'v3/@claude-flow/plugin-agent-federation/dist/**'
|
||||
- 'v3/docs/adr/ADR-104-federation-wire-transport.md'
|
||||
- 'scripts/smoke-adr104-transport.mjs'
|
||||
- 'verification/witness-fixes.json'
|
||||
- '.github/workflows/no-phantom-agentic-flow-subpath.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
guard:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Rule 1 — midstream-aware-loader must not static-import agentic-flow/transport/loader
|
||||
# Scoped to the file the #2578 fix touched. midstream-aware-loader.ts
|
||||
# is what plugin.ts uses at runtime (via loadFederationTransport), so a
|
||||
# regression here breaks the runtime path and the external Check 8
|
||||
# verifier. A dynamic `import('agentic-flow/transport/loader')` guarded
|
||||
# by try/catch is the intentional optional path and stays allowed.
|
||||
run: |
|
||||
node -e "
|
||||
const { readFileSync } = require('fs');
|
||||
const target = 'v3/@claude-flow/plugin-agent-federation/src/transport/midstream-aware-loader.ts';
|
||||
const src = readFileSync(target, 'utf-8');
|
||||
const re = /^\s*(?:import|export)(?:\s+type)?\s+[^;]*?from\s+['\"]agentic-flow\/transport\/loader['\"]/m;
|
||||
if (re.test(src)) {
|
||||
console.error('#2578 regression: ' + target + ' static-imports agentic-flow/transport/loader.');
|
||||
console.error('That subpath is not in agentic-flow\\'s published exports map — external Check 8 verifiers ERR_MODULE_NOT_FOUND.');
|
||||
console.error('Use the locally-declared type surface + dynamic import guarded by try/catch instead.');
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('OK — ' + target + ' does not static-import the phantom subpath.');
|
||||
"
|
||||
|
||||
- name: Rule 2 — ADR-104 witness marker must not be the phantom subpath
|
||||
run: |
|
||||
node -e "
|
||||
const { readFileSync } = require('fs');
|
||||
const witness = JSON.parse(readFileSync('verification/witness-fixes.json', 'utf-8'));
|
||||
const fixes = witness.fixes || witness || [];
|
||||
const entries = Array.isArray(fixes) ? fixes : Object.values(fixes);
|
||||
const adr104 = entries.find(f => f && f.id === 'ADR-104-transport');
|
||||
if (!adr104) {
|
||||
console.error('#2578 regression guard: ADR-104-transport entry missing from verification/witness-fixes.json');
|
||||
process.exit(1);
|
||||
}
|
||||
if (adr104.marker === 'agentic-flow/transport/loader') {
|
||||
console.error('#2578 regression: ADR-104-transport marker reverted to the phantom subpath.');
|
||||
console.error('agentic-flow does not export ./transport/loader — external Check 8 verifiers will ERR_MODULE_NOT_FOUND.');
|
||||
console.error('Use a marker that appears in the plugin\\'s own dist (e.g. loadFederationTransport).');
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('OK — ADR-104-transport marker is \"' + adr104.marker + '\" (not the phantom subpath).');
|
||||
"
|
||||
|
||||
- name: Rule 3 — ADR-104 smoke import target must be plugin loader
|
||||
run: node scripts/smoke-adr104-transport.mjs
|
||||
@@ -0,0 +1,196 @@
|
||||
# Weekly composite audit (ADR-150 Phase 2 — iter 7).
|
||||
#
|
||||
# Runs `harness oia-audit` against the ruflo repo every Sunday at
|
||||
# 04:17 UTC (off-peak, off-the-hour to avoid the global :00 thundering
|
||||
# herd; see CronCreate "Avoid the :00 and :30 minute marks").
|
||||
#
|
||||
# The audit bundles three orthogonal MetaHarness static-analysis
|
||||
# surfaces — oia-manifest + threat-model + mcp-scan — into one
|
||||
# timestamped record uploaded as a CI artifact. Failure threshold:
|
||||
# composite worst severity >= HIGH fails the workflow.
|
||||
#
|
||||
# Accumulated artifacts (retained 90 days) enable drift detection
|
||||
# over time without needing a persistent memory store in CI.
|
||||
#
|
||||
# ADR-150 graceful degradation: when metaharness is unavailable, the
|
||||
# script emits a degraded payload and exits 0 — the workflow
|
||||
# continues, and the artifact records the degraded state.
|
||||
name: oia-audit-weekly
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '17 4 * * 0' # Sundays at 04:17 UTC
|
||||
workflow_dispatch: # manual trigger for ad-hoc audits
|
||||
# iter 109 — parameterize the policy thresholds so ad-hoc runs can
|
||||
# explore stricter/looser gates without editing YAML.
|
||||
inputs:
|
||||
threshold:
|
||||
description: 'Structural similarity threshold (default 0.85 — scheduled). Lower = stricter drift alert.'
|
||||
type: string
|
||||
default: '0.85'
|
||||
required: false
|
||||
alert_on_new_severity:
|
||||
description: 'Alert on any introduced finding ≥ this severity'
|
||||
type: choice
|
||||
options:
|
||||
- info
|
||||
- low
|
||||
- medium
|
||||
- warn
|
||||
- high
|
||||
- error
|
||||
- critical
|
||||
default: high
|
||||
required: false
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'plugins/ruflo-metaharness/scripts/oia-audit.mjs'
|
||||
- 'plugins/ruflo-metaharness/scripts/_harness.mjs'
|
||||
- '.github/workflows/oia-audit-weekly.yml'
|
||||
|
||||
jobs:
|
||||
audit:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Run composite audit (alert on HIGH)
|
||||
# --dry-run because CI has no persistent memory store; the
|
||||
# artifact upload below is the durability mechanism.
|
||||
# --alert-on-worst high fails the job on any composite HIGH
|
||||
# finding; medium/low/clean pass.
|
||||
run: |
|
||||
node plugins/ruflo-metaharness/scripts/oia-audit.mjs \
|
||||
--path . \
|
||||
--dry-run \
|
||||
--alert-on-worst high \
|
||||
--format json \
|
||||
> /tmp/oia-audit.json
|
||||
cat /tmp/oia-audit.json | head -80
|
||||
# Extract composite severity for the workflow summary
|
||||
WORST=$(node -e "
|
||||
const j = JSON.parse(require('fs').readFileSync('/tmp/oia-audit.json'));
|
||||
console.log(j.composite?.worst || 'unknown');
|
||||
")
|
||||
echo "## Composite worst severity: \`$WORST\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Artifact uploaded under \`oia-audit-$(date -u +%Y-%m-%d)\`." >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Upload audit artifact (90-day retention for drift tracking)
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: oia-audit-${{ github.run_id }}
|
||||
path: /tmp/oia-audit.json
|
||||
retention-days: 90
|
||||
|
||||
# iter 69 — close the loop: the weekly cron has always claimed
|
||||
# "Accumulated artifacts enable drift detection over time" (line 13)
|
||||
# but the diff step was never wired. Iter-67's --baseline-file
|
||||
# fastpath made it cheap (~1.4s). Now every Sunday's run diffs
|
||||
# against the most recent prior weekly artifact and surfaces the
|
||||
# structural distance + alert.
|
||||
# iter 70 — `if: always()` so drift detection fires EVEN when the
|
||||
# audit step exits non-zero (HIGH alert). The pre-iter-70 default
|
||||
# conditional skipped drift exactly when it was most valuable:
|
||||
# the weeks where something was breaking.
|
||||
- name: Download prior week's audit artifact (if any)
|
||||
id: prior-artifact
|
||||
if: always()
|
||||
continue-on-error: true
|
||||
run: |
|
||||
set +e
|
||||
# List recent successful runs of this workflow, pick the most
|
||||
# recent one BEFORE this run, then download its artifact.
|
||||
PREV_RUN=$(gh run list \
|
||||
--workflow=oia-audit-weekly.yml \
|
||||
--status=success \
|
||||
--limit=10 \
|
||||
--json databaseId,headSha \
|
||||
--jq ".[] | select(.databaseId != ${{ github.run_id }}) | .databaseId" \
|
||||
| head -1)
|
||||
if [ -z "$PREV_RUN" ]; then
|
||||
echo "No prior successful run found — first weekly audit. Skipping drift step."
|
||||
echo "has_prior=false" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
echo "Downloading prior audit from run $PREV_RUN..."
|
||||
gh run download "$PREV_RUN" \
|
||||
--name "oia-audit-${PREV_RUN}" \
|
||||
--dir /tmp/prior \
|
||||
&& echo "has_prior=true" >> $GITHUB_OUTPUT \
|
||||
&& ls -la /tmp/prior/
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
- name: Compute structural drift vs prior week (iter 69)
|
||||
# iter 70 — always() AND has-prior: fires on the failure path too
|
||||
if: always() && steps.prior-artifact.outputs.has_prior == 'true'
|
||||
run: |
|
||||
set -e
|
||||
# iter-67 fastest path: --baseline-file skips audit-list + memory roundtrip
|
||||
# iter 79 — also gate on new HIGH-severity findings. Catches the
|
||||
# case where structure stayed similar but a security regression
|
||||
# appeared (e.g., new mcp-scan HIGH finding). Either gate can fire.
|
||||
# iter 109 — parameterized policy thresholds via workflow_dispatch
|
||||
# inputs. Scheduled runs fall back to 0.85 / high.
|
||||
THRESHOLD="${{ inputs.threshold || '0.85' }}"
|
||||
ALERT_SEV="${{ inputs.alert_on_new_severity || 'high' }}"
|
||||
node plugins/ruflo-metaharness/scripts/drift-from-history.mjs \
|
||||
--baseline-file /tmp/prior/oia-audit.json \
|
||||
--dry-run \
|
||||
--threshold "$THRESHOLD" \
|
||||
--alert-on-new-severity "$ALERT_SEV" \
|
||||
--format json \
|
||||
> /tmp/drift-trend.json || true
|
||||
# Extract verdict for the workflow summary
|
||||
VERDICT=$(node -e "
|
||||
const j = JSON.parse(require('fs').readFileSync('/tmp/drift-trend.json'));
|
||||
console.log(j.drift?.structuralDistance?.verdict || 'unavailable');
|
||||
")
|
||||
OVERALL=$(node -e "
|
||||
const j = JSON.parse(require('fs').readFileSync('/tmp/drift-trend.json'));
|
||||
console.log(j.drift?.structuralDistance?.overall ?? 'n/a');
|
||||
")
|
||||
ALERT=$(node -e "
|
||||
const j = JSON.parse(require('fs').readFileSync('/tmp/drift-trend.json'));
|
||||
console.log(j.alert?.triggered ? 'TRIGGERED' : 'OK');
|
||||
")
|
||||
# iter 97 — also surface the fast-path label so the Actions UI
|
||||
# shows which iter-66/67 path executed. Confirms cron used the
|
||||
# baseline-file path (expected ~1.4s) and not slow path (~26s).
|
||||
PATH_TAKEN=$(node -e "
|
||||
const j = JSON.parse(require('fs').readFileSync('/tmp/drift-trend.json'));
|
||||
const t = j.timing || {};
|
||||
console.log((t.path || 'unknown') + ' (wall ' + (t.parallelWallMs ?? '?') + 'ms)');
|
||||
")
|
||||
echo "## Drift vs prior week: \`$VERDICT\` (similarity=$OVERALL)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Alert status: \`$ALERT\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Path: \`$PATH_TAKEN\`" >> $GITHUB_STEP_SUMMARY
|
||||
# iter 108 — fail the workflow if the cron accidentally fell
|
||||
# through to the slow path. The cron's --baseline-file flag
|
||||
# is the load-bearing invariant for cron-budget correctness;
|
||||
# if it disappears, the audit takes ~26s instead of ~1.4s
|
||||
# and the iter-7 weekly-cron budget exhausts on slower runners.
|
||||
PATH_LABEL=$(node -e "
|
||||
const j = JSON.parse(require('fs').readFileSync('/tmp/drift-trend.json'));
|
||||
console.log(j.timing?.path || 'unknown');
|
||||
")
|
||||
if [ "$PATH_LABEL" != "file" ]; then
|
||||
echo "::error::Cron drift step expected timing.path='file' but got '$PATH_LABEL' — the iter-67 fastpath flag may have regressed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Upload drift trend artifact
|
||||
# iter 70 — same always() pattern; the trend artifact is most
|
||||
# useful for forensics when the workflow has failed.
|
||||
if: always() && steps.prior-artifact.outputs.has_prior == 'true'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: drift-trend-${{ github.run_id }}
|
||||
path: /tmp/drift-trend.json
|
||||
retention-days: 90
|
||||
@@ -0,0 +1,666 @@
|
||||
name: 🔄 Automated Rollback Manager
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["🔍 Verification Pipeline", "🎯 Truth Scoring Pipeline", "🔗 Cross-Agent Integration Tests"]
|
||||
types: [completed]
|
||||
branches: [main, develop]
|
||||
push:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
rollback_target:
|
||||
description: 'Target commit SHA or tag for rollback'
|
||||
required: true
|
||||
rollback_reason:
|
||||
description: 'Reason for rollback'
|
||||
required: true
|
||||
default: 'Manual rollback requested'
|
||||
emergency_mode:
|
||||
description: 'Emergency rollback mode (skip confirmations)'
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
rollback_scope:
|
||||
description: 'Rollback scope'
|
||||
required: false
|
||||
default: 'application'
|
||||
type: choice
|
||||
options:
|
||||
- application
|
||||
- database
|
||||
- infrastructure
|
||||
- full
|
||||
|
||||
env:
|
||||
NODE_VERSION: '20'
|
||||
ROLLBACK_RETENTION_DAYS: 90
|
||||
CRITICAL_FAILURE_THRESHOLD: 3
|
||||
MONITORING_WINDOW_MINUTES: 15
|
||||
|
||||
jobs:
|
||||
# Detect failure conditions
|
||||
failure-detection:
|
||||
name: 🚨 Failure Detection
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'workflow_run' || github.event_name == 'push'
|
||||
outputs:
|
||||
rollback-required: ${{ steps.detect.outputs.rollback-required }}
|
||||
failure-type: ${{ steps.detect.outputs.failure-type }}
|
||||
failure-severity: ${{ steps.detect.outputs.failure-severity }}
|
||||
rollback-target: ${{ steps.detect.outputs.rollback-target }}
|
||||
rollback-session-id: ${{ steps.detect.outputs.rollback-session-id }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 50
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: Detect failure conditions
|
||||
id: detect
|
||||
run: |
|
||||
echo "🚨 Analyzing failure conditions..."
|
||||
|
||||
ROLLBACK_SESSION="rollback-$(date +%Y%m%d-%H%M%S)-${{ github.sha }}"
|
||||
echo "rollback-session-id=$ROLLBACK_SESSION" >> $GITHUB_OUTPUT
|
||||
|
||||
ROLLBACK_REQUIRED="false"
|
||||
FAILURE_TYPE="none"
|
||||
FAILURE_SEVERITY="low"
|
||||
ROLLBACK_TARGET=""
|
||||
|
||||
# Check workflow run results if triggered by workflow_run
|
||||
if [ "${{ github.event_name }}" = "workflow_run" ]; then
|
||||
WORKFLOW_CONCLUSION="${{ github.event.workflow_run.conclusion }}"
|
||||
WORKFLOW_NAME="${{ github.event.workflow_run.name }}"
|
||||
|
||||
echo "Workflow: $WORKFLOW_NAME"
|
||||
echo "Conclusion: $WORKFLOW_CONCLUSION"
|
||||
|
||||
if [ "$WORKFLOW_CONCLUSION" = "failure" ]; then
|
||||
ROLLBACK_REQUIRED="true"
|
||||
FAILURE_TYPE="ci_failure"
|
||||
|
||||
# Determine severity based on workflow type
|
||||
case "$WORKFLOW_NAME" in
|
||||
*"Verification Pipeline"*)
|
||||
FAILURE_SEVERITY="high"
|
||||
;;
|
||||
*"Truth Scoring"*)
|
||||
FAILURE_SEVERITY="medium"
|
||||
;;
|
||||
*"Integration Tests"*)
|
||||
FAILURE_SEVERITY="high"
|
||||
;;
|
||||
*)
|
||||
FAILURE_SEVERITY="medium"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check for recent commit history to find safe rollback target
|
||||
if [ "$ROLLBACK_REQUIRED" = "true" ]; then
|
||||
# Find the last successful commit (simplified logic)
|
||||
ROLLBACK_TARGET=$(git log --oneline -10 --grep="✅" --grep="🏁" | head -1 | cut -d' ' -f1)
|
||||
if [ -z "$ROLLBACK_TARGET" ]; then
|
||||
ROLLBACK_TARGET="HEAD~1"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "rollback-required=$ROLLBACK_REQUIRED" >> $GITHUB_OUTPUT
|
||||
echo "failure-type=$FAILURE_TYPE" >> $GITHUB_OUTPUT
|
||||
echo "failure-severity=$FAILURE_SEVERITY" >> $GITHUB_OUTPUT
|
||||
echo "rollback-target=$ROLLBACK_TARGET" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "🔍 Detection Results:"
|
||||
echo " Rollback Required: $ROLLBACK_REQUIRED"
|
||||
echo " Failure Type: $FAILURE_TYPE"
|
||||
echo " Severity: $FAILURE_SEVERITY"
|
||||
echo " Target: $ROLLBACK_TARGET"
|
||||
|
||||
- name: Create failure report
|
||||
if: steps.detect.outputs.rollback-required == 'true'
|
||||
run: |
|
||||
echo "📋 Creating failure report..."
|
||||
|
||||
mkdir -p rollback-data
|
||||
|
||||
cat > rollback-data/failure-report.json << EOF
|
||||
{
|
||||
"sessionId": "${{ steps.detect.outputs.rollback-session-id }}",
|
||||
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
||||
"trigger": {
|
||||
"event": "${{ github.event_name }}",
|
||||
"workflow": "${{ github.event.workflow_run.name || 'N/A' }}",
|
||||
"conclusion": "${{ github.event.workflow_run.conclusion || 'N/A' }}",
|
||||
"commit": "${{ github.sha }}",
|
||||
"branch": "${{ github.ref_name }}"
|
||||
},
|
||||
"failure": {
|
||||
"type": "${{ steps.detect.outputs.failure-type }}",
|
||||
"severity": "${{ steps.detect.outputs.failure-severity }}",
|
||||
"rollbackRequired": true
|
||||
},
|
||||
"rollback": {
|
||||
"target": "${{ steps.detect.outputs.rollback-target }}",
|
||||
"reason": "Automated rollback due to ${{ steps.detect.outputs.failure-type }}"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
- name: Upload failure detection results
|
||||
if: steps.detect.outputs.rollback-required == 'true'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: failure-detection-${{ steps.detect.outputs.rollback-session-id }}
|
||||
path: rollback-data/
|
||||
retention-days: ${{ env.ROLLBACK_RETENTION_DAYS }}
|
||||
|
||||
# Pre-rollback validation
|
||||
pre-rollback-validation:
|
||||
name: 🔍 Pre-Rollback Validation
|
||||
runs-on: ubuntu-latest
|
||||
needs: failure-detection
|
||||
if: needs.failure-detection.outputs.rollback-required == 'true' || github.event_name == 'workflow_dispatch'
|
||||
outputs:
|
||||
validation-passed: ${{ steps.validate.outputs.validation-passed }}
|
||||
backup-created: ${{ steps.validate.outputs.backup-created }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 100
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci --legacy-peer-deps
|
||||
|
||||
- name: Validate rollback target
|
||||
id: validate
|
||||
run: |
|
||||
echo "🔍 Validating rollback target..."
|
||||
|
||||
ROLLBACK_TARGET="${{ github.event.inputs.rollback_target || needs.failure-detection.outputs.rollback-target }}"
|
||||
VALIDATION_PASSED="false"
|
||||
BACKUP_CREATED="false"
|
||||
|
||||
if [ -n "$ROLLBACK_TARGET" ]; then
|
||||
# Check if target commit exists
|
||||
if git cat-file -e "$ROLLBACK_TARGET^{commit}" 2>/dev/null; then
|
||||
echo "✅ Rollback target $ROLLBACK_TARGET is valid"
|
||||
|
||||
# Check if target is reachable from current branch
|
||||
if git merge-base --is-ancestor "$ROLLBACK_TARGET" HEAD; then
|
||||
echo "✅ Target is ancestor of current HEAD"
|
||||
VALIDATION_PASSED="true"
|
||||
else
|
||||
echo "❌ Target is not an ancestor of current HEAD"
|
||||
fi
|
||||
else
|
||||
echo "❌ Rollback target $ROLLBACK_TARGET does not exist"
|
||||
fi
|
||||
else
|
||||
echo "❌ No rollback target specified"
|
||||
fi
|
||||
|
||||
echo "validation-passed=$VALIDATION_PASSED" >> $GITHUB_OUTPUT
|
||||
echo "backup-created=$BACKUP_CREATED" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create current state backup
|
||||
if: steps.validate.outputs.validation-passed == 'true'
|
||||
run: |
|
||||
echo "💾 Creating current state backup..."
|
||||
|
||||
mkdir -p rollback-data/backup
|
||||
|
||||
# Create backup metadata
|
||||
cat > rollback-data/backup/backup-metadata.json << EOF
|
||||
{
|
||||
"backupId": "backup-$(date +%Y%m%d-%H%M%S)",
|
||||
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
||||
"sourceCommit": "${{ github.sha }}",
|
||||
"sourceBranch": "${{ github.ref_name }}",
|
||||
"rollbackTarget": "${{ github.event.inputs.rollback_target || needs.failure-detection.outputs.rollback-target }}",
|
||||
"backupType": "pre-rollback"
|
||||
}
|
||||
EOF
|
||||
|
||||
# Create git bundle for backup
|
||||
git bundle create rollback-data/backup/current-state.bundle HEAD
|
||||
|
||||
# Backup package.json and important config files
|
||||
cp package.json rollback-data/backup/ 2>/dev/null || true
|
||||
cp package-lock.json rollback-data/backup/ 2>/dev/null || true
|
||||
cp claude-flow.config.json rollback-data/backup/ 2>/dev/null || true
|
||||
|
||||
echo "✅ Backup created successfully"
|
||||
|
||||
- name: Test rollback target viability
|
||||
if: steps.validate.outputs.validation-passed == 'true'
|
||||
run: |
|
||||
set -e # Exit on any error
|
||||
echo "🧪 Testing rollback target viability..."
|
||||
|
||||
ROLLBACK_TARGET="${{ github.event.inputs.rollback_target || needs.failure-detection.outputs.rollback-target }}"
|
||||
|
||||
# Create temporary branch for testing
|
||||
git checkout -b test-rollback-temp "$ROLLBACK_TARGET"
|
||||
|
||||
# Test if the target can build (strict error checking)
|
||||
echo "Installing dependencies..."
|
||||
npm ci --legacy-peer-deps
|
||||
|
||||
echo "Testing build..."
|
||||
npm run build:ts
|
||||
|
||||
# Switch back to original branch
|
||||
git checkout "${{ github.ref_name }}"
|
||||
git branch -D test-rollback-temp
|
||||
|
||||
echo "✅ Rollback target viability tested successfully"
|
||||
|
||||
- name: Upload pre-rollback validation
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: pre-rollback-validation-${{ needs.failure-detection.outputs.rollback-session-id || 'manual' }}
|
||||
path: rollback-data/
|
||||
retention-days: ${{ env.ROLLBACK_RETENTION_DAYS }}
|
||||
|
||||
# Execute rollback
|
||||
execute-rollback:
|
||||
name: 🔄 Execute Rollback
|
||||
runs-on: ubuntu-latest
|
||||
needs: [failure-detection, pre-rollback-validation]
|
||||
if: needs.pre-rollback-validation.outputs.validation-passed == 'true' && (needs.failure-detection.outputs.failure-severity == 'high' || github.event.inputs.emergency_mode == 'true' || github.event_name == 'workflow_dispatch')
|
||||
environment:
|
||||
name: rollback-approval
|
||||
outputs:
|
||||
rollback-executed: ${{ steps.rollback.outputs.rollback-executed }}
|
||||
rollback-commit: ${{ steps.rollback.outputs.rollback-commit }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 100
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: Download validation artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: pre-rollback-validation-${{ needs.failure-detection.outputs.rollback-session-id || 'manual' }}
|
||||
path: rollback-data/
|
||||
|
||||
- name: Configure Git
|
||||
run: |
|
||||
git config --global user.name "GitHub Actions Rollback Bot"
|
||||
git config --global user.email "actions@github.com"
|
||||
|
||||
- name: Execute rollback
|
||||
id: rollback
|
||||
run: |
|
||||
echo "🔄 Executing rollback..."
|
||||
|
||||
ROLLBACK_TARGET="${{ github.event.inputs.rollback_target || needs.failure-detection.outputs.rollback-target }}"
|
||||
ROLLBACK_REASON="${{ github.event.inputs.rollback_reason || 'Automated rollback due to CI failure' }}"
|
||||
ROLLBACK_EXECUTED="false"
|
||||
ROLLBACK_COMMIT=""
|
||||
|
||||
echo "Target: $ROLLBACK_TARGET"
|
||||
echo "Reason: $ROLLBACK_REASON"
|
||||
|
||||
if [ -n "$ROLLBACK_TARGET" ]; then
|
||||
# Create rollback commit
|
||||
echo "Creating rollback commit..."
|
||||
|
||||
# Reset to target commit but keep it as a new commit
|
||||
git reset --hard "$ROLLBACK_TARGET"
|
||||
|
||||
# Create a revert commit with metadata
|
||||
cat > ROLLBACK_INFO.md << EOF
|
||||
# Rollback Information
|
||||
|
||||
**Rollback Timestamp:** $(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||
**Target Commit:** $ROLLBACK_TARGET
|
||||
**Reason:** $ROLLBACK_REASON
|
||||
**Triggered By:** ${{ github.actor }}
|
||||
**Session ID:** ${{ needs.failure-detection.outputs.rollback-session-id || 'manual' }}
|
||||
|
||||
## Original State
|
||||
- **Commit:** ${{ github.sha }}
|
||||
- **Branch:** ${{ github.ref_name }}
|
||||
|
||||
## Rollback Details
|
||||
- **Scope:** ${{ github.event.inputs.rollback_scope || 'application' }}
|
||||
- **Emergency Mode:** ${{ github.event.inputs.emergency_mode || 'false' }}
|
||||
|
||||
This rollback was executed automatically by the Rollback Manager workflow.
|
||||
EOF
|
||||
|
||||
git add ROLLBACK_INFO.md
|
||||
git commit -m "🔄 Automated rollback to $ROLLBACK_TARGET
|
||||
|
||||
Reason: $ROLLBACK_REASON
|
||||
Session: ${{ needs.failure-detection.outputs.rollback-session-id || 'manual' }}
|
||||
Scope: ${{ github.event.inputs.rollback_scope || 'application' }}
|
||||
|
||||
🤖 Generated by GitHub Actions Rollback Manager
|
||||
|
||||
Co-Authored-By: Rollback Manager <noreply@github.com>"
|
||||
|
||||
ROLLBACK_COMMIT=$(git rev-parse HEAD)
|
||||
ROLLBACK_EXECUTED="true"
|
||||
|
||||
echo "✅ Rollback commit created: $ROLLBACK_COMMIT"
|
||||
else
|
||||
echo "❌ No rollback target specified"
|
||||
fi
|
||||
|
||||
echo "rollback-executed=$ROLLBACK_EXECUTED" >> $GITHUB_OUTPUT
|
||||
echo "rollback-commit=$ROLLBACK_COMMIT" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Push rollback commit
|
||||
if: steps.rollback.outputs.rollback-executed == 'true'
|
||||
run: |
|
||||
echo "📤 Pushing rollback commit..."
|
||||
|
||||
# Force push the rollback (use with caution)
|
||||
if [ "${{ github.event.inputs.emergency_mode }}" = "true" ]; then
|
||||
git push origin HEAD:${{ github.ref_name }} --force-with-lease
|
||||
else
|
||||
git push origin HEAD:${{ github.ref_name }}
|
||||
fi
|
||||
|
||||
echo "✅ Rollback pushed successfully"
|
||||
|
||||
- name: Create rollback tag
|
||||
if: steps.rollback.outputs.rollback-executed == 'true'
|
||||
run: |
|
||||
echo "🏷️ Creating rollback tag..."
|
||||
|
||||
TAG_NAME="rollback-$(date +%Y%m%d-%H%M%S)"
|
||||
|
||||
git tag -a "$TAG_NAME" -m "Rollback executed on $(date -u)
|
||||
|
||||
Target: ${{ github.event.inputs.rollback_target || needs.failure-detection.outputs.rollback-target }}
|
||||
Reason: ${{ github.event.inputs.rollback_reason || 'Automated rollback' }}
|
||||
Session: ${{ needs.failure-detection.outputs.rollback-session-id || 'manual' }}"
|
||||
|
||||
git push origin "$TAG_NAME"
|
||||
|
||||
echo "✅ Rollback tag $TAG_NAME created"
|
||||
|
||||
# Post-rollback verification
|
||||
post-rollback-verification:
|
||||
name: ✅ Post-Rollback Verification
|
||||
runs-on: ubuntu-latest
|
||||
needs: [failure-detection, execute-rollback]
|
||||
if: needs.execute-rollback.outputs.rollback-executed == 'true'
|
||||
steps:
|
||||
- name: Checkout rolled back code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ needs.execute-rollback.outputs.rollback-commit }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci --legacy-peer-deps
|
||||
|
||||
- name: Verify build functionality
|
||||
run: |
|
||||
echo "🔍 Verifying build functionality..."
|
||||
|
||||
# Test basic build
|
||||
npm run build:ts || (echo "❌ Build failed after rollback" && exit 1)
|
||||
|
||||
echo "✅ Build verification passed"
|
||||
|
||||
- name: Run smoke tests
|
||||
run: |
|
||||
echo "🧪 Running smoke tests..."
|
||||
|
||||
# Run basic tests to ensure functionality
|
||||
timeout 120s npm run test:unit || echo "⚠️ Some tests failed"
|
||||
|
||||
echo "✅ Smoke tests completed"
|
||||
|
||||
- name: Verify CLI functionality
|
||||
run: |
|
||||
echo "🖥️ Verifying CLI functionality..."
|
||||
|
||||
# Test basic CLI commands
|
||||
node dist/cli/main.js --version || (echo "❌ CLI verification failed" && exit 1)
|
||||
node dist/cli/main.js --help > /dev/null || (echo "❌ CLI help failed" && exit 1)
|
||||
|
||||
echo "✅ CLI verification passed"
|
||||
|
||||
- name: System health check
|
||||
run: |
|
||||
echo "💊 Running system health check..."
|
||||
|
||||
node -e "
|
||||
async function healthCheck() {
|
||||
const checks = {
|
||||
packageJson: { status: 'unknown', message: '' },
|
||||
dependencies: { status: 'unknown', message: '' },
|
||||
configuration: { status: 'unknown', message: '' },
|
||||
overall: { status: 'unknown', healthy: false }
|
||||
};
|
||||
|
||||
try {
|
||||
// Check package.json integrity
|
||||
const pkg = require('./package.json');
|
||||
if (pkg.name && pkg.version) {
|
||||
checks.packageJson.status = 'passed';
|
||||
checks.packageJson.message = \`Package: \${pkg.name}@\${pkg.version}\`;
|
||||
}
|
||||
|
||||
// Check dependencies
|
||||
const deps = Object.keys(pkg.dependencies || {}).length;
|
||||
const devDeps = Object.keys(pkg.devDependencies || {}).length;
|
||||
checks.dependencies.status = 'passed';
|
||||
checks.dependencies.message = \`\${deps} runtime, \${devDeps} dev dependencies\`;
|
||||
|
||||
// Check configuration files
|
||||
const fs = require('fs');
|
||||
if (fs.existsSync('tsconfig.json')) {
|
||||
checks.configuration.status = 'passed';
|
||||
checks.configuration.message = 'Configuration files present';
|
||||
}
|
||||
|
||||
// Overall health
|
||||
const passedChecks = Object.values(checks).filter(c => c.status === 'passed').length;
|
||||
checks.overall.healthy = passedChecks >= 3;
|
||||
checks.overall.status = checks.overall.healthy ? 'passed' : 'failed';
|
||||
|
||||
} catch (e) {
|
||||
checks.overall.status = 'error';
|
||||
checks.overall.message = e.message;
|
||||
}
|
||||
|
||||
console.log('Health Check Results:', JSON.stringify(checks, null, 2));
|
||||
|
||||
if (!checks.overall.healthy) {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
healthCheck().catch(console.error);
|
||||
"
|
||||
|
||||
# Rollback monitoring
|
||||
rollback-monitoring:
|
||||
name: 📊 Rollback Monitoring
|
||||
runs-on: ubuntu-latest
|
||||
needs: [failure-detection, execute-rollback, post-rollback-verification]
|
||||
if: needs.execute-rollback.outputs.rollback-executed == 'true'
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Monitor system stability
|
||||
run: |
|
||||
echo "📊 Monitoring system stability after rollback..."
|
||||
|
||||
# Monitor for specified window
|
||||
MONITOR_DURATION="${{ env.MONITORING_WINDOW_MINUTES }}"
|
||||
echo "Monitoring for $MONITOR_DURATION minutes..."
|
||||
|
||||
# Simulate monitoring (in real scenario, this would check actual metrics)
|
||||
sleep 30 # Short monitoring for demo
|
||||
|
||||
echo "✅ Monitoring completed - system appears stable"
|
||||
|
||||
- name: Generate rollback report
|
||||
run: |
|
||||
echo "📋 Generating rollback report..."
|
||||
|
||||
mkdir -p rollback-reports
|
||||
|
||||
cat > rollback-reports/rollback-report.json << EOF
|
||||
{
|
||||
"sessionId": "${{ needs.failure-detection.outputs.rollback-session-id || 'manual' }}",
|
||||
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
||||
"rollback": {
|
||||
"executed": true,
|
||||
"commit": "${{ needs.execute-rollback.outputs.rollback-commit }}",
|
||||
"target": "${{ github.event.inputs.rollback_target || needs.failure-detection.outputs.rollback-target }}",
|
||||
"reason": "${{ github.event.inputs.rollback_reason || 'Automated rollback' }}"
|
||||
},
|
||||
"verification": {
|
||||
"buildPassed": true,
|
||||
"testsPassed": true,
|
||||
"cliWorking": true,
|
||||
"systemHealthy": true
|
||||
},
|
||||
"monitoring": {
|
||||
"duration": "${{ env.MONITORING_WINDOW_MINUTES }} minutes",
|
||||
"systemStable": true,
|
||||
"issuesDetected": 0
|
||||
},
|
||||
"status": "completed_successfully"
|
||||
}
|
||||
EOF
|
||||
|
||||
# Generate markdown report
|
||||
cat > rollback-reports/rollback-report.md << 'EOF'
|
||||
# 🔄 Rollback Execution Report
|
||||
|
||||
**Session ID:** ${{ needs.failure-detection.outputs.rollback-session-id || 'manual' }}
|
||||
**Execution Time:** $(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||
**Status:** ✅ COMPLETED SUCCESSFULLY
|
||||
|
||||
## Rollback Details
|
||||
|
||||
- **Target Commit:** ${{ github.event.inputs.rollback_target || needs.failure-detection.outputs.rollback-target }}
|
||||
- **Rollback Commit:** ${{ needs.execute-rollback.outputs.rollback-commit }}
|
||||
- **Reason:** ${{ github.event.inputs.rollback_reason || 'Automated rollback' }}
|
||||
- **Scope:** ${{ github.event.inputs.rollback_scope || 'application' }}
|
||||
|
||||
## Verification Results
|
||||
|
||||
| Check | Status |
|
||||
|-------|--------|
|
||||
| Build | ✅ Passed |
|
||||
| Tests | ✅ Passed |
|
||||
| CLI | ✅ Working |
|
||||
| Health | ✅ Healthy |
|
||||
|
||||
## Post-Rollback Monitoring
|
||||
|
||||
- **Duration:** ${{ env.MONITORING_WINDOW_MINUTES }} minutes
|
||||
- **System Stability:** ✅ Stable
|
||||
- **Issues Detected:** 0
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. ✅ System has been successfully rolled back
|
||||
2. ✅ All verification checks passed
|
||||
3. ✅ Monitoring completed successfully
|
||||
4. 🔍 Investigate original failure cause
|
||||
5. 🛠️ Implement fixes before next deployment
|
||||
|
||||
---
|
||||
*Generated by Automated Rollback Manager*
|
||||
EOF
|
||||
|
||||
- name: Upload rollback reports
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: rollback-reports-${{ needs.failure-detection.outputs.rollback-session-id || 'manual' }}
|
||||
path: rollback-reports/
|
||||
retention-days: ${{ env.ROLLBACK_RETENTION_DAYS }}
|
||||
|
||||
- name: Notify stakeholders
|
||||
if: github.event_name != 'workflow_dispatch'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const report = `
|
||||
## 🔄 Automated Rollback Executed
|
||||
|
||||
**Status:** ✅ COMPLETED SUCCESSFULLY
|
||||
**Commit:** ${{ needs.execute-rollback.outputs.rollback-commit }}
|
||||
**Target:** ${{ github.event.inputs.rollback_target || needs.failure-detection.outputs.rollback-target }}
|
||||
**Reason:** ${{ github.event.inputs.rollback_reason || 'Automated rollback due to CI failure' }}
|
||||
|
||||
### Verification Summary
|
||||
- ✅ Build successful
|
||||
- ✅ Tests passing
|
||||
- ✅ CLI functional
|
||||
- ✅ System healthy
|
||||
|
||||
The system has been automatically rolled back and is now stable.
|
||||
Please investigate the original failure before the next deployment.
|
||||
`;
|
||||
|
||||
// Create an issue for tracking
|
||||
github.rest.issues.create({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
title: `🔄 Automated Rollback Executed - ${new Date().toISOString().split('T')[0]}`,
|
||||
body: report,
|
||||
labels: ['rollback', 'automated', 'incident']
|
||||
});
|
||||
|
||||
# Manual rollback approval (for non-emergency cases)
|
||||
manual-rollback-approval:
|
||||
name: ⏳ Manual Rollback Approval
|
||||
runs-on: ubuntu-latest
|
||||
needs: [failure-detection, pre-rollback-validation]
|
||||
if: needs.failure-detection.outputs.rollback-required == 'true' && needs.failure-detection.outputs.failure-severity != 'high' && github.event.inputs.emergency_mode != 'true'
|
||||
environment:
|
||||
name: rollback-manual-approval
|
||||
steps:
|
||||
- name: Manual approval required
|
||||
run: |
|
||||
echo "⏳ Manual approval required for rollback"
|
||||
echo "Failure Type: ${{ needs.failure-detection.outputs.failure-type }}"
|
||||
echo "Severity: ${{ needs.failure-detection.outputs.failure-severity }}"
|
||||
echo "Target: ${{ needs.failure-detection.outputs.rollback-target }}"
|
||||
echo ""
|
||||
echo "This rollback requires manual approval due to:"
|
||||
echo "- Non-critical failure severity"
|
||||
echo "- No emergency mode specified"
|
||||
echo ""
|
||||
echo "Please review the failure details and approve if rollback is needed."
|
||||
@@ -0,0 +1,30 @@
|
||||
# Structural smoke for the ruflo-agent plugin (local WASM runtime — rvagent —
|
||||
# plus the Anthropic Managed Agents cloud runtime, ADR-115). No network: it's
|
||||
# a static check of the plugin manifest, skills, commands, MCP-tool references,
|
||||
# and ADR cross-references. Triggers on changes to the plugin or this workflow.
|
||||
name: ruflo-agent-smoke
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop, v3]
|
||||
paths:
|
||||
- 'plugins/ruflo-agent/**'
|
||||
- '.github/workflows/ruflo-agent-smoke.yml'
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'plugins/ruflo-agent/**'
|
||||
- '.github/workflows/ruflo-agent-smoke.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
smoke:
|
||||
name: ruflo-agent structural smoke
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
- name: Run plugin smoke
|
||||
run: bash plugins/ruflo-agent/scripts/smoke.sh
|
||||
@@ -0,0 +1,171 @@
|
||||
name: 📊 Status Badges Update
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["🔍 Verification Pipeline", "🎯 Truth Scoring Pipeline", "🔗 Cross-Agent Integration Tests"]
|
||||
types: [completed]
|
||||
push:
|
||||
branches: [main]
|
||||
schedule:
|
||||
- cron: '0 6 * * *' # Daily at 6 AM UTC
|
||||
|
||||
jobs:
|
||||
update-badges:
|
||||
name: 📊 Update Status Badges
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/main'
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Generate badge data
|
||||
run: |
|
||||
echo "📊 Generating badge data..."
|
||||
|
||||
mkdir -p badge-data
|
||||
|
||||
# Get latest workflow runs
|
||||
VERIFICATION_STATUS="${{ github.event.workflow_run.conclusion || 'unknown' }}"
|
||||
WORKFLOW_NAME="${{ github.event.workflow_run.name || 'unknown' }}"
|
||||
|
||||
# Determine badge colors and status
|
||||
case "$VERIFICATION_STATUS" in
|
||||
"success")
|
||||
BADGE_COLOR="brightgreen"
|
||||
BADGE_STATUS="passing"
|
||||
;;
|
||||
"failure")
|
||||
BADGE_COLOR="red"
|
||||
BADGE_STATUS="failing"
|
||||
;;
|
||||
*)
|
||||
BADGE_COLOR="yellow"
|
||||
BADGE_STATUS="unknown"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Create badge JSON
|
||||
cat > badge-data/status.json << EOF
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"label": "pipeline",
|
||||
"message": "$BADGE_STATUS",
|
||||
"color": "$BADGE_COLOR",
|
||||
"style": "flat-square"
|
||||
}
|
||||
EOF
|
||||
|
||||
# Truth scoring badge
|
||||
if [ "$WORKFLOW_NAME" = "🎯 Truth Scoring Pipeline" ]; then
|
||||
TRUTH_COLOR="brightgreen"
|
||||
TRUTH_MESSAGE="85+"
|
||||
if [ "$VERIFICATION_STATUS" = "failure" ]; then
|
||||
TRUTH_COLOR="red"
|
||||
TRUTH_MESSAGE="<85"
|
||||
fi
|
||||
|
||||
cat > badge-data/truth-score.json << EOF
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"label": "truth score",
|
||||
"message": "$TRUTH_MESSAGE",
|
||||
"color": "$TRUTH_COLOR",
|
||||
"style": "flat-square"
|
||||
}
|
||||
EOF
|
||||
fi
|
||||
|
||||
# Integration tests badge
|
||||
if [ "$WORKFLOW_NAME" = "🔗 Cross-Agent Integration Tests" ]; then
|
||||
INTEGRATION_COLOR="brightgreen"
|
||||
INTEGRATION_MESSAGE="passing"
|
||||
if [ "$VERIFICATION_STATUS" = "failure" ]; then
|
||||
INTEGRATION_COLOR="red"
|
||||
INTEGRATION_MESSAGE="failing"
|
||||
fi
|
||||
|
||||
cat > badge-data/integration.json << EOF
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"label": "integration",
|
||||
"message": "$INTEGRATION_MESSAGE",
|
||||
"color": "$INTEGRATION_COLOR",
|
||||
"style": "flat-square"
|
||||
}
|
||||
EOF
|
||||
fi
|
||||
|
||||
- name: Update README badges
|
||||
run: |
|
||||
echo "📝 Updating README badges..."
|
||||
|
||||
# Check if README has badge section
|
||||
if grep -q "<!-- BADGES-START -->" README.md; then
|
||||
echo "Found badge section, updating..."
|
||||
|
||||
# Create new badge section
|
||||
cat > new-badges.md << 'EOF'
|
||||
<!-- BADGES-START -->
|
||||
[](https://github.com/ruvnet/claude-code-flow/actions/workflows/verification-pipeline.yml)
|
||||
[](https://github.com/ruvnet/claude-code-flow/actions/workflows/truth-scoring.yml)
|
||||
[](https://github.com/ruvnet/claude-code-flow/actions/workflows/integration-tests.yml)
|
||||
[](https://github.com/ruvnet/claude-code-flow/actions/workflows/rollback-manager.yml)
|
||||
[](https://github.com/ruvnet/claude-code-flow/actions/workflows/ci.yml)
|
||||
[](LICENSE)
|
||||
[](https://www.npmjs.com/package/claude-flow)
|
||||
<!-- BADGES-END -->
|
||||
EOF
|
||||
|
||||
# Replace badge section in README
|
||||
awk '
|
||||
BEGIN { in_badges = 0 }
|
||||
/<!-- BADGES-START -->/ {
|
||||
in_badges = 1
|
||||
while ((getline line < "new-badges.md") > 0) {
|
||||
print line
|
||||
}
|
||||
close("new-badges.md")
|
||||
next
|
||||
}
|
||||
/<!-- BADGES-END -->/ {
|
||||
in_badges = 0
|
||||
next
|
||||
}
|
||||
!in_badges { print }
|
||||
' README.md > README.tmp && mv README.tmp README.md
|
||||
|
||||
rm -f new-badges.md
|
||||
else
|
||||
echo "No badge section found in README.md"
|
||||
fi
|
||||
|
||||
- name: Commit badge updates
|
||||
run: |
|
||||
git config --local user.email "action@github.com"
|
||||
git config --local user.name "GitHub Action"
|
||||
|
||||
if git diff --quiet README.md; then
|
||||
echo "No changes to commit"
|
||||
else
|
||||
git add README.md
|
||||
git commit -m "📊 Update status badges
|
||||
|
||||
🤖 Generated by GitHub Actions
|
||||
|
||||
Co-Authored-By: Badge Updater <noreply@github.com>"
|
||||
git push
|
||||
fi
|
||||
|
||||
- name: Upload badge data
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: badge-data-$(date +%Y%m%d)
|
||||
path: badge-data/
|
||||
retention-days: 7
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,57 @@
|
||||
name: Validate Marketplace
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '.claude-plugin/**'
|
||||
- 'plugins/**'
|
||||
pull_request:
|
||||
paths:
|
||||
- '.claude-plugin/**'
|
||||
- 'plugins/**'
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
- name: Validate marketplace.json
|
||||
run: |
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const catalog = JSON.parse(fs.readFileSync('.claude-plugin/marketplace.json'));
|
||||
console.log('Marketplace:', catalog.name);
|
||||
console.log('Plugins:', catalog.plugins.length);
|
||||
for (const p of catalog.plugins) {
|
||||
const dir = p.source;
|
||||
if (!fs.existsSync(dir)) throw new Error('Missing plugin dir: ' + dir);
|
||||
const manifest = dir + '/.claude-plugin/plugin.json';
|
||||
if (!fs.existsSync(manifest)) throw new Error('Missing manifest: ' + manifest);
|
||||
const m = JSON.parse(fs.readFileSync(manifest));
|
||||
if (!m.name || !m.description || !m.version) throw new Error('Manifest missing required fields: ' + manifest);
|
||||
if (!m.author || !m.author.name) throw new Error('Manifest missing author: ' + manifest);
|
||||
console.log(' OK', m.name, m.version);
|
||||
}
|
||||
console.log('All plugins validated.');
|
||||
"
|
||||
- name: Validate plugin structure
|
||||
run: |
|
||||
for dir in plugins/*/; do
|
||||
manifest="$dir/.claude-plugin/plugin.json"
|
||||
if [ ! -f "$manifest" ]; then echo "FAIL: Missing $manifest"; exit 1; fi
|
||||
node -e "JSON.parse(require('fs').readFileSync('${manifest}'))" || exit 1
|
||||
|
||||
# Check skills use directory/SKILL.md format
|
||||
if [ -d "${dir}skills" ]; then
|
||||
for skill_dir in "${dir}skills"/*/; do
|
||||
[ -d "$skill_dir" ] || continue
|
||||
if [ ! -f "${skill_dir}SKILL.md" ]; then
|
||||
echo "FAIL: Missing SKILL.md in $skill_dir"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
fi
|
||||
echo "OK: $dir"
|
||||
done
|
||||
@@ -0,0 +1,415 @@
|
||||
name: 🔍 Verification Pipeline
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop, alpha-*]
|
||||
pull_request:
|
||||
branches: [main, develop]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
verification_mode:
|
||||
description: 'Verification mode'
|
||||
required: false
|
||||
default: 'full'
|
||||
type: choice
|
||||
options:
|
||||
- full
|
||||
- quick
|
||||
- security-only
|
||||
|
||||
env:
|
||||
NODE_VERSION: '20'
|
||||
CACHE_VERSION: v1
|
||||
|
||||
jobs:
|
||||
# Pre-verification setup and validation
|
||||
setup-verification:
|
||||
name: 🚀 Setup Verification
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
verification-id: ${{ steps.setup.outputs.verification-id }}
|
||||
test-matrix: ${{ steps.setup.outputs.test-matrix }}
|
||||
cache-key: ${{ steps.setup.outputs.cache-key }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
|
||||
- name: Generate verification ID
|
||||
id: setup
|
||||
run: |
|
||||
VERIFICATION_ID="verify-$(date +%Y%m%d-%H%M%S)-${{ github.sha }}"
|
||||
echo "verification-id=$VERIFICATION_ID" >> $GITHUB_OUTPUT
|
||||
echo "test-matrix={\"include\":[{\"os\":\"ubuntu-latest\",\"node\":\"18\"},{\"os\":\"ubuntu-latest\",\"node\":\"20\"},{\"os\":\"macos-latest\",\"node\":\"20\"},{\"os\":\"windows-latest\",\"node\":\"20\"}]}" >> $GITHUB_OUTPUT
|
||||
echo "cache-key=${{ env.CACHE_VERSION }}-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci --legacy-peer-deps
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
node_modules
|
||||
~/.npm
|
||||
key: ${{ steps.setup.outputs.cache-key }}
|
||||
restore-keys: |
|
||||
${{ env.CACHE_VERSION }}-${{ runner.os }}-
|
||||
|
||||
# Security verification
|
||||
security-verification:
|
||||
name: 🛡️ Security Verification
|
||||
runs-on: ubuntu-latest
|
||||
needs: setup-verification
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
|
||||
- name: Restore dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
node_modules
|
||||
~/.npm
|
||||
key: ${{ needs.setup-verification.outputs.cache-key }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci --legacy-peer-deps
|
||||
|
||||
- name: Security audit
|
||||
run: |
|
||||
echo "🔍 Running security audit..."
|
||||
npm audit --audit-level=moderate || true
|
||||
npm audit --audit-level=high --json > security-audit.json || true
|
||||
|
||||
- name: License compliance check
|
||||
run: |
|
||||
echo "📋 Checking license compliance..."
|
||||
npx license-checker --onlyAllow 'MIT;Apache-2.0;BSD-2-Clause;BSD-3-Clause;ISC;CC0-1.0;Unlicense' \
|
||||
--excludePrivatePackages \
|
||||
--json > license-report.json || true
|
||||
|
||||
- name: Dependency vulnerability scan
|
||||
run: |
|
||||
echo "🔍 Scanning for vulnerabilities..."
|
||||
npx audit-ci --config .audit-ci.json || true
|
||||
|
||||
- name: Upload security reports
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: security-reports-${{ needs.setup-verification.outputs.verification-id }}
|
||||
path: |
|
||||
security-audit.json
|
||||
license-report.json
|
||||
retention-days: 30
|
||||
|
||||
# Code quality verification
|
||||
code-quality:
|
||||
name: 📝 Code Quality
|
||||
runs-on: ubuntu-latest
|
||||
needs: setup-verification
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
|
||||
- name: Restore dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
node_modules
|
||||
~/.npm
|
||||
key: ${{ needs.setup-verification.outputs.cache-key }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci --legacy-peer-deps
|
||||
|
||||
- name: ESLint code analysis
|
||||
run: |
|
||||
echo "🔍 Running ESLint..."
|
||||
npm run lint -- --format=json --output-file=eslint-report.json || true
|
||||
npm run lint
|
||||
|
||||
- name: TypeScript type checking
|
||||
run: |
|
||||
echo "🔍 Type checking..."
|
||||
npm run typecheck || echo "⚠️ Type checking skipped (TypeScript compiler crash)"
|
||||
continue-on-error: true
|
||||
|
||||
- name: Format checking
|
||||
run: |
|
||||
echo "🎨 Checking code formatting..."
|
||||
npm run format
|
||||
git diff --exit-code || echo "⚠️ Some files need formatting (non-blocking)"
|
||||
continue-on-error: true
|
||||
|
||||
- name: Complexity analysis
|
||||
run: |
|
||||
echo "📊 Analyzing code complexity..."
|
||||
npx complexity-report --format json --output complexity-report.json src/ || true
|
||||
|
||||
- name: Upload quality reports
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: quality-reports-${{ needs.setup-verification.outputs.verification-id }}
|
||||
path: |
|
||||
eslint-report.json
|
||||
complexity-report.json
|
||||
retention-days: 30
|
||||
|
||||
# Multi-platform testing
|
||||
test-verification:
|
||||
name: 🧪 Test Verification (${{ matrix.os }}, Node ${{ matrix.node }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: [setup-verification, security-verification]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.setup-verification.outputs.test-matrix) }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js ${{ matrix.node }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
if [ "${{ runner.os }}" == "Linux" ]; then
|
||||
npm ci --legacy-peer-deps
|
||||
else
|
||||
# Skip optional platform-specific dependencies on macOS/Windows
|
||||
npm ci --legacy-peer-deps --omit=optional || npm ci --legacy-peer-deps --force
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
echo "🧪 Running unit tests..."
|
||||
npm run test:unit || echo "⚠️ Some unit tests failed (Jest teardown issues - non-blocking)"
|
||||
continue-on-error: true
|
||||
|
||||
- name: Run integration tests
|
||||
run: |
|
||||
echo "🔗 Running integration tests..."
|
||||
npm run test:integration || echo "⚠️ Some integration tests failed (non-blocking)"
|
||||
continue-on-error: true
|
||||
|
||||
- name: Run performance tests
|
||||
if: matrix.os == 'ubuntu-latest' && matrix.node == '20'
|
||||
run: |
|
||||
echo "⚡ Running performance tests..."
|
||||
npm run test:performance || echo "⚠️ Some performance tests failed (non-blocking)"
|
||||
continue-on-error: true
|
||||
|
||||
- name: Generate coverage report
|
||||
if: matrix.os == 'ubuntu-latest' && matrix.node == '20'
|
||||
run: |
|
||||
echo "📊 Generating coverage report..."
|
||||
npm run test:coverage || echo "⚠️ Coverage generation failed (non-blocking)"
|
||||
continue-on-error: true
|
||||
|
||||
- name: Upload test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-results-${{ matrix.os }}-node${{ matrix.node }}-${{ needs.setup-verification.outputs.verification-id }}
|
||||
path: |
|
||||
coverage/
|
||||
test-reports/
|
||||
retention-days: 30
|
||||
|
||||
# Build verification - simplified for V3 monorepo structure
|
||||
build-verification:
|
||||
name: 🏗️ Build Verification
|
||||
runs-on: ubuntu-latest
|
||||
needs: [setup-verification, code-quality]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci --legacy-peer-deps
|
||||
|
||||
- name: Build CLI package
|
||||
run: |
|
||||
echo "🔨 Building CLI package..."
|
||||
cd v3/@claude-flow/cli && npm run build || echo "✅ CLI build completed (or already built)"
|
||||
continue-on-error: true
|
||||
|
||||
- name: Verify CLI availability
|
||||
run: |
|
||||
echo "✅ Verifying CLI..."
|
||||
test -f v3/@claude-flow/cli/bin/cli.js && echo "CLI binary exists" || echo "⚠️ CLI binary not found (non-blocking)"
|
||||
continue-on-error: true
|
||||
|
||||
# Pre-warm npx cache so downstream --version smoke checks don't pay
|
||||
# cold-install cost for the CLI + wrapper (#2561).
|
||||
- name: Pre-warm npx cache for CLI smoke checks
|
||||
run: |
|
||||
npm install -g @claude-flow/cli@alpha ruflo@alpha --prefer-offline --no-audit --no-fund || \
|
||||
echo "⚠️ Pre-warm install skipped (non-blocking)"
|
||||
continue-on-error: true
|
||||
|
||||
- name: Package for distribution
|
||||
run: |
|
||||
echo "📦 Creating package..."
|
||||
npm pack || echo "⚠️ Pack skipped"
|
||||
ls -la *.tgz 2>/dev/null || echo "No tgz files created"
|
||||
continue-on-error: true
|
||||
|
||||
# Documentation verification - simplified checks
|
||||
docs-verification:
|
||||
name: 📚 Documentation Verification
|
||||
runs-on: ubuntu-latest
|
||||
needs: setup-verification
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Check documentation files
|
||||
run: |
|
||||
echo "📋 Verifying documentation..."
|
||||
test -f README.md && echo "✅ README.md exists" || echo "⚠️ README.md missing"
|
||||
test -f CHANGELOG.md && echo "✅ CHANGELOG.md exists" || echo "⚠️ CHANGELOG.md missing"
|
||||
test -f LICENSE && echo "✅ LICENSE exists" || echo "⚠️ LICENSE missing"
|
||||
# At least README must exist
|
||||
test -f README.md || (echo "❌ README.md required" && exit 1)
|
||||
|
||||
- name: Validate package.json structure
|
||||
run: |
|
||||
echo "📦 Validating package.json..."
|
||||
node -e "const p = require('./package.json'); console.log('✅ Package:', p.name, p.version);"
|
||||
|
||||
# Performance benchmarking
|
||||
performance-verification:
|
||||
name: ⚡ Performance Verification
|
||||
runs-on: ubuntu-latest
|
||||
needs: [setup-verification, build-verification]
|
||||
if: github.event_name == 'push' || github.event.inputs.verification_mode == 'full'
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
|
||||
# NOTE: The previous `download-artifact` step here pointed at an
|
||||
# artifact name (`build-artifacts-${verification-id}`) that no job
|
||||
# in this workflow has ever produced — `build-verification` only
|
||||
# runs `npm pack`, it doesn't upload. The step worked by accident
|
||||
# while `download-artifact@v3` returned a warning; @v4 makes it a
|
||||
# hard error and surfaces this as a failure. Build locally instead.
|
||||
- name: Install dependencies
|
||||
run: npm ci --legacy-peer-deps
|
||||
|
||||
- name: Build CLI for benchmarks
|
||||
run: |
|
||||
echo "🔨 Building CLI for benchmarks..."
|
||||
cd v3/@claude-flow/cli && npm run build || echo "⚠️ CLI build skipped (non-blocking)"
|
||||
continue-on-error: true
|
||||
|
||||
- name: Run performance benchmarks
|
||||
run: |
|
||||
echo "⚡ Running performance benchmarks..."
|
||||
npm run test:benchmark || echo "⚠️ Benchmarks skipped"
|
||||
|
||||
- name: Memory leak detection
|
||||
run: |
|
||||
echo "🔍 Checking for memory leaks..."
|
||||
# Prefer the v3 CLI binary if built; fall back gracefully so the
|
||||
# job stays informational rather than failing on missing dist.
|
||||
if [ -f v3/@claude-flow/cli/dist/bin.js ]; then
|
||||
node --expose-gc --max-old-space-size=128 v3/@claude-flow/cli/dist/bin.js --version || true
|
||||
elif [ -f dist/cli/main.js ]; then
|
||||
node --expose-gc --max-old-space-size=128 dist/cli/main.js --version || true
|
||||
else
|
||||
echo "⚠️ No CLI binary found — skipping memory-leak smoke (non-blocking)"
|
||||
fi
|
||||
|
||||
- name: Upload performance reports
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: performance-reports-${{ needs.setup-verification.outputs.verification-id }}
|
||||
path: |
|
||||
benchmark-results/
|
||||
performance-reports/
|
||||
retention-days: 30
|
||||
|
||||
# Final verification report
|
||||
verification-report:
|
||||
name: 📊 Verification Report
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- setup-verification
|
||||
- security-verification
|
||||
- code-quality
|
||||
- test-verification
|
||||
- build-verification
|
||||
- docs-verification
|
||||
if: always()
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Generate verification report
|
||||
run: |
|
||||
echo "📊 Generating verification report..."
|
||||
cat > verification-summary.md << EOF
|
||||
# Verification Pipeline Report
|
||||
|
||||
**Verification ID:** ${{ needs.setup-verification.outputs.verification-id }}
|
||||
**Commit:** ${{ github.sha }}
|
||||
**Branch:** ${{ github.ref_name }}
|
||||
|
||||
## Results Summary
|
||||
|
||||
| Component | Status |
|
||||
|-----------|--------|
|
||||
| Security | ${{ needs.security-verification.result }} |
|
||||
| Code Quality | ${{ needs.code-quality.result }} |
|
||||
| Tests | ${{ needs.test-verification.result }} |
|
||||
| Build | ${{ needs.build-verification.result }} |
|
||||
| Documentation | ${{ needs.docs-verification.result }} |
|
||||
|
||||
## Verification Complete
|
||||
|
||||
Pipeline finished. Check individual jobs for details.
|
||||
EOF
|
||||
cat verification-summary.md
|
||||
|
||||
- name: Upload verification summary
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: verification-summary-${{ needs.setup-verification.outputs.verification-id }}
|
||||
path: verification-summary.md
|
||||
retention-days: 30
|
||||
Reference in New Issue
Block a user