name: CI on: push: branches: [main, dev] pull_request: branches: [main, dev] workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true permissions: contents: read jobs: lint-and-typecheck: name: Lint & Type Check runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: persist-credentials: false - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - name: Install dependencies run: npm ci - name: Type check run: npx tsc --noEmit - name: Lint run: npm run lint --if-present test: name: Test runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: persist-credentials: false - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - name: Install dependencies run: npm ci - name: Run functional tests run: npm test -- --run - name: Run subagent-lock performance test run: npm exec vitest -- run tests/perf/subagent-lock.bench.ts --fileParallelism=false --maxWorkers=1 test-windows: name: Test (Windows path suite) runs-on: windows-latest steps: - uses: actions/checkout@v4 with: persist-credentials: false - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - name: Install dependencies run: npm ci # Scoped to the cross-platform path/encoding suites on a real Windows # runner. The ubuntu-only `test` job exercises POSIX separators, so # forward-slash assumptions in project-dir encoding and worktree # transcript resolution (see #3274, #3276) can slip through it. Running # the full `npm test` suite here is not yet possible: it has pre-existing # platform-specific failures on Windows (e.g. python-repl sandbox, lsp # devcontainer, session-end path interpolation) that are out of scope for # this gate. Keep this list in sync with the path-handling test files. - name: Run Windows path-handling tests run: >- npx vitest run src/utils/__tests__/encode-project-path.test.ts src/__tests__/resolve-transcript-path.test.ts src/__tests__/session-history-search.test.ts src/cli/__tests__/session-search.test.ts src/lib/__tests__/worktree-paths.test.ts src/hooks/project-memory/__tests__/hot-path-tracker.test.ts src/hooks/merge-readiness/__tests__/win-cross-platform.test.ts src/hooks/merge-readiness/__tests__/runtime.test.ts src/hooks/merge-readiness/__tests__/tool-flow.test.ts build: name: Build runs-on: ubuntu-latest needs: [lint-and-typecheck, test] steps: - uses: actions/checkout@v4 with: persist-credentials: false - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - name: Install dependencies run: npm ci - name: Build run: npm run build - name: Verify bridge registers all merge-readiness tools (source/packaged parity) run: | set -e for tool in merge_readiness_start merge_readiness_set_content merge_readiness_record_answer merge_readiness_report merge_readiness_cancel; do if ! grep -q "\"$tool\"" bridge/mcp-server.cjs; then echo "::error::Packaged bridge/mcp-server.cjs is missing tool: $tool. Source/packaged parity broken (run npm run build)." exit 1 fi echo "bridge registers $tool" done - name: Check dist size run: | DIST_SIZE=$(du -sm dist | cut -f1) echo "📦 Dist size: ${DIST_SIZE}MB" if [ "$DIST_SIZE" -gt 50 ]; then echo "⚠️ Warning: dist folder is larger than 50MB!" fi - name: Upload build artifacts uses: actions/upload-artifact@v4 with: name: dist path: dist/ retention-days: 7 no-committed-build-artifacts: name: No Committed Build Artifacts runs-on: ubuntu-latest # PR-only: the maintainer regenerates dist/ and bridge/ in the merge/release # commit, so pushes to dev/main legitimately change them. Contributors must not. if: github.event_name == 'pull_request' steps: - uses: actions/checkout@v4 with: fetch-depth: 0 persist-credentials: false - name: Reject committed dist/ or bridge/ changes env: BASE_SHA: ${{ github.event.pull_request.base.sha }} run: | CHANGED=$(git diff --name-only "$BASE_SHA" HEAD -- dist/ bridge/ || true) if [ -n "$CHANGED" ]; then echo "::error::This PR commits generated build artifacts. dist/ and bridge/ are produced by 'npm run build' and must NOT be committed — the maintainer regenerates them at merge/release. Run 'git restore dist/ bridge/' and recommit. See .github/CLAUDE.md." echo "Offending files:" echo "$CHANGED" | sed 's/^/ - /' exit 1 fi echo "✅ No committed dist/ or bridge/ changes." multirepo-paths-gate: name: Multi-repo Path Gate (AST-grep) runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, windows-latest] steps: - uses: actions/checkout@v4 with: persist-credentials: false - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - name: Install dependencies run: npm ci - name: Check for raw .omc path constructions run: node scripts/ci/check-multirepo-paths.mjs version-check: name: Version Consistency Check runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: persist-credentials: false - name: Check version consistency run: | PKG_VERSION=$(node -p "require('./package.json').version") PLUGIN_VERSION=$(node -p "require('./.claude-plugin/plugin.json').version") MARKET_VERSION=$(node -p "require('./.claude-plugin/marketplace.json').version") echo "package.json: $PKG_VERSION" echo ".claude-plugin/plugin.json: $PLUGIN_VERSION" echo ".claude-plugin/marketplace.json: $MARKET_VERSION" if [ "$PKG_VERSION" != "$PLUGIN_VERSION" ] || [ "$PKG_VERSION" != "$MARKET_VERSION" ]; then echo "" echo "❌ Version mismatch!" echo " package.json: $PKG_VERSION" echo " plugin.json: $PLUGIN_VERSION" echo " marketplace.json: $MARKET_VERSION" echo "" echo "All three files must have the same version." exit 1 fi echo "✅ All versions match: $PKG_VERSION" npm-pack-test: name: npm pack + install test runs-on: ubuntu-latest needs: build steps: - uses: actions/checkout@v4 with: persist-credentials: false - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - name: Install dependencies run: npm ci - name: Build run: npm run build - name: npm pack test run: | echo "📦 Creating tarball..." npm pack TARBALL=$(ls -t oh-my-claude-sisyphus-*.tgz | head -1) echo "📦 Tarball: $TARBALL" echo "🔎 Verifying the packaged MCP bridge registers every merge-readiness tool..." for tool in merge_readiness_start merge_readiness_set_content merge_readiness_record_answer merge_readiness_report merge_readiness_cancel; do if ! tar -xOf "$TARBALL" package/bridge/mcp-server.cjs | grep -q "\"$tool\""; then echo "::error::npm package bridge/mcp-server.cjs is missing tool: $tool" exit 1 fi echo "packaged bridge registers $tool" done # ENOTEMPTY hardening (mirrors .github/workflows/upgrade-test.yml): # the global node_modules dir is reused across runs on GitHub-hosted # runners and inside the GitHub-hosted tool cache. A previously # interrupted `npm install -g` can leave the resolved package dir # and/or npm's staging temp dirs (`.oh-my-claude-sisyphus-*`) behind, # so the next install fails with # npm ERR! ENOTEMPTY: directory not empty, rename # '.../oh-my-claude-sisyphus' -> '.../.oh-my-claude-sisyphus-XXXX' # Remove any stale global package + npm staging dirs before # installing, then retry a few times so a transient rename race does # not fail the run. A persistent install failure still exits non-zero. PKG=oh-my-claude-sisyphus GLOBAL_ROOT="$(npm root -g)" clean_stale_global_pkg() { # `:?` guards against an empty GLOBAL_ROOT so we never rm -rf "/". rm -rf "${GLOBAL_ROOT:?}/$PKG" 2>/dev/null || true find "$GLOBAL_ROOT" -maxdepth 1 -name ".$PKG-*" -exec rm -rf {} + 2>/dev/null || true } echo "📦 Testing global install from tarball..." clean_stale_global_pkg ATTEMPTS=3 INSTALLED=false for attempt in $(seq 1 "$ATTEMPTS"); do echo "Installing $TARBALL globally (attempt $attempt/$ATTEMPTS)" if npm install -g "./$TARBALL" --no-audit --no-fund; then INSTALLED=true break fi echo "Install attempt $attempt failed; cleaning stale global state and retrying." clean_stale_global_pkg sleep 2 done if [ "$INSTALLED" != "true" ]; then echo "❌ npm install -g ./$TARBALL failed after $ATTEMPTS attempts" exit 1 fi echo "🔍 Checking omc command..." which omc echo "🔍 Testing omc --version..." omc --version VERSION_OUTPUT=$(omc --version 2>&1) if [ $? -ne 0 ]; then echo "❌ omc --version failed!" echo "$VERSION_OUTPUT" exit 1 fi echo "✅ omc --version: $VERSION_OUTPUT" echo "🔍 Testing omc --help..." omc --help HELP_OUTPUT=$(omc --help 2>&1) if [ $? -ne 0 ]; then echo "❌ omc --help failed!" echo "$HELP_OUTPUT" exit 1 fi echo "✅ npm pack + install test passed!"