mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-09 03:17:54 +00:00
* ci: add fork-safe PR autofix pipeline
Two-workflow split posts prettier + eslint --fix output as inline
review-comment suggestions on PRs (including fork PRs) without running
fork-controlled ESLint plugins under a privileged token.
- pr-autofix.yml: untrusted, runs lint:fix/format with permissions: {},
uploads diff artifact. paths-ignore on lockfiles/snapshots/dist to
avoid reviewdog 406 on >3k-line diffs.
- pr-autofix-publish.yml: trusted workflow_run consumer. Validates every
metadata.json field with regex allowlists before exporting to
GITHUB_OUTPUT (closes head_ref newline-injection vector). Concurrency
keyed on PR number with fork fallback to head-repo+branch. Reviewdog
pinned to v0.21.0. Sticky comment posts only when patch is non-empty
(no noise on clean PRs); body carries a fenced gitnexus-autofix JSON
block under a stable HTML marker for agent parsing. gh API calls go
through a small retry helper for transient 5xx.
Branch protection should enable merge queue + 'require branches up to
date' to handle PR freshness; chinthakagodawita/autoupdate is dropped
(unmaintained since 2023).
* ci(autofix): close zizmor template-injection findings
Move fork-controlled values (head.ref, head.repo.full_name, head.sha,
pr.number, github.repository) into the step's env: block instead of
interpolating them with `${{ }}` directly into the bash run body. The
job has permissions:{} today so this is defence-in-depth, but a future
scope grant on the untrusted half would otherwise turn a malicious
branch name into shell injection.
Add pr-autofix-publish.yml to the documented dangerous-triggers ignore
list — workflow_run is required to post sticky comments on fork PRs
and the file's structural defences (no fork checkout, allowlist on
metadata.json, base_repo equality check) match the existing
ci-report.yml exemption.
* ci(autofix): close remaining review findings
- Add an actionlint job to workflow-lint.yml. Catches YAML syntax,
expression typing, shellcheck-inside-run, and deprecated runner
labels on every .github/** PR — closes the gap that let pr-autofix's
YAML literal-block bug reach review on this branch.
- pr-autofix-publish.yml emits a `gitnexus/autofix` Check Run on the
PR head SHA: conclusion `success` for clean, `neutral` (with
distinct output titles) for suggestions-posted vs.
skipped-too-large. Stable name lets agents read the outcome via
`gh pr checks` without parsing the sticky comment.
- Document the autofix signal contract in CONTRIBUTING.md — sticky
marker, fenced gitnexus-autofix JSON schema, Check Run name. One
source of truth so the marker / schema fields don't drift across
the workflow files and consumers.
* ci: fix actionlint/shellcheck findings on PR #1446
Closes the actionlint warnings the new lint job (workflow-lint.yml's
actionlint runner) surfaced once it was wired into CI. Mostly
shellcheck-style cleanups across three workflows.
pr-autofix-publish.yml
- SC2170: `[ "${{ steps.meta.outputs.changed_lines }}" -gt 3000 ]`
interpolates a literal string into bash, breaking shellcheck's
arithmetic-comparison parse. Move `changed_lines` through env: as
`CHANGED_LINES` and reference as `$CHANGED_LINES` inside bash.
ci-report.yml (Read PR metadata step)
- SC2002 ×2: `cat file | tr` -> `tr < file`.
- SC2129: three consecutive `>> "$GITHUB_OUTPUT"` redirects collapsed
into one `{ ...; } >> "$GITHUB_OUTPUT"` group.
ci-report.yml (Build report step)
- SC2162 ×2: `read VAR1 VAR2` -> `read -r VAR1 VAR2` so backslashes
in test-results.json output aren't mangled.
- SC2034: drop unused `SUITES` aggregate. The per-framework suite
counts (CLI_SU, WEB_SU) are now read into `_` placeholders since
the report doesn't surface them anywhere.
release-candidate.yml
- SC2129 ×2: collapse consecutive `>> "$GITHUB_OUTPUT"` redirects in
the rc-version computation step and the tag-push step into one
grouped block each.
192 lines
11 KiB
Markdown
192 lines
11 KiB
Markdown
# Contributing to GitNexus
|
||
|
||
How to propose changes, run checks locally, and open pull requests.
|
||
|
||
## License
|
||
|
||
This project uses the [PolyForm Noncommercial License 1.0.0](https://polyformproject.org/licenses/noncommercial/1.0.0/). By contributing, you agree your contributions are licensed under the same terms unless stated otherwise.
|
||
|
||
## Where to discuss
|
||
|
||
- **Issues & feature ideas:** use [GitHub Issues](https://github.com/abhigyanpatwari/GitNexus/issues) for the upstream repo, or your fork’s tracker if you work from a fork.
|
||
- **Community:** see the Discord link in the root [README.md](README.md).
|
||
|
||
## Development setup
|
||
|
||
1. Clone the repository.
|
||
2. **CLI / MCP package:** `cd gitnexus && npm install && npm run build`
|
||
3. **Web UI (if needed):** `cd gitnexus-web && npm install`
|
||
4. Run tests as described in [TESTING.md](TESTING.md).
|
||
|
||
## Branch and pull requests
|
||
|
||
- Use short-lived branches off the default branch of the repo you are targeting.
|
||
- **PR titles MUST follow the conventional-commit format** — `pr-labeler.yml` enforces this on every PR and auto-applies the matching label so release notes group the change correctly.
|
||
- **PR description:** what changed, why, how to verify (commands), and any risk or rollback notes.
|
||
|
||
### Pull request titles
|
||
|
||
Format: `<type>[(scope)][!]: <subject>`
|
||
|
||
Allowed types and the release-notes section each one lands in (defined in `.github/release.yml`):
|
||
|
||
| Type | Label applied | Release-notes section |
|
||
|------|---------------|-----------------------|
|
||
| `feat` | `enhancement` | 🚀 Features |
|
||
| `fix` | `bug` | 🐛 Bug Fixes |
|
||
| `perf` | `performance` | 🏎️ Performance |
|
||
| `refactor` | `refactor` | 🔄 Refactoring |
|
||
| `test` | `test` | 🧪 Tests |
|
||
| `ci` | `ci` | 👷 CI/CD |
|
||
| `build` / `deps` | `dependencies` | 📦 Dependencies |
|
||
| `docs` | `documentation` | (grouped under Other Changes unless a Docs section is added) |
|
||
| `chore` / `revert` | `chore` | (excluded from release notes) |
|
||
|
||
Append `!` to the type (e.g. `feat(api)!: drop /v1 endpoint`) or include `BREAKING CHANGE:` in the PR body to flag a breaking change — the labeler then adds the `breaking` label and the 💥 Breaking Changes section is rendered first.
|
||
|
||
Examples:
|
||
|
||
```text
|
||
feat(web): add smart chat scroll
|
||
fix(extractors): resolve silent contract mis-resolution
|
||
perf: avoid O(n²) traversal in heritage walker
|
||
chore(deps): bump vitest to 3.0.0
|
||
ci: standardize workflow concurrency
|
||
```
|
||
|
||
Commits within a PR may use any style — only the **merged PR title** shows up in release notes, so that's the one the convention applies to.
|
||
|
||
## Before you open a PR
|
||
|
||
- [ ] Tests pass for the packages you touched (`gitnexus` and/or `gitnexus-web`).
|
||
- [ ] Typecheck passes: `npx tsc --noEmit` in `gitnexus/` and `npx tsc -b --noEmit` in `gitnexus-web/`.
|
||
- [ ] No secrets, tokens, or machine-specific paths committed.
|
||
- [ ] Documentation updated if behavior or public CLI/MCP contract changes.
|
||
- [ ] Pre-commit hook runs clean (`.husky/pre-commit` — formatting via lint-staged + typecheck for staged packages; tests run in CI only).
|
||
|
||
## Code review
|
||
|
||
Maintainers may request changes for correctness, tests, performance, or consistency with existing patterns. Keeping diffs focused makes review faster.
|
||
|
||
## GitHub Actions — Concurrency Convention
|
||
|
||
Every workflow under `.github/workflows/` MUST declare a top-level `concurrency:` block using this convention:
|
||
|
||
- **Group key** starts with `${{ github.workflow }}` so no two workflows can collide on the same group name. The discriminator that follows is chosen per event shape:
|
||
- Branch/tag scope: `${{ github.workflow }}-${{ github.ref }}`
|
||
- Per-PR scope (for `issue_comment`, `pull_request_review*`, `pull_request` meta events): `${{ github.workflow }}-${{ github.event.pull_request.number || github.event.issue.number }}`
|
||
- `workflow_run` scope (e.g. `ci-report.yml`): `${{ github.workflow }}-${{ github.event.workflow_run.pull_requests[0].number || format('{0}/{1}', github.event.workflow_run.head_repository.full_name, github.event.workflow_run.head_branch) }}` — the fork fallback must be stable across reruns (never `workflow_run.id`, which is per-run-unique and defeats serialization).
|
||
- Global single-slot (manual dispatch utilities): `${{ github.workflow }}`
|
||
- **Reusable workflows invoked via `workflow_call`:** do NOT use `${{ github.workflow }}` in the group key — in called-workflow context its evaluation is ambiguous and can resolve to the caller's name, which would deadlock against the caller's own group. Use a hardcoded literal prefix and a `github.event_name`-aware expression that falls through to `github.run_id` for reusable invocations (see `ci.yml` for the canonical form). Approved literal prefixes: `CI-` (`ci.yml`) and `docker-build-push-` (`docker.yml`). The `check-workflow-concurrency.py` validation script must be updated whenever a new approved literal prefix is added.
|
||
- **Merge queue (`merge_group`)**: when this event is added, use `${{ github.workflow }}-${{ github.event.merge_group.head_ref }}` with `cancel-in-progress: false` (every queue entry is a distinct ref; never cancel).
|
||
- **`cancel-in-progress` policy:**
|
||
|
||
| Event | `cancel-in-progress` | Why |
|
||
|-------|----------------------|-----|
|
||
| `pull_request` CI run | `true` | New push supersedes old run |
|
||
| `push` to `main` | `false` | Every main commit gets validated |
|
||
| Tag push (`v*` publish) | `false` | Never cancel mid-publish |
|
||
| `push` to `main` for release-candidate | `false` | Never cancel mid-RC publish |
|
||
| `workflow_dispatch` (release/publish) | `false` | Manual runs are intentional |
|
||
| `workflow_run` (sticky-comment reports) | `false` | Serialize, don't race |
|
||
| Per-PR bot workflows (`@claude`, review) | `false` | Serialize comments per PR |
|
||
| PR-meta re-checks (pr-description-check) | `true` | Cheap, latest wins |
|
||
| Single-slot utilities (triage sweep) | `true` | Latest dispatch supersedes |
|
||
|
||
- For workflows that serve multiple events at once (e.g. `ci.yml` handles `pull_request`, `push`, and `workflow_call`), make `cancel-in-progress` event-aware:
|
||
|
||
```yaml
|
||
concurrency:
|
||
group: ${{ github.workflow }}-${{ github.ref }}
|
||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||
```
|
||
|
||
- When adding a new workflow, copy the concurrency block from an existing workflow of the same event shape.
|
||
|
||
## CI automation contracts
|
||
|
||
Two workflows produce machine-readable signals on every PR. Coding agents and humans alike can rely on the names and shapes below — change them with intent.
|
||
|
||
### `gitnexus/autofix`
|
||
|
||
`pr-autofix.yml` (untrusted) + `pr-autofix-publish.yml` (trusted) run `prettier --write` and `eslint --fix` against the PR head and surface the diff as inline review-comment suggestions. Three signals are emitted:
|
||
|
||
| Surface | Where | Notes |
|
||
|---|---|---|
|
||
| Sticky PR comment | Top-level comment with the HTML marker `<!-- gitnexus:pr-autofix-summary -->` and heading `## :sparkles: PR Autofix`. Only posted when there is something to fix; clean PRs stay silent. | Edit-in-place via marker; one comment per PR. |
|
||
| Fenced JSON block | Inside the sticky, fenced as `gitnexus-autofix`. Schema `gitnexus.pr-autofix/v1` with fields `state` (`suggestions-posted` \| `skipped-too-large`), `pr_number`, `head_sha`, `changed_lines`, `run_id`. | Parseable signal — preferred over regexing prose. |
|
||
| Check Run | Stable name `gitnexus/autofix` on the PR head SHA. Conclusion: `success` (clean) or `neutral` (suggestions-posted / skipped-too-large). The output title disambiguates the two `neutral` cases. | Surfaced under PR Checks; readable via `gh pr checks <pr>`. |
|
||
|
||
To detect outcome from an agent: `gh pr checks <pr> --json name,conclusion,output | jq '.[] | select(.name == "gitnexus/autofix")'`.
|
||
|
||
Forks are supported. The untrusted half runs fork code with `permissions: {}` and ships the diff as an artifact; the trusted publish job consumes only the diff (data, not code) and posts the comment + check run.
|
||
|
||
## AI-assisted contributions
|
||
|
||
If you use coding agents, follow project context files (e.g. `AGENTS.md`, `CLAUDE.md`) and avoid drive-by refactors unrelated to the issue. Prefer incremental, test-backed changes.
|
||
|
||
## Releases
|
||
|
||
Two publish workflows ship `gitnexus` to npm:
|
||
|
||
- **Stable** (`.github/workflows/publish.yml`) — triggered by pushing any `v*`
|
||
tag. Publishes to the `latest` dist-tag with a changelog-backed GitHub
|
||
release. Maintainers are expected to tag from `main` as a convention; the
|
||
workflow itself does not enforce branch reachability.
|
||
- **Release Candidate** (`.github/workflows/release-candidate.yml`) — runs on
|
||
every push to `main` (typically a merged PR) plus manual dispatch. Docs-only
|
||
changes are skipped via `paths-ignore`. Publishes to the `rc` dist-tag with
|
||
version `X.Y.Z-rc.N` and a GitHub prerelease, where:
|
||
- `X.Y.Z` is selected automatically. On push (and on dispatch with
|
||
`bump: auto`, the default) the workflow **continues the active rc cycle**:
|
||
if the registry already has `X.Y.Z-rc.*` versions with `X.Y.Z` > current
|
||
`latest`, it reuses the highest such base; otherwise it patch-bumps
|
||
from `latest`. Dispatching with `bump: patch|minor|major` **resets**
|
||
the cycle from `latest`.
|
||
- `N` is auto-incremented against existing `X.Y.Z-rc.*` entries on the
|
||
registry. First rc for a given base is `rc.1`.
|
||
- After the npm publish succeeds, the workflow calls `docker.yml` as a
|
||
reusable workflow to build and push the corresponding RC Docker images
|
||
(e.g. `ghcr.io/abhigyanpatwari/gitnexus:1.7.0-rc.1`, mirrored to
|
||
`docker.io/akonlabs/gitnexus:1.7.0-rc.1`). The images are signed
|
||
with Cosign; the OIDC identity is `docker.yml@refs/heads/main` (the
|
||
caller's ref — see README.md § Docker for the verify command).
|
||
|
||
Idempotency: the workflow pushes an `rc/<HEAD_SHA>` marker tag and a
|
||
`v<RC>` release tag **atomically, before** calling `npm publish`. The guard
|
||
refuses to re-run once the marker exists, so a post-publish failure will
|
||
not mint a duplicate rc for the same commit. The `v<RC>` tag points at a
|
||
detached release commit whose `package.json` matches the npm tarball
|
||
exactly (traceable releases). Recovery after a partial failure:
|
||
|
||
```bash
|
||
git push --delete origin rc/<HEAD_SHA> v<RC>
|
||
# then redispatch the workflow with force: true
|
||
```
|
||
|
||
**Docker-only partial failure:** if `publish` succeeds (npm tarball + tags
|
||
are live) but the `docker` job subsequently fails (e.g. GHCR flakiness),
|
||
the npm RC is already published and the `rc/<HEAD_SHA>` marker is in place.
|
||
Re-running `release-candidate.yml` with `force: true` will abort at the
|
||
"Version already exists on npm" guard. To recover without cutting a new RC:
|
||
|
||
```bash
|
||
# 1. Manually trigger only the docker workflow, passing the existing RC tag:
|
||
gh workflow run docker.yml --ref main -f tag=v<RC_VERSION>
|
||
# (requires a workflow_dispatch trigger on docker.yml — see note below)
|
||
```
|
||
|
||
Because `docker.yml` intentionally has no `workflow_dispatch` (images are
|
||
tag-driven by design), the practical recovery options are:
|
||
- Wait for the next commit on `main`, which will cut a new RC that includes
|
||
the Docker build.
|
||
- Manually run `docker build` + `docker push` locally and sign with Cosign
|
||
against the same digest.
|
||
- Delete `rc/<HEAD_SHA>` and `v<RC>` tags, then redispatch with `force:
|
||
true` to re-run the full RC pipeline (cuts a new RC number).
|
||
|
||
The rc workflow never moves `latest`. To verify after a change, inspect dist-tags:
|
||
|
||
```bash
|
||
npm view gitnexus dist-tags
|
||
```
|