docs: strengthen collaboration governance and AGENTS engineering protocol (#263)
* docs: harden collaboration policy and review automation * ci(docs): remove unsupported lychee --exclude-mail flag * docs(governance): reduce automation side-effects and tighten risk controls * docs(governance): add backlog pruning and supersede protocol * docs(agents): codify engineering principles and risk-tier workflow * docs(readme): add centered star history section at bottom * docs(agents): enforce privacy-safe and neutral test wording * docs(governance): enforce privacy-safe and neutral collaboration checks * fix(ci): satisfy rustfmt and discord schema test fields * docs(governance): require ZeroClaw-native identity wording * docs(agents): add ZeroClaw identity-safe naming palette * docs(governance): codify code naming and architecture contracts * docs(contributing): add naming and architecture good/bad examples * docs(pr): reduce checkbox TODOs and shift to label-first metadata * docs(pr): remove duplicate collaboration track field * ci(labeler): auto-derive module labels and expand provider hints * ci(labeler): auto-apply trusted contributor on PRs and issues * fix(ci): apply rustfmt updates from latest main * ci(labels): flatten namespaces and add contributor tiers * chore: drop stale rustfmt-only drift * ci: scope Rust and docs checks by change set * ci: exclude non-markdown docs from docs-quality targets * ci: satisfy actionlint shellcheck output style * ci(labels): auto-correct manual contributor tier edits * ci(labeler): auto-correct risk label edits * ci(labeler): auto-correct size label edits --------- Co-authored-by: Chummy <183474434+chumyin@users.noreply.github.com>
This commit is contained in:
parent
b5d9f72023
commit
6d56a040ce
16 changed files with 1635 additions and 154 deletions
57
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
57
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
|
@ -1,12 +1,15 @@
|
||||||
name: Bug Report
|
name: Bug Report
|
||||||
description: Report a reproducible defect in ZeroClaw
|
description: Report a reproducible defect in ZeroClaw
|
||||||
title: "[Bug]: "
|
title: "[Bug]: "
|
||||||
|
labels:
|
||||||
|
- bug
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
Thanks for taking the time to report a bug.
|
Thanks for taking the time to report a bug.
|
||||||
Please provide a minimal reproducible case so maintainers can triage quickly.
|
Please provide a minimal reproducible case so maintainers can triage quickly.
|
||||||
|
Do not include personal/sensitive data; redact and anonymize all logs/payloads.
|
||||||
|
|
||||||
- type: input
|
- type: input
|
||||||
id: summary
|
id: summary
|
||||||
|
|
@ -17,6 +20,34 @@ body:
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: component
|
||||||
|
attributes:
|
||||||
|
label: Affected component
|
||||||
|
options:
|
||||||
|
- runtime/daemon
|
||||||
|
- provider
|
||||||
|
- channel
|
||||||
|
- memory
|
||||||
|
- security/sandbox
|
||||||
|
- tooling/ci
|
||||||
|
- docs
|
||||||
|
- unknown
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: severity
|
||||||
|
attributes:
|
||||||
|
label: Severity
|
||||||
|
options:
|
||||||
|
- S0 - data loss / security risk
|
||||||
|
- S1 - workflow blocked
|
||||||
|
- S2 - degraded behavior
|
||||||
|
- S3 - minor issue
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: current
|
id: current
|
||||||
attributes:
|
attributes:
|
||||||
|
|
@ -48,11 +79,23 @@ body:
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: impact
|
||||||
|
attributes:
|
||||||
|
label: Impact
|
||||||
|
description: Who is affected, how often, and practical consequences.
|
||||||
|
placeholder: |
|
||||||
|
Affected users: ...
|
||||||
|
Frequency: always/intermittent
|
||||||
|
Consequence: ...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: logs
|
id: logs
|
||||||
attributes:
|
attributes:
|
||||||
label: Logs / stack traces
|
label: Logs / stack traces
|
||||||
description: Paste relevant logs (redact secrets).
|
description: Paste relevant logs (redact secrets, personal identifiers, and sensitive data).
|
||||||
render: text
|
render: text
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
|
|
@ -91,3 +134,15 @@ body:
|
||||||
- No, first-time setup
|
- No, first-time setup
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
id: checks
|
||||||
|
attributes:
|
||||||
|
label: Pre-flight checks
|
||||||
|
options:
|
||||||
|
- label: I reproduced this on the latest main branch or latest release.
|
||||||
|
required: true
|
||||||
|
- label: I redacted secrets/tokens from logs.
|
||||||
|
required: true
|
||||||
|
- label: I removed personal identifiers and replaced identity-specific data with neutral placeholders.
|
||||||
|
required: true
|
||||||
|
|
|
||||||
7
.github/ISSUE_TEMPLATE/config.yml
vendored
7
.github/ISSUE_TEMPLATE/config.yml
vendored
|
|
@ -1,8 +1,11 @@
|
||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: Security vulnerability report
|
- name: Security vulnerability report
|
||||||
url: https://github.com/theonlyhennygod/zeroclaw/security/policy
|
url: https://github.com/zeroclaw-labs/zeroclaw/security/policy
|
||||||
about: Please report security vulnerabilities privately via SECURITY.md policy.
|
about: Please report security vulnerabilities privately via SECURITY.md policy.
|
||||||
- name: Contribution guide
|
- name: Contribution guide
|
||||||
url: https://github.com/theonlyhennygod/zeroclaw/blob/main/CONTRIBUTING.md
|
url: https://github.com/zeroclaw-labs/zeroclaw/blob/main/CONTRIBUTING.md
|
||||||
about: Please read contribution and PR requirements before opening an issue.
|
about: Please read contribution and PR requirements before opening an issue.
|
||||||
|
- name: PR workflow & reviewer expectations
|
||||||
|
url: https://github.com/zeroclaw-labs/zeroclaw/blob/main/docs/pr-workflow.md
|
||||||
|
about: Read risk-based PR tracks, CI gates, and merge criteria before filing feature requests.
|
||||||
|
|
|
||||||
55
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
55
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
|
|
@ -1,19 +1,31 @@
|
||||||
name: Feature Request
|
name: Feature Request
|
||||||
description: Propose an improvement or new capability
|
description: Propose an improvement or new capability
|
||||||
title: "[Feature]: "
|
title: "[Feature]: "
|
||||||
|
labels:
|
||||||
|
- enhancement
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
Thanks for sharing your idea.
|
Thanks for sharing your idea.
|
||||||
Please focus on user value, constraints, and rollout safety.
|
Please focus on user value, constraints, and rollout safety.
|
||||||
|
Do not include personal/sensitive data; use neutral project-scoped placeholders.
|
||||||
|
|
||||||
- type: input
|
- type: input
|
||||||
|
id: summary
|
||||||
|
attributes:
|
||||||
|
label: Summary
|
||||||
|
description: One-line statement of the requested capability.
|
||||||
|
placeholder: Add a provider-level retry budget override for long-running channels.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
id: problem
|
id: problem
|
||||||
attributes:
|
attributes:
|
||||||
label: Problem statement
|
label: Problem statement
|
||||||
description: What user problem are you trying to solve?
|
description: What user pain does this solve and why is current behavior insufficient?
|
||||||
placeholder: Teams need a way to ...
|
placeholder: Teams operating in unstable networks cannot tune retries per provider...
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
|
@ -21,8 +33,17 @@ body:
|
||||||
id: proposal
|
id: proposal
|
||||||
attributes:
|
attributes:
|
||||||
label: Proposed solution
|
label: Proposed solution
|
||||||
description: Describe the preferred solution.
|
description: Describe preferred behavior and interfaces.
|
||||||
placeholder: Add a new subcommand / trait implementation ...
|
placeholder: Add `[provider.retry]` config and enforce bounds in config validation.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: non_goals
|
||||||
|
attributes:
|
||||||
|
label: Non-goals / out of scope
|
||||||
|
description: Clarify what should not be included in the first iteration.
|
||||||
|
placeholder: No UI changes, no cross-provider dynamic adaptation in v1.
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
|
@ -31,16 +52,28 @@ body:
|
||||||
attributes:
|
attributes:
|
||||||
label: Alternatives considered
|
label: Alternatives considered
|
||||||
description: What alternatives did you evaluate?
|
description: What alternatives did you evaluate?
|
||||||
placeholder: Keep current behavior, use external tool, etc.
|
placeholder: Keep current behavior, use wrapper scripts, etc.
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: acceptance
|
||||||
|
attributes:
|
||||||
|
label: Acceptance criteria
|
||||||
|
description: What outcomes would make this request complete?
|
||||||
|
placeholder: |
|
||||||
|
- Config key is documented and validated
|
||||||
|
- Runtime path uses configured retry budget
|
||||||
|
- Regression tests cover fallback and invalid config
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: architecture
|
id: architecture
|
||||||
attributes:
|
attributes:
|
||||||
label: Architecture impact
|
label: Architecture impact
|
||||||
description: Which subsystem(s) are affected?
|
description: Which subsystem(s) are affected?
|
||||||
placeholder: providers/, channels/, memory/, runtime/, security/ ...
|
placeholder: providers/, channels/, memory/, runtime/, security/, docs/ ...
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
|
@ -62,3 +95,13 @@ body:
|
||||||
- Yes
|
- Yes
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
id: hygiene
|
||||||
|
attributes:
|
||||||
|
label: Data hygiene checks
|
||||||
|
options:
|
||||||
|
- label: I removed personal/sensitive data from examples, payloads, and logs.
|
||||||
|
required: true
|
||||||
|
- label: I used neutral, project-focused wording and placeholders.
|
||||||
|
required: true
|
||||||
|
|
|
||||||
35
.github/dependabot.yml
vendored
Normal file
35
.github/dependabot.yml
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
version: 2
|
||||||
|
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: cargo
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
|
target-branch: main
|
||||||
|
open-pull-requests-limit: 5
|
||||||
|
labels:
|
||||||
|
- "dependencies"
|
||||||
|
groups:
|
||||||
|
rust-minor-patch:
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
|
update-types:
|
||||||
|
- minor
|
||||||
|
- patch
|
||||||
|
|
||||||
|
- package-ecosystem: github-actions
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
|
target-branch: main
|
||||||
|
open-pull-requests-limit: 3
|
||||||
|
labels:
|
||||||
|
- "ci"
|
||||||
|
- "dependencies"
|
||||||
|
groups:
|
||||||
|
actions-minor-patch:
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
|
update-types:
|
||||||
|
- minor
|
||||||
|
- patch
|
||||||
112
.github/labeler.yml
vendored
112
.github/labeler.yml
vendored
|
|
@ -1,59 +1,147 @@
|
||||||
"type: docs":
|
"docs":
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
- "**/*.md"
|
- "**/*.md"
|
||||||
|
- "**/*.mdx"
|
||||||
- "LICENSE"
|
- "LICENSE"
|
||||||
|
- ".markdownlint-cli2.yaml"
|
||||||
|
|
||||||
"type: dependencies":
|
"dependencies":
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
- "Cargo.toml"
|
- "Cargo.toml"
|
||||||
- "Cargo.lock"
|
- "Cargo.lock"
|
||||||
- "deny.toml"
|
- "deny.toml"
|
||||||
|
- ".github/dependabot.yml"
|
||||||
|
|
||||||
"type: ci":
|
"ci":
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
- ".github/**"
|
- ".github/**"
|
||||||
- ".githooks/**"
|
- ".githooks/**"
|
||||||
|
|
||||||
"area: providers":
|
"core":
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
- "src/providers/**"
|
- "src/*.rs"
|
||||||
|
|
||||||
"area: channels":
|
"agent":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "src/agent/**"
|
||||||
|
|
||||||
|
"channel":
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
- "src/channels/**"
|
- "src/channels/**"
|
||||||
|
|
||||||
"area: memory":
|
"gateway":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "src/gateway/**"
|
||||||
|
|
||||||
|
"config":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "src/config/**"
|
||||||
|
|
||||||
|
"cron":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "src/cron/**"
|
||||||
|
|
||||||
|
"daemon":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "src/daemon/**"
|
||||||
|
|
||||||
|
"doctor":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "src/doctor/**"
|
||||||
|
|
||||||
|
"health":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "src/health/**"
|
||||||
|
|
||||||
|
"heartbeat":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "src/heartbeat/**"
|
||||||
|
|
||||||
|
"integration":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "src/integrations/**"
|
||||||
|
|
||||||
|
"memory":
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
- "src/memory/**"
|
- "src/memory/**"
|
||||||
|
|
||||||
"area: security":
|
"security":
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
- "src/security/**"
|
- "src/security/**"
|
||||||
|
|
||||||
"area: runtime":
|
"runtime":
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
- "src/runtime/**"
|
- "src/runtime/**"
|
||||||
|
|
||||||
"area: tools":
|
"onboard":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "src/onboard/**"
|
||||||
|
|
||||||
|
"provider":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "src/providers/**"
|
||||||
|
|
||||||
|
"service":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "src/service/**"
|
||||||
|
|
||||||
|
"skillforge":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "src/skillforge/**"
|
||||||
|
|
||||||
|
"skills":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "src/skills/**"
|
||||||
|
|
||||||
|
"tool":
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
- "src/tools/**"
|
- "src/tools/**"
|
||||||
|
|
||||||
"area: observability":
|
"tunnel":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "src/tunnel/**"
|
||||||
|
|
||||||
|
"observability":
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
- "src/observability/**"
|
- "src/observability/**"
|
||||||
|
|
||||||
"area: tests":
|
"tests":
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
- "tests/**"
|
- "tests/**"
|
||||||
|
|
||||||
|
"scripts":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "scripts/**"
|
||||||
|
|
||||||
|
"dev":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "dev/**"
|
||||||
|
|
|
||||||
83
.github/pull_request_template.md
vendored
83
.github/pull_request_template.md
vendored
|
|
@ -7,33 +7,30 @@ Describe this PR in 2-5 bullets:
|
||||||
- What changed:
|
- What changed:
|
||||||
- What did **not** change (scope boundary):
|
- What did **not** change (scope boundary):
|
||||||
|
|
||||||
## Change Type
|
## Label Snapshot (required)
|
||||||
|
|
||||||
- [ ] Bug fix
|
- Risk label (`risk: low|medium|high`):
|
||||||
- [ ] Feature
|
- Size label (`size: XS|S|M|L|XL`, auto-managed/read-only):
|
||||||
- [ ] Refactor
|
- Scope labels (`core|agent|channel|config|cron|daemon|doctor|gateway|health|heartbeat|integration|memory|observability|onboard|provider|runtime|security|service|skillforge|skills|tool|tunnel|docs|dependencies|ci|tests|scripts|dev`, comma-separated):
|
||||||
- [ ] Docs
|
- Module labels (`<module>:<component>`, for example `channel:telegram`, `provider:kimi`, `tool:shell`):
|
||||||
- [ ] Security hardening
|
- Contributor tier label (`experienced contributor|principal contributor|distinguished contributor`, auto-managed/read-only; author merged PRs >=10/20/50):
|
||||||
- [ ] Chore / infra
|
- If any auto-label is incorrect, note requested correction:
|
||||||
|
|
||||||
## Scope
|
## Change Metadata
|
||||||
|
|
||||||
- [ ] Core runtime / daemon
|
- Change type (`bug|feature|refactor|docs|security|chore`):
|
||||||
- [ ] Provider integration
|
- Primary scope (`runtime|provider|channel|memory|security|ci|docs|multi`):
|
||||||
- [ ] Channel integration
|
|
||||||
- [ ] Memory / storage
|
|
||||||
- [ ] Security / sandbox
|
|
||||||
- [ ] CI / release / tooling
|
|
||||||
- [ ] Documentation
|
|
||||||
|
|
||||||
## Linked Issue
|
## Linked Issue
|
||||||
|
|
||||||
- Closes #
|
- Closes #
|
||||||
- Related #
|
- Related #
|
||||||
|
- Depends on # (if stacked)
|
||||||
|
- Supersedes # (if replacing older PR)
|
||||||
|
|
||||||
## Testing
|
## Validation Evidence (required)
|
||||||
|
|
||||||
Commands and result summary (required):
|
Commands and result summary:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo fmt --all -- --check
|
cargo fmt --all -- --check
|
||||||
|
|
@ -41,9 +38,10 @@ cargo clippy --all-targets -- -D warnings
|
||||||
cargo test
|
cargo test
|
||||||
```
|
```
|
||||||
|
|
||||||
If any command is intentionally skipped, explain why.
|
- Evidence provided (test/log/trace/screenshot/perf):
|
||||||
|
- If any command is intentionally skipped, explain why:
|
||||||
|
|
||||||
## Security Impact
|
## Security Impact (required)
|
||||||
|
|
||||||
- New permissions/capabilities? (`Yes/No`)
|
- New permissions/capabilities? (`Yes/No`)
|
||||||
- New external network calls? (`Yes/No`)
|
- New external network calls? (`Yes/No`)
|
||||||
|
|
@ -51,20 +49,49 @@ If any command is intentionally skipped, explain why.
|
||||||
- File system access scope changed? (`Yes/No`)
|
- File system access scope changed? (`Yes/No`)
|
||||||
- If any `Yes`, describe risk and mitigation:
|
- If any `Yes`, describe risk and mitigation:
|
||||||
|
|
||||||
|
## Privacy and Data Hygiene (required)
|
||||||
|
|
||||||
|
- Data-hygiene status (`pass|needs-follow-up`):
|
||||||
|
- Redaction/anonymization notes:
|
||||||
|
- Neutral wording confirmation (use ZeroClaw/project-native labels if identity-like wording is needed):
|
||||||
|
|
||||||
|
## Compatibility / Migration
|
||||||
|
|
||||||
|
- Backward compatible? (`Yes/No`)
|
||||||
|
- Config/env changes? (`Yes/No`)
|
||||||
|
- Migration needed? (`Yes/No`)
|
||||||
|
- If yes, exact upgrade steps:
|
||||||
|
|
||||||
|
## Human Verification (required)
|
||||||
|
|
||||||
|
What was personally validated beyond CI:
|
||||||
|
|
||||||
|
- Verified scenarios:
|
||||||
|
- Edge cases checked:
|
||||||
|
- What was not verified:
|
||||||
|
|
||||||
|
## Side Effects / Blast Radius (required)
|
||||||
|
|
||||||
|
- Affected subsystems/workflows:
|
||||||
|
- Potential unintended effects:
|
||||||
|
- Guardrails/monitoring for early detection:
|
||||||
|
|
||||||
## Agent Collaboration Notes (recommended)
|
## Agent Collaboration Notes (recommended)
|
||||||
|
|
||||||
- [ ] If agent/automation tools were used, I added brief workflow notes.
|
- Agent tools used (if any):
|
||||||
- [ ] I included concrete validation evidence for this change.
|
- Workflow/plan summary (if any):
|
||||||
- [ ] I can explain design choices and rollback steps.
|
|
||||||
|
|
||||||
If agent tools were used, optional context:
|
|
||||||
|
|
||||||
- Tool(s):
|
|
||||||
- Prompt/plan summary:
|
|
||||||
- Verification focus:
|
- Verification focus:
|
||||||
|
- Confirmation: naming + architecture boundaries followed (`AGENTS.md` + `CONTRIBUTING.md`):
|
||||||
|
|
||||||
## Rollback Plan
|
## Rollback Plan (required)
|
||||||
|
|
||||||
- Fast rollback command/path:
|
- Fast rollback command/path:
|
||||||
- Feature flags or config toggles (if any):
|
- Feature flags or config toggles (if any):
|
||||||
- Observable failure symptoms:
|
- Observable failure symptoms:
|
||||||
|
|
||||||
|
## Risks and Mitigations
|
||||||
|
|
||||||
|
List real risks in this PR (or write `None`).
|
||||||
|
|
||||||
|
- Risk:
|
||||||
|
- Mitigation:
|
||||||
|
|
|
||||||
215
.github/workflows/auto-response.yml
vendored
215
.github/workflows/auto-response.yml
vendored
|
|
@ -2,14 +2,123 @@ name: Auto Response
|
||||||
|
|
||||||
on:
|
on:
|
||||||
issues:
|
issues:
|
||||||
types: [opened]
|
types: [opened, reopened, labeled, unlabeled]
|
||||||
pull_request_target:
|
pull_request_target:
|
||||||
types: [opened]
|
types: [opened, labeled, unlabeled]
|
||||||
|
|
||||||
permissions: {}
|
permissions: {}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
contributor-tier-issues:
|
||||||
|
if: >-
|
||||||
|
(github.event_name == 'issues' &&
|
||||||
|
(github.event.action == 'opened' || github.event.action == 'reopened' || github.event.action == 'labeled' || github.event.action == 'unlabeled')) ||
|
||||||
|
(github.event_name == 'pull_request_target' &&
|
||||||
|
(github.event.action == 'labeled' || github.event.action == 'unlabeled'))
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
steps:
|
||||||
|
- name: Apply contributor tier label for issue author
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const owner = context.repo.owner;
|
||||||
|
const repo = context.repo.repo;
|
||||||
|
const issue = context.payload.issue;
|
||||||
|
const pullRequest = context.payload.pull_request;
|
||||||
|
const target = issue ?? pullRequest;
|
||||||
|
const legacyTrustedContributorLabel = "trusted contributor";
|
||||||
|
const contributorTierRules = [
|
||||||
|
{ label: "distinguished contributor", minMergedPRs: 50 },
|
||||||
|
{ label: "principal contributor", minMergedPRs: 20 },
|
||||||
|
{ label: "experienced contributor", minMergedPRs: 10 },
|
||||||
|
];
|
||||||
|
const contributorTierLabels = contributorTierRules.map((rule) => rule.label);
|
||||||
|
const contributorTierColor = "39FF14";
|
||||||
|
const managedContributorLabels = new Set([
|
||||||
|
legacyTrustedContributorLabel,
|
||||||
|
...contributorTierLabels,
|
||||||
|
]);
|
||||||
|
const action = context.payload.action;
|
||||||
|
const changedLabel = context.payload.label?.name;
|
||||||
|
|
||||||
|
if (!target) return;
|
||||||
|
if ((action === "labeled" || action === "unlabeled") && !managedContributorLabels.has(changedLabel)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const author = target.user;
|
||||||
|
if (!author || author.type === "Bot") return;
|
||||||
|
|
||||||
|
async function ensureContributorTierLabels() {
|
||||||
|
for (const label of contributorTierLabels) {
|
||||||
|
try {
|
||||||
|
const { data: existing } = await github.rest.issues.getLabel({ owner, repo, name: label });
|
||||||
|
const currentColor = (existing.color || "").toUpperCase();
|
||||||
|
if (currentColor !== contributorTierColor) {
|
||||||
|
await github.rest.issues.updateLabel({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
name: label,
|
||||||
|
new_name: label,
|
||||||
|
color: contributorTierColor,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error.status !== 404) throw error;
|
||||||
|
await github.rest.issues.createLabel({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
name: label,
|
||||||
|
color: contributorTierColor,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectContributorTier(mergedCount) {
|
||||||
|
const matchedTier = contributorTierRules.find((rule) => mergedCount >= rule.minMergedPRs);
|
||||||
|
return matchedTier ? matchedTier.label : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let contributorTierLabel = null;
|
||||||
|
try {
|
||||||
|
const { data: mergedSearch } = await github.rest.search.issuesAndPullRequests({
|
||||||
|
q: `repo:${owner}/${repo} is:pr is:merged author:${author.login}`,
|
||||||
|
per_page: 1,
|
||||||
|
});
|
||||||
|
const mergedCount = mergedSearch.total_count || 0;
|
||||||
|
contributorTierLabel = selectContributorTier(mergedCount);
|
||||||
|
} catch (error) {
|
||||||
|
core.warning(`failed to evaluate contributor tier status: ${error.message}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ensureContributorTierLabels();
|
||||||
|
|
||||||
|
const { data: currentLabels } = await github.rest.issues.listLabelsOnIssue({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: target.number,
|
||||||
|
});
|
||||||
|
const keepLabels = currentLabels
|
||||||
|
.map((label) => label.name)
|
||||||
|
.filter((label) => label !== legacyTrustedContributorLabel && !contributorTierLabels.includes(label));
|
||||||
|
|
||||||
|
if (contributorTierLabel) {
|
||||||
|
keepLabels.push(contributorTierLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
await github.rest.issues.setLabels({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: target.number,
|
||||||
|
labels: [...new Set(keepLabels)],
|
||||||
|
});
|
||||||
|
|
||||||
first-interaction:
|
first-interaction:
|
||||||
|
if: github.event.action == 'opened'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
issues: write
|
issues: write
|
||||||
|
|
@ -38,3 +147,105 @@ jobs:
|
||||||
- Scope is focused (prefer one concern per PR)
|
- Scope is focused (prefer one concern per PR)
|
||||||
|
|
||||||
See `CONTRIBUTING.md` and `docs/pr-workflow.md` for full collaboration rules.
|
See `CONTRIBUTING.md` and `docs/pr-workflow.md` for full collaboration rules.
|
||||||
|
|
||||||
|
labeled-routes:
|
||||||
|
if: github.event.action == 'labeled'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
steps:
|
||||||
|
- name: Handle label-driven responses
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const label = context.payload.label?.name;
|
||||||
|
if (!label) return;
|
||||||
|
|
||||||
|
const issue = context.payload.issue;
|
||||||
|
const pullRequest = context.payload.pull_request;
|
||||||
|
const target = issue ?? pullRequest;
|
||||||
|
if (!target) return;
|
||||||
|
|
||||||
|
const isIssue = Boolean(issue);
|
||||||
|
const issueNumber = target.number;
|
||||||
|
const owner = context.repo.owner;
|
||||||
|
const repo = context.repo.repo;
|
||||||
|
|
||||||
|
const rules = [
|
||||||
|
{
|
||||||
|
label: "r:support",
|
||||||
|
close: true,
|
||||||
|
closeIssuesOnly: true,
|
||||||
|
closeReason: "not_planned",
|
||||||
|
message:
|
||||||
|
"This looks like a usage/support request. Please use README + docs first, then open a focused bug with repro details if behavior is incorrect.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "r:needs-repro",
|
||||||
|
close: false,
|
||||||
|
message:
|
||||||
|
"Thanks for the report. Please add deterministic repro steps, exact environment, and redacted logs so maintainers can triage quickly.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "invalid",
|
||||||
|
close: true,
|
||||||
|
closeIssuesOnly: true,
|
||||||
|
closeReason: "not_planned",
|
||||||
|
message:
|
||||||
|
"Closing as invalid based on current information. If this is still relevant, open a new issue with updated evidence and reproducible steps.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "duplicate",
|
||||||
|
close: true,
|
||||||
|
closeIssuesOnly: true,
|
||||||
|
closeReason: "not_planned",
|
||||||
|
message:
|
||||||
|
"Closing as duplicate. Please continue discussion in the canonical linked issue/PR.",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const rule = rules.find((entry) => entry.label === label);
|
||||||
|
if (!rule) return;
|
||||||
|
|
||||||
|
const marker = `<!-- auto-response:${rule.label} -->`;
|
||||||
|
const comments = await github.paginate(github.rest.issues.listComments, {
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: issueNumber,
|
||||||
|
per_page: 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
const alreadyCommented = comments.some((comment) =>
|
||||||
|
(comment.body || "").includes(marker)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!alreadyCommented) {
|
||||||
|
await github.rest.issues.createComment({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: issueNumber,
|
||||||
|
body: `${rule.message}\n\n${marker}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rule.close) return;
|
||||||
|
if (rule.closeIssuesOnly && !isIssue) return;
|
||||||
|
if (target.state === "closed") return;
|
||||||
|
|
||||||
|
if (isIssue) {
|
||||||
|
await github.rest.issues.update({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: issueNumber,
|
||||||
|
state: "closed",
|
||||||
|
state_reason: rule.closeReason || "not_planned",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await github.rest.issues.update({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: issueNumber,
|
||||||
|
state: "closed",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
||||||
124
.github/workflows/ci.yml
vendored
124
.github/workflows/ci.yml
vendored
|
|
@ -2,7 +2,7 @@ name: CI
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main, develop]
|
branches: [main]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
|
|
||||||
|
|
@ -22,6 +22,9 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
outputs:
|
||||||
docs_only: ${{ steps.scope.outputs.docs_only }}
|
docs_only: ${{ steps.scope.outputs.docs_only }}
|
||||||
|
docs_changed: ${{ steps.scope.outputs.docs_changed }}
|
||||||
|
rust_changed: ${{ steps.scope.outputs.rust_changed }}
|
||||||
|
docs_files: ${{ steps.scope.outputs.docs_files }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
|
|
@ -33,6 +36,13 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
write_empty_docs_files() {
|
||||||
|
{
|
||||||
|
echo "docs_files<<EOF"
|
||||||
|
echo "EOF"
|
||||||
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
}
|
||||||
|
|
||||||
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
||||||
BASE="${{ github.event.pull_request.base.sha }}"
|
BASE="${{ github.event.pull_request.base.sha }}"
|
||||||
else
|
else
|
||||||
|
|
@ -40,17 +50,30 @@ jobs:
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "$BASE" ] || ! git cat-file -e "$BASE^{commit}" 2>/dev/null; then
|
if [ -z "$BASE" ] || ! git cat-file -e "$BASE^{commit}" 2>/dev/null; then
|
||||||
echo "docs_only=false" >> "$GITHUB_OUTPUT"
|
{
|
||||||
|
echo "docs_only=false"
|
||||||
|
echo "docs_changed=false"
|
||||||
|
echo "rust_changed=true"
|
||||||
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
write_empty_docs_files
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
CHANGED="$(git diff --name-only "$BASE" HEAD || true)"
|
CHANGED="$(git diff --name-only "$BASE" HEAD || true)"
|
||||||
if [ -z "$CHANGED" ]; then
|
if [ -z "$CHANGED" ]; then
|
||||||
echo "docs_only=false" >> "$GITHUB_OUTPUT"
|
{
|
||||||
|
echo "docs_only=false"
|
||||||
|
echo "docs_changed=false"
|
||||||
|
echo "rust_changed=false"
|
||||||
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
write_empty_docs_files
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
docs_only=true
|
docs_only=true
|
||||||
|
docs_changed=false
|
||||||
|
rust_changed=false
|
||||||
|
docs_files=()
|
||||||
while IFS= read -r file; do
|
while IFS= read -r file; do
|
||||||
[ -z "$file" ] && continue
|
[ -z "$file" ] && continue
|
||||||
|
|
||||||
|
|
@ -58,21 +81,43 @@ jobs:
|
||||||
|| [[ "$file" == *.md ]] \
|
|| [[ "$file" == *.md ]] \
|
||||||
|| [[ "$file" == *.mdx ]] \
|
|| [[ "$file" == *.mdx ]] \
|
||||||
|| [[ "$file" == "LICENSE" ]] \
|
|| [[ "$file" == "LICENSE" ]] \
|
||||||
|
|| [[ "$file" == ".markdownlint-cli2.yaml" ]] \
|
||||||
|| [[ "$file" == .github/ISSUE_TEMPLATE/* ]] \
|
|| [[ "$file" == .github/ISSUE_TEMPLATE/* ]] \
|
||||||
|| [[ "$file" == .github/pull_request_template.md ]]; then
|
|| [[ "$file" == .github/pull_request_template.md ]]; then
|
||||||
|
if [[ "$file" == *.md ]] \
|
||||||
|
|| [[ "$file" == *.mdx ]] \
|
||||||
|
|| [[ "$file" == "LICENSE" ]] \
|
||||||
|
|| [[ "$file" == .github/pull_request_template.md ]]; then
|
||||||
|
docs_changed=true
|
||||||
|
docs_files+=("$file")
|
||||||
|
fi
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
docs_only=false
|
docs_only=false
|
||||||
break
|
|
||||||
|
if [[ "$file" == src/* ]] \
|
||||||
|
|| [[ "$file" == tests/* ]] \
|
||||||
|
|| [[ "$file" == "Cargo.toml" ]] \
|
||||||
|
|| [[ "$file" == "Cargo.lock" ]] \
|
||||||
|
|| [[ "$file" == "deny.toml" ]]; then
|
||||||
|
rust_changed=true
|
||||||
|
fi
|
||||||
done <<< "$CHANGED"
|
done <<< "$CHANGED"
|
||||||
|
|
||||||
echo "docs_only=$docs_only" >> "$GITHUB_OUTPUT"
|
{
|
||||||
|
echo "docs_only=$docs_only"
|
||||||
|
echo "docs_changed=$docs_changed"
|
||||||
|
echo "rust_changed=$rust_changed"
|
||||||
|
echo "docs_files<<EOF"
|
||||||
|
printf '%s\n' "${docs_files[@]}"
|
||||||
|
echo "EOF"
|
||||||
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
name: Format & Lint
|
name: Format & Lint
|
||||||
needs: [changes]
|
needs: [changes]
|
||||||
if: needs.changes.outputs.docs_only != 'true'
|
if: needs.changes.outputs.rust_changed == 'true'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
steps:
|
steps:
|
||||||
|
|
@ -92,7 +137,7 @@ jobs:
|
||||||
test:
|
test:
|
||||||
name: Test
|
name: Test
|
||||||
needs: [changes]
|
needs: [changes]
|
||||||
if: needs.changes.outputs.docs_only != 'true'
|
if: needs.changes.outputs.rust_changed == 'true'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
steps:
|
steps:
|
||||||
|
|
@ -107,7 +152,7 @@ jobs:
|
||||||
build:
|
build:
|
||||||
name: Build (Smoke)
|
name: Build (Smoke)
|
||||||
needs: [changes]
|
needs: [changes]
|
||||||
if: needs.changes.outputs.docs_only != 'true'
|
if: needs.changes.outputs.rust_changed == 'true'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
|
|
||||||
|
|
@ -129,10 +174,45 @@ jobs:
|
||||||
- name: Skip heavy jobs for docs-only change
|
- name: Skip heavy jobs for docs-only change
|
||||||
run: echo "Docs-only change detected. Rust lint/test/build skipped."
|
run: echo "Docs-only change detected. Rust lint/test/build skipped."
|
||||||
|
|
||||||
|
non-rust:
|
||||||
|
name: Non-Rust Fast Path
|
||||||
|
needs: [changes]
|
||||||
|
if: needs.changes.outputs.docs_only != 'true' && needs.changes.outputs.rust_changed != 'true'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Skip Rust jobs for non-Rust change scope
|
||||||
|
run: echo "No Rust-impacting files changed. Rust lint/test/build skipped."
|
||||||
|
|
||||||
|
docs-quality:
|
||||||
|
name: Docs Quality
|
||||||
|
needs: [changes]
|
||||||
|
if: needs.changes.outputs.docs_changed == 'true'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Markdown lint
|
||||||
|
uses: DavidAnson/markdownlint-cli2-action@v20
|
||||||
|
with:
|
||||||
|
globs: ${{ needs.changes.outputs.docs_files }}
|
||||||
|
|
||||||
|
- name: Link check (offline)
|
||||||
|
uses: lycheeverse/lychee-action@v2
|
||||||
|
with:
|
||||||
|
fail: true
|
||||||
|
args: >-
|
||||||
|
--offline
|
||||||
|
--no-progress
|
||||||
|
--format detailed
|
||||||
|
${{ needs.changes.outputs.docs_files }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
ci-required:
|
ci-required:
|
||||||
name: CI Required Gate
|
name: CI Required Gate
|
||||||
if: always()
|
if: always()
|
||||||
needs: [changes, lint, test, build, docs-only]
|
needs: [changes, lint, test, build, docs-only, non-rust, docs-quality]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Enforce required status
|
- name: Enforce required status
|
||||||
|
|
@ -140,11 +220,31 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
docs_changed="${{ needs.changes.outputs.docs_changed }}"
|
||||||
|
rust_changed="${{ needs.changes.outputs.rust_changed }}"
|
||||||
|
docs_result="${{ needs.docs-quality.result }}"
|
||||||
|
|
||||||
if [ "${{ needs.changes.outputs.docs_only }}" = "true" ]; then
|
if [ "${{ needs.changes.outputs.docs_only }}" = "true" ]; then
|
||||||
|
echo "docs=${docs_result}"
|
||||||
|
if [ "$docs_changed" = "true" ] && [ "$docs_result" != "success" ]; then
|
||||||
|
echo "Docs-only change touched markdown docs, but docs-quality did not pass."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
echo "Docs-only fast path passed."
|
echo "Docs-only fast path passed."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ "$rust_changed" != "true" ]; then
|
||||||
|
echo "rust_changed=false (non-rust fast path)"
|
||||||
|
echo "docs=${docs_result}"
|
||||||
|
if [ "$docs_changed" = "true" ] && [ "$docs_result" != "success" ]; then
|
||||||
|
echo "Docs changed but docs-quality did not pass."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Non-rust fast path passed."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
lint_result="${{ needs.lint.result }}"
|
lint_result="${{ needs.lint.result }}"
|
||||||
test_result="${{ needs.test.result }}"
|
test_result="${{ needs.test.result }}"
|
||||||
build_result="${{ needs.build.result }}"
|
build_result="${{ needs.build.result }}"
|
||||||
|
|
@ -152,10 +252,16 @@ jobs:
|
||||||
echo "lint=${lint_result}"
|
echo "lint=${lint_result}"
|
||||||
echo "test=${test_result}"
|
echo "test=${test_result}"
|
||||||
echo "build=${build_result}"
|
echo "build=${build_result}"
|
||||||
|
echo "docs=${docs_result}"
|
||||||
|
|
||||||
if [ "$lint_result" != "success" ] || [ "$test_result" != "success" ] || [ "$build_result" != "success" ]; then
|
if [ "$lint_result" != "success" ] || [ "$test_result" != "success" ] || [ "$build_result" != "success" ]; then
|
||||||
echo "Required CI jobs did not pass."
|
echo "Required CI jobs did not pass."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ "$docs_changed" = "true" ] && [ "$docs_result" != "success" ]; then
|
||||||
|
echo "Docs changed but docs-quality did not pass."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
echo "All required CI jobs passed."
|
echo "All required CI jobs passed."
|
||||||
|
|
|
||||||
457
.github/workflows/labeler.yml
vendored
457
.github/workflows/labeler.yml
vendored
|
|
@ -2,7 +2,7 @@ name: PR Labeler
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request_target:
|
pull_request_target:
|
||||||
types: [opened, reopened, synchronize, edited]
|
types: [opened, reopened, synchronize, edited, labeled, unlabeled]
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
@ -17,15 +17,373 @@ jobs:
|
||||||
uses: actions/labeler@v5
|
uses: actions/labeler@v5
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
sync-labels: true
|
||||||
|
|
||||||
- name: Apply size label
|
- name: Apply size/risk/module labels
|
||||||
uses: actions/github-script@v7
|
uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const pr = context.payload.pull_request;
|
const pr = context.payload.pull_request;
|
||||||
|
const owner = context.repo.owner;
|
||||||
|
const repo = context.repo.repo;
|
||||||
|
const action = context.payload.action;
|
||||||
|
const changedLabel = context.payload.label?.name;
|
||||||
|
|
||||||
const sizeLabels = ["size: XS", "size: S", "size: M", "size: L", "size: XL"];
|
const sizeLabels = ["size: XS", "size: S", "size: M", "size: L", "size: XL"];
|
||||||
const labelColor = "BFDADC";
|
const computedRiskLabels = ["risk: low", "risk: medium", "risk: high"];
|
||||||
const changedLines = (pr.additions || 0) + (pr.deletions || 0);
|
const manualRiskOverrideLabel = "risk: manual";
|
||||||
|
const managedEnforcedLabels = new Set([
|
||||||
|
...sizeLabels,
|
||||||
|
manualRiskOverrideLabel,
|
||||||
|
...computedRiskLabels,
|
||||||
|
]);
|
||||||
|
const legacyTrustedContributorLabel = "trusted contributor";
|
||||||
|
|
||||||
|
if ((action === "labeled" || action === "unlabeled") && !managedEnforcedLabels.has(changedLabel)) {
|
||||||
|
core.info(`skip non-size/risk label event: ${changedLabel || "unknown"}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const contributorTierRules = [
|
||||||
|
{ label: "distinguished contributor", minMergedPRs: 50 },
|
||||||
|
{ label: "principal contributor", minMergedPRs: 20 },
|
||||||
|
{ label: "experienced contributor", minMergedPRs: 10 },
|
||||||
|
];
|
||||||
|
const contributorTierLabels = contributorTierRules.map((rule) => rule.label);
|
||||||
|
const contributorTierColor = "39FF14";
|
||||||
|
|
||||||
|
const managedPathLabels = [
|
||||||
|
"docs",
|
||||||
|
"dependencies",
|
||||||
|
"ci",
|
||||||
|
"core",
|
||||||
|
"agent",
|
||||||
|
"channel",
|
||||||
|
"config",
|
||||||
|
"cron",
|
||||||
|
"daemon",
|
||||||
|
"doctor",
|
||||||
|
"gateway",
|
||||||
|
"health",
|
||||||
|
"heartbeat",
|
||||||
|
"integration",
|
||||||
|
"memory",
|
||||||
|
"observability",
|
||||||
|
"onboard",
|
||||||
|
"provider",
|
||||||
|
"runtime",
|
||||||
|
"security",
|
||||||
|
"service",
|
||||||
|
"skillforge",
|
||||||
|
"skills",
|
||||||
|
"tool",
|
||||||
|
"tunnel",
|
||||||
|
"tests",
|
||||||
|
"scripts",
|
||||||
|
"dev",
|
||||||
|
];
|
||||||
|
|
||||||
|
const moduleNamespaceRules = [
|
||||||
|
{ root: "src/agent/", prefix: "agent", coreEntries: new Set(["mod.rs"]) },
|
||||||
|
{ root: "src/channels/", prefix: "channel", coreEntries: new Set(["mod.rs", "traits.rs"]) },
|
||||||
|
{ root: "src/config/", prefix: "config", coreEntries: new Set(["mod.rs", "schema.rs"]) },
|
||||||
|
{ root: "src/cron/", prefix: "cron", coreEntries: new Set(["mod.rs"]) },
|
||||||
|
{ root: "src/daemon/", prefix: "daemon", coreEntries: new Set(["mod.rs"]) },
|
||||||
|
{ root: "src/doctor/", prefix: "doctor", coreEntries: new Set(["mod.rs"]) },
|
||||||
|
{ root: "src/gateway/", prefix: "gateway", coreEntries: new Set(["mod.rs"]) },
|
||||||
|
{ root: "src/health/", prefix: "health", coreEntries: new Set(["mod.rs"]) },
|
||||||
|
{ root: "src/heartbeat/", prefix: "heartbeat", coreEntries: new Set(["mod.rs"]) },
|
||||||
|
{ root: "src/integrations/", prefix: "integration", coreEntries: new Set(["mod.rs", "registry.rs"]) },
|
||||||
|
{ root: "src/memory/", prefix: "memory", coreEntries: new Set(["mod.rs", "traits.rs"]) },
|
||||||
|
{ root: "src/observability/", prefix: "observability", coreEntries: new Set(["mod.rs", "traits.rs"]) },
|
||||||
|
{ root: "src/onboard/", prefix: "onboard", coreEntries: new Set(["mod.rs"]) },
|
||||||
|
{ root: "src/providers/", prefix: "provider", coreEntries: new Set(["mod.rs", "traits.rs"]) },
|
||||||
|
{ root: "src/runtime/", prefix: "runtime", coreEntries: new Set(["mod.rs", "traits.rs"]) },
|
||||||
|
{ root: "src/security/", prefix: "security", coreEntries: new Set(["mod.rs"]) },
|
||||||
|
{ root: "src/service/", prefix: "service", coreEntries: new Set(["mod.rs"]) },
|
||||||
|
{ root: "src/skillforge/", prefix: "skillforge", coreEntries: new Set(["mod.rs"]) },
|
||||||
|
{ root: "src/skills/", prefix: "skills", coreEntries: new Set(["mod.rs"]) },
|
||||||
|
{ root: "src/tools/", prefix: "tool", coreEntries: new Set(["mod.rs", "traits.rs"]) },
|
||||||
|
{ root: "src/tunnel/", prefix: "tunnel", coreEntries: new Set(["mod.rs"]) },
|
||||||
|
];
|
||||||
|
const managedModulePrefixes = [...new Set(moduleNamespaceRules.map((rule) => `${rule.prefix}:`))];
|
||||||
|
|
||||||
|
const staticLabelColors = {
|
||||||
|
"size: XS": "BFDADC",
|
||||||
|
"size: S": "BFDADC",
|
||||||
|
"size: M": "BFDADC",
|
||||||
|
"size: L": "BFDADC",
|
||||||
|
"size: XL": "BFDADC",
|
||||||
|
"risk: low": "2EA043",
|
||||||
|
"risk: medium": "FBCA04",
|
||||||
|
"risk: high": "D73A49",
|
||||||
|
"risk: manual": "1F6FEB",
|
||||||
|
docs: "1D76DB",
|
||||||
|
dependencies: "C26F00",
|
||||||
|
ci: "8250DF",
|
||||||
|
core: "24292F",
|
||||||
|
agent: "2EA043",
|
||||||
|
channel: "1D76DB",
|
||||||
|
config: "0969DA",
|
||||||
|
cron: "9A6700",
|
||||||
|
daemon: "57606A",
|
||||||
|
doctor: "0E8A8A",
|
||||||
|
gateway: "D73A49",
|
||||||
|
health: "0E8A8A",
|
||||||
|
heartbeat: "0E8A8A",
|
||||||
|
integration: "8250DF",
|
||||||
|
memory: "1F883D",
|
||||||
|
observability: "6E7781",
|
||||||
|
onboard: "B62DBA",
|
||||||
|
provider: "5319E7",
|
||||||
|
runtime: "C26F00",
|
||||||
|
security: "B60205",
|
||||||
|
service: "0052CC",
|
||||||
|
skillforge: "A371F7",
|
||||||
|
skills: "6F42C1",
|
||||||
|
tool: "D73A49",
|
||||||
|
tunnel: "0052CC",
|
||||||
|
tests: "0E8A16",
|
||||||
|
scripts: "B08800",
|
||||||
|
dev: "6E7781",
|
||||||
|
};
|
||||||
|
for (const label of contributorTierLabels) {
|
||||||
|
staticLabelColors[label] = contributorTierColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
const modulePrefixColors = {
|
||||||
|
"agent:": "2EA043",
|
||||||
|
"channel:": "1D76DB",
|
||||||
|
"config:": "0969DA",
|
||||||
|
"cron:": "9A6700",
|
||||||
|
"daemon:": "57606A",
|
||||||
|
"doctor:": "0E8A8A",
|
||||||
|
"gateway:": "D73A49",
|
||||||
|
"health:": "0E8A8A",
|
||||||
|
"heartbeat:": "0E8A8A",
|
||||||
|
"integration:": "8250DF",
|
||||||
|
"memory:": "1F883D",
|
||||||
|
"observability:": "6E7781",
|
||||||
|
"onboard:": "B62DBA",
|
||||||
|
"provider:": "5319E7",
|
||||||
|
"runtime:": "C26F00",
|
||||||
|
"security:": "B60205",
|
||||||
|
"service:": "0052CC",
|
||||||
|
"skillforge:": "A371F7",
|
||||||
|
"skills:": "6F42C1",
|
||||||
|
"tool:": "D73A49",
|
||||||
|
"tunnel:": "0052CC",
|
||||||
|
};
|
||||||
|
|
||||||
|
const providerKeywordHints = [
|
||||||
|
"deepseek",
|
||||||
|
"moonshot",
|
||||||
|
"kimi",
|
||||||
|
"qwen",
|
||||||
|
"mistral",
|
||||||
|
"doubao",
|
||||||
|
"baichuan",
|
||||||
|
"yi",
|
||||||
|
"siliconflow",
|
||||||
|
"vertex",
|
||||||
|
"azure",
|
||||||
|
"perplexity",
|
||||||
|
"venice",
|
||||||
|
"vercel",
|
||||||
|
"cloudflare",
|
||||||
|
"synthetic",
|
||||||
|
"opencode",
|
||||||
|
"zai",
|
||||||
|
"glm",
|
||||||
|
"minimax",
|
||||||
|
"bedrock",
|
||||||
|
"qianfan",
|
||||||
|
"groq",
|
||||||
|
"together",
|
||||||
|
"fireworks",
|
||||||
|
"cohere",
|
||||||
|
"openai",
|
||||||
|
"openrouter",
|
||||||
|
"anthropic",
|
||||||
|
"gemini",
|
||||||
|
"ollama",
|
||||||
|
];
|
||||||
|
|
||||||
|
const channelKeywordHints = [
|
||||||
|
"telegram",
|
||||||
|
"discord",
|
||||||
|
"slack",
|
||||||
|
"whatsapp",
|
||||||
|
"matrix",
|
||||||
|
"irc",
|
||||||
|
"imessage",
|
||||||
|
"email",
|
||||||
|
"cli",
|
||||||
|
];
|
||||||
|
|
||||||
|
function isDocsLike(path) {
|
||||||
|
return (
|
||||||
|
path.startsWith("docs/") ||
|
||||||
|
path.endsWith(".md") ||
|
||||||
|
path.endsWith(".mdx") ||
|
||||||
|
path === "LICENSE" ||
|
||||||
|
path === ".markdownlint-cli2.yaml" ||
|
||||||
|
path === ".github/pull_request_template.md" ||
|
||||||
|
path.startsWith(".github/ISSUE_TEMPLATE/")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeLabelSegment(segment) {
|
||||||
|
return (segment || "")
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/\.rs$/g, "")
|
||||||
|
.replace(/[^a-z0-9_-]+/g, "-")
|
||||||
|
.replace(/^-+|-+$/g, "")
|
||||||
|
.slice(0, 40);
|
||||||
|
}
|
||||||
|
|
||||||
|
function containsKeyword(text, keyword) {
|
||||||
|
const escaped = keyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||||
|
const pattern = new RegExp(`(^|[^a-z0-9_])${escaped}([^a-z0-9_]|$)`, "i");
|
||||||
|
return pattern.test(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
function colorForLabel(label) {
|
||||||
|
if (staticLabelColors[label]) return staticLabelColors[label];
|
||||||
|
const matchedPrefix = Object.keys(modulePrefixColors).find((prefix) => label.startsWith(prefix));
|
||||||
|
if (matchedPrefix) return modulePrefixColors[matchedPrefix];
|
||||||
|
return "BFDADC";
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureLabel(name) {
|
||||||
|
const expectedColor = colorForLabel(name);
|
||||||
|
try {
|
||||||
|
const { data: existing } = await github.rest.issues.getLabel({ owner, repo, name });
|
||||||
|
const currentColor = (existing.color || "").toUpperCase();
|
||||||
|
if (currentColor !== expectedColor) {
|
||||||
|
await github.rest.issues.updateLabel({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
name,
|
||||||
|
new_name: name,
|
||||||
|
color: expectedColor,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error.status !== 404) throw error;
|
||||||
|
await github.rest.issues.createLabel({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
name,
|
||||||
|
color: expectedColor,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectContributorTier(mergedCount) {
|
||||||
|
const matchedTier = contributorTierRules.find((rule) => mergedCount >= rule.minMergedPRs);
|
||||||
|
return matchedTier ? matchedTier.label : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = await github.paginate(github.rest.pulls.listFiles, {
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
pull_number: pr.number,
|
||||||
|
per_page: 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
const detectedModuleLabels = new Set();
|
||||||
|
for (const file of files) {
|
||||||
|
const path = (file.filename || "").toLowerCase();
|
||||||
|
for (const rule of moduleNamespaceRules) {
|
||||||
|
if (!path.startsWith(rule.root)) continue;
|
||||||
|
|
||||||
|
const relative = path.slice(rule.root.length);
|
||||||
|
if (!relative) continue;
|
||||||
|
|
||||||
|
const first = relative.split("/")[0];
|
||||||
|
const firstStem = first.endsWith(".rs") ? first.slice(0, -3) : first;
|
||||||
|
let segment = firstStem;
|
||||||
|
|
||||||
|
if (rule.coreEntries.has(first) || rule.coreEntries.has(firstStem)) {
|
||||||
|
segment = "core";
|
||||||
|
}
|
||||||
|
|
||||||
|
segment = normalizeLabelSegment(segment);
|
||||||
|
if (!segment) continue;
|
||||||
|
|
||||||
|
detectedModuleLabels.add(`${rule.prefix}:${segment}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const providerRelevantFiles = files.filter((file) => {
|
||||||
|
const path = file.filename || "";
|
||||||
|
return (
|
||||||
|
path.startsWith("src/providers/") ||
|
||||||
|
path.startsWith("src/integrations/") ||
|
||||||
|
path.startsWith("src/onboard/") ||
|
||||||
|
path.startsWith("src/config/")
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (providerRelevantFiles.length > 0) {
|
||||||
|
const searchableText = [
|
||||||
|
pr.title || "",
|
||||||
|
pr.body || "",
|
||||||
|
...providerRelevantFiles.map((file) => file.filename || ""),
|
||||||
|
...providerRelevantFiles.map((file) => file.patch || ""),
|
||||||
|
]
|
||||||
|
.join("\n")
|
||||||
|
.toLowerCase();
|
||||||
|
|
||||||
|
for (const keyword of providerKeywordHints) {
|
||||||
|
if (containsKeyword(searchableText, keyword)) {
|
||||||
|
detectedModuleLabels.add(`provider:${keyword}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const channelRelevantFiles = files.filter((file) => {
|
||||||
|
const path = file.filename || "";
|
||||||
|
return (
|
||||||
|
path.startsWith("src/channels/") ||
|
||||||
|
path.startsWith("src/onboard/") ||
|
||||||
|
path.startsWith("src/config/")
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (channelRelevantFiles.length > 0) {
|
||||||
|
const searchableText = [
|
||||||
|
pr.title || "",
|
||||||
|
pr.body || "",
|
||||||
|
...channelRelevantFiles.map((file) => file.filename || ""),
|
||||||
|
...channelRelevantFiles.map((file) => file.patch || ""),
|
||||||
|
]
|
||||||
|
.join("\n")
|
||||||
|
.toLowerCase();
|
||||||
|
|
||||||
|
for (const keyword of channelKeywordHints) {
|
||||||
|
if (containsKeyword(searchableText, keyword)) {
|
||||||
|
detectedModuleLabels.add(`channel:${keyword}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: currentLabels } = await github.rest.issues.listLabelsOnIssue({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: pr.number,
|
||||||
|
});
|
||||||
|
const currentLabelNames = currentLabels.map((label) => label.name);
|
||||||
|
|
||||||
|
const excludedLockfiles = new Set(["Cargo.lock"]);
|
||||||
|
const changedLines = files.reduce((total, file) => {
|
||||||
|
const path = file.filename || "";
|
||||||
|
if (isDocsLike(path) || excludedLockfiles.has(path)) {
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
return total + (file.additions || 0) + (file.deletions || 0);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
let sizeLabel = "size: XL";
|
let sizeLabel = "size: XL";
|
||||||
if (changedLines <= 80) sizeLabel = "size: XS";
|
if (changedLines <= 80) sizeLabel = "size: XS";
|
||||||
|
|
@ -33,38 +391,85 @@ jobs:
|
||||||
else if (changedLines <= 500) sizeLabel = "size: M";
|
else if (changedLines <= 500) sizeLabel = "size: M";
|
||||||
else if (changedLines <= 1000) sizeLabel = "size: L";
|
else if (changedLines <= 1000) sizeLabel = "size: L";
|
||||||
|
|
||||||
for (const label of sizeLabels) {
|
const hasHighRiskPath = files.some((file) => {
|
||||||
|
const path = file.filename || "";
|
||||||
|
return (
|
||||||
|
path.startsWith("src/security/") ||
|
||||||
|
path.startsWith("src/runtime/") ||
|
||||||
|
path.startsWith("src/gateway/") ||
|
||||||
|
path.startsWith("src/tools/") ||
|
||||||
|
path.startsWith(".github/workflows/")
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasMediumRiskPath = files.some((file) => {
|
||||||
|
const path = file.filename || "";
|
||||||
|
return (
|
||||||
|
path.startsWith("src/") ||
|
||||||
|
path === "Cargo.toml" ||
|
||||||
|
path === "Cargo.lock" ||
|
||||||
|
path === "deny.toml" ||
|
||||||
|
path.startsWith(".githooks/")
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
let riskLabel = "risk: low";
|
||||||
|
if (hasHighRiskPath) {
|
||||||
|
riskLabel = "risk: high";
|
||||||
|
} else if (hasMediumRiskPath) {
|
||||||
|
riskLabel = "risk: medium";
|
||||||
|
}
|
||||||
|
|
||||||
|
const labelsToEnsure = new Set([
|
||||||
|
...sizeLabels,
|
||||||
|
...computedRiskLabels,
|
||||||
|
manualRiskOverrideLabel,
|
||||||
|
...managedPathLabels,
|
||||||
|
...contributorTierLabels,
|
||||||
|
...detectedModuleLabels,
|
||||||
|
]);
|
||||||
|
|
||||||
|
for (const label of labelsToEnsure) {
|
||||||
|
await ensureLabel(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
let contributorTierLabel = null;
|
||||||
|
const authorLogin = pr.user?.login;
|
||||||
|
if (authorLogin && pr.user?.type !== "Bot") {
|
||||||
try {
|
try {
|
||||||
await github.rest.issues.getLabel({
|
const { data: mergedSearch } = await github.rest.search.issuesAndPullRequests({
|
||||||
owner: context.repo.owner,
|
q: `repo:${owner}/${repo} is:pr is:merged author:${authorLogin}`,
|
||||||
repo: context.repo.repo,
|
per_page: 1,
|
||||||
name: label,
|
|
||||||
});
|
});
|
||||||
|
const mergedCount = mergedSearch.total_count || 0;
|
||||||
|
contributorTierLabel = selectContributorTier(mergedCount);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.status !== 404) throw error;
|
core.warning(`failed to compute contributor tier label: ${error.message}`);
|
||||||
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({
|
const hasManualRiskOverride = currentLabelNames.includes(manualRiskOverrideLabel);
|
||||||
owner: context.repo.owner,
|
const keepNonManagedLabels = currentLabelNames.filter((label) => {
|
||||||
repo: context.repo.repo,
|
if (label === manualRiskOverrideLabel) return true;
|
||||||
issue_number: pr.number,
|
if (label === legacyTrustedContributorLabel) return false;
|
||||||
|
if (contributorTierLabels.includes(label)) return false;
|
||||||
|
if (sizeLabels.includes(label) || computedRiskLabels.includes(label)) return false;
|
||||||
|
if (managedModulePrefixes.some((prefix) => label.startsWith(prefix))) return false;
|
||||||
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
const keepLabels = currentLabels
|
const manualRiskSelection =
|
||||||
.map((label) => label.name)
|
currentLabelNames.find((label) => computedRiskLabels.includes(label)) || riskLabel;
|
||||||
.filter((label) => !sizeLabels.includes(label));
|
|
||||||
|
const moduleLabelList = [...detectedModuleLabels];
|
||||||
|
const contributorLabelList = contributorTierLabel ? [contributorTierLabel] : [];
|
||||||
|
const nextLabels = hasManualRiskOverride
|
||||||
|
? [...new Set([...keepNonManagedLabels, ...moduleLabelList, ...contributorLabelList, sizeLabel, manualRiskSelection])]
|
||||||
|
: [...new Set([...keepNonManagedLabels, ...moduleLabelList, ...contributorLabelList, sizeLabel, riskLabel])];
|
||||||
|
|
||||||
const nextLabels = [...new Set([...keepLabels, sizeLabel])];
|
|
||||||
await github.rest.issues.setLabels({
|
await github.rest.issues.setLabels({
|
||||||
owner: context.repo.owner,
|
owner,
|
||||||
repo: context.repo.repo,
|
repo,
|
||||||
issue_number: pr.number,
|
issue_number: pr.number,
|
||||||
labels: nextLabels,
|
labels: nextLabels,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
15
.markdownlint-cli2.yaml
Normal file
15
.markdownlint-cli2.yaml
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
config:
|
||||||
|
default: true
|
||||||
|
MD013: false
|
||||||
|
MD007: false
|
||||||
|
MD031: false
|
||||||
|
MD032: false
|
||||||
|
MD033: false
|
||||||
|
MD040: false
|
||||||
|
MD041: false
|
||||||
|
MD060: false
|
||||||
|
MD024:
|
||||||
|
allow_different_nesting: true
|
||||||
|
|
||||||
|
ignores:
|
||||||
|
- "target/**"
|
||||||
242
AGENTS.md
242
AGENTS.md
|
|
@ -1,4 +1,4 @@
|
||||||
# AGENTS.md — ZeroClaw Agent Coding Guide
|
# AGENTS.md — ZeroClaw Agent Engineering Protocol
|
||||||
|
|
||||||
This file defines the default working protocol for coding agents in this repository.
|
This file defines the default working protocol for coding agents in this repository.
|
||||||
Scope: entire repository.
|
Scope: entire repository.
|
||||||
|
|
@ -25,7 +25,111 @@ Key extension points:
|
||||||
- `src/observability/traits.rs` (`Observer`)
|
- `src/observability/traits.rs` (`Observer`)
|
||||||
- `src/runtime/traits.rs` (`RuntimeAdapter`)
|
- `src/runtime/traits.rs` (`RuntimeAdapter`)
|
||||||
|
|
||||||
## 2) Repository Map (High-Level)
|
## 2) Deep Architecture Observations (Why This Protocol Exists)
|
||||||
|
|
||||||
|
These codebase realities should drive every design decision:
|
||||||
|
|
||||||
|
1. **Trait + factory architecture is the stability backbone**
|
||||||
|
- Extension points are intentionally explicit and swappable.
|
||||||
|
- Most features should be added via trait implementation + factory registration, not cross-cutting rewrites.
|
||||||
|
2. **Security-critical surfaces are first-class and internet-adjacent**
|
||||||
|
- `src/gateway/`, `src/security/`, `src/tools/`, `src/runtime/` carry high blast radius.
|
||||||
|
- Defaults already lean secure-by-default (pairing, bind safety, limits, secret handling); keep it that way.
|
||||||
|
3. **Performance and binary size are product goals, not nice-to-have**
|
||||||
|
- `Cargo.toml` release profile and dependency choices optimize for size and determinism.
|
||||||
|
- Convenience dependencies and broad abstractions can silently regress these goals.
|
||||||
|
4. **Config and runtime contracts are user-facing API**
|
||||||
|
- `src/config/schema.rs` and CLI commands are effectively public interfaces.
|
||||||
|
- Backward compatibility and explicit migration matter.
|
||||||
|
5. **The project now runs in high-concurrency collaboration mode**
|
||||||
|
- CI + docs governance + label routing are part of the product delivery system.
|
||||||
|
- PR throughput is a design constraint; not just a maintainer inconvenience.
|
||||||
|
|
||||||
|
## 3) Engineering Principles (Normative)
|
||||||
|
|
||||||
|
These principles are mandatory by default. They are not slogans; they are implementation constraints.
|
||||||
|
|
||||||
|
### 3.1 KISS (Keep It Simple, Stupid)
|
||||||
|
|
||||||
|
**Why here:** Runtime + security behavior must stay auditable under pressure.
|
||||||
|
|
||||||
|
Required:
|
||||||
|
|
||||||
|
- Prefer straightforward control flow over clever meta-programming.
|
||||||
|
- Prefer explicit match branches and typed structs over hidden dynamic behavior.
|
||||||
|
- Keep error paths obvious and localized.
|
||||||
|
|
||||||
|
### 3.2 YAGNI (You Aren't Gonna Need It)
|
||||||
|
|
||||||
|
**Why here:** Premature features increase attack surface and maintenance burden.
|
||||||
|
|
||||||
|
Required:
|
||||||
|
|
||||||
|
- Do not add new config keys, trait methods, feature flags, or workflow branches without a concrete accepted use case.
|
||||||
|
- Do not introduce speculative “future-proof” abstractions without at least one current caller.
|
||||||
|
- Keep unsupported paths explicit (error out) rather than adding partial fake support.
|
||||||
|
|
||||||
|
### 3.3 DRY + Rule of Three
|
||||||
|
|
||||||
|
**Why here:** Naive DRY can create brittle shared abstractions across providers/channels/tools.
|
||||||
|
|
||||||
|
Required:
|
||||||
|
|
||||||
|
- Duplicate small, local logic when it preserves clarity.
|
||||||
|
- Extract shared utilities only after repeated, stable patterns (rule-of-three).
|
||||||
|
- When extracting, preserve module boundaries and avoid hidden coupling.
|
||||||
|
|
||||||
|
### 3.4 SRP + ISP (Single Responsibility + Interface Segregation)
|
||||||
|
|
||||||
|
**Why here:** Trait-driven architecture already encodes subsystem boundaries.
|
||||||
|
|
||||||
|
Required:
|
||||||
|
|
||||||
|
- Keep each module focused on one concern.
|
||||||
|
- Extend behavior by implementing existing narrow traits whenever possible.
|
||||||
|
- Avoid fat interfaces and “god modules” that mix policy + transport + storage.
|
||||||
|
|
||||||
|
### 3.5 Fail Fast + Explicit Errors
|
||||||
|
|
||||||
|
**Why here:** Silent fallback in agent runtimes can create unsafe or costly behavior.
|
||||||
|
|
||||||
|
Required:
|
||||||
|
|
||||||
|
- Prefer explicit `bail!`/errors for unsupported or unsafe states.
|
||||||
|
- Never silently broaden permissions/capabilities.
|
||||||
|
- Document fallback behavior when fallback is intentional and safe.
|
||||||
|
|
||||||
|
### 3.6 Secure by Default + Least Privilege
|
||||||
|
|
||||||
|
**Why here:** Gateway/tools/runtime can execute actions with real-world side effects.
|
||||||
|
|
||||||
|
Required:
|
||||||
|
|
||||||
|
- Deny-by-default for access and exposure boundaries.
|
||||||
|
- Never log secrets, raw tokens, or sensitive payloads.
|
||||||
|
- Keep network/filesystem/shell scope as narrow as possible unless explicitly justified.
|
||||||
|
|
||||||
|
### 3.7 Determinism + Reproducibility
|
||||||
|
|
||||||
|
**Why here:** Reliable CI and low-latency triage depend on deterministic behavior.
|
||||||
|
|
||||||
|
Required:
|
||||||
|
|
||||||
|
- Prefer reproducible commands and locked dependency behavior in CI-sensitive paths.
|
||||||
|
- Keep tests deterministic (no flaky timing/network dependence without guardrails).
|
||||||
|
- Ensure local validation commands map to CI expectations.
|
||||||
|
|
||||||
|
### 3.8 Reversibility + Rollback-First Thinking
|
||||||
|
|
||||||
|
**Why here:** Fast recovery is mandatory under high PR volume.
|
||||||
|
|
||||||
|
Required:
|
||||||
|
|
||||||
|
- Keep changes easy to revert (small scope, clear blast radius).
|
||||||
|
- For risky changes, define rollback path before merge.
|
||||||
|
- Avoid mixed mega-patches that block safe rollback.
|
||||||
|
|
||||||
|
## 4) Repository Map (High-Level)
|
||||||
|
|
||||||
- `src/main.rs` — CLI entrypoint and command routing
|
- `src/main.rs` — CLI entrypoint and command routing
|
||||||
- `src/lib.rs` — module exports and shared command enums
|
- `src/lib.rs` — module exports and shared command enums
|
||||||
|
|
@ -37,73 +141,93 @@ Key extension points:
|
||||||
- `src/providers/` — model providers and resilient wrapper
|
- `src/providers/` — model providers and resilient wrapper
|
||||||
- `src/channels/` — Telegram/Discord/Slack/etc channels
|
- `src/channels/` — Telegram/Discord/Slack/etc channels
|
||||||
- `src/tools/` — tool execution surface (shell, file, memory, browser)
|
- `src/tools/` — tool execution surface (shell, file, memory, browser)
|
||||||
- `src/runtime/` — runtime adapters (currently native)
|
- `src/runtime/` — runtime adapters (currently native/docker)
|
||||||
- `docs/` — architecture + process docs
|
- `docs/` — architecture + process docs
|
||||||
- `.github/` — CI, templates, automation workflows
|
- `.github/` — CI, templates, automation workflows
|
||||||
|
|
||||||
## 3) Non-Negotiable Engineering Constraints
|
## 5) Risk Tiers by Path (Review Depth Contract)
|
||||||
|
|
||||||
### 3.1 Performance and Footprint
|
Use these tiers when deciding validation depth and review rigor.
|
||||||
|
|
||||||
- Prefer minimal dependencies; avoid adding crates unless clearly justified.
|
- **Low risk**: docs/chore/tests-only changes
|
||||||
- Preserve release-size profile assumptions in `Cargo.toml`.
|
- **Medium risk**: most `src/**` behavior changes without boundary/security impact
|
||||||
- Avoid unnecessary allocations, clones, and blocking operations.
|
- **High risk**: `src/security/**`, `src/runtime/**`, `src/gateway/**`, `src/tools/**`, `.github/workflows/**`, access-control boundaries
|
||||||
- Keep startup path lean; avoid heavy initialization in command parsing flow.
|
|
||||||
|
|
||||||
### 3.2 Security and Safety
|
When uncertain, classify as higher risk.
|
||||||
|
|
||||||
- Treat `src/security/`, `src/gateway/`, `src/tools/` as high-risk surfaces.
|
## 6) Agent Workflow (Required)
|
||||||
- Never broaden filesystem/network execution scope without explicit policy checks.
|
|
||||||
- Never log secrets, tokens, raw credentials, or sensitive payloads.
|
|
||||||
- Keep default behavior secure-by-default (deny-by-default where applicable).
|
|
||||||
|
|
||||||
### 3.3 Stability and Compatibility
|
|
||||||
|
|
||||||
- Preserve CLI contract unless change is intentional and documented.
|
|
||||||
- Prefer explicit errors over silent fallback for unsupported critical paths.
|
|
||||||
- Keep changes local; avoid cross-module refactors in unrelated tasks.
|
|
||||||
|
|
||||||
## 4) Agent Workflow (Required)
|
|
||||||
|
|
||||||
1. **Read before write**
|
1. **Read before write**
|
||||||
- Inspect existing module and adjacent tests before editing.
|
- Inspect existing module, factory wiring, and adjacent tests before editing.
|
||||||
2. **Define scope boundary**
|
2. **Define scope boundary**
|
||||||
- One concern per PR; avoid mixed feature+refactor+infra patches.
|
- One concern per PR; avoid mixed feature+refactor+infra patches.
|
||||||
3. **Implement minimal patch**
|
3. **Implement minimal patch**
|
||||||
- Follow KISS/YAGNI/DRY; no speculative abstractions.
|
- Apply KISS/YAGNI/DRY rule-of-three explicitly.
|
||||||
4. **Validate by risk**
|
4. **Validate by risk tier**
|
||||||
- Docs-only: keep checks lightweight.
|
- Docs-only: lightweight checks.
|
||||||
- Code changes: run relevant checks and tests.
|
- Code/risky changes: full relevant checks and focused scenarios.
|
||||||
5. **Document impact**
|
5. **Document impact**
|
||||||
- Update docs/PR notes for behavior, risk, rollback.
|
- Update docs/PR notes for behavior, risk, side effects, and rollback.
|
||||||
|
6. **Respect queue hygiene**
|
||||||
|
- If stacked PR: declare `Depends on #...`.
|
||||||
|
- If replacing old PR: declare `Supersedes #...`.
|
||||||
|
|
||||||
## 5) Change Playbooks
|
### 6.1 Code Naming Contract (Required)
|
||||||
|
|
||||||
### 5.1 Adding a Provider
|
Apply these naming rules for all code changes unless a subsystem has a stronger existing pattern.
|
||||||
|
|
||||||
|
- Use Rust standard casing consistently: modules/files `snake_case`, types/traits/enums `PascalCase`, functions/variables `snake_case`, constants/statics `SCREAMING_SNAKE_CASE`.
|
||||||
|
- Name types and modules by domain role, not implementation detail (for example `DiscordChannel`, `SecurityPolicy`, `MemoryStore` over vague names like `Manager`/`Helper`).
|
||||||
|
- Keep trait implementer naming explicit and predictable: `<ProviderName>Provider`, `<ChannelName>Channel`, `<ToolName>Tool`, `<BackendName>Memory`.
|
||||||
|
- Keep factory registration keys stable, lowercase, and user-facing (for example `"openai"`, `"discord"`, `"shell"`), and avoid alias sprawl without migration need.
|
||||||
|
- Name tests by behavior/outcome (`<subject>_<expected_behavior>`) and keep fixture identifiers neutral/project-scoped.
|
||||||
|
- If identity-like naming is required in tests/examples, use ZeroClaw-native labels only (`ZeroClawAgent`, `zeroclaw_user`, `zeroclaw_node`).
|
||||||
|
|
||||||
|
### 6.2 Architecture Boundary Contract (Required)
|
||||||
|
|
||||||
|
Use these rules to keep the trait/factory architecture stable under growth.
|
||||||
|
|
||||||
|
- Extend capabilities by adding trait implementations + factory wiring first; avoid cross-module rewrites for isolated features.
|
||||||
|
- Keep dependency direction inward to contracts: concrete integrations depend on trait/config/util layers, not on other concrete integrations.
|
||||||
|
- Avoid creating cross-subsystem coupling (for example provider code importing channel internals, tool code mutating gateway policy directly).
|
||||||
|
- Keep module responsibilities single-purpose: orchestration in `agent/`, transport in `channels/`, model I/O in `providers/`, policy in `security/`, execution in `tools/`.
|
||||||
|
- Introduce new shared abstractions only after repeated use (rule-of-three), with at least one real caller in current scope.
|
||||||
|
- For config/schema changes, treat keys as public contract: document defaults, compatibility impact, and migration/rollback path.
|
||||||
|
|
||||||
|
## 7) Change Playbooks
|
||||||
|
|
||||||
|
### 7.1 Adding a Provider
|
||||||
|
|
||||||
- Implement `Provider` in `src/providers/`.
|
- Implement `Provider` in `src/providers/`.
|
||||||
- Register in `src/providers/mod.rs` factory.
|
- Register in `src/providers/mod.rs` factory.
|
||||||
- Add focused tests for factory wiring and error paths.
|
- Add focused tests for factory wiring and error paths.
|
||||||
|
- Avoid provider-specific behavior leaks into shared orchestration code.
|
||||||
|
|
||||||
### 5.2 Adding a Channel
|
### 7.2 Adding a Channel
|
||||||
|
|
||||||
- Implement `Channel` in `src/channels/`.
|
- Implement `Channel` in `src/channels/`.
|
||||||
- Ensure `send`, `listen`, and `health_check` semantics are consistent.
|
- Keep `send`, `listen`, `health_check`, typing semantics consistent.
|
||||||
- Cover auth/allowlist/health behavior with tests.
|
- Cover auth/allowlist/health behavior with tests.
|
||||||
|
|
||||||
### 5.3 Adding a Tool
|
### 7.3 Adding a Tool
|
||||||
|
|
||||||
- Implement `Tool` in `src/tools/` with strict parameter schema.
|
- Implement `Tool` in `src/tools/` with strict parameter schema.
|
||||||
- Validate and sanitize all inputs.
|
- Validate and sanitize all inputs.
|
||||||
- Return structured `ToolResult`; avoid panics in runtime path.
|
- Return structured `ToolResult`; avoid panics in runtime path.
|
||||||
|
|
||||||
### 5.4 Security / Runtime / Gateway Changes
|
### 7.4 Memory / Runtime / Config Changes
|
||||||
|
|
||||||
|
- Keep compatibility explicit (config defaults, migration impact, fallback behavior).
|
||||||
|
- Add targeted tests for boundary conditions and unsupported values.
|
||||||
|
- Avoid hidden side effects in startup path.
|
||||||
|
|
||||||
|
### 7.5 Security / Gateway / CI Changes
|
||||||
|
|
||||||
- Include threat/risk notes and rollback strategy.
|
- Include threat/risk notes and rollback strategy.
|
||||||
- Add or update tests for boundary checks and failure modes.
|
- Add/update tests or validation evidence for failure modes and boundaries.
|
||||||
- Keep observability useful but non-sensitive.
|
- Keep observability useful but non-sensitive.
|
||||||
|
|
||||||
## 6) Validation Matrix
|
## 8) Validation Matrix
|
||||||
|
|
||||||
Default local checks for code changes:
|
Default local checks for code changes:
|
||||||
|
|
||||||
|
|
@ -113,31 +237,57 @@ cargo clippy --all-targets -- -D warnings
|
||||||
cargo test
|
cargo test
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Additional expectations by change type:
|
||||||
|
|
||||||
|
- **Docs/template-only**: run markdown lint and relevant doc checks.
|
||||||
|
- **Workflow changes**: validate YAML syntax; run workflow lint/sanity checks when available.
|
||||||
|
- **Security/runtime/gateway/tools**: include at least one boundary/failure-mode validation.
|
||||||
|
|
||||||
If full checks are impractical, run the most relevant subset and document what was skipped and why.
|
If full checks are impractical, run the most relevant subset and document what was skipped and why.
|
||||||
|
|
||||||
For workflow/template-only changes, at least ensure YAML/template syntax validity.
|
## 9) Collaboration and PR Discipline
|
||||||
|
|
||||||
## 7) Collaboration and PR Discipline
|
- Follow `.github/pull_request_template.md` fully (including side effects / blast radius).
|
||||||
|
|
||||||
- Follow `.github/pull_request_template.md`.
|
|
||||||
- Keep PR descriptions concrete: problem, change, non-goals, risk, rollback.
|
- Keep PR descriptions concrete: problem, change, non-goals, risk, rollback.
|
||||||
- Use conventional commit titles.
|
- Use conventional commit titles.
|
||||||
- Prefer small PRs (`size: XS/S/M`) when possible.
|
- Prefer small PRs (`size: XS/S/M`) when possible.
|
||||||
|
- Agent-assisted PRs are welcome, **but contributors remain accountable for understanding what their code will do**.
|
||||||
|
|
||||||
|
### 9.1 Privacy/Sensitive Data and Neutral Wording (Required)
|
||||||
|
|
||||||
|
Treat privacy and neutrality as merge gates, not best-effort guidelines.
|
||||||
|
|
||||||
|
- Never commit personal or sensitive data in code, docs, tests, fixtures, snapshots, logs, examples, or commit messages.
|
||||||
|
- Prohibited data includes (non-exhaustive): real names, personal emails, phone numbers, addresses, access tokens, API keys, credentials, IDs, and private URLs.
|
||||||
|
- Use neutral project-scoped placeholders (for example: `user_a`, `test_user`, `project_bot`, `example.com`) instead of real identity data.
|
||||||
|
- Test names/messages/fixtures must be impersonal and system-focused; avoid first-person or identity-specific language.
|
||||||
|
- If identity-like context is unavoidable, use ZeroClaw-scoped roles/labels only (for example: `ZeroClawAgent`, `ZeroClawOperator`, `zeroclaw_user`) and avoid real-world personas.
|
||||||
|
- Recommended identity-safe naming palette (use when identity-like context is required):
|
||||||
|
- actor labels: `ZeroClawAgent`, `ZeroClawOperator`, `ZeroClawMaintainer`, `zeroclaw_user`
|
||||||
|
- service/runtime labels: `zeroclaw_bot`, `zeroclaw_service`, `zeroclaw_runtime`, `zeroclaw_node`
|
||||||
|
- environment labels: `zeroclaw_project`, `zeroclaw_workspace`, `zeroclaw_channel`
|
||||||
|
- If reproducing external incidents, redact and anonymize all payloads before committing.
|
||||||
|
- Before push, review `git diff --cached` specifically for accidental sensitive strings and identity leakage.
|
||||||
|
|
||||||
Reference docs:
|
Reference docs:
|
||||||
|
|
||||||
- `CONTRIBUTING.md`
|
- `CONTRIBUTING.md`
|
||||||
- `docs/pr-workflow.md`
|
- `docs/pr-workflow.md`
|
||||||
|
- `docs/reviewer-playbook.md`
|
||||||
|
- `docs/ci-map.md`
|
||||||
|
|
||||||
## 8) Anti-Patterns (Do Not)
|
## 10) Anti-Patterns (Do Not)
|
||||||
|
|
||||||
- Do not add heavy dependencies for minor convenience.
|
- Do not add heavy dependencies for minor convenience.
|
||||||
- Do not silently weaken security policy or access constraints.
|
- Do not silently weaken security policy or access constraints.
|
||||||
|
- Do not add speculative config/feature flags “just in case”.
|
||||||
- Do not mix massive formatting-only changes with functional changes.
|
- Do not mix massive formatting-only changes with functional changes.
|
||||||
- Do not modify unrelated modules "while here".
|
- Do not modify unrelated modules “while here”.
|
||||||
- Do not bypass failing checks without explicit explanation.
|
- Do not bypass failing checks without explicit explanation.
|
||||||
|
- Do not hide behavior-changing side effects in refactor commits.
|
||||||
|
- Do not include personal identity or sensitive information in test data, examples, docs, or commits.
|
||||||
|
|
||||||
## 9) Handoff Template (Agent -> Agent / Maintainer)
|
## 11) Handoff Template (Agent -> Agent / Maintainer)
|
||||||
|
|
||||||
When handing off work, include:
|
When handing off work, include:
|
||||||
|
|
||||||
|
|
@ -147,12 +297,12 @@ When handing off work, include:
|
||||||
4. Remaining risks / unknowns
|
4. Remaining risks / unknowns
|
||||||
5. Next recommended action
|
5. Next recommended action
|
||||||
|
|
||||||
## 10) Vibe Coding Guardrails
|
## 12) Vibe Coding Guardrails
|
||||||
|
|
||||||
When working in a fast iterative "vibe coding" style:
|
When working in fast iterative mode:
|
||||||
|
|
||||||
- Keep each iteration reversible (small commits, clear rollback).
|
- Keep each iteration reversible (small commits, clear rollback).
|
||||||
- Validate assumptions with code search before implementing.
|
- Validate assumptions with code search before implementing.
|
||||||
- Prefer deterministic behavior over clever shortcuts.
|
- Prefer deterministic behavior over clever shortcuts.
|
||||||
- Do not "ship and hope" on security-sensitive paths.
|
- Do not “ship and hope” on security-sensitive paths.
|
||||||
- If uncertain, leave a concrete TODO with verification context, not a hidden guess.
|
- If uncertain, leave a concrete TODO with verification context, not a hidden guess.
|
||||||
|
|
|
||||||
125
CONTRIBUTING.md
125
CONTRIBUTING.md
|
|
@ -6,7 +6,7 @@ Thanks for your interest in contributing to ZeroClaw! This guide will help you g
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Clone the repo
|
# Clone the repo
|
||||||
git clone https://github.com/theonlyhennygod/zeroclaw.git
|
git clone https://github.com/zeroclaw-labs/zeroclaw.git
|
||||||
cd zeroclaw
|
cd zeroclaw
|
||||||
|
|
||||||
# Enable the pre-push hook (runs fmt, clippy, tests before every push)
|
# Enable the pre-push hook (runs fmt, clippy, tests before every push)
|
||||||
|
|
@ -37,6 +37,60 @@ git push --no-verify
|
||||||
|
|
||||||
> **Note:** CI runs the same checks, so skipped hooks will be caught on the PR.
|
> **Note:** CI runs the same checks, so skipped hooks will be caught on the PR.
|
||||||
|
|
||||||
|
## Collaboration Tracks (Risk-Based)
|
||||||
|
|
||||||
|
To keep review throughput high without lowering quality, every PR should map to one track:
|
||||||
|
|
||||||
|
| Track | Typical scope | Required review depth |
|
||||||
|
|---|---|---|
|
||||||
|
| **Track A (Low risk)** | docs/tests/chore, isolated refactors, no security/runtime/CI impact | 1 maintainer review + green `CI Required Gate` |
|
||||||
|
| **Track B (Medium risk)** | providers/channels/memory/tools behavior changes | 1 subsystem-aware review + explicit validation evidence |
|
||||||
|
| **Track C (High risk)** | `src/security/**`, `src/runtime/**`, `src/gateway/**`, `.github/workflows/**`, access-control boundaries | 2-pass review (fast triage + deep risk review), rollback plan required |
|
||||||
|
|
||||||
|
When in doubt, choose the higher track.
|
||||||
|
|
||||||
|
## Documentation Optimization Principles
|
||||||
|
|
||||||
|
To keep docs useful under high PR volume, we use these rules:
|
||||||
|
|
||||||
|
- **Single source of truth**: policy lives in docs, not scattered across PR comments.
|
||||||
|
- **Decision-oriented content**: every checklist item should directly help accept/reject a change.
|
||||||
|
- **Risk-proportionate detail**: high-risk paths need deeper evidence; low-risk paths stay lightweight.
|
||||||
|
- **Side-effect visibility**: document blast radius, failure modes, and rollback before merge.
|
||||||
|
- **Automation assists, humans decide**: bots triage and label, but merge accountability stays human.
|
||||||
|
|
||||||
|
### Documentation System Map
|
||||||
|
|
||||||
|
| Doc | Primary purpose | When to update |
|
||||||
|
|---|---|---|
|
||||||
|
| `CONTRIBUTING.md` | contributor contract and readiness baseline | contributor expectations or policy changes |
|
||||||
|
| `docs/pr-workflow.md` | governance logic and merge contract | workflow/risk/merge gate changes |
|
||||||
|
| `docs/reviewer-playbook.md` | reviewer operating checklist | review depth or triage behavior changes |
|
||||||
|
| `docs/ci-map.md` | CI ownership and triage entry points | workflow trigger/job ownership changes |
|
||||||
|
|
||||||
|
## PR Definition of Ready (DoR)
|
||||||
|
|
||||||
|
Before requesting review, ensure all of the following are true:
|
||||||
|
|
||||||
|
- Scope is focused to a single concern.
|
||||||
|
- `.github/pull_request_template.md` is fully completed.
|
||||||
|
- Relevant local validation has been run (`fmt`, `clippy`, `test`, scenario checks).
|
||||||
|
- Security impact and rollback path are explicitly described.
|
||||||
|
- No personal/sensitive data is introduced in code/docs/tests/fixtures/logs/examples/commit messages.
|
||||||
|
- Tests/fixtures/examples use neutral project-scoped wording (no identity-specific or first-person phrasing).
|
||||||
|
- If identity-like wording is required, use ZeroClaw-centric labels only (for example: `ZeroClawAgent`, `ZeroClawOperator`, `zeroclaw_user`).
|
||||||
|
- Linked issue (or rationale for no issue) is included.
|
||||||
|
|
||||||
|
## PR Definition of Done (DoD)
|
||||||
|
|
||||||
|
A PR is merge-ready when:
|
||||||
|
|
||||||
|
- `CI Required Gate` is green.
|
||||||
|
- Required reviewers approved (including CODEOWNERS paths).
|
||||||
|
- Risk level matches changed paths (`risk: low/medium/high`).
|
||||||
|
- User-visible behavior, migration, and rollback notes are complete.
|
||||||
|
- Follow-up TODOs are explicit and tracked in issues.
|
||||||
|
|
||||||
## High-Volume Collaboration Rules
|
## High-Volume Collaboration Rules
|
||||||
|
|
||||||
When PR traffic is high (especially with AI-assisted contributions), these rules keep quality and throughput stable:
|
When PR traffic is high (especially with AI-assisted contributions), these rules keep quality and throughput stable:
|
||||||
|
|
@ -45,10 +99,15 @@ When PR traffic is high (especially with AI-assisted contributions), these rules
|
||||||
- **Small PRs first**: prefer PR size `XS/S/M`; split large work into stacked PRs.
|
- **Small PRs first**: prefer PR size `XS/S/M`; split large work into stacked PRs.
|
||||||
- **Template is mandatory**: complete every section in `.github/pull_request_template.md`.
|
- **Template is mandatory**: complete every section in `.github/pull_request_template.md`.
|
||||||
- **Explicit rollback**: every PR must include a fast rollback path.
|
- **Explicit rollback**: every PR must include a fast rollback path.
|
||||||
- **Security-first review**: changes in `src/security/`, runtime, and CI need stricter validation.
|
- **Security-first review**: changes in `src/security/`, runtime, gateway, and CI need stricter validation.
|
||||||
|
- **Risk-first triage**: use labels (`risk: high`, `risk: medium`, `risk: low`) to route review depth.
|
||||||
|
- **Privacy-first hygiene**: redact/anonymize sensitive payloads and keep tests/examples neutral and project-scoped.
|
||||||
|
- **Identity normalization**: when identity traits are unavoidable, use ZeroClaw/project-native roles instead of personal or real-world identities.
|
||||||
|
- **Supersede hygiene**: if your PR replaces an older open PR, add `Supersedes #...` and request maintainers close the outdated one.
|
||||||
|
|
||||||
Full maintainer workflow: [`docs/pr-workflow.md`](docs/pr-workflow.md).
|
Full maintainer workflow: [`docs/pr-workflow.md`](docs/pr-workflow.md).
|
||||||
CI workflow ownership and triage map: [`docs/ci-map.md`](docs/ci-map.md).
|
CI workflow ownership and triage map: [`docs/ci-map.md`](docs/ci-map.md).
|
||||||
|
Reviewer operating checklist: [`docs/reviewer-playbook.md`](docs/reviewer-playbook.md).
|
||||||
|
|
||||||
## Agent Collaboration Guidance
|
## Agent Collaboration Guidance
|
||||||
|
|
||||||
|
|
@ -59,6 +118,7 @@ For smoother agent-to-agent and human-to-agent review:
|
||||||
- Keep PR summaries concrete (problem, change, non-goals).
|
- Keep PR summaries concrete (problem, change, non-goals).
|
||||||
- Include reproducible validation evidence (`fmt`, `clippy`, `test`, scenario checks).
|
- Include reproducible validation evidence (`fmt`, `clippy`, `test`, scenario checks).
|
||||||
- Add brief workflow notes when automation materially influenced design/code.
|
- Add brief workflow notes when automation materially influenced design/code.
|
||||||
|
- Agent-assisted PRs are welcome, but contributors remain accountable for understanding what the code does and what it could affect.
|
||||||
- Call out uncertainty and risky edges explicitly.
|
- Call out uncertainty and risky edges explicitly.
|
||||||
|
|
||||||
We do **not** require PRs to declare an AI-vs-human line ratio.
|
We do **not** require PRs to declare an AI-vs-human line ratio.
|
||||||
|
|
@ -80,6 +140,57 @@ src/
|
||||||
└── security/ # Sandboxing → SecurityPolicy
|
└── security/ # Sandboxing → SecurityPolicy
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Code Naming Conventions (Required)
|
||||||
|
|
||||||
|
Use these defaults unless an existing subsystem pattern clearly overrides them.
|
||||||
|
|
||||||
|
- **Rust casing**: modules/files `snake_case`, types/traits/enums `PascalCase`, functions/variables `snake_case`, constants `SCREAMING_SNAKE_CASE`.
|
||||||
|
- **Domain-first naming**: prefer explicit role names such as `DiscordChannel`, `SecurityPolicy`, `SqliteMemory` over ambiguous names (`Manager`, `Util`, `Helper`).
|
||||||
|
- **Trait implementers**: keep predictable suffixes (`*Provider`, `*Channel`, `*Tool`, `*Memory`, `*Observer`, `*RuntimeAdapter`).
|
||||||
|
- **Factory keys**: keep lowercase and stable (`openai`, `discord`, `shell`); avoid adding aliases without migration need.
|
||||||
|
- **Tests**: use behavior-oriented names (`subject_expected_behavior`) and neutral project-scoped fixtures.
|
||||||
|
- **Identity-like labels**: if unavoidable, use ZeroClaw-native identifiers only (`ZeroClawAgent`, `zeroclaw_user`, `zeroclaw_node`).
|
||||||
|
|
||||||
|
## Architecture Boundary Rules (Required)
|
||||||
|
|
||||||
|
Keep architecture extensible and auditable by following these boundaries.
|
||||||
|
|
||||||
|
- Extend features via trait implementations + factory registration before considering broad refactors.
|
||||||
|
- Keep dependency direction contract-first: concrete integrations depend on shared traits/config/util, not on other concrete integrations.
|
||||||
|
- Avoid cross-subsystem coupling (provider ↔ channel internals, tools mutating security/gateway internals directly, etc.).
|
||||||
|
- Keep responsibilities single-purpose by module (`agent` orchestration, `channels` transport, `providers` model I/O, `security` policy, `tools` execution, `memory` persistence).
|
||||||
|
- Introduce shared abstractions only after repeated stable use (rule-of-three) and at least one current caller.
|
||||||
|
- Treat `src/config/schema.rs` keys as public contract; document compatibility impact, migration steps, and rollback path for changes.
|
||||||
|
|
||||||
|
## Naming and Architecture Examples (Bad vs Good)
|
||||||
|
|
||||||
|
Use these quick examples to align implementation choices before opening a PR.
|
||||||
|
|
||||||
|
### Naming examples
|
||||||
|
|
||||||
|
- **Bad**: `Manager`, `Helper`, `doStuff`, `tmp_data`
|
||||||
|
- **Good**: `DiscordChannel`, `SecurityPolicy`, `send_message`, `channel_allowlist`
|
||||||
|
|
||||||
|
- **Bad test name**: `test1` / `works`
|
||||||
|
- **Good test name**: `allowlist_denies_unknown_user`, `provider_returns_error_on_invalid_model`
|
||||||
|
|
||||||
|
- **Bad identity-like label**: `john_user`, `alice_bot`
|
||||||
|
- **Good identity-like label**: `ZeroClawAgent`, `zeroclaw_user`, `zeroclaw_node`
|
||||||
|
|
||||||
|
### Architecture boundary examples
|
||||||
|
|
||||||
|
- **Bad**: channel implementation directly imports provider internals to call model APIs.
|
||||||
|
- **Good**: channel emits normalized `ChannelMessage`; agent/runtime orchestrates provider calls via trait contracts.
|
||||||
|
|
||||||
|
- **Bad**: tool mutates gateway/security policy directly from execution path.
|
||||||
|
- **Good**: tool returns structured `ToolResult`; policy enforcement remains in security/runtime boundaries.
|
||||||
|
|
||||||
|
- **Bad**: adding broad shared abstraction before any repeated caller.
|
||||||
|
- **Good**: keep local logic first; extract shared abstraction only after stable rule-of-three evidence.
|
||||||
|
|
||||||
|
- **Bad**: config key changes without migration notes.
|
||||||
|
- **Good**: config/schema changes include defaults, compatibility impact, migration steps, and rollback guidance.
|
||||||
|
|
||||||
## How to Add a New Provider
|
## How to Add a New Provider
|
||||||
|
|
||||||
Create `src/providers/your_provider.rs`:
|
Create `src/providers/your_provider.rs`:
|
||||||
|
|
@ -215,11 +326,15 @@ impl Tool for YourTool {
|
||||||
- [ ] PR template sections are completed (including security + rollback)
|
- [ ] PR template sections are completed (including security + rollback)
|
||||||
- [ ] `cargo fmt --all -- --check` — code is formatted
|
- [ ] `cargo fmt --all -- --check` — code is formatted
|
||||||
- [ ] `cargo clippy --all-targets -- -D warnings` — no warnings
|
- [ ] `cargo clippy --all-targets -- -D warnings` — no warnings
|
||||||
- [ ] `cargo test` — all 129+ tests pass
|
- [ ] `cargo test` — all tests pass locally or skipped tests are explained
|
||||||
- [ ] New code has inline `#[cfg(test)]` tests
|
- [ ] New code has inline `#[cfg(test)]` tests
|
||||||
- [ ] No new dependencies unless absolutely necessary (we optimize for binary size)
|
- [ ] No new dependencies unless absolutely necessary (we optimize for binary size)
|
||||||
- [ ] README updated if adding user-facing features
|
- [ ] README updated if adding user-facing features
|
||||||
- [ ] Follows existing code patterns and conventions
|
- [ ] Follows existing code patterns and conventions
|
||||||
|
- [ ] Follows code naming conventions and architecture boundary rules in this guide
|
||||||
|
- [ ] No personal/sensitive data in code/docs/tests/fixtures/logs/examples/commit messages
|
||||||
|
- [ ] Test names/messages/fixtures/examples are neutral and project-focused
|
||||||
|
- [ ] Any required identity-like wording uses ZeroClaw/project-native labels only
|
||||||
|
|
||||||
## Commit Convention
|
## Commit Convention
|
||||||
|
|
||||||
|
|
@ -252,12 +367,16 @@ Recommended scope keys in commit titles:
|
||||||
- **Bugs**: Include OS, Rust version, steps to reproduce, expected vs actual
|
- **Bugs**: Include OS, Rust version, steps to reproduce, expected vs actual
|
||||||
- **Features**: Describe the use case, propose which trait to extend
|
- **Features**: Describe the use case, propose which trait to extend
|
||||||
- **Security**: See [SECURITY.md](SECURITY.md) for responsible disclosure
|
- **Security**: See [SECURITY.md](SECURITY.md) for responsible disclosure
|
||||||
|
- **Privacy**: Redact/anonymize all personal data and sensitive identifiers before posting logs/payloads
|
||||||
|
|
||||||
## Maintainer Merge Policy
|
## Maintainer Merge Policy
|
||||||
|
|
||||||
- Require passing `CI Required Gate` before merge.
|
- Require passing `CI Required Gate` before merge.
|
||||||
|
- Require docs quality checks when docs are touched.
|
||||||
- Require review approval for non-trivial changes.
|
- Require review approval for non-trivial changes.
|
||||||
- Require CODEOWNERS review for protected paths.
|
- Require CODEOWNERS review for protected paths.
|
||||||
|
- Use risk labels to determine review depth, scope labels (`core`, `provider`, `channel`, `security`, etc.) to route ownership, and module labels (`<module>:<component>`, e.g. `channel:telegram`, `provider:kimi`, `tool:shell`) to route subsystem expertise.
|
||||||
|
- Contributor tier labels are auto-applied on PRs and issues by merged PR count: `experienced contributor` (>=10), `principal contributor` (>=20), `distinguished contributor` (>=50). Treat them as read-only automation labels; manual edits are auto-corrected.
|
||||||
- Prefer squash merge with conventional commit title.
|
- Prefer squash merge with conventional commit title.
|
||||||
- Revert fast on regressions; re-land with tests.
|
- Revert fast on regressions; re-land with tests.
|
||||||
|
|
||||||
|
|
|
||||||
20
README.md
20
README.md
|
|
@ -65,7 +65,7 @@ ls -lh target/release/zeroclaw
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/theonlyhennygod/zeroclaw.git
|
git clone https://github.com/zeroclaw-labs/zeroclaw.git
|
||||||
cd zeroclaw
|
cd zeroclaw
|
||||||
cargo build --release
|
cargo build --release
|
||||||
cargo install --path . --force
|
cargo install --path . --force
|
||||||
|
|
@ -445,6 +445,16 @@ To skip the hook when you need a quick push during development:
|
||||||
git push --no-verify
|
git push --no-verify
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Collaboration & Docs
|
||||||
|
|
||||||
|
For high-throughput collaboration and consistent reviews:
|
||||||
|
|
||||||
|
- Contribution guide: [CONTRIBUTING.md](CONTRIBUTING.md)
|
||||||
|
- PR workflow policy: [docs/pr-workflow.md](docs/pr-workflow.md)
|
||||||
|
- Reviewer playbook (triage + deep review): [docs/reviewer-playbook.md](docs/reviewer-playbook.md)
|
||||||
|
- CI ownership and triage map: [docs/ci-map.md](docs/ci-map.md)
|
||||||
|
- Security disclosure policy: [SECURITY.md](SECURITY.md)
|
||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
ZeroClaw is an open-source project maintained with passion. If you find it useful and would like to support its continued development, hardware for testing, and coffee for the maintainer, you can support me here:
|
ZeroClaw is an open-source project maintained with passion. If you find it useful and would like to support its continued development, hardware for testing, and coffee for the maintainer, you can support me here:
|
||||||
|
|
@ -470,3 +480,11 @@ See [CONTRIBUTING.md](CONTRIBUTING.md). Implement a trait, submit a PR:
|
||||||
---
|
---
|
||||||
|
|
||||||
**ZeroClaw** — Zero overhead. Zero compromise. Deploy anywhere. Swap anything. 🦀
|
**ZeroClaw** — Zero overhead. Zero compromise. Deploy anywhere. Swap anything. 🦀
|
||||||
|
|
||||||
|
## Star History
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://www.star-history.com/#zeroclaw-labs/zeroclaw&Date">
|
||||||
|
<img src="https://api.star-history.com/svg?repos=zeroclaw-labs/zeroclaw&type=Date" alt="Star History Chart" />
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ Merge-blocking checks should stay small and deterministic. Optional checks are u
|
||||||
### Merge-Blocking
|
### Merge-Blocking
|
||||||
|
|
||||||
- `.github/workflows/ci.yml` (`CI`)
|
- `.github/workflows/ci.yml` (`CI`)
|
||||||
- Purpose: Rust validation (`fmt`, `clippy`, `test`, release build smoke)
|
- Purpose: Rust validation (`fmt`, `clippy`, `test`, release build smoke) + docs quality checks when docs change
|
||||||
- Merge gate: `CI Required Gate`
|
- Merge gate: `CI Required Gate`
|
||||||
- `.github/workflows/workflow-sanity.yml` (`Workflow Sanity`)
|
- `.github/workflows/workflow-sanity.yml` (`Workflow Sanity`)
|
||||||
- Purpose: lint GitHub workflow files (`actionlint`, tab checks)
|
- Purpose: lint GitHub workflow files (`actionlint`, tab checks)
|
||||||
|
|
@ -27,24 +27,35 @@ Merge-blocking checks should stay small and deterministic. Optional checks are u
|
||||||
### Optional Repository Automation
|
### Optional Repository Automation
|
||||||
|
|
||||||
- `.github/workflows/labeler.yml` (`PR Labeler`)
|
- `.github/workflows/labeler.yml` (`PR Labeler`)
|
||||||
- Purpose: path labels + size labels
|
- Purpose: scope/path labels + size/risk labels + fine-grained module labels (`<module>:<component>`)
|
||||||
|
- Additional behavior: provider-related keywords in provider/config/onboard/integration changes are promoted to `provider:*` labels (for example `provider:kimi`, `provider:deepseek`)
|
||||||
|
- Additional behavior: applies contributor tiers on PRs by merged PR count (`experienced` >=10, `principal` >=20, `distinguished` >=50)
|
||||||
|
- Additional behavior: risk + size labels are auto-corrected on manual PR label edits (`labeled`/`unlabeled` events); apply `risk: manual` when maintainers intentionally override automated risk selection
|
||||||
|
- High-risk heuristic paths: `src/security/**`, `src/runtime/**`, `src/gateway/**`, `src/tools/**`, `.github/workflows/**`
|
||||||
|
- Guardrail: maintainers can apply `risk: manual` to freeze automated risk recalculation
|
||||||
- `.github/workflows/auto-response.yml` (`Auto Response`)
|
- `.github/workflows/auto-response.yml` (`Auto Response`)
|
||||||
- Purpose: first-time contributor onboarding messages
|
- Purpose: first-time contributor onboarding + label-driven response routing (`r:support`, `r:needs-repro`, etc.)
|
||||||
|
- Additional behavior: applies contributor tiers on issues by merged PR count (`experienced` >=10, `principal` >=20, `distinguished` >=50)
|
||||||
|
- Additional behavior: contributor-tier labels are treated as automation-managed (manual add/remove on PR/issue is auto-corrected)
|
||||||
|
- Guardrail: label-based close routes are issue-only; PRs are never auto-closed by route labels
|
||||||
- `.github/workflows/stale.yml` (`Stale`)
|
- `.github/workflows/stale.yml` (`Stale`)
|
||||||
- Purpose: stale issue/PR lifecycle automation
|
- Purpose: stale issue/PR lifecycle automation
|
||||||
|
- `.github/dependabot.yml` (`Dependabot`)
|
||||||
|
- Purpose: grouped, rate-limited dependency update PRs (Cargo + GitHub Actions)
|
||||||
- `.github/workflows/pr-hygiene.yml` (`PR Hygiene`)
|
- `.github/workflows/pr-hygiene.yml` (`PR Hygiene`)
|
||||||
- Purpose: nudge stale-but-active PRs to rebase/re-run required checks before queue starvation
|
- Purpose: nudge stale-but-active PRs to rebase/re-run required checks before queue starvation
|
||||||
|
|
||||||
## Trigger Map
|
## Trigger Map
|
||||||
|
|
||||||
- `CI`: push to `main`/`develop`, PRs to `main`
|
- `CI`: push to `main`, PRs to `main`
|
||||||
- `Docker`: push to `main`, tag push (`v*`), PRs touching docker/workflow files, manual dispatch
|
- `Docker`: push to `main`, tag push (`v*`), PRs touching docker/workflow files, manual dispatch
|
||||||
- `Release`: tag push (`v*`)
|
- `Release`: tag push (`v*`)
|
||||||
- `Security Audit`: push to `main`, PRs to `main`, weekly schedule
|
- `Security Audit`: push to `main`, PRs to `main`, weekly schedule
|
||||||
- `Workflow Sanity`: PR/push when `.github/workflows/**`, `.github/*.yml`, or `.github/*.yaml` change
|
- `Workflow Sanity`: PR/push when `.github/workflows/**`, `.github/*.yml`, or `.github/*.yaml` change
|
||||||
- `PR Labeler`: `pull_request_target` lifecycle events
|
- `PR Labeler`: `pull_request_target` lifecycle events
|
||||||
- `Auto Response`: issue opened, `pull_request_target` opened
|
- `Auto Response`: issue opened/labeled, `pull_request_target` opened/labeled
|
||||||
- `Stale`: daily schedule, manual dispatch
|
- `Stale`: daily schedule, manual dispatch
|
||||||
|
- `Dependabot`: weekly dependency maintenance windows
|
||||||
- `PR Hygiene`: every 12 hours schedule, manual dispatch
|
- `PR Hygiene`: every 12 hours schedule, manual dispatch
|
||||||
|
|
||||||
## Fast Triage Guide
|
## Fast Triage Guide
|
||||||
|
|
@ -54,10 +65,21 @@ Merge-blocking checks should stay small and deterministic. Optional checks are u
|
||||||
3. Release failures on tags: inspect `.github/workflows/release.yml`.
|
3. Release failures on tags: inspect `.github/workflows/release.yml`.
|
||||||
4. Security failures: inspect `.github/workflows/security.yml` and `deny.toml`.
|
4. Security failures: inspect `.github/workflows/security.yml` and `deny.toml`.
|
||||||
5. Workflow syntax/lint failures: inspect `.github/workflows/workflow-sanity.yml`.
|
5. Workflow syntax/lint failures: inspect `.github/workflows/workflow-sanity.yml`.
|
||||||
|
6. Docs failures in CI: inspect `docs-quality` job logs in `.github/workflows/ci.yml`.
|
||||||
|
|
||||||
## Maintenance Rules
|
## Maintenance Rules
|
||||||
|
|
||||||
- Keep merge-blocking checks deterministic and reproducible (`--locked` where applicable).
|
- Keep merge-blocking checks deterministic and reproducible (`--locked` where applicable).
|
||||||
- Prefer explicit workflow permissions (least privilege).
|
- Prefer explicit workflow permissions (least privilege).
|
||||||
- Use path filters for expensive workflows when practical.
|
- Use path filters for expensive workflows when practical.
|
||||||
|
- Keep docs quality checks low-noise (`markdownlint` + offline link checks).
|
||||||
|
- Keep dependency update volume controlled (grouping + PR limits).
|
||||||
- Avoid mixing onboarding/community automation with merge-gating logic.
|
- Avoid mixing onboarding/community automation with merge-gating logic.
|
||||||
|
|
||||||
|
## Automation Side-Effect Controls
|
||||||
|
|
||||||
|
- Prefer deterministic automation that can be manually overridden (`risk: manual`) when context is nuanced.
|
||||||
|
- Keep auto-response comments deduplicated to prevent triage noise.
|
||||||
|
- Keep auto-close behavior scoped to issues; maintainers own PR close/merge decisions.
|
||||||
|
- If automation is wrong, correct labels first, then continue review with explicit rationale.
|
||||||
|
- Use `superseded` / `stale-candidate` labels to prune duplicate or dormant PRs before deep review.
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,10 @@ This document defines how ZeroClaw handles high PR volume while maintaining:
|
||||||
- High sustainability
|
- High sustainability
|
||||||
- High security
|
- High security
|
||||||
|
|
||||||
Related reference: [`docs/ci-map.md`](ci-map.md) for per-workflow ownership, triggers, and triage flow.
|
Related references:
|
||||||
|
|
||||||
|
- [`docs/ci-map.md`](ci-map.md) for per-workflow ownership, triggers, and triage flow.
|
||||||
|
- [`docs/reviewer-playbook.md`](reviewer-playbook.md) for day-to-day reviewer execution.
|
||||||
|
|
||||||
## 1) Governance Goals
|
## 1) Governance Goals
|
||||||
|
|
||||||
|
|
@ -17,6 +20,18 @@ Related reference: [`docs/ci-map.md`](ci-map.md) for per-workflow ownership, tri
|
||||||
2. Keep CI signal quality high (fast feedback, low false positives).
|
2. Keep CI signal quality high (fast feedback, low false positives).
|
||||||
3. Keep security review explicit for risky surfaces.
|
3. Keep security review explicit for risky surfaces.
|
||||||
4. Keep changes easy to reason about and easy to revert.
|
4. Keep changes easy to reason about and easy to revert.
|
||||||
|
5. Keep repository artifacts free of personal/sensitive data leakage.
|
||||||
|
|
||||||
|
### Governance Design Logic (Control Loop)
|
||||||
|
|
||||||
|
This workflow is intentionally layered to reduce reviewer load while keeping accountability clear:
|
||||||
|
|
||||||
|
1. **Intake classification**: path/size/risk/module labels route the PR to the right review depth.
|
||||||
|
2. **Deterministic validation**: merge gate depends on reproducible checks, not subjective comments.
|
||||||
|
3. **Risk-based review depth**: high-risk paths trigger deep review; low-risk paths stay fast.
|
||||||
|
4. **Rollback-first merge contract**: every merge path includes concrete recovery steps.
|
||||||
|
|
||||||
|
Automation assists with triage and guardrails, but final merge accountability remains with human maintainers and PR authors.
|
||||||
|
|
||||||
## 2) Required Repository Settings
|
## 2) Required Repository Settings
|
||||||
|
|
||||||
|
|
@ -34,8 +49,8 @@ Maintain these branch protection rules on `main`:
|
||||||
### Step A: Intake
|
### Step A: Intake
|
||||||
|
|
||||||
- Contributor opens PR with full `.github/pull_request_template.md`.
|
- Contributor opens PR with full `.github/pull_request_template.md`.
|
||||||
- `PR Labeler` applies path labels + size labels.
|
- `PR Labeler` applies scope/path labels + size labels + risk labels + module labels (for example `channel:telegram`, `provider:kimi`, `tool:shell`) and contributor tiers by merged PR count (`experienced` >=10, `principal` >=20, `distinguished` >=50).
|
||||||
- `Auto Response` posts first-time contributor guidance.
|
- `Auto Response` posts first-time guidance and handles label-driven routing for low-signal items.
|
||||||
|
|
||||||
### Step B: Validation
|
### Step B: Validation
|
||||||
|
|
||||||
|
|
@ -46,7 +61,7 @@ Maintain these branch protection rules on `main`:
|
||||||
### Step C: Review
|
### Step C: Review
|
||||||
|
|
||||||
- Reviewers prioritize by risk and size labels.
|
- Reviewers prioritize by risk and size labels.
|
||||||
- Security-sensitive paths (`src/security`, runtime, CI) require maintainer attention.
|
- Security-sensitive paths (`src/security`, `src/runtime`, `src/gateway`, and CI workflows) require maintainer attention.
|
||||||
- Large PRs (`size: L`/`size: XL`) should be split unless strongly justified.
|
- Large PRs (`size: L`/`size: XL`) should be split unless strongly justified.
|
||||||
|
|
||||||
### Step D: Merge
|
### Step D: Merge
|
||||||
|
|
@ -55,7 +70,26 @@ Maintain these branch protection rules on `main`:
|
||||||
- PR title should follow Conventional Commit style.
|
- PR title should follow Conventional Commit style.
|
||||||
- Merge only when rollback path is documented.
|
- Merge only when rollback path is documented.
|
||||||
|
|
||||||
## 4) PR Size Policy
|
## 4) PR Readiness Contracts (DoR / DoD)
|
||||||
|
|
||||||
|
### Definition of Ready (before requesting review)
|
||||||
|
|
||||||
|
- PR template fully completed.
|
||||||
|
- Scope boundary is explicit (what changed / what did not).
|
||||||
|
- Validation evidence attached (not just "CI will check").
|
||||||
|
- Security and rollback fields completed for risky paths.
|
||||||
|
- Privacy/data-hygiene checks are completed and test language is neutral/project-scoped.
|
||||||
|
- If identity-like wording appears in tests/examples, it is normalized to ZeroClaw/project-native labels.
|
||||||
|
|
||||||
|
### Definition of Done (merge-ready)
|
||||||
|
|
||||||
|
- `CI Required Gate` is green.
|
||||||
|
- Required reviewers approved (including CODEOWNERS paths).
|
||||||
|
- Risk class labels match touched paths.
|
||||||
|
- Migration/compatibility impact is documented.
|
||||||
|
- Rollback path is concrete and fast.
|
||||||
|
|
||||||
|
## 5) PR Size Policy
|
||||||
|
|
||||||
- `size: XS` <= 80 changed lines
|
- `size: XS` <= 80 changed lines
|
||||||
- `size: S` <= 250 changed lines
|
- `size: S` <= 250 changed lines
|
||||||
|
|
@ -69,7 +103,12 @@ Policy:
|
||||||
- `L/XL` PRs need explicit justification and tighter test evidence.
|
- `L/XL` PRs need explicit justification and tighter test evidence.
|
||||||
- If a large feature is unavoidable, split into stacked PRs.
|
- If a large feature is unavoidable, split into stacked PRs.
|
||||||
|
|
||||||
## 5) AI/Agent Contribution Policy
|
Automation behavior:
|
||||||
|
|
||||||
|
- `PR Labeler` applies `size:*` labels from effective changed lines.
|
||||||
|
- Docs-only/lockfile-heavy PRs are normalized to avoid size inflation.
|
||||||
|
|
||||||
|
## 6) AI/Agent Contribution Policy
|
||||||
|
|
||||||
AI-assisted PRs are welcome, and review can also be agent-assisted.
|
AI-assisted PRs are welcome, and review can also be agent-assisted.
|
||||||
|
|
||||||
|
|
@ -93,22 +132,43 @@ Review emphasis for AI-heavy PRs:
|
||||||
- Error handling and fallback behavior
|
- Error handling and fallback behavior
|
||||||
- Performance and memory regressions
|
- Performance and memory regressions
|
||||||
|
|
||||||
## 6) Review SLA and Queue Discipline
|
## 7) Review SLA and Queue Discipline
|
||||||
|
|
||||||
- First maintainer triage target: within 48 hours.
|
- First maintainer triage target: within 48 hours.
|
||||||
- If PR is blocked, maintainer leaves one actionable checklist.
|
- If PR is blocked, maintainer leaves one actionable checklist.
|
||||||
- `stale` automation is used to keep queue healthy; maintainers can apply `no-stale` when needed.
|
- `stale` automation is used to keep queue healthy; maintainers can apply `no-stale` when needed.
|
||||||
- `pr-hygiene` automation checks open PRs every 12 hours and posts a nudge when a PR has no new commits for 48+ hours and is either behind `main` or missing/failing `CI Required Gate` on the head commit.
|
- `pr-hygiene` automation checks open PRs every 12 hours and posts a nudge when a PR has no new commits for 48+ hours and is either behind `main` or missing/failing `CI Required Gate` on the head commit.
|
||||||
|
|
||||||
## 7) Security and Stability Rules
|
Backlog pressure controls:
|
||||||
|
|
||||||
|
- Use a review queue budget: limit concurrent deep-review PRs per maintainer and keep the rest in triage state.
|
||||||
|
- For stacked work, require explicit `Depends on #...` so review order is deterministic.
|
||||||
|
- If a new PR replaces an older open PR, require `Supersedes #...` and close the older one after maintainer confirmation.
|
||||||
|
- Mark dormant/redundant PRs with `stale-candidate` or `superseded` to reduce duplicate review effort.
|
||||||
|
|
||||||
|
Issue triage discipline:
|
||||||
|
|
||||||
|
- `r:needs-repro` for incomplete bug reports (request deterministic repro before deep triage).
|
||||||
|
- `r:support` for usage/help items better handled outside bug backlog.
|
||||||
|
- `invalid` / `duplicate` labels trigger **issue-only** closing automation with guidance.
|
||||||
|
|
||||||
|
Automation side-effect guards:
|
||||||
|
|
||||||
|
- `Auto Response` deduplicates label-based comments to avoid spam.
|
||||||
|
- Automated close routes are limited to issues, not PRs.
|
||||||
|
- Maintainers can freeze automated risk recalculation with `risk: manual` when context demands human override.
|
||||||
|
|
||||||
|
## 8) Security and Stability Rules
|
||||||
|
|
||||||
Changes in these areas require stricter review and stronger test evidence:
|
Changes in these areas require stricter review and stronger test evidence:
|
||||||
|
|
||||||
- `src/security/**`
|
- `src/security/**`
|
||||||
- runtime process management
|
- runtime process management
|
||||||
|
- gateway ingress/authentication behavior (`src/gateway/**`)
|
||||||
- filesystem access boundaries
|
- filesystem access boundaries
|
||||||
- network/authentication behavior
|
- network/authentication behavior
|
||||||
- GitHub workflows and release pipeline
|
- GitHub workflows and release pipeline
|
||||||
|
- tools with execution capability (`src/tools/**`)
|
||||||
|
|
||||||
Minimum for risky PRs:
|
Minimum for risky PRs:
|
||||||
|
|
||||||
|
|
@ -116,7 +176,14 @@ Minimum for risky PRs:
|
||||||
- mitigation notes
|
- mitigation notes
|
||||||
- rollback steps
|
- rollback steps
|
||||||
|
|
||||||
## 8) Failure Recovery
|
Recommended for high-risk PRs:
|
||||||
|
|
||||||
|
- include a focused test proving boundary behavior
|
||||||
|
- include one explicit failure-mode scenario and expected degradation
|
||||||
|
|
||||||
|
For agent-assisted contributions, reviewers should also verify the author demonstrates understanding of runtime behavior and blast radius.
|
||||||
|
|
||||||
|
## 9) Failure Recovery
|
||||||
|
|
||||||
If a merged PR causes regressions:
|
If a merged PR causes regressions:
|
||||||
|
|
||||||
|
|
@ -126,16 +193,18 @@ If a merged PR causes regressions:
|
||||||
|
|
||||||
Prefer fast restore of service quality over delayed perfect fixes.
|
Prefer fast restore of service quality over delayed perfect fixes.
|
||||||
|
|
||||||
## 9) Maintainer Checklist (Merge-Ready)
|
## 10) Maintainer Checklist (Merge-Ready)
|
||||||
|
|
||||||
- Scope is focused and understandable.
|
- Scope is focused and understandable.
|
||||||
- CI gate is green.
|
- CI gate is green.
|
||||||
|
- Docs-quality checks are green when docs changed.
|
||||||
- Security impact fields are complete.
|
- Security impact fields are complete.
|
||||||
|
- Privacy/data-hygiene fields are complete and evidence is redacted/anonymized.
|
||||||
- Agent workflow notes are sufficient for reproducibility (if automation was used).
|
- Agent workflow notes are sufficient for reproducibility (if automation was used).
|
||||||
- Rollback plan is explicit.
|
- Rollback plan is explicit.
|
||||||
- Commit title follows Conventional Commits.
|
- Commit title follows Conventional Commits.
|
||||||
|
|
||||||
## 10) Agent Review Operating Model
|
## 11) Agent Review Operating Model
|
||||||
|
|
||||||
To keep review quality stable under high PR volume, we use a two-lane review model:
|
To keep review quality stable under high PR volume, we use a two-lane review model:
|
||||||
|
|
||||||
|
|
@ -145,6 +214,8 @@ To keep review quality stable under high PR volume, we use a two-lane review mod
|
||||||
- Confirm CI gate signal (`CI Required Gate`).
|
- Confirm CI gate signal (`CI Required Gate`).
|
||||||
- Confirm risk class via labels and touched paths.
|
- Confirm risk class via labels and touched paths.
|
||||||
- Confirm rollback statement exists.
|
- Confirm rollback statement exists.
|
||||||
|
- Confirm privacy/data-hygiene section and neutral wording requirements are satisfied.
|
||||||
|
- Confirm any required identity-like wording uses ZeroClaw/project-native terminology.
|
||||||
|
|
||||||
### Lane B: Deep review (risk-based)
|
### Lane B: Deep review (risk-based)
|
||||||
|
|
||||||
|
|
@ -155,7 +226,7 @@ Required for high-risk changes (security/runtime/gateway/CI):
|
||||||
- Validate backward compatibility and migration impact.
|
- Validate backward compatibility and migration impact.
|
||||||
- Validate observability/logging impact.
|
- Validate observability/logging impact.
|
||||||
|
|
||||||
## 11) Queue Priority and Label Discipline
|
## 12) Queue Priority and Label Discipline
|
||||||
|
|
||||||
Triage order recommendation:
|
Triage order recommendation:
|
||||||
|
|
||||||
|
|
@ -167,9 +238,12 @@ Label discipline:
|
||||||
|
|
||||||
- Path labels identify subsystem ownership quickly.
|
- Path labels identify subsystem ownership quickly.
|
||||||
- Size labels drive batching strategy.
|
- Size labels drive batching strategy.
|
||||||
|
- Risk labels drive review depth (`risk: low/medium/high`).
|
||||||
|
- Module labels (`<module>:<component>`) improve reviewer routing for integration-specific changes and future newly-added modules.
|
||||||
|
- `risk: manual` allows maintainers to preserve a human risk judgment when automation lacks context.
|
||||||
- `no-stale` is reserved for accepted-but-blocked work.
|
- `no-stale` is reserved for accepted-but-blocked work.
|
||||||
|
|
||||||
## 12) Agent Handoff Contract
|
## 13) Agent Handoff Contract
|
||||||
|
|
||||||
When one agent hands off to another (or to a maintainer), include:
|
When one agent hands off to another (or to a maintainer), include:
|
||||||
|
|
||||||
|
|
|
||||||
110
docs/reviewer-playbook.md
Normal file
110
docs/reviewer-playbook.md
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
# Reviewer Playbook
|
||||||
|
|
||||||
|
This playbook is the operational companion to [`docs/pr-workflow.md`](pr-workflow.md).
|
||||||
|
Use it to reduce review latency without reducing quality.
|
||||||
|
|
||||||
|
## 1) Review Objectives
|
||||||
|
|
||||||
|
- Keep queue throughput predictable.
|
||||||
|
- Keep risk review proportionate to change risk.
|
||||||
|
- Keep merge decisions reproducible and auditable.
|
||||||
|
|
||||||
|
## 2) 5-Minute Intake Triage
|
||||||
|
|
||||||
|
For every new PR, do a fast intake pass:
|
||||||
|
|
||||||
|
1. Confirm template completeness (`summary`, `validation`, `security`, `rollback`).
|
||||||
|
2. Confirm labels (`size:*`, `risk:*`, scope labels such as `provider`/`channel`/`security`, module-scoped labels such as `channel:*`/`provider:*`/`tool:*`, and contributor tier labels when applicable) are present and plausible.
|
||||||
|
3. Confirm CI signal status (`CI Required Gate`).
|
||||||
|
4. Confirm scope is one concern (reject mixed mega-PRs unless justified).
|
||||||
|
5. Confirm privacy/data-hygiene and neutral test wording requirements are satisfied.
|
||||||
|
|
||||||
|
If any intake requirement fails, leave one actionable checklist comment instead of deep review.
|
||||||
|
|
||||||
|
## 3) Risk-to-Depth Matrix
|
||||||
|
|
||||||
|
| Risk label | Typical touched paths | Minimum review depth |
|
||||||
|
|---|---|---|
|
||||||
|
| `risk: low` | docs/tests/chore, isolated non-runtime changes | 1 reviewer + CI gate |
|
||||||
|
| `risk: medium` | `src/providers/**`, `src/channels/**`, `src/memory/**`, `src/config/**` | 1 subsystem-aware reviewer + behavior verification |
|
||||||
|
| `risk: high` | `src/security/**`, `src/runtime/**`, `src/gateway/**`, `src/tools/**`, `.github/workflows/**` | fast triage + deep review, strong rollback and failure-mode checks |
|
||||||
|
|
||||||
|
When uncertain, treat as `risk: high`.
|
||||||
|
|
||||||
|
If automated risk labeling is contextually wrong, maintainers can apply `risk: manual` and set the final risk label explicitly.
|
||||||
|
|
||||||
|
## 4) Fast-Lane Checklist (All PRs)
|
||||||
|
|
||||||
|
- Scope boundary is explicit and believable.
|
||||||
|
- Validation commands are present and results are coherent.
|
||||||
|
- User-facing behavior changes are documented.
|
||||||
|
- Author demonstrates understanding of behavior and blast radius (especially for agent-assisted PRs).
|
||||||
|
- Rollback path is concrete (not just “revert”).
|
||||||
|
- Compatibility/migration impacts are clear.
|
||||||
|
- No personal/sensitive data leakage in diff artifacts; examples/tests remain neutral and project-scoped.
|
||||||
|
- If identity-like wording exists, it uses ZeroClaw/project-native roles (not personal or real-world identities).
|
||||||
|
- Naming and architecture boundaries follow project contracts (`AGENTS.md`, `CONTRIBUTING.md`).
|
||||||
|
|
||||||
|
## 5) Deep Review Checklist (High Risk)
|
||||||
|
|
||||||
|
For high-risk PRs, verify at least one example in each category:
|
||||||
|
|
||||||
|
- **Security boundaries**: deny-by-default behavior preserved, no accidental scope broadening.
|
||||||
|
- **Failure modes**: error handling is explicit and degrades safely.
|
||||||
|
- **Contract stability**: CLI/config/API compatibility preserved or migration documented.
|
||||||
|
- **Observability**: failures are diagnosable without leaking secrets.
|
||||||
|
- **Rollback safety**: revert path and blast radius are clear.
|
||||||
|
|
||||||
|
## 6) Issue Triage Playbook
|
||||||
|
|
||||||
|
Use labels to keep backlog actionable:
|
||||||
|
|
||||||
|
- `r:needs-repro` for incomplete bug reports.
|
||||||
|
- `r:support` for usage/support questions better routed outside bug backlog.
|
||||||
|
- `duplicate` / `invalid` for non-actionable duplicates/noise.
|
||||||
|
- `no-stale` for accepted work waiting on external blockers.
|
||||||
|
- Request redaction if logs/payloads include personal identifiers or sensitive data.
|
||||||
|
|
||||||
|
## 7) Review Comment Style
|
||||||
|
|
||||||
|
Prefer checklist-style comments with one of these outcomes:
|
||||||
|
|
||||||
|
- **Ready to merge** (explicitly say why).
|
||||||
|
- **Needs author action** (ordered list of blockers).
|
||||||
|
- **Needs deeper security/runtime review** (state exact risk and requested evidence).
|
||||||
|
|
||||||
|
Avoid vague comments that create back-and-forth latency.
|
||||||
|
|
||||||
|
## 8) Automation Override Protocol
|
||||||
|
|
||||||
|
Use this when automation output creates review side effects:
|
||||||
|
|
||||||
|
1. **Incorrect risk label**: add `risk: manual`, then set the intended `risk:*` label.
|
||||||
|
2. **Incorrect auto-close on issue triage**: reopen issue, remove route label, and leave one clarifying comment.
|
||||||
|
3. **Label spam/noise**: keep one canonical maintainer comment and remove redundant route labels.
|
||||||
|
4. **Ambiguous PR scope**: request split before deep review.
|
||||||
|
|
||||||
|
### PR Backlog Pruning Protocol
|
||||||
|
|
||||||
|
When review demand exceeds capacity, apply this order:
|
||||||
|
|
||||||
|
1. Keep active bug/security PRs (`size: XS/S`) at the top of queue.
|
||||||
|
2. Ask overlapping PRs to consolidate; close older ones as `superseded` after acknowledgement.
|
||||||
|
3. Mark dormant PRs as `stale-candidate` before stale closure window starts.
|
||||||
|
4. Require rebase + fresh validation before reopening stale/superseded technical work.
|
||||||
|
|
||||||
|
## 9) Handoff Protocol
|
||||||
|
|
||||||
|
If handing off review to another maintainer/agent, include:
|
||||||
|
|
||||||
|
1. Scope summary
|
||||||
|
2. Current risk class and why
|
||||||
|
3. What has been validated already
|
||||||
|
4. Open blockers
|
||||||
|
5. Suggested next action
|
||||||
|
|
||||||
|
## 10) Weekly Queue Hygiene
|
||||||
|
|
||||||
|
- Review stale queue and apply `no-stale` only to accepted-but-blocked work.
|
||||||
|
- Prioritize `size: XS/S` bug/security PRs first.
|
||||||
|
- Convert recurring support issues into docs updates and auto-response guidance.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue