chore(ci): establish PR governance for agent collaboration (#177)
* chore(ci): establish PR governance for agent collaboration * docs: add AGENTS playbook and strengthen agent collaboration workflow --------- Co-authored-by: chumyin <183474434+chumyin@users.noreply.github.com>
This commit is contained in:
parent
dca95cac7a
commit
dfe648d5ae
14 changed files with 1020 additions and 19 deletions
10
.github/CODEOWNERS
vendored
Normal file
10
.github/CODEOWNERS
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# Default owner for all files
|
||||
* @theonlyhennygod
|
||||
|
||||
# High-risk surfaces
|
||||
/src/security/** @theonlyhennygod
|
||||
/src/runtime/** @theonlyhennygod
|
||||
/src/memory/** @theonlyhennygod
|
||||
/.github/** @theonlyhennygod
|
||||
/Cargo.toml @theonlyhennygod
|
||||
/Cargo.lock @theonlyhennygod
|
||||
93
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
93
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
name: Bug Report
|
||||
description: Report a reproducible defect in ZeroClaw
|
||||
title: "[Bug]: "
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to report a bug.
|
||||
Please provide a minimal reproducible case so maintainers can triage quickly.
|
||||
|
||||
- type: input
|
||||
id: summary
|
||||
attributes:
|
||||
label: Summary
|
||||
description: One-line description of the problem.
|
||||
placeholder: zeroclaw daemon exits immediately when ...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: current
|
||||
attributes:
|
||||
label: Current behavior
|
||||
description: What is happening now?
|
||||
placeholder: The process exits with ...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
description: What should happen instead?
|
||||
placeholder: The daemon should stay alive and ...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: reproduce
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: Please provide exact commands/config.
|
||||
placeholder: |
|
||||
1. zeroclaw onboard --interactive
|
||||
2. zeroclaw daemon
|
||||
3. Observe crash in logs
|
||||
render: bash
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Logs / stack traces
|
||||
description: Paste relevant logs (redact secrets).
|
||||
render: text
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: ZeroClaw version
|
||||
placeholder: v0.1.0 / commit SHA
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: rust
|
||||
attributes:
|
||||
label: Rust version
|
||||
placeholder: rustc 1.xx.x
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: os
|
||||
attributes:
|
||||
label: Operating system
|
||||
placeholder: Ubuntu 24.04 / macOS 15 / Windows 11
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: regression
|
||||
attributes:
|
||||
label: Regression?
|
||||
options:
|
||||
- Unknown
|
||||
- Yes, it worked before
|
||||
- No, first-time setup
|
||||
validations:
|
||||
required: true
|
||||
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Security vulnerability report
|
||||
url: https://github.com/theonlyhennygod/zeroclaw/security/policy
|
||||
about: Please report security vulnerabilities privately via SECURITY.md policy.
|
||||
- name: Contribution guide
|
||||
url: https://github.com/theonlyhennygod/zeroclaw/blob/main/CONTRIBUTING.md
|
||||
about: Please read contribution and PR requirements before opening an issue.
|
||||
64
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
64
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
name: Feature Request
|
||||
description: Propose an improvement or new capability
|
||||
title: "[Feature]: "
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for sharing your idea.
|
||||
Please focus on user value, constraints, and rollout safety.
|
||||
|
||||
- type: input
|
||||
id: problem
|
||||
attributes:
|
||||
label: Problem statement
|
||||
description: What user problem are you trying to solve?
|
||||
placeholder: Teams need a way to ...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: proposal
|
||||
attributes:
|
||||
label: Proposed solution
|
||||
description: Describe the preferred solution.
|
||||
placeholder: Add a new subcommand / trait implementation ...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: Alternatives considered
|
||||
description: What alternatives did you evaluate?
|
||||
placeholder: Keep current behavior, use external tool, etc.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: architecture
|
||||
attributes:
|
||||
label: Architecture impact
|
||||
description: Which subsystem(s) are affected?
|
||||
placeholder: providers/, channels/, memory/, runtime/, security/ ...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: risk
|
||||
attributes:
|
||||
label: Risk and rollback
|
||||
description: Main risk + how to disable/revert quickly.
|
||||
placeholder: Risk is ... rollback is ...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: breaking
|
||||
attributes:
|
||||
label: Breaking change?
|
||||
options:
|
||||
- No
|
||||
- Yes
|
||||
validations:
|
||||
required: true
|
||||
59
.github/labeler.yml
vendored
Normal file
59
.github/labeler.yml
vendored
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
"type: docs":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "docs/**"
|
||||
- "**/*.md"
|
||||
- "LICENSE"
|
||||
|
||||
"type: dependencies":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "Cargo.toml"
|
||||
- "Cargo.lock"
|
||||
- "deny.toml"
|
||||
|
||||
"type: ci":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- ".github/**"
|
||||
- ".githooks/**"
|
||||
|
||||
"area: providers":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/providers/**"
|
||||
|
||||
"area: channels":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/channels/**"
|
||||
|
||||
"area: memory":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/memory/**"
|
||||
|
||||
"area: security":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/security/**"
|
||||
|
||||
"area: runtime":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/runtime/**"
|
||||
|
||||
"area: tools":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/tools/**"
|
||||
|
||||
"area: observability":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "src/observability/**"
|
||||
|
||||
"area: tests":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "tests/**"
|
||||
70
.github/pull_request_template.md
vendored
Normal file
70
.github/pull_request_template.md
vendored
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
## Summary
|
||||
|
||||
Describe this PR in 2-5 bullets:
|
||||
|
||||
- Problem:
|
||||
- Why it matters:
|
||||
- What changed:
|
||||
- What did **not** change (scope boundary):
|
||||
|
||||
## Change Type
|
||||
|
||||
- [ ] Bug fix
|
||||
- [ ] Feature
|
||||
- [ ] Refactor
|
||||
- [ ] Docs
|
||||
- [ ] Security hardening
|
||||
- [ ] Chore / infra
|
||||
|
||||
## Scope
|
||||
|
||||
- [ ] Core runtime / daemon
|
||||
- [ ] Provider integration
|
||||
- [ ] Channel integration
|
||||
- [ ] Memory / storage
|
||||
- [ ] Security / sandbox
|
||||
- [ ] CI / release / tooling
|
||||
- [ ] Documentation
|
||||
|
||||
## Linked Issue
|
||||
|
||||
- Closes #
|
||||
- Related #
|
||||
|
||||
## Testing
|
||||
|
||||
Commands and result summary (required):
|
||||
|
||||
```bash
|
||||
cargo fmt --all -- --check
|
||||
cargo clippy --all-targets -- -D warnings
|
||||
cargo test
|
||||
```
|
||||
|
||||
If any command is intentionally skipped, explain why.
|
||||
|
||||
## Security Impact
|
||||
|
||||
- New permissions/capabilities? (`Yes/No`)
|
||||
- New external network calls? (`Yes/No`)
|
||||
- Secrets/tokens handling changed? (`Yes/No`)
|
||||
- File system access scope changed? (`Yes/No`)
|
||||
- If any `Yes`, describe risk and mitigation:
|
||||
|
||||
## Agent Collaboration Notes (recommended)
|
||||
|
||||
- [ ] If agent/automation tools were used, I added brief workflow notes.
|
||||
- [ ] I included concrete validation evidence for this change.
|
||||
- [ ] I can explain design choices and rollback steps.
|
||||
|
||||
If agent tools were used, optional context:
|
||||
|
||||
- Tool(s):
|
||||
- Prompt/plan summary:
|
||||
- Verification focus:
|
||||
|
||||
## Rollback Plan
|
||||
|
||||
- Fast rollback command/path:
|
||||
- Feature flags or config toggles (if any):
|
||||
- Observable failure symptoms:
|
||||
40
.github/workflows/auto-response.yml
vendored
Normal file
40
.github/workflows/auto-response.yml
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
name: Auto Response
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
first-interaction:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Greet first-time contributors
|
||||
uses: actions/first-interaction@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-message: |
|
||||
Thanks for opening this issue.
|
||||
|
||||
Before maintainers triage it, please confirm:
|
||||
- Repro steps are complete and run on latest `main`
|
||||
- Environment details are included (OS, Rust version, ZeroClaw version)
|
||||
- Sensitive values are redacted
|
||||
|
||||
This helps us keep issue throughput high and response latency low.
|
||||
pr-message: |
|
||||
Thanks for contributing to ZeroClaw.
|
||||
|
||||
For faster review, please ensure:
|
||||
- PR template sections are fully completed
|
||||
- `cargo fmt --all -- --check`, `cargo clippy --all-targets -- -D warnings`, and `cargo test` are included
|
||||
- If automation/agents were used heavily, add brief workflow notes
|
||||
- Scope is focused (prefer one concern per PR)
|
||||
|
||||
See `CONTRIBUTING.md` and `docs/pr-workflow.md` for full collaboration rules.
|
||||
137
.github/workflows/ci.yml
vendored
137
.github/workflows/ci.yml
vendored
|
|
@ -6,14 +6,86 @@ on:
|
|||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
name: Detect Change Scope
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
docs_only: ${{ steps.scope.outputs.docs_only }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Detect docs-only changes
|
||||
id: scope
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
||||
BASE="${{ github.event.pull_request.base.sha }}"
|
||||
else
|
||||
BASE="${{ github.event.before }}"
|
||||
fi
|
||||
|
||||
if [ -z "$BASE" ] || ! git cat-file -e "$BASE^{commit}" 2>/dev/null; then
|
||||
echo "docs_only=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
CHANGED="$(git diff --name-only "$BASE" HEAD || true)"
|
||||
if [ -z "$CHANGED" ]; then
|
||||
echo "docs_only=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
docs_only=true
|
||||
while IFS= read -r file; do
|
||||
[ -z "$file" ] && continue
|
||||
case "$file" in
|
||||
docs/*|*.md|*.mdx|LICENSE|.github/ISSUE_TEMPLATE/*|.github/pull_request_template.md)
|
||||
;;
|
||||
*)
|
||||
docs_only=false
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done <<< "$CHANGED"
|
||||
|
||||
echo "docs_only=$docs_only" >> "$GITHUB_OUTPUT"
|
||||
|
||||
lint:
|
||||
name: Format & Lint
|
||||
needs: [changes]
|
||||
if: needs.changes.outputs.docs_only != 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Run rustfmt
|
||||
run: cargo fmt --all -- --check
|
||||
- name: Run clippy
|
||||
run: cargo clippy --all-targets -- -D warnings
|
||||
|
||||
test:
|
||||
name: Test
|
||||
needs: [changes]
|
||||
if: needs.changes.outputs.docs_only != 'true'
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true # Don't block PRs
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
|
|
@ -22,24 +94,55 @@ jobs:
|
|||
run: cargo test --verbose
|
||||
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ${{ matrix.os }}
|
||||
continue-on-error: true # Don't block PRs on build failures
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
target: x86_64-unknown-linux-gnu
|
||||
- os: macos-latest
|
||||
target: x86_64-apple-darwin
|
||||
- os: macos-latest
|
||||
target: aarch64-apple-darwin
|
||||
- os: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
name: Build (Smoke)
|
||||
needs: [changes]
|
||||
if: needs.changes.outputs.docs_only != 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Build
|
||||
run: cargo build --release --verbose
|
||||
- name: Build release binary
|
||||
run: cargo build --release --locked --verbose
|
||||
|
||||
docs-only:
|
||||
name: Docs-Only Fast Path
|
||||
needs: [changes]
|
||||
if: needs.changes.outputs.docs_only == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Skip heavy jobs for docs-only change
|
||||
run: echo "Docs-only change detected. Rust lint/test/build skipped."
|
||||
|
||||
ci-required:
|
||||
name: CI Required Gate
|
||||
if: always()
|
||||
needs: [changes, lint, test, build, docs-only]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Enforce required status
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [ "${{ needs.changes.outputs.docs_only }}" = "true" ]; then
|
||||
echo "Docs-only fast path passed."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
lint_result="${{ needs.lint.result }}"
|
||||
test_result="${{ needs.test.result }}"
|
||||
build_result="${{ needs.build.result }}"
|
||||
|
||||
echo "lint=${lint_result}"
|
||||
echo "test=${test_result}"
|
||||
echo "build=${build_result}"
|
||||
|
||||
if [ "$lint_result" != "success" ] || [ "$test_result" != "success" ] || [ "$build_result" != "success" ]; then
|
||||
echo "Required CI jobs did not pass."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "All required CI jobs passed."
|
||||
|
|
|
|||
70
.github/workflows/labeler.yml
vendored
Normal file
70
.github/workflows/labeler.yml
vendored
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
name: PR Labeler
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, reopened, synchronize, edited]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
label:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Apply path labels
|
||||
uses: actions/labeler@v5
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Apply size label
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const pr = context.payload.pull_request;
|
||||
const sizeLabels = ["size: XS", "size: S", "size: M", "size: L", "size: XL"];
|
||||
const labelColor = "BFDADC";
|
||||
const changedLines = (pr.additions || 0) + (pr.deletions || 0);
|
||||
|
||||
let sizeLabel = "size: XL";
|
||||
if (changedLines <= 80) sizeLabel = "size: XS";
|
||||
else if (changedLines <= 250) sizeLabel = "size: S";
|
||||
else if (changedLines <= 500) sizeLabel = "size: M";
|
||||
else if (changedLines <= 1000) sizeLabel = "size: L";
|
||||
|
||||
for (const label of sizeLabels) {
|
||||
try {
|
||||
await github.rest.issues.getLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
name: label,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.status !== 404) throw error;
|
||||
await github.rest.issues.createLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
name: label,
|
||||
color: labelColor,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const { data: currentLabels } = await github.rest.issues.listLabelsOnIssue({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pr.number,
|
||||
});
|
||||
|
||||
const keepLabels = currentLabels
|
||||
.map((label) => label.name)
|
||||
.filter((label) => !sizeLabels.includes(label));
|
||||
|
||||
const nextLabels = [...new Set([...keepLabels, sizeLabel])];
|
||||
await github.rest.issues.setLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pr.number,
|
||||
labels: nextLabels,
|
||||
});
|
||||
44
.github/workflows/stale.yml
vendored
Normal file
44
.github/workflows/stale.yml
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
name: Stale
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "20 2 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Mark stale issues and pull requests
|
||||
uses: actions/stale@v9
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
days-before-issue-stale: 21
|
||||
days-before-issue-close: 7
|
||||
days-before-pr-stale: 14
|
||||
days-before-pr-close: 7
|
||||
stale-issue-label: stale
|
||||
stale-pr-label: stale
|
||||
exempt-issue-labels: security,pinned,no-stale,maintainer
|
||||
exempt-pr-labels: no-stale,maintainer
|
||||
remove-stale-when-updated: true
|
||||
exempt-all-assignees: true
|
||||
operations-per-run: 300
|
||||
stale-issue-message: |
|
||||
This issue was automatically marked as stale due to inactivity.
|
||||
Please provide an update, reproduction details, or current status to keep it open.
|
||||
close-issue-message: |
|
||||
Closing this issue due to inactivity.
|
||||
If the problem still exists on the latest `main`, please open a new issue with fresh repro steps.
|
||||
close-issue-reason: not_planned
|
||||
stale-pr-message: |
|
||||
This PR was automatically marked as stale due to inactivity.
|
||||
Please rebase/update and post the latest validation results.
|
||||
close-pr-message: |
|
||||
Closing this PR due to inactivity.
|
||||
Maintainers can reopen once the branch is updated and validation is provided.
|
||||
63
.github/workflows/workflow-sanity.yml
vendored
Normal file
63
.github/workflows/workflow-sanity.yml
vendored
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
name: Workflow Sanity
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/**"
|
||||
- ".github/*.yml"
|
||||
- ".github/*.yaml"
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- ".github/workflows/**"
|
||||
- ".github/*.yml"
|
||||
- ".github/*.yaml"
|
||||
|
||||
concurrency:
|
||||
group: workflow-sanity-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
no-tabs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Fail on tabs in workflow files
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
python - <<'PY'
|
||||
from __future__ import annotations
|
||||
|
||||
import pathlib
|
||||
import sys
|
||||
|
||||
root = pathlib.Path(".github/workflows")
|
||||
bad: list[str] = []
|
||||
for path in sorted(root.rglob("*.yml")):
|
||||
if b"\t" in path.read_bytes():
|
||||
bad.append(str(path))
|
||||
for path in sorted(root.rglob("*.yaml")):
|
||||
if b"\t" in path.read_bytes():
|
||||
bad.append(str(path))
|
||||
|
||||
if bad:
|
||||
print("Tabs found in workflow file(s):")
|
||||
for path in bad:
|
||||
print(f"- {path}")
|
||||
sys.exit(1)
|
||||
PY
|
||||
|
||||
actionlint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Lint GitHub workflows
|
||||
uses: rhysd/actionlint@v1
|
||||
Loading…
Add table
Add a link
Reference in a new issue