searxng-http-mcp 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. searxng_http_mcp-1.0.0/.claude/rules/architecture.md +31 -0
  2. searxng_http_mcp-1.0.0/.claude/rules/commands.md +21 -0
  3. searxng_http_mcp-1.0.0/.claude/rules/workflow.md +16 -0
  4. searxng_http_mcp-1.0.0/.claude-plugin/marketplace.json +25 -0
  5. searxng_http_mcp-1.0.0/.dockerignore +22 -0
  6. searxng_http_mcp-1.0.0/.github/dependabot.yml +22 -0
  7. searxng_http_mcp-1.0.0/.github/workflows/build.yml +167 -0
  8. searxng_http_mcp-1.0.0/.github/workflows/codeql.yml +35 -0
  9. searxng_http_mcp-1.0.0/.github/workflows/dependabot-auto-merge.yml +33 -0
  10. searxng_http_mcp-1.0.0/.github/workflows/reusable-test.yml +66 -0
  11. searxng_http_mcp-1.0.0/.github/workflows/scorecard.yml +34 -0
  12. searxng_http_mcp-1.0.0/.github/workflows/test.yml +110 -0
  13. searxng_http_mcp-1.0.0/.gitignore +40 -0
  14. searxng_http_mcp-1.0.0/CLAUDE.md +3 -0
  15. searxng_http_mcp-1.0.0/CONTRIBUTING.md +91 -0
  16. searxng_http_mcp-1.0.0/Dockerfile +27 -0
  17. searxng_http_mcp-1.0.0/LICENSE +21 -0
  18. searxng_http_mcp-1.0.0/PKG-INFO +820 -0
  19. searxng_http_mcp-1.0.0/README.md +806 -0
  20. searxng_http_mcp-1.0.0/README.zh-CN.md +813 -0
  21. searxng_http_mcp-1.0.0/SECURITY.md +37 -0
  22. searxng_http_mcp-1.0.0/assets/banner-dark.svg +17 -0
  23. searxng_http_mcp-1.0.0/assets/banner-light.svg +16 -0
  24. searxng_http_mcp-1.0.0/assets/browser_login.svg +30 -0
  25. searxng_http_mcp-1.0.0/assets/icons/claude.svg +1 -0
  26. searxng_http_mcp-1.0.0/assets/icons/cline.svg +1 -0
  27. searxng_http_mcp-1.0.0/assets/icons/codex.svg +1 -0
  28. searxng_http_mcp-1.0.0/assets/icons/cursor.svg +1 -0
  29. searxng_http_mcp-1.0.0/assets/icons/githubcopilot.svg +1 -0
  30. searxng_http_mcp-1.0.0/assets/icons/hermes.svg +1 -0
  31. searxng_http_mcp-1.0.0/assets/icons/opencode.svg +1 -0
  32. searxng_http_mcp-1.0.0/assets/icons/windsurf.svg +1 -0
  33. searxng_http_mcp-1.0.0/docs/distribution.md +38 -0
  34. searxng_http_mcp-1.0.0/glama.json +41 -0
  35. searxng_http_mcp-1.0.0/mcp_server/__init__.py +0 -0
  36. searxng_http_mcp-1.0.0/mcp_server/app.py +50 -0
  37. searxng_http_mcp-1.0.0/mcp_server/auth.py +38 -0
  38. searxng_http_mcp-1.0.0/mcp_server/main.py +17 -0
  39. searxng_http_mcp-1.0.0/mcp_server/patch_settings.py +27 -0
  40. searxng_http_mcp-1.0.0/mcp_server/proxy.py +80 -0
  41. searxng_http_mcp-1.0.0/mcp_server/tools.py +341 -0
  42. searxng_http_mcp-1.0.0/plugins/local/.claude-plugin/plugin.json +11 -0
  43. searxng_http_mcp-1.0.0/plugins/local/.mcp.json +8 -0
  44. searxng_http_mcp-1.0.0/plugins/local/agents/web-searcher.md +104 -0
  45. searxng_http_mcp-1.0.0/plugins/local/skills/web-search-via-searxng/SKILL.md +89 -0
  46. searxng_http_mcp-1.0.0/plugins/remote/.claude-plugin/plugin.json +11 -0
  47. searxng_http_mcp-1.0.0/plugins/remote/.mcp.json +11 -0
  48. searxng_http_mcp-1.0.0/plugins/remote/agents/web-searcher.md +104 -0
  49. searxng_http_mcp-1.0.0/plugins/remote/skills/web-search-via-searxng/SKILL.md +89 -0
  50. searxng_http_mcp-1.0.0/pyproject.toml +33 -0
  51. searxng_http_mcp-1.0.0/scripts/entrypoint.sh +46 -0
  52. searxng_http_mcp-1.0.0/scripts/glama-settings.yml +11 -0
  53. searxng_http_mcp-1.0.0/scripts/glama-start.sh +24 -0
  54. searxng_http_mcp-1.0.0/scripts/healthcheck.sh +14 -0
  55. searxng_http_mcp-1.0.0/scripts/review-pr.sh +68 -0
  56. searxng_http_mcp-1.0.0/server.json +59 -0
  57. searxng_http_mcp-1.0.0/tests/__init__.py +0 -0
  58. searxng_http_mcp-1.0.0/tests/stdio_server_mock.py +66 -0
  59. searxng_http_mcp-1.0.0/tests/test_app.py +77 -0
  60. searxng_http_mcp-1.0.0/tests/test_auth.py +73 -0
  61. searxng_http_mcp-1.0.0/tests/test_http.py +168 -0
  62. searxng_http_mcp-1.0.0/tests/test_patch_settings.py +46 -0
  63. searxng_http_mcp-1.0.0/tests/test_proxy.py +89 -0
  64. searxng_http_mcp-1.0.0/tests/test_stdio.py +72 -0
  65. searxng_http_mcp-1.0.0/tests/test_tools.py +484 -0
  66. searxng_http_mcp-1.0.0/uv.lock +648 -0
@@ -0,0 +1,31 @@
1
+ ## Architecture
2
+
3
+ ```
4
+ mcp_server/ # MCP server package
5
+ main.py # Entry point (HTTP or stdio mode)
6
+ app.py # Starlette ASGI app (MCP + auth + reverse proxy)
7
+ tools.py # MCP tools (search, autocomplete, engine_info)
8
+ auth.py # API key + Basic Auth middleware
9
+ proxy.py # SearXNG reverse proxy
10
+ patch_settings.py # SearXNG settings patcher
11
+ tests/ # pytest + pytest-anyio
12
+ plugins/ # Claude Code plugin (local + remote variants)
13
+ ```
14
+
15
+ ## Code Style
16
+
17
+ - Python 3.14+, type annotations with `Annotated` + `Field`
18
+ - Async throughout (`async def`, `httpx.AsyncClient`)
19
+ - No comments unless explaining non-obvious "why"
20
+
21
+ ## Testing
22
+
23
+ - `pytest-anyio` for async tests (`@pytest.mark.anyio`)
24
+ - Mock `httpx.AsyncClient` for unit tests
25
+ - `stdio_client` / `streamable_http_client` for transport integration tests
26
+ - `plugins/local/` and `plugins/remote/` skills and agents must stay identical
27
+
28
+ ## Gotchas
29
+
30
+ - `docs/superpowers/` is in `.gitignore` — local plugin skill files, not tracked in git
31
+ - `mcp_server` is not installed as a package — tests need `PYTHONPATH` set to project root
@@ -0,0 +1,21 @@
1
+ ## Commands
2
+
3
+ ```bash
4
+ # Install dependencies
5
+ pip install "mcp[cli]" pytest pytest-anyio httpx pytest-cov pyyaml
6
+
7
+ # Run tests
8
+ pytest tests/ -v
9
+
10
+ # Run single test file
11
+ pytest tests/test_tools.py -v
12
+
13
+ # Run with coverage
14
+ pytest tests/ -v --cov=mcp_server --cov-report=term-missing --cov-fail-under=80
15
+
16
+ # Start HTTP server (requires SearXNG on port 8080)
17
+ python -m mcp_server.main
18
+
19
+ # Start stdio server
20
+ python -m mcp_server.main --stdio
21
+ ```
@@ -0,0 +1,16 @@
1
+ ## Project Workflow
2
+
3
+ - Branching: `dev` is the development branch, `main` is the release branch. All changes go through PRs.
4
+ - PR rules: feature/fix branches → dev → main. PRs to main must come from dev only.
5
+ - Copilot review flow: After PR to dev, wait for CI to pass, then confirm Copilot review is submitted (use `gh api repos/{owner}/{repo}/pulls/{number}/reviews` to verify `copilot-pull-request-reviewer[bot]` has posted a review — don't just check comment count). Fix feedback on the same branch, resolve addressed comments, then merge. Delete local branch after merge (remote branch is auto-deleted by GitHub).
6
+ - CI: push triggers test.yml. PRs to main run policy checks only (source-branch verification + fork protections), no tests.
7
+ - Rebase all local branches (feat/fix/docs/ci etc.) onto latest dev before PR (`git fetch origin && git rebase origin/dev`).
8
+ - After push, wait for push-triggered test.yml CI to pass before creating PR.
9
+ - Merge strategy: always use merge commit (GitHub: "Create a merge commit", CLI: `gh pr merge --merge`). Never use squash or rebase merge.
10
+ - Commit style: Conventional Commits.
11
+ - `.github/` directory is protected — fork PRs cannot modify it.
12
+ - Review external PRs locally using `scripts/review-pr.sh` (runs tests in isolated Docker container). Never execute untrusted code directly on the host.
13
+ - When waiting for CI/Copilot review, use Bash tool's `run_in_background` parameter to poll status (e.g., `gh run watch`). Don't block the conversation with sleep.
14
+ - Copilot review comments must be actually fixed before resolving. Never batch-resolve without fixing.
15
+ - Manage GitHub Rulesets via `gh api repos/{owner}/{repo}/rulesets` — no need for Web UI.
16
+ - Reusable workflow status check names use the format `{caller-job} / {reusable-job}` (e.g., `call-test / test`). Keep this in mind when configuring required checks.
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "searxng-http-mcp",
3
+ "owner": {
4
+ "name": "whw23"
5
+ },
6
+ "description": "SearXNG metasearch engine MCP server",
7
+ "plugins": [
8
+ {
9
+ "name": "searxng-http-mcp",
10
+ "source": "./plugins/local",
11
+ "description": "SearXNG MCP server (local Docker stdio) — zero-config, self-contained",
12
+ "version": "1.1.0",
13
+ "license": "MIT",
14
+ "keywords": ["search", "searxng", "web-search", "mcp", "local", "docker"]
15
+ },
16
+ {
17
+ "name": "searxng-http-mcp-remote",
18
+ "source": "./plugins/remote",
19
+ "description": "SearXNG MCP server (remote HTTP) — connect to a deployed instance",
20
+ "version": "1.1.0",
21
+ "license": "MIT",
22
+ "keywords": ["search", "searxng", "web-search", "mcp", "remote", "http"]
23
+ }
24
+ ]
25
+ }
@@ -0,0 +1,22 @@
1
+ .git
2
+ .github
3
+ .venv
4
+ .pytest_cache
5
+ .playwright-mcp
6
+ .claude
7
+ .claude-plugin
8
+ .vscode
9
+ __pycache__
10
+ tests
11
+ plugins
12
+ scripts/review-pr.sh
13
+ docs
14
+ assets
15
+ *.md
16
+ !README.md
17
+ LICENSE
18
+ .gitignore
19
+ .dockerignore
20
+ .mcp.json
21
+ .coverage
22
+ server.json
@@ -0,0 +1,22 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "pip"
4
+ directory: "/"
5
+ target-branch: "dev"
6
+ schedule:
7
+ interval: "weekly"
8
+ open-pull-requests-limit: 5
9
+
10
+ - package-ecosystem: "github-actions"
11
+ directory: "/"
12
+ target-branch: "dev"
13
+ schedule:
14
+ interval: "weekly"
15
+ open-pull-requests-limit: 5
16
+
17
+ - package-ecosystem: "docker"
18
+ directory: "/"
19
+ target-branch: "dev"
20
+ schedule:
21
+ interval: "weekly"
22
+ open-pull-requests-limit: 5
@@ -0,0 +1,167 @@
1
+ name: Build and Publish
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ schedule:
7
+ - cron: "17 3 * * *"
8
+
9
+ permissions: read-all
10
+
11
+ env:
12
+ IMAGE_NAME: ghcr.io/whw23/searxng-http-mcp
13
+ UPSTREAM_IMAGE: ghcr.io/searxng/searxng:latest
14
+
15
+ jobs:
16
+ test:
17
+ uses: ./.github/workflows/reusable-test.yml
18
+
19
+ build:
20
+ needs: test
21
+ runs-on: ubuntu-latest
22
+ permissions:
23
+ contents: read
24
+ packages: write
25
+ id-token: write
26
+ security-events: write
27
+ env:
28
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
29
+
30
+ steps:
31
+ - name: Checkout
32
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
33
+
34
+ - name: Check upstream image digest
35
+ id: upstream
36
+ run: |
37
+ docker pull $UPSTREAM_IMAGE
38
+ DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' $UPSTREAM_IMAGE)
39
+ echo "digest=$DIGEST" >> "$GITHUB_OUTPUT"
40
+
41
+ UPSTREAM_VERSION=$(docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' $UPSTREAM_IMAGE | grep -oP '__SEARXNG_VERSION=\K.*' || echo "unknown")
42
+ echo "version=$UPSTREAM_VERSION" >> "$GITHUB_OUTPUT"
43
+ env:
44
+ UPSTREAM_IMAGE: ${{ env.UPSTREAM_IMAGE }}
45
+
46
+ - name: Restore cached upstream digest
47
+ if: github.event_name == 'schedule'
48
+ id: cache
49
+ uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
50
+ with:
51
+ path: /tmp/upstream-digest
52
+ key: upstream-digest-${{ steps.upstream.outputs.digest }}
53
+ restore-keys: upstream-digest-
54
+
55
+ - name: Check if upstream changed
56
+ if: github.event_name == 'schedule'
57
+ id: changed
58
+ run: |
59
+ if [ -f /tmp/upstream-digest ] && [ "$(cat /tmp/upstream-digest)" = "${{ steps.upstream.outputs.digest }}" ]; then
60
+ echo "skip=true" >> "$GITHUB_OUTPUT"
61
+ echo "Upstream unchanged, skipping build."
62
+ else
63
+ echo "skip=false" >> "$GITHUB_OUTPUT"
64
+ fi
65
+
66
+ - name: Decide whether to build
67
+ id: decide
68
+ run: |
69
+ if [ "$EVENT" != "schedule" ] || [ "$SKIP" != "true" ]; then
70
+ echo "should_build=true" >> "$GITHUB_OUTPUT"
71
+ else
72
+ echo "should_build=false" >> "$GITHUB_OUTPUT"
73
+ fi
74
+ env:
75
+ EVENT: ${{ github.event_name }}
76
+ SKIP: ${{ steps.changed.outputs.skip }}
77
+
78
+ - name: Save upstream digest
79
+ if: steps.decide.outputs.should_build == 'true'
80
+ run: echo "${{ steps.upstream.outputs.digest }}" > /tmp/upstream-digest
81
+
82
+ - name: Set up QEMU
83
+ if: steps.decide.outputs.should_build == 'true'
84
+ uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4
85
+
86
+ - name: Set up Docker Buildx
87
+ if: steps.decide.outputs.should_build == 'true'
88
+ uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
89
+
90
+ - name: Login to GHCR
91
+ if: steps.decide.outputs.should_build == 'true'
92
+ uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
93
+ with:
94
+ registry: ghcr.io
95
+ username: ${{ github.actor }}
96
+ password: ${{ secrets.GITHUB_TOKEN }}
97
+
98
+ - name: Generate tags
99
+ if: steps.decide.outputs.should_build == 'true'
100
+ id: tags
101
+ run: |
102
+ SHORT_SHA=$(echo "$GITHUB_SHA" | cut -c1-7)
103
+ UPSTREAM_VERSION="${{ steps.upstream.outputs.version }}"
104
+ FULL_TAG="${UPSTREAM_VERSION}-${SHORT_SHA}"
105
+ echo "full_tag=$FULL_TAG" >> "$GITHUB_OUTPUT"
106
+ env:
107
+ GITHUB_SHA: ${{ github.sha }}
108
+
109
+ - name: Build and push
110
+ if: steps.decide.outputs.should_build == 'true'
111
+ id: build
112
+ uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
113
+ with:
114
+ context: .
115
+ push: true
116
+ platforms: linux/amd64,linux/arm64
117
+ tags: |
118
+ ${{ env.IMAGE_NAME }}:${{ steps.tags.outputs.full_tag }}
119
+ ${{ env.IMAGE_NAME }}:latest
120
+ cache-from: type=gha
121
+ cache-to: type=gha,mode=max
122
+
123
+ - name: Scan image for vulnerabilities
124
+ if: steps.decide.outputs.should_build == 'true'
125
+ uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
126
+ with:
127
+ image-ref: ${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}
128
+ format: sarif
129
+ output: trivy-results.sarif
130
+ severity: CRITICAL,HIGH
131
+
132
+ - name: Upload Trivy results to GitHub Security
133
+ if: steps.decide.outputs.should_build == 'true'
134
+ uses: github/codeql-action/upload-sarif@7fd177fa680c9881b53cdab4d346d32574c9f7f4 # v3
135
+ with:
136
+ sarif_file: trivy-results.sarif
137
+
138
+ - name: Install Cosign
139
+ if: steps.decide.outputs.should_build == 'true'
140
+ uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
141
+
142
+ - name: Sign container image
143
+ if: steps.decide.outputs.should_build == 'true'
144
+ run: |
145
+ cosign sign --yes "$IMAGE@$DIGEST"
146
+ env:
147
+ IMAGE: ${{ env.IMAGE_NAME }}
148
+ DIGEST: ${{ steps.build.outputs.digest }}
149
+
150
+ - name: Install mcp-publisher
151
+ if: steps.decide.outputs.should_build == 'true'
152
+ run: |
153
+ curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_linux_amd64.tar.gz" | tar xz mcp-publisher
154
+
155
+ - name: Update server.json version
156
+ if: steps.decide.outputs.should_build == 'true'
157
+ run: |
158
+ VERSION="$(date -u +%Y).${{ github.run_number }}.0"
159
+ jq --arg v "$VERSION" '.version = $v' server.json > server.tmp && mv server.tmp server.json
160
+
161
+ - name: Authenticate to MCP Registry
162
+ if: steps.decide.outputs.should_build == 'true'
163
+ run: ./mcp-publisher login github-oidc
164
+
165
+ - name: Publish to MCP Registry
166
+ if: steps.decide.outputs.should_build == 'true'
167
+ run: ./mcp-publisher publish || echo "Publish failed (version may already exist), continuing."
@@ -0,0 +1,35 @@
1
+ name: CodeQL
2
+
3
+ on:
4
+ push:
5
+ branches: [main, dev]
6
+ pull_request:
7
+ branches: [dev]
8
+ schedule:
9
+ - cron: "37 4 * * 1"
10
+
11
+ permissions: read-all
12
+
13
+ jobs:
14
+ analyze:
15
+ runs-on: ubuntu-latest
16
+ permissions:
17
+ contents: read
18
+ security-events: write
19
+ strategy:
20
+ matrix:
21
+ language: [python]
22
+ steps:
23
+ - name: Checkout
24
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
25
+
26
+ - name: Initialize CodeQL
27
+ uses: github/codeql-action/init@7fd177fa680c9881b53cdab4d346d32574c9f7f4 # v3
28
+ with:
29
+ languages: ${{ matrix.language }}
30
+
31
+ - name: Autobuild
32
+ uses: github/codeql-action/autobuild@7fd177fa680c9881b53cdab4d346d32574c9f7f4 # v3
33
+
34
+ - name: Perform CodeQL Analysis
35
+ uses: github/codeql-action/analyze@7fd177fa680c9881b53cdab4d346d32574c9f7f4 # v3
@@ -0,0 +1,33 @@
1
+ name: Dependabot Auto-Merge
2
+
3
+ on:
4
+ pull_request_target:
5
+ branches: [dev]
6
+
7
+ permissions: read-all
8
+
9
+ jobs:
10
+ auto-merge:
11
+ if: github.event.pull_request.user.login == 'dependabot[bot]'
12
+ runs-on: ubuntu-latest
13
+ permissions:
14
+ contents: write
15
+ pull-requests: write
16
+ steps:
17
+ - name: Fetch Dependabot metadata
18
+ id: metadata
19
+ uses: dependabot/fetch-metadata@21025c705c08248db411dc16f3619e6b5f9ea21a # v2
20
+ with:
21
+ github-token: ${{ secrets.GITHUB_TOKEN }}
22
+
23
+ - name: Approve and auto-merge patch/minor/security updates
24
+ if: >-
25
+ steps.metadata.outputs.update-type == 'version-update:semver-patch' ||
26
+ steps.metadata.outputs.update-type == 'version-update:semver-minor' ||
27
+ steps.metadata.outputs.update-type == 'security-update'
28
+ run: |
29
+ gh pr review "$PR_URL" --approve
30
+ gh pr merge "$PR_URL" --auto --merge
31
+ env:
32
+ PR_URL: ${{ github.event.pull_request.html_url }}
33
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,66 @@
1
+ name: Reusable Test
2
+
3
+ on:
4
+ workflow_call:
5
+
6
+ permissions:
7
+ contents: read
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - name: Checkout
14
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
15
+ with:
16
+ fetch-depth: 2
17
+
18
+ - name: Set up Python
19
+ uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
20
+ with:
21
+ python-version: "3.14"
22
+ cache: pip
23
+
24
+ - name: Install dependencies
25
+ run: pip install "mcp[cli]" pytest pytest-anyio httpx pytest-cov pyyaml
26
+
27
+ - name: Run tests
28
+ run: pytest tests/ -v --tb=short --junitxml=test-results.xml --cov=mcp_server --cov-report=term-missing --cov-report=xml:coverage.xml --cov-fail-under=80
29
+
30
+ - name: Coverage summary
31
+ if: always()
32
+ run: |
33
+ if [ -f coverage.xml ]; then
34
+ echo "## Coverage Report" >> "$GITHUB_STEP_SUMMARY"
35
+ echo "" >> "$GITHUB_STEP_SUMMARY"
36
+ echo '```' >> "$GITHUB_STEP_SUMMARY"
37
+ python -m coverage report 2>/dev/null || echo "Coverage data not available"
38
+ echo '```' >> "$GITHUB_STEP_SUMMARY"
39
+ fi
40
+
41
+ - name: Test report summary
42
+ if: always()
43
+ uses: test-summary/action@37b508cfee6d4d080eedd00b5bb240a6a784a6a5 # v2
44
+ with:
45
+ paths: test-results.xml
46
+
47
+ - name: Upload test artifacts
48
+ if: always()
49
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
50
+ with:
51
+ name: test-reports
52
+ path: |
53
+ test-results.xml
54
+ coverage.xml
55
+
56
+ consistency-test:
57
+ runs-on: ubuntu-latest
58
+ steps:
59
+ - name: Checkout
60
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
61
+
62
+ - name: Check plugin consistency (local vs remote)
63
+ run: |
64
+ diff -r plugins/local/skills plugins/remote/skills || { echo "::error::Skills differ between local and remote plugins"; exit 1; }
65
+ diff -r plugins/local/agents plugins/remote/agents || { echo "::error::Agents differ between local and remote plugins"; exit 1; }
66
+ echo "Plugins are consistent."
@@ -0,0 +1,34 @@
1
+ name: OpenSSF Scorecard
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ schedule:
7
+ - cron: "23 4 * * 1"
8
+
9
+ permissions: read-all
10
+
11
+ jobs:
12
+ analysis:
13
+ runs-on: ubuntu-latest
14
+ permissions:
15
+ contents: read
16
+ security-events: write
17
+ id-token: write
18
+ steps:
19
+ - name: Checkout
20
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
21
+ with:
22
+ persist-credentials: false
23
+
24
+ - name: Run Scorecard
25
+ uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1
26
+ with:
27
+ results_file: results.sarif
28
+ results_format: sarif
29
+ publish_results: true
30
+
31
+ - name: Upload SARIF
32
+ uses: github/codeql-action/upload-sarif@7fd177fa680c9881b53cdab4d346d32574c9f7f4 # v3
33
+ with:
34
+ sarif_file: results.sarif
@@ -0,0 +1,110 @@
1
+ name: Test
2
+
3
+ on:
4
+ push:
5
+ branches-ignore: [main]
6
+ pull_request:
7
+ branches: [dev, main]
8
+
9
+ permissions:
10
+ contents: read
11
+ pull-requests: read
12
+
13
+ jobs:
14
+ check-pr-source:
15
+ if: github.event_name == 'pull_request' && github.base_ref == 'main'
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - name: Verify PRs to main come from upstream dev
19
+ run: |
20
+ if [ "$IS_FORK" = "true" ]; then
21
+ echo "::error::Fork PRs cannot target main. Please PR to dev instead."
22
+ exit 1
23
+ fi
24
+ if [ "$HEAD_REF" != "dev" ]; then
25
+ echo "::error::PRs to main must come from the dev branch, got '$HEAD_REF'"
26
+ exit 1
27
+ fi
28
+ env:
29
+ HEAD_REF: ${{ github.head_ref }}
30
+ IS_FORK: ${{ github.event.pull_request.head.repo.fork }}
31
+
32
+ check-protected-files:
33
+ if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true
34
+ runs-on: ubuntu-latest
35
+ steps:
36
+ - name: Block fork PRs that modify .github/
37
+ run: |
38
+ CHANGED=$(gh pr diff "$PR_NUMBER" --name-only --repo "$REPO")
39
+ PROTECTED=$(echo "$CHANGED" | grep -E '^\.github/' || true)
40
+ if [ -n "$PROTECTED" ]; then
41
+ echo "::error::Fork PRs cannot modify files in .github/. Changed: $PROTECTED"
42
+ exit 1
43
+ fi
44
+ env:
45
+ GH_TOKEN: ${{ github.token }}
46
+ PR_NUMBER: ${{ github.event.pull_request.number }}
47
+ REPO: ${{ github.repository }}
48
+
49
+ check-changes:
50
+ if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'dev')
51
+ runs-on: ubuntu-latest
52
+ outputs:
53
+ skip: ${{ steps.changes.outputs.skip }}
54
+ steps:
55
+ - name: Checkout
56
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
57
+ with:
58
+ fetch-depth: 0
59
+
60
+ - name: Check for docs-only changes
61
+ id: changes
62
+ run: |
63
+ if [ "$EVENT_NAME" = "pull_request" ]; then
64
+ FILES=$(gh pr diff "$PR_NUMBER" --name-only --repo "$REPO")
65
+ else
66
+ if [ "$BEFORE_SHA" = "0000000000000000000000000000000000000000" ]; then
67
+ FILES=$(git diff --name-only HEAD~1)
68
+ else
69
+ FILES=$(git diff --name-only "$BEFORE_SHA".."$AFTER_SHA" 2>/dev/null || git diff --name-only HEAD~1)
70
+ fi
71
+ fi
72
+ NON_DOCS=$(echo "$FILES" | grep -v -E '(^[^/]*\.md$|^docs/|^assets/|^LICENSE$)' || true)
73
+ if [ -z "$NON_DOCS" ]; then
74
+ echo "skip=true" >> "$GITHUB_OUTPUT"
75
+ echo "Only docs changed, skipping tests."
76
+ else
77
+ echo "skip=false" >> "$GITHUB_OUTPUT"
78
+ fi
79
+ env:
80
+ GH_TOKEN: ${{ github.token }}
81
+ EVENT_NAME: ${{ github.event_name }}
82
+ PR_NUMBER: ${{ github.event.pull_request.number }}
83
+ REPO: ${{ github.repository }}
84
+ BEFORE_SHA: ${{ github.event.before }}
85
+ AFTER_SHA: ${{ github.sha }}
86
+
87
+ call-test:
88
+ needs: check-changes
89
+ if: needs.check-changes.outputs.skip != 'true'
90
+ uses: ./.github/workflows/reusable-test.yml
91
+
92
+ test-gate:
93
+ needs: [check-changes, call-test]
94
+ if: always() && (github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'dev'))
95
+ runs-on: ubuntu-latest
96
+ steps:
97
+ - name: Evaluate test results
98
+ run: |
99
+ if [ "$SKIP" = "true" ]; then
100
+ echo "Tests skipped (docs-only changes)"
101
+ exit 0
102
+ fi
103
+ if [ "$RESULT" != "success" ]; then
104
+ echo "Tests failed or were cancelled"
105
+ exit 1
106
+ fi
107
+ echo "All tests passed"
108
+ env:
109
+ SKIP: ${{ needs.check-changes.outputs.skip }}
110
+ RESULT: ${{ needs.call-test.result }}
@@ -0,0 +1,40 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ *.egg-info/
7
+ dist/
8
+ build/
9
+ *.egg
10
+
11
+ # Virtual environment
12
+ .venv/
13
+ venv/
14
+ env/
15
+
16
+ # IDE
17
+ .idea/
18
+ .vscode/
19
+ *.swp
20
+ *.swo
21
+
22
+ # Environment
23
+ .env
24
+ .env.local
25
+
26
+ # OS
27
+ .DS_Store
28
+ Thumbs.db
29
+
30
+ # Docs (plugin caches)
31
+ docs/superpowers/
32
+
33
+ # Playwright MCP
34
+ .playwright-mcp/
35
+
36
+ # Test / Coverage
37
+ .pytest_cache/
38
+ .coverage
39
+ htmlcov/
40
+ .superpowers/
@@ -0,0 +1,3 @@
1
+ # CLAUDE.md
2
+
3
+ Rules are auto-discovered from `.claude/rules/`.