chore: import upstream snapshot with attribution
CI / Test (ubuntu-latest, Node 18.x, bun) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 18.x, npm) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 18.x, pnpm) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 18.x, yarn) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 20.x, bun) (push) Failing after 17m13s
CI / Test (ubuntu-latest, Node 20.x, npm) (push) Failing after 18m42s
CI / Test (ubuntu-latest, Node 20.x, pnpm) (push) Failing after 15m0s
CI / Test (ubuntu-latest, Node 20.x, yarn) (push) Failing after 49m44s
CI / Test (ubuntu-latest, Node 22.x, bun) (push) Failing after 51m55s
CI / Test (ubuntu-latest, Node 22.x, pnpm) (push) Failing after 21m57s
CI / Test (ubuntu-latest, Node 22.x, npm) (push) Failing after 37m39s
CI / Test (ubuntu-latest, Node 22.x, yarn) (push) Failing after 34m7s
CI / Validate Components (push) Failing after 37m15s
CI / Python Tests (push) Failing after 10m1s
CI / Security Scan (push) Failing after 10m1s
CI / Lint (push) Failing after 17m12s
CI / Coverage (push) Failing after 20m19s
CI / Test (macos-latest, Node 18.x, bun) (push) Has been cancelled
CI / Test (macos-latest, Node 18.x, npm) (push) Has been cancelled
CI / Test (macos-latest, Node 18.x, pnpm) (push) Has been cancelled
CI / Test (macos-latest, Node 18.x, yarn) (push) Has been cancelled
CI / Test (windows-latest, Node 18.x, npm) (push) Has been cancelled
CI / Test (windows-latest, Node 18.x, pnpm) (push) Has been cancelled
CI / Test (windows-latest, Node 18.x, yarn) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, bun) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, npm) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, pnpm) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, yarn) (push) Has been cancelled
CI / Test (windows-latest, Node 20.x, npm) (push) Has been cancelled
CI / Test (windows-latest, Node 20.x, pnpm) (push) Has been cancelled
CI / Test (windows-latest, Node 20.x, yarn) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, bun) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, npm) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, pnpm) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, yarn) (push) Has been cancelled
CI / Test (windows-latest, Node 22.x, npm) (push) Has been cancelled
CI / Test (windows-latest, Node 22.x, pnpm) (push) Has been cancelled
CI / Test (windows-latest, Node 22.x, yarn) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 11:55:55 +08:00
commit d48cda4081
3322 changed files with 668744 additions and 0 deletions
+272
View File
@@ -0,0 +1,272 @@
name: CI
on:
push:
branches: [main, 'release/**']
tags: ['v*']
pull_request:
branches: [main]
# Prevent duplicate runs
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# Minimal permissions
permissions:
contents: read
jobs:
test:
name: Test (${{ matrix.os }}, Node ${{ matrix.node }}, ${{ matrix.pm }})
runs-on: ${{ matrix.os }}
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node: ['18.x', '20.x', '22.x']
pm: [npm, pnpm, yarn, bun]
exclude:
# Bun has limited Windows support
- os: windows-latest
pm: bun
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Setup Node.js ${{ matrix.node }}
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: ${{ matrix.node }}
# Package manager setup
- name: Setup pnpm
if: matrix.pm == 'pnpm' && matrix.node != '18.x'
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
with:
# Keep an explicit pnpm major because this repo's packageManager is Yarn.
version: 10
- name: Setup pnpm (via Corepack)
if: matrix.pm == 'pnpm' && matrix.node == '18.x'
shell: bash
run: |
corepack enable
corepack prepare pnpm@9 --activate
- name: Setup Yarn (via Corepack)
if: matrix.pm == 'yarn'
shell: bash
run: |
corepack enable
corepack prepare yarn@stable --activate
- name: Setup Bun
if: matrix.pm == 'bun'
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
# Install dependencies
# COREPACK_ENABLE_STRICT=0 allows pnpm to install even though
# package.json declares "packageManager": "yarn@..."
- name: Install dependencies
shell: bash
env:
COREPACK_ENABLE_STRICT: '0'
npm_config_ignore_scripts: 'true'
YARN_ENABLE_SCRIPTS: 'false'
run: |
case "${{ matrix.pm }}" in
npm) npm ci --ignore-scripts ;;
# pnpm v10 can fail CI on ignored native build scripts
# (for example msgpackr-extract) even though this repo is Yarn-native
# and pnpm is only exercised here as a compatibility lane.
pnpm) pnpm install --ignore-scripts --config.strict-dep-builds=false --no-frozen-lockfile ;;
# Yarn Berry (v4+) removed --ignore-engines; engine checking is no longer a core feature
yarn) yarn install --mode=skip-build ;;
bun) bun install --ignore-scripts ;;
*) echo "Unsupported package manager: ${{ matrix.pm }}" && exit 1 ;;
esac
# Run tests
- name: Run tests
run: node tests/run-all.js
env:
CLAUDE_CODE_PACKAGE_MANAGER: ${{ matrix.pm }}
# Upload test artifacts on failure
- name: Upload test artifacts
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: test-results-${{ matrix.os }}-node${{ matrix.node }}-${{ matrix.pm }}
path: |
tests/
!tests/node_modules/
validate:
name: Validate Components
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '20.x'
- name: Install validation dependencies
run: npm ci --ignore-scripts
- name: Validate agents
run: node scripts/ci/validate-agents.js
continue-on-error: false
- name: Validate hooks
run: node scripts/ci/validate-hooks.js
continue-on-error: false
- name: Validate commands
run: node scripts/ci/validate-commands.js
continue-on-error: false
- name: Validate skills
run: node scripts/ci/validate-skills.js
continue-on-error: false
- name: Validate install manifests
run: node scripts/ci/validate-install-manifests.js
continue-on-error: false
- name: Validate workflow security
run: node scripts/ci/validate-workflow-security.js
continue-on-error: false
- name: Validate rules
run: node scripts/ci/validate-rules.js
continue-on-error: false
- name: Validate catalog counts
run: node scripts/ci/catalog.js --text
continue-on-error: false
- name: Validate command registry
run: npm run command-registry:check
continue-on-error: false
- name: Check unicode safety
run: node scripts/ci/check-unicode-safety.js
continue-on-error: false
- name: Validate no personal paths
run: node scripts/ci/validate-no-personal-paths.js
continue-on-error: false
python-tests:
name: Python Tests
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Setup Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.11'
- name: Install Python dependencies
run: python -m pip install --upgrade pip && python -m pip install -e '.[dev]'
- name: Run Python tests
run: python -m pytest tests/test_*.py -m "not integration"
security:
name: Security Scan
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '20.x'
- name: Install audit dependencies
run: npm ci --ignore-scripts
- name: Run npm audit
run: |
npm audit signatures
npm audit --audit-level=high
- name: Run supply-chain IOC scan
run: npm run security:ioc-scan
coverage:
name: Coverage
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '20.x'
- name: Install dependencies
run: npm ci --ignore-scripts
- name: Run coverage
run: npm run coverage
- name: Upload coverage report
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: coverage-ubuntu-node20-npm
path: coverage/
lint:
name: Lint
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '20.x'
- name: Install dependencies
run: npm ci --ignore-scripts
- name: Run lint
run: npm run lint
@@ -0,0 +1,100 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow lets you generate SLSA provenance file for your project.
# The generation satisfies level 3 for the provenance requirements - see https://slsa.dev/spec/v0.1/requirements
# The project is an initiative of the OpenSSF (openssf.org) and is developed at
# https://github.com/slsa-framework/slsa-github-generator.
# The provenance file can be verified using https://github.com/slsa-framework/slsa-verifier.
# For more information about SLSA and how it improves the supply-chain, visit slsa.dev.
name: SLSA generic generator
on:
workflow_dispatch:
release:
types:
- published
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
actions: write
outputs:
package_file: ${{ steps.build.outputs.package_file }}
digests: ${{ steps.hash.outputs.digests }}
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "20.x"
- name: Install dependencies
run: npm ci --ignore-scripts
- name: Build artifacts
id: build
run: |
set -euo pipefail
npm pack --json > npm-pack.json
PACKAGE_FILE=$(node -e "
const fs = require('fs');
const data = JSON.parse(fs.readFileSync('npm-pack.json', 'utf8'));
console.log(data[0].filename);
")
echo "package_file=${PACKAGE_FILE}" >> "${GITHUB_OUTPUT}"
- name: Generate subject for provenance
id: hash
run: |
set -euo pipefail
FILE="${{ steps.build.outputs.package_file }}"
if [ ! -f "$FILE" ]; then
echo "Package file not found: $FILE"
exit 1
fi
DIGESTS=$(sha256sum "$FILE" | base64 -w0)
echo "digests=${DIGESTS}" >> "${GITHUB_OUTPUT}"
- name: Upload artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ steps.build.outputs.package_file }}
path: ${{ steps.build.outputs.package_file }}
if-no-files-found: error
provenance:
needs:
- build
permissions:
actions: read
id-token: write
contents: write
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@f7dd8c54c2067bafc12ca7a55595d5ee9b75204a # v2.1.0
with:
base64-subjects: ${{ needs.build.outputs.digests }}
upload-assets: true
+56
View File
@@ -0,0 +1,56 @@
name: Scheduled Maintenance
on:
schedule:
- cron: '0 9 * * 1' # Weekly Monday 9am UTC
workflow_dispatch:
permissions:
contents: read
issues: write
pull-requests: write
jobs:
dependency-check:
name: Check Dependencies
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '20.x'
- name: Check for outdated packages
run: npm outdated || true
security-audit:
name: Security Audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '20.x'
- name: Run security audit
run: |
if [ -f package-lock.json ]; then
npm ci --ignore-scripts
npm audit signatures
npm audit --audit-level=high
else
echo "No package-lock.json found; skipping npm audit"
fi
stale:
name: Stale Issues/PRs
runs-on: ubuntu-latest
steps:
- uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0
with:
stale-issue-message: 'This issue is stale due to inactivity.'
stale-pr-message: 'This PR is stale due to inactivity.'
days-before-stale: 30
days-before-close: 7
+192
View File
@@ -0,0 +1,192 @@
name: Monthly Metrics Snapshot
on:
schedule:
- cron: '0 14 1 * *' # Monthly on the 1st at 14:00 UTC
workflow_dispatch:
permissions:
contents: read
issues: write
jobs:
snapshot:
name: Update metrics issue
runs-on: ubuntu-latest
steps:
- name: Update monthly metrics issue
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const title = "Monthly Metrics Snapshot";
const label = "metrics-snapshot";
const monthKey = new Date().toISOString().slice(0, 7);
function parseLastPage(linkHeader) {
if (!linkHeader) return null;
const match = linkHeader.match(/&page=(\d+)>; rel="last"/);
return match ? Number(match[1]) : null;
}
function escapeRegex(value) {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function fmt(value) {
if (value === null || value === undefined) return "n/a";
return Number(value).toLocaleString("en-US");
}
async function getNpmDownloads(range, pkg) {
try {
const res = await fetch(`https://api.npmjs.org/downloads/point/${range}/${pkg}`);
if (!res.ok) return null;
const data = await res.json();
return data.downloads ?? null;
} catch {
return null;
}
}
async function getContributorsCount() {
try {
const resp = await github.rest.repos.listContributors({
owner,
repo,
per_page: 1,
anon: "false"
});
return parseLastPage(resp.headers.link) ?? resp.data.length;
} catch {
return null;
}
}
async function getReleasesCount() {
try {
const resp = await github.rest.repos.listReleases({
owner,
repo,
per_page: 1
});
return parseLastPage(resp.headers.link) ?? resp.data.length;
} catch {
return null;
}
}
async function getTraffic(metric) {
try {
const route = metric === "clones"
? "GET /repos/{owner}/{repo}/traffic/clones"
: "GET /repos/{owner}/{repo}/traffic/views";
const resp = await github.request(route, { owner, repo });
return resp.data?.count ?? null;
} catch {
return null;
}
}
const [
mainWeek,
shieldWeek,
mainMonth,
shieldMonth,
repoData,
contributors,
releases,
views14d,
clones14d
] = await Promise.all([
getNpmDownloads("last-week", "ecc-universal"),
getNpmDownloads("last-week", "ecc-agentshield"),
getNpmDownloads("last-month", "ecc-universal"),
getNpmDownloads("last-month", "ecc-agentshield"),
github.rest.repos.get({ owner, repo }),
getContributorsCount(),
getReleasesCount(),
getTraffic("views"),
getTraffic("clones")
]);
const stars = repoData.data.stargazers_count;
const forks = repoData.data.forks_count;
const tableHeader = [
"| Month (UTC) | ecc-universal (week) | ecc-agentshield (week) | ecc-universal (30d) | ecc-agentshield (30d) | Stars | Forks | Contributors | GitHub App installs (manual) | Views (14d) | Clones (14d) | Releases |",
"|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|"
].join("\n");
const row = `| ${monthKey} | ${fmt(mainWeek)} | ${fmt(shieldWeek)} | ${fmt(mainMonth)} | ${fmt(shieldMonth)} | ${fmt(stars)} | ${fmt(forks)} | ${fmt(contributors)} | n/a | ${fmt(views14d)} | ${fmt(clones14d)} | ${fmt(releases)} |`;
const intro = [
"# Monthly Metrics Snapshot",
"",
"Automated monthly snapshot for sponsor/partner reporting.",
"",
"- `GitHub App installs (manual)` is intentionally manual until a stable public API path is available.",
"- Traffic metrics are 14-day rolling windows from the GitHub traffic API and can show `n/a` if unavailable.",
"",
tableHeader
].join("\n");
try {
await github.rest.issues.getLabel({ owner, repo, name: label });
} catch (error) {
if (error.status === 404) {
await github.rest.issues.createLabel({
owner,
repo,
name: label,
color: "0e8a16",
description: "Automated monthly project metrics snapshots"
});
} else {
throw error;
}
}
const issuesResp = await github.rest.issues.listForRepo({
owner,
repo,
state: "open",
labels: label,
per_page: 100
});
let issue = issuesResp.data.find((item) => item.title === title);
if (!issue) {
const created = await github.rest.issues.create({
owner,
repo,
title,
labels: [label],
body: `${intro}\n${row}\n`
});
console.log(`Created issue #${created.data.number}`);
return;
}
const currentBody = issue.body || "";
const rowPattern = new RegExp(`^\\| ${escapeRegex(monthKey)} \\|.*$`, "m");
let body;
if (rowPattern.test(currentBody)) {
body = currentBody.replace(rowPattern, row);
console.log(`Refreshed issue #${issue.number} snapshot row for ${monthKey}`);
} else {
body = currentBody.includes("| Month (UTC) |")
? `${currentBody.trimEnd()}\n${row}\n`
: `${intro}\n${row}\n`;
}
await github.rest.issues.update({
owner,
repo,
issue_number: issue.number,
body
});
console.log(`Updated issue #${issue.number}`);
+29
View File
@@ -0,0 +1,29 @@
name: Release Announce
on:
release:
types: [published]
permissions:
contents: read
discussions: write
jobs:
announce:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Announce release to Discord + Discussions
run: node scripts/discord/release-announce.mjs
env:
DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN }}
DISCORD_ANNOUNCE_CHANNEL_ID: ${{ secrets.DISCORD_ANNOUNCE_CHANNEL_ID }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository }}
RELEASE_NAME: ${{ github.event.release.name }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
RELEASE_URL: ${{ github.event.release.html_url }}
RELEASE_BODY: ${{ github.event.release.body }}
+154
View File
@@ -0,0 +1,154 @@
name: Release
on:
push:
tags: ['v*']
permissions:
contents: read
jobs:
verify:
name: Verify Release
runs-on: ubuntu-latest
outputs:
already_published: ${{ steps.npm_publish_state.outputs.already_published }}
dist_tag: ${{ steps.npm_publish_state.outputs.dist_tag }}
package_file: ${{ steps.pack.outputs.package_file }}
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '20.x'
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: npm ci --ignore-scripts
- name: Run supply-chain IOC scan
run: npm run security:ioc-scan
- name: Verify OpenCode package payload
run: node tests/scripts/build-opencode.test.js
- name: Verify OMP adapter payload
run: node tests/omp/omp-plugin.test.js
- name: Validate version tag
run: |
if ! [[ "${REF_NAME}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then
echo "Invalid version tag format. Expected vX.Y.Z or vX.Y.Z-prerelease"
exit 1
fi
env:
REF_NAME: ${{ github.ref_name }}
- name: Verify package version matches tag
env:
TAG_NAME: ${{ github.ref_name }}
run: |
TAG_VERSION="${TAG_NAME#v}"
PACKAGE_VERSION=$(node -p "require('./package.json').version")
if [ "$TAG_VERSION" != "$PACKAGE_VERSION" ]; then
echo "::error::Tag version ($TAG_VERSION) does not match package.json version ($PACKAGE_VERSION)"
echo "Run: ./scripts/release.sh $TAG_VERSION"
exit 1
fi
- name: Verify release metadata stays in sync
run: node tests/plugin-manifest.test.js
- name: Check npm publish state
id: npm_publish_state
run: |
PACKAGE_NAME=$(node -p "require('./package.json').name")
PACKAGE_VERSION=$(node -p "require('./package.json').version")
NPM_DIST_TAG=$(node -p "require('./package.json').version.includes('-') ? 'next' : 'latest'")
if npm view "${PACKAGE_NAME}@${PACKAGE_VERSION}" version >/dev/null 2>&1; then
echo "already_published=true" >> "$GITHUB_OUTPUT"
else
echo "already_published=false" >> "$GITHUB_OUTPUT"
fi
echo "dist_tag=${NPM_DIST_TAG}" >> "$GITHUB_OUTPUT"
- name: Generate release highlights
id: highlights
env:
TAG_NAME: ${{ github.ref_name }}
run: |
TAG_VERSION="${TAG_NAME#v}"
cat > release_body.md <<EOF
## ECC ${TAG_VERSION}
### What This Release Focuses On
- Harness reliability and hook stability across Claude Code, Cursor, OpenCode, and Codex
- Stronger eval-driven workflows and quality gates
- Better operator UX for autonomous loop execution
### Notable Changes
- Session persistence and hook lifecycle fixes
- Expanded skills and command coverage for harness performance work
- Improved release-note generation and changelog hygiene
### Notes
- npm package: \`ecc-universal\`
- Claude marketplace/plugin identifier: \`ecc@ecc\`
- For migration tips and compatibility notes, see README and CHANGELOG.
EOF
- name: Pack npm artifact
id: pack
run: |
npm pack --json > npm-pack.json
PACKAGE_FILE=$(node -e "const fs = require('fs'); const data = JSON.parse(fs.readFileSync('npm-pack.json', 'utf8')); console.log(data[0].filename)")
echo "package_file=${PACKAGE_FILE}" >> "$GITHUB_OUTPUT"
- name: Upload release artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ecc-release-artifacts
path: |
release_body.md
${{ steps.pack.outputs.package_file }}
if-no-files-found: error
publish:
name: Publish Release
runs-on: ubuntu-latest
needs: verify
permissions:
contents: write
id-token: write
steps:
- name: Download release artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ecc-release-artifacts
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '20.x'
registry-url: 'https://registry.npmjs.org'
- name: Create GitHub Release
uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1
with:
body_path: release_body.md
generate_release_notes: true
prerelease: ${{ contains(github.ref_name, '-') }}
make_latest: ${{ contains(github.ref_name, '-') && 'false' || 'true' }}
- name: Publish npm package
if: needs.verify.outputs.already_published != 'true'
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npm publish "${{ needs.verify.outputs.package_file }}" --access public --provenance --tag "${{ needs.verify.outputs.dist_tag }}"
+172
View File
@@ -0,0 +1,172 @@
name: Reusable Release Workflow
on:
workflow_call:
inputs:
tag:
description: 'Version tag (e.g., v1.0.0)'
required: true
type: string
generate-notes:
description: 'Auto-generate release notes'
required: false
type: boolean
default: true
secrets:
NPM_TOKEN:
required: false
workflow_dispatch:
inputs:
tag:
description: 'Version tag to release or republish (e.g., v2.0.0-rc.1)'
required: true
type: string
generate-notes:
description: 'Auto-generate release notes'
required: false
type: boolean
default: true
permissions:
contents: read
jobs:
verify:
name: Verify Release
runs-on: ubuntu-latest
outputs:
already_published: ${{ steps.npm_publish_state.outputs.already_published }}
dist_tag: ${{ steps.npm_publish_state.outputs.dist_tag }}
package_file: ${{ steps.pack.outputs.package_file }}
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
ref: ${{ inputs.tag }}
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '20.x'
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: npm ci --ignore-scripts
- name: Run supply-chain IOC scan
run: npm run security:ioc-scan
- name: Verify OpenCode package payload
run: node tests/scripts/build-opencode.test.js
- name: Verify OMP adapter payload
run: node tests/omp/omp-plugin.test.js
- name: Validate version tag
env:
INPUT_TAG: ${{ inputs.tag }}
run: |
if ! [[ "$INPUT_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then
echo "Invalid version tag format. Expected vX.Y.Z or vX.Y.Z-prerelease"
exit 1
fi
- name: Verify package version matches tag
env:
INPUT_TAG: ${{ inputs.tag }}
run: |
TAG_VERSION="${INPUT_TAG#v}"
PACKAGE_VERSION=$(node -p "require('./package.json').version")
if [ "$TAG_VERSION" != "$PACKAGE_VERSION" ]; then
echo "::error::Tag version ($TAG_VERSION) does not match package.json version ($PACKAGE_VERSION)"
echo "Run: ./scripts/release.sh $TAG_VERSION"
exit 1
fi
- name: Verify release metadata stays in sync
run: node tests/plugin-manifest.test.js
- name: Check npm publish state
id: npm_publish_state
run: |
PACKAGE_NAME=$(node -p "require('./package.json').name")
PACKAGE_VERSION=$(node -p "require('./package.json').version")
NPM_DIST_TAG=$(node -p "require('./package.json').version.includes('-') ? 'next' : 'latest'")
if npm view "${PACKAGE_NAME}@${PACKAGE_VERSION}" version >/dev/null 2>&1; then
echo "already_published=true" >> "$GITHUB_OUTPUT"
else
echo "already_published=false" >> "$GITHUB_OUTPUT"
fi
echo "dist_tag=${NPM_DIST_TAG}" >> "$GITHUB_OUTPUT"
- name: Generate release highlights
env:
TAG_NAME: ${{ inputs.tag }}
run: |
TAG_VERSION="${TAG_NAME#v}"
cat > release_body.md <<EOF
## ECC ${TAG_VERSION}
### What This Release Focuses On
- Harness reliability and cross-platform compatibility
- Eval-driven quality improvements
- Better workflow and operator ergonomics
### Package Notes
- npm package: \`ecc-universal\`
- Claude marketplace/plugin identifier: \`ecc@ecc\`
EOF
- name: Pack npm artifact
id: pack
run: |
npm pack --json > npm-pack.json
PACKAGE_FILE=$(node -e "const fs = require('fs'); const data = JSON.parse(fs.readFileSync('npm-pack.json', 'utf8')); console.log(data[0].filename)")
echo "package_file=${PACKAGE_FILE}" >> "$GITHUB_OUTPUT"
- name: Upload release artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ecc-release-artifacts
path: |
release_body.md
${{ steps.pack.outputs.package_file }}
if-no-files-found: error
publish:
name: Publish Release
runs-on: ubuntu-latest
needs: verify
permissions:
contents: write
id-token: write
steps:
- name: Download release artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ecc-release-artifacts
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '20.x'
registry-url: 'https://registry.npmjs.org'
- name: Create GitHub Release
uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1
with:
tag_name: ${{ inputs.tag }}
body_path: release_body.md
generate_release_notes: ${{ inputs.generate-notes }}
prerelease: ${{ contains(inputs.tag, '-') }}
make_latest: ${{ contains(inputs.tag, '-') && 'false' || 'true' }}
- name: Publish npm package
if: needs.verify.outputs.already_published != 'true'
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npm publish "${{ needs.verify.outputs.package_file }}" --access public --provenance --tag "${{ needs.verify.outputs.dist_tag }}"
+98
View File
@@ -0,0 +1,98 @@
name: Reusable Test Workflow
on:
workflow_call:
inputs:
os:
description: 'Operating system'
required: false
type: string
default: 'ubuntu-latest'
node-version:
description: 'Node.js version'
required: false
type: string
default: '20.x'
package-manager:
description: 'Package manager to use'
required: false
type: string
default: 'npm'
jobs:
test:
name: Test
runs-on: ${{ inputs.os }}
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: ${{ inputs.node-version }}
- name: Setup pnpm
if: inputs.package-manager == 'pnpm' && inputs.node-version != '18.x'
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
with:
# Keep an explicit pnpm major because this repo's packageManager is Yarn.
version: 10
- name: Setup pnpm (via Corepack)
if: inputs.package-manager == 'pnpm' && inputs.node-version == '18.x'
shell: bash
run: |
corepack enable
corepack prepare pnpm@9 --activate
- name: Setup Yarn (via Corepack)
if: inputs.package-manager == 'yarn'
shell: bash
run: |
corepack enable
corepack prepare yarn@stable --activate
- name: Setup Bun
if: inputs.package-manager == 'bun'
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
# COREPACK_ENABLE_STRICT=0 allows pnpm to install even though
# package.json declares "packageManager": "yarn@..."
- name: Install dependencies
shell: bash
env:
COREPACK_ENABLE_STRICT: '0'
npm_config_ignore_scripts: 'true'
YARN_ENABLE_SCRIPTS: 'false'
PACKAGE_MANAGER: ${{ inputs.package-manager }}
run: |
case "$PACKAGE_MANAGER" in
npm) npm ci --ignore-scripts ;;
# pnpm v10 can fail CI on ignored native build scripts
# (for example msgpackr-extract) even though this repo is Yarn-native
# and pnpm is only exercised here as a compatibility lane.
pnpm) pnpm install --ignore-scripts --config.strict-dep-builds=false --no-frozen-lockfile ;;
# Yarn Berry (v4+) removed --ignore-engines; engine checking is no longer a core feature
yarn) yarn install --mode=skip-build ;;
bun) bun install --ignore-scripts ;;
*) echo "Unsupported package manager: $PACKAGE_MANAGER" && exit 1 ;;
esac
- name: Run tests
run: node tests/run-all.js
env:
CLAUDE_CODE_PACKAGE_MANAGER: ${{ inputs.package-manager }}
- name: Upload test artifacts
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: test-results-${{ inputs.os }}-node${{ inputs.node-version }}-${{ inputs.package-manager }}
path: |
tests/
!tests/node_modules/
+57
View File
@@ -0,0 +1,57 @@
name: Reusable Validation Workflow
on:
workflow_call:
inputs:
node-version:
description: 'Node.js version'
required: false
type: string
default: '20.x'
jobs:
validate:
name: Validate Components
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: ${{ inputs.node-version }}
- name: Install validation dependencies
run: npm ci --ignore-scripts
- name: Validate agents
run: node scripts/ci/validate-agents.js
- name: Validate hooks
run: node scripts/ci/validate-hooks.js
- name: Validate commands
run: node scripts/ci/validate-commands.js
- name: Validate skills
run: node scripts/ci/validate-skills.js
- name: Validate install manifests
run: node scripts/ci/validate-install-manifests.js
- name: Validate workflow security
run: node scripts/ci/validate-workflow-security.js
- name: Validate rules
run: node scripts/ci/validate-rules.js
- name: Check unicode safety
run: node scripts/ci/check-unicode-safety.js
- name: Validate no personal paths
run: node scripts/ci/validate-no-personal-paths.js
+65
View File
@@ -0,0 +1,65 @@
name: Supply-Chain Watch
on:
schedule:
- cron: '17 */6 * * *'
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
permissions:
contents: read
jobs:
ioc-watch:
name: IOC watch
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '20.x'
- name: Install dependencies without lifecycle scripts
run: npm ci --ignore-scripts
- name: Verify registry signatures and advisories
run: |
npm audit signatures
npm audit --audit-level=high
- name: Validate IOC scanner fixtures
run: node tests/ci/scan-supply-chain-iocs.test.js
- name: Validate advisory source fixtures
run: node tests/ci/supply-chain-advisory-sources.test.js
- name: Generate IOC report
run: |
mkdir -p artifacts
node scripts/ci/scan-supply-chain-iocs.js --json > artifacts/supply-chain-ioc-report.json
- name: Generate advisory source report
run: node scripts/ci/supply-chain-advisory-sources.js --refresh --json > artifacts/supply-chain-advisory-sources.json
- name: Validate workflow hardening rules
run: node scripts/ci/validate-workflow-security.js
- name: Upload IOC report
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: supply-chain-ioc-report
path: |
artifacts/supply-chain-ioc-report.json
artifacts/supply-chain-advisory-sources.json
retention-days: 14