Commit graph

9 commits

Author SHA1 Message Date
Gergő Magyar
83fbd4be26
refactor(ci): unify release pipeline under publish.yml (#1610)
Collapse release-candidate.yml into publish.yml so there is exactly one workflow that publishes gitnexus to npm, creates GitHub Releases, and triggers Docker builds — for both release candidates and stable releases. Closes #1609 architecturally.

A first-stage `route` job classifies push-to-main / push-tag / workflow_dispatch into `rc` / `stable` modes and fails closed on malformed shapes. RC path runs rc-guard → ci.yml → publish (mint GitHub App token → checkout with persist-credentials:false → resolve next rc version → atomic v-tag + rc/<SHA> marker push → vtag integrity gate → npm publish via OIDC → GitHub prerelease → if: failure() cleanup) → docker.yml. Stable path verifies package.json matches the tag and publishes to `latest` via OIDC (no docker).

Hardening:

  • Self-trigger prevention via negative-glob `tags: ['v*', '!v*-rc.*']` — the bug class behind #1609 cannot recur.
  • Two distinct actions/checkout steps per mode (no conditional `token:` expression footgun).
  • Workflow-level `permissions: {}` deny-all + per-job grants; `id-token: write` only where OIDC is used.
  • npm Trusted Publishing replaces NPM_TOKEN (delete the secret after the first successful publish).
  • GitHub App installation token (actions/create-github-app-token@v3.2.0) replaces the long-lived RELEASE_PUSH_TOKEN PAT (delete after first successful RC).
  • vtag integrity gate fails closed on empty / mode-mismatched output (prevents Release named `main` from a github.ref fallback).
  • Annotation-injection sanitization on every logged ref.
  • Explicit `secrets:` passthrough on docker.yml (DOCKERHUB_USERNAME, DOCKERHUB_TOKEN); ci.yml no longer inherits anything.
  • `if: failure()` cleanup auto-deletes v-tag + rc-marker on partial failure (eliminates the external-consumer phantom-version ingestion window).
  • ACTIONS_STEP_DEBUG window closed via `set +x` wrap on the inline auth-header compute.
  • Curated retry-loud error handling on `gh api` bot-user-id lookup and `npx semver`.

Pre-merge validation:

  • 10-reviewer multi-agent code-review pass; 14 findings fixed inline (commit 820cefae), 6 deferred to follow-ups.
  • End-to-end dry-run rehearsal via workflow_dispatch (run 25919563064) validated route classification, rc-guard, App token mint, RC checkout, version resolver, vtag synthetic-regex check, and faithful tarball pack at the bumped version.
  • All zizmor findings on the unification commits closed.
  • Branch-protection required checks all green.

Post-merge actions:

  • After the first successful RC, delete the `NPM_TOKEN` and `RELEASE_PUSH_TOKEN` secrets — they are no longer used.
  • The first real RC after merge is the live-fire test for steps dry-run could not exercise (atomic tag push, real npm OIDC handshake, GitHub Release creation, docker.yml under explicit secrets passthrough). The if: failure() cleanup step handles the partial-failure recovery automatically; the Rollback Runbook in CONTRIBUTING.md covers the rare cases auto-cleanup can't reach.
2026-05-16 07:46:56 +01:00
Gergő Magyar
6906be3695
feat(autofix): replace inline reviewdog with /autofix ChatOps button (#1458)
* fix(autofix): verify reviewdog actually posted before claiming "click Apply"

The sticky summary comment was stating "Posted formatting suggestions
inline. Click Apply suggestion on each" even when reviewdog landed zero
inline review comments — typical case: the formatter touched lines
outside the PR's added range, so `-filter-mode=added` (correctly)
filtered everything out. The script unconditionally set `posted=true`
after running reviewdog regardless of whether any comments were
actually created, leaving the user staring at a sticky that promised
buttons that didn't exist.

The publish job now snapshots the count of `github-actions[bot]` review
comments before and after reviewdog. If the delta is zero, surface a
new `diff-no-overlap` UI state that tells the user plainly:

  "Formatter found fixable issues, but they're on lines outside this
  PR's added range — there's nothing to click here. Run locally:
  npm run lint:fix && npm run format."

Plus a matching `gitnexus/autofix` Check Run conclusion (still neutral,
distinct title) so agents reading `gh pr checks` see the same signal.

Three states are now machine-distinguishable in the sticky's
gitnexus-autofix JSON block: suggestions-posted (delta > 0),
diff-no-overlap (delta == 0), skipped-too-large (>3k lines).

* feat(autofix): replace inline reviewdog with /autofix ChatOps button

Pivot the PR autofix UX from per-line reviewdog suggestions to a single
slash-command button. Contributors comment `/autofix` on the PR; a new
trusted workflow downloads the existing autofix patch artifact, applies
it to the PR head, and pushes a commit back.

Why:
- 3K+ diffs hit GitHub's review-comment API 406 limit -> dead end.
- Diffs where the formatter touches lines outside the PR's added range
  ("no-overlap") get filtered by reviewdog's -filter-mode=added -> dead
  end (PR #1457 patched the lying sticky but the underlying UX gap
  remained).
- Per-line click-Apply-suggestion is high-friction for big diffs and
  easy to apply unevenly.
- A single `git apply` + push works at any size and lands fixes
  atomically.

Changes:
- pr-autofix-publish.yml: remove `Install reviewdog` and
  `Post inline suggestions` steps. Collapse three sticky states
  (suggestions-posted, diff-no-overlap, skipped-too-large) into one
  (fixes-available). Bump JSON schema v1 -> v2 with `apply_command`
  field; all v1 fields preserved.
- pr-autofix-apply.yml (new): triggers on issue_comment with body
  `/autofix`, validates body via strict regex, validates commenter
  has write/admin/maintain or is the PR author, locates latest
  successful pr-autofix run for PR head SHA, downloads artifact,
  applies patch, pushes commit. Reacts +1/-1/eyes on triggering
  comment per outcome. Idempotent (`git apply --check --reverse`
  detects already-applied state).
- CONTRIBUTING.md: document v2 schema and the /autofix flow,
  including the maintainer-edit requirement for fork PR pushes.

Trust posture: apply workflow runs from default-branch code only,
under issue_comment trigger. Comment body and author login flow
through env vars and pattern-matched, never interpolated into shell.
Permission gate (write/admin/maintain OR PR author) before any
artifact fetch. Fork PRs require "Allow edits by maintainers"
(GitHub-native; we don't bypass).

Net YAML: -139 lines in publish.yml, +260 in apply.yml. Removes
reviewdog binary pin and the entire review-comment API surface.

* fix(autofix): address Codex adversarial findings on PR #1458

Two findings from the Codex adversarial review of the autofix ChatOps
pivot. Both are localized YAML changes that close trust gaps the pivot
inherited from the original PR #1446 design.

U1 — Cross-verify metadata against workflow_run authority
(.github/workflows/pr-autofix-publish.yml):
  Previously the trusted publisher accepted pr_number, head_sha, and
  head_repo from metadata.json after only an allowlist regex. A
  fork-controlled `npm run lint:fix` could have written a syntactically
  valid metadata.json referencing another PR/SHA, redirecting the
  write-scoped sticky/check-run onto an attacker-chosen target.

  New `Verify metadata against workflow_run authority` step compares
  artifact-claimed identity against:
    - github.event.workflow_run.head_sha
    - github.event.workflow_run.head_repository.full_name
    - workflow_run.pull_requests[].number (within-repo PRs)
    - gh api commits/{sha}/pulls fallback (fork PRs, where
      pull_requests[] is empty)
  Fail closed on mismatch — no sticky, no check-run, no override.

U2 — Lease-protected push in apply workflow
(.github/workflows/pr-autofix-apply.yml):
  Previously the apply step pushed `HEAD:${HEAD_REF}` plain. A force-
  push between resolve (Step 5) and push (Step 9) would silently
  fast-forward an older commit graph over the contributor's newer
  state.

  Push now uses `--force-with-lease=refs/heads/${HEAD_REF}:${HEAD_SHA}`
  against the SHA resolved earlier. Distinct `lease-failed` result code
  + retry-message reply, separated from `push-failed` (fork without
  maintainer-edit) so contributors can diagnose the actual cause.

Plan: docs/plans/2026-05-09-005-fix-autofix-codex-adversarial-findings-plan.md
(local-only per repo convention).

Trust posture preserved: no new permissions, no new workflows, no
contract change. JSON v2 schema unchanged. CodeQL js/server-side-
request-forgery and template-injection posture unchanged — all new
inputs flow via env vars and pattern-matched.

* fix(autofix): close zizmor credential-persistence finding on apply checkout

actions/checkout's default behavior writes the GITHUB_TOKEN into
.git/config as an extraheader. The token then sits on disk in the
checkout directory — an actions/upload-artifact step on that
directory would leak it. We don't upload, but zizmor's
credential-persistence lint correctly flags the latent risk.

Set persist-credentials: false on the Checkout PR head step. Provide
push auth inline via `git -c http.extraheader="Authorization: Basic
<base64-of-x-access-token:TOKEN>"` so the credential never lands on
disk and never appears in process listings (the URL form
https://x-access-token:TOKEN@… is rejected here because it leaks via
ps and git remote -v).

Push lease semantics from U2 unchanged — same --force-with-lease
against the resolved HEAD_SHA, same lease-failed/push-failed/stale
result codes.

* fix(review): apply autofix feedback

ce-code-review surfaced 15 findings on PR #1458; this commit applies
the 7 with concrete fixes (#1, #2, #3, #4, #5, #9, #13). Five P2
findings (#6, #7, #8, #10, #12) are recorded as residual actionable
work for follow-up; two advisory items (#11, #14) skipped.

#1 — applied_run_id schema drift (CONTRIBUTING.md):
  v2 docs claimed `state: applied` enum value and an `applied_run_id`
  field that no code path emits. Trimmed docs to match what the
  workflow actually writes (state: fixes-available; v1 field set as
  superset). Implementing the apply-side sticky upsert that would
  populate `applied_run_id` is deferred — cleaner than carrying a
  contract claim with no code.

#2 — result= unset between idempotency probe and lease push
(pr-autofix-apply.yml):
  After `git apply --check` passed, an early non-zero exit from
  `git config` / `git apply` / `git add` / `git commit` left
  `result=` unset, sending the user to the `*` "unexpected state
  (`unknown`)" arm. Wrapped the apply/commit phase in a single
  if-test that sets `result=apply-failed` on any failure. New
  React-and-reply branch surfaces an actionable message.

#3 — permission lookup conflated transient API failures with denial
(pr-autofix-apply.yml):
  `gh api … 2>/dev/null || echo "none"` swallowed 5xx, 429 secondary
  rate-limit, and network failures, surfacing them as a public 👎
  refusal to legitimate maintainers. Now distinguishes 404
  (genuine non-collaborator) from other API failures via stderr
  match. New `allowed=api-failed` state triggers a 😕 reaction with
  a "transient API failure, retry" reply instead of a misleading
  refusal.

#4 — lease-failure grep missed git's "remote rejected" / branch-
deleted phrasings (pr-autofix-apply.yml):
  Real lease failures got classified as `push-failed` →
  user told to enable maintainer-edit, which won't help. Expanded
  regex to match `remote rejected` and `! [rejected]`.

#5 — broken bullet continuation in CONTRIBUTING.md release-candidate
section: rejoined the split bullet so it renders correctly.

#9 — base64 GITHUB_TOKEN bypassed GitHub's secret-masker
(pr-autofix-apply.yml):
  Added `::add-mask::${auth_header}` immediately after construction
  so any subsequent log line (set -x, GIT_TRACE) gets *** redacted.

#13 — misleading schema-bump comment in pr-autofix-publish.yml:
  Comment claimed all v1 fields preserved exactly, but the `state`
  enum was redefined v1→v2. Updated to make the migration path
  explicit (v1 readers see unfamiliar schema, fall back to prose).

Residual actionable work (deferred to follow-up):
  #6 locate step gh api retry; #7 artifact-expired graceful fallback;
  #8 re-entrancy comment-spam guard; #10 producer-still-running UX;
  #12 gh_retry wrapper for apply.yml.

Validations: yaml.safe_load OK, check-workflow-concurrency.py OK.

* fix(autofix): apply remaining ce-code-review residual findings (#6, #7, #8, #10, #12)

Pulls the deferred items from the previous review pass into this PR so
the workflow ships with full reliability + UX coverage rather than
follow-up debt.

#6 + #12 — gh_retry wrapper on idempotent GETs in apply.yml:
  Permission lookup, PR metadata fetch, and workflow-run lookup are now
  wrapped in the same gh_retry helper publish.yml uses (3 attempts,
  linear backoff). Reaction/comment POSTs remain unwrapped (retrying
  POST would dupe the resource).

#10 — producer-still-running UX:
  The locate step now distinguishes three cases via `found_status`
  output: success (proceed), in-progress / queued / pending / waiting
  (reply  "wait for autofix run to finish"), not-found (reply 🤔
  "push a commit"), api-failed (reply ⚠️ "transient API failure"). The
  "no successful autofix run" message no longer fires immediately after
  a fresh push while the producer is still mid-run.

#7 — artifact-expired graceful fallback:
  actions/download-artifact gains `continue-on-error: true`. The apply
  step distinguishes patch-file-missing (artifact expired, 1-day
  retention elapsed) from patch-file-zero-bytes (formatter found
  nothing). New `result=artifact-expired` case +  "push a new commit
  to regenerate" reply.

#8 — re-entrancy loop guard:
  After checkout but before applying, check if HEAD itself is a
  github-actions[bot] `chore(autofix)` commit. If so, refuse to
  re-apply (`result=loop-prevented`) with a 🔁 reply telling the user
  to push a human-authored commit or revert before retrying. Prevents
  formatter-config-drift loops where an automated agent watching the
  sticky could pump arbitrary apply commits.

Net effect: every code path in apply.yml now sets a meaningful `result=`
that maps to a specific user-facing reaction + reply. The `*` "unexpected
state (unknown)" arm becomes truly unreachable in normal operation.

Validations: yaml.safe_load OK, check-workflow-concurrency.py OK.

* fix(autofix): refresh stale reviewdog comments + reject patches touching .github/

Two follow-up findings on PR #1458:

#1 — Stale reviewdog references in workflow header comments:
  pr-autofix-publish.yml's header still described the removed inline-
  suggestion path ("posts inline review-comment suggestions to the PR
  using `reviewdog`", "Reviewdog reporter: github-pr-review reads
  $REVIEWDOG_GITHUB_API_TOKEN…"). The Check Run permissions comment
  enumerated the old outcomes (clean / suggestions-posted /
  skipped-too-large) instead of the current set (clean / fixes-
  available). pr-autofix.yml's header described the trusted job as
  posting "inline review-comment suggestions" and the changed_lines
  comment referenced the dead 3000-line cap. Refreshed all three to
  describe the actual sticky + Check Run + /autofix flow.

#2 — Reject patches touching .github/ (sensitive-paths guard):
  Theoretical supply-chain vector: a malicious PR could ship a custom
  prettier/ESLint config that reformats workflow YAML, dependabot.yml,
  or CODEOWNERS. The producer would capture those edits in
  autofix.patch; a maintainer running `/autofix` would push them under
  `contents: write` without human review. The default GITHUB_TOKEN
  lacks the `workflows` scope so workflow-file pushes would fail at
  the platform layer anyway, but as a generic `push-failed` (which
  misleads users into enabling maintainer-edit). Reject early with
  a specific reason.

  Match runs against the patch with grep on `^(diff --git|---|+++)
  [ab]?/?\.github/`. New `result=sensitive-paths` case + 🛑 reply
  telling the user to apply .github/ formatter changes manually.

  Documented the constraint in CONTRIBUTING.md under the /autofix
  section so contributors aren't surprised when the workflow refuses
  a patch that includes formatter changes to workflow files.

Validations: yaml.safe_load OK, check-workflow-concurrency.py OK.
2026-05-09 16:32:38 +01:00
Gergő Magyar
b5627f27d8
ci: add fork-safe PR autofix pipeline (#1446)
* 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.
2026-05-09 13:35:04 +01:00
Gergő Magyar
ac9246cfd3
ci(docker): mirror signed images to Docker Hub alongside GHCR (#1029)
* ci(docker): mirror signed images to Docker Hub alongside GHCR

docker.yml now publishes to docker.io/abhigyanpatwari/gitnexus{,-web} in
the same build step as the existing GHCR push, so both registries receive
the same digest, the same Cosign keyless signature, and the same SBOM /
build-provenance attestations. The Docker Hub login uses new repo secrets
DOCKERHUB_USERNAME / DOCKERHUB_TOKEN (scoped PAT, not account password).

Supply-chain guarantees carry over unchanged: the signing loop iterates
metadata-action's full tag set, so Docker Hub tags get signed at the
identical digest under the same docker.yml@refs/tags/v* identity. The
ClusterImagePolicy is extended with docker.io / index.docker.io / bare-
namespace globs so admission cannot be sidestepped by registry-prefix
choice. README and .env.example document both registries; RC section in
CONTRIBUTING.md notes the Docker Hub mirror tag.

Closes #1027

* ci(docker): publish to akonlabs Docker Hub namespace; add PR dry-run CI

- Hardcode `akonlabs` as the Docker Hub namespace in metadata-action and
  both attestation subject-names (Docker Hub org differs from GitHub org
  `abhigyanpatwari`, so `github.repository_owner` would produce the wrong ref)
- Update docs (.env.example, README, CONTRIBUTING) and the Kubernetes
  ClusterImagePolicy globs to reference `akonlabs/gitnexus{,-web}`
- Add `pull_request` trigger so the image build runs as CI on every PR
  (build only — no push, sign, or attestation)
- Add `workflow_dispatch` with `dry_run: boolean` (default true) for
  manual build-only runs; all publish steps gated on
  `github.event_name != 'pull_request' && !inputs.dry_run`
2026-04-23 18:59:26 +01:00
Copilot
fa39a4b4a5
fix(docker): build and push Docker images for Release Candidates (#978) 2026-04-19 07:46:21 +01:00
Gergő Magyar
0a4b31b3c5
docs: optimize context files for LLM accuracy and token efficiency (#857)
* docs: optimize context files for LLM accuracy and token efficiency

Fix factual errors across all five root context files and optimize
for LLM context window efficiency.

Corrections:
- Web UI: "runs entirely in WASM" -> thin client backed by HTTP API
- Pre-commit hook: "typecheck + tests" -> formatting + typecheck only
- MCP tools: 7 -> 16 (added api_impact, route_map, tool_map,
  shape_check, group_list/query/sync/contracts/status)
- Default serve port: 3741 -> 4747
- E2E tests: "5 tests" -> 7 spec files
- ESLint: "no config" -> eslint.config.mjs exists with TS/React rules
- npm test: "vitest run test/unit" -> "vitest run" (full suite)
- Removed nonexistent test:all script
- ci-quality.yml: added missing format + lint job descriptions
- Pipeline phase deps: added missing structure dep on mro/communities/processes
- Ingestion entry: added missing run-analyze.ts intermediate orchestrator
- Tools Quick Reference: added missing list_repos
- Group tool examples: fixed param name (group -> name)
- Removed stale vite-plugin-wasm gotcha
- Added gitnexus-shared to repository layout tables

New documentation:
- ARCHITECTURE.md: language-agnostic graph feeding (provider pattern,
  unified capture tags, import resolution tiers, chunked parse, MRO)
- ARCHITECTURE.md: full analysis flow (10 stages with progress %)
- ARCHITECTURE.md: storage layout, LadybugDB schema, embeddings, search
- ARCHITECTURE.md: DAG runner internals (Kahn's sort, dep isolation, error handling)

Token optimization:
- Removed filler prose, compressed descriptions into dense tables
- Front-loaded key facts in every section
- Eliminated redundancy between sections
- AGENTS.md: 219 -> 201 lines. ARCHITECTURE.md: 192 -> 298 lines
  (more info in fewer tokens via tables and structure)

* docs: optimize GUARDRAILS.md for LLM context efficiency

Tighten prose without losing information:
- Compressed intro, scope section, and Signs format labels
- Shortened Sign headers (removed "Sign:" prefix)
- Replaced verbose "Instruction/Reason" labels with "Do/Why"
- Removed trailing whitespace and redundant emphasis
2026-04-16 08:43:11 +01:00
Gergő Magyar
109a3c6946
ci: standardize workflow concurrency and automate release-note labeling (#837)
* ci: standardize workflow concurrency and automate release-note labeling

Concurrency — prevent racing CI jobs
 - Every top-level workflow now declares an explicit concurrency block.
 - PR runs cancel-in-progress on supersede; main/push/workflow_call/publish
   runs queue instead of cancelling so every commit and every release is
   validated end-to-end.
 - ci.yml uses a literal `CI-` prefix (not `${{ github.workflow }}`) and a
   per-run nested group for workflow_call invocations, avoiding a potential
   deadlock with publish.yml and release-candidate.yml callers whose own
   concurrency groups could otherwise collide with the called workflow.
 - ci-report.yml falls back to `<head-repo>/<head-branch>` for fork PRs
   (stable across reruns) instead of the per-run-unique workflow_run.id
   which did not actually serialize anything.
 - ci-quality.yml enforces the convention: fails CI if any non-reusable
   workflow lacks a concurrency block or a reusable workflow declares one.

Release-note automation
 - New pr-labeler.yml: amannn/action-semantic-pull-request enforces
   conventional-commit PR titles on pull_request (fork-safe, read-only);
   release-drafter/release-drafter with disable-releaser: true applies the
   matching label under pull_request_target (write-scoped). sync-labels in
   .github/release-drafter.yml removes managed autolabels that no longer
   match (e.g. when `!` or `BREAKING CHANGE:` is dropped from a PR).
 - .github/release.yml (unchanged) continues to map labels to categorized
   release-notes sections.
 - dependabot.yml added for the github-actions ecosystem so pinned SHAs
   auto-refresh on a weekly cadence.

Docs
 - CONTRIBUTING.md documents the concurrency convention, the
   conventional-commit PR-title rules, and the reusable-workflow exception.

Follow-up to verify before relying on the labeler in anger
 - gh api repos/amannn/action-semantic-pull-request/git/refs/tags/v5.5.3
 - gh api repos/release-drafter/release-drafter/git/refs/tags/v6.0.0
 - Confirm release-drafter reads its config from the base ref (not fork
   head) when invoked via pull_request_target.

* ci: address PR review feedback on concurrency and labeler workflows

Two blocking fixes
 - pr-labeler.yml: separate concurrency slots for pull_request and
   pull_request_target. Previously both triggers shared a single group
   with cancel-in-progress: true, so the privileged autolabel run could
   cancel the title-validation check mid-run and leave a required status
   in a permanent cancelled state.
 - pr-labeler.yml autolabel job: add contents: read. release-drafter's
   context.config() reads .github/release-drafter.yml from the default
   branch via the repo-contents API and 403s without the scope. Job-level
   permissions nullify all unlisted scopes so an explicit grant is needed.

Two non-blocking improvements
 - Replace the hardcoded reusable-workflow allowlist in ci-quality.yml
   with dynamic on:-block parsing. New workflow_call-only workflows no
   longer produce false-positive convention failures.
 - Implement actual group-key validation. The check now also asserts that
   every concurrency.group expression references either ${{ github.workflow }}
   or the literal CI- prefix (the documented ci.yml exception).
 - Script extracted to .github/scripts/check-workflow-concurrency.py so
   it is runnable locally and independently testable.
2026-04-15 13:24:53 +01:00
Gergő Magyar
baf3f9e37d
feat(ci): add release-candidate publish pipeline (#825)
Some checks are pending
CI / quality (push) Waiting to run
CI / tests (push) Waiting to run
CI / e2e (push) Waiting to run
CI / Save PR Metadata (push) Blocked by required conditions
CI / CI Gate (push) Blocked by required conditions
Release Candidate / Check if release candidate should run (push) Waiting to run
Release Candidate / ci (push) Blocked by required conditions
Release Candidate / Publish release candidate to npm (push) Blocked by required conditions
* feat(ci): add release-candidate publish pipeline

Auto-publishes gitnexus@rc on every merge to main. Version scheme is
canonical semver X.Y.Z-rc.N where the base is the current npm 'latest'
bumped by the 'bump' input (default patch) and N auto-increments by
querying existing rc versions on the registry. First rc for a new base
is rc.1; the counter resets naturally when the base advances after a
stable release.

- Reuses ci.yml via workflow_call so tests must pass before publish
- SHA-pinned actions, per-job permission scoping, provenance enabled
- Guard job dedupes duplicate dispatches against HEAD via v*-rc.* tags
- Docs-only pushes skipped via paths-ignore
- workflow_dispatch inputs: bump (patch/minor/major), force (override guard)
- Publishes under the 'rc' dist-tag so 'latest' is never moved
- Tags commits as v<rc-version> and creates GitHub prereleases

* fix(ci): address release-candidate review feedback

- Sort rc tags by creatordate (handles out-of-order pushes correctly)
- Fail fast on npm registry errors; only fall back to package.json on E404
- Drop unused pull-requests: write permission on the reused CI job
- Add secrets: inherit so any future CI secrets are available to sub-jobs
- Remove unused reltag step output

* fix(ci): address Copilot review comments

- Correct concurrency comment (runs serialize on same ref, not overlap)
- Apply E404-only fallback to 'npm view versions' query, matching the
  pattern used for the 'npm view version' query
- README: clarify that docs-only merges don't trigger rc publish
- CONTRIBUTING: drop 'from main' claim for publish.yml; the tag-push
  trigger does not enforce branch reachability

* fix(ci): address adversarial review — idempotency, cycle continuity, tag integrity

Codex adversarial review flagged three release-safety issues in the rc
pipeline. Fixes:

1. Cycle continuity (H). Non-patch rc trains no longer collapse back to
   patch on the next push. 'bump' input accepts a new 'auto' value
   (default) that infers the active rc base from the registry: if any
   X.Y.Z-rc.* exists with X.Y.Z > latest, continue that base; otherwise
   patch-bump. Explicit patch/minor/major still forces a cycle reset and
   now also bypasses the dedup guard so an explicit dispatch on a
   tagged HEAD is honored.

2. Idempotency across post-publish failures (H). The guard marker
   ('rc/<HEAD_SHA>' lightweight tag) and the release tag ('v<RC>'
   annotated) are now pushed atomically *before* 'npm publish'. A
   publish failure leaves the marker in place and the guard refuses to
   re-publish. Added a defensive 'npm view <pkg>@<rc> version' check
   before publish to catch registry-level races. Recovery path
   documented in CONTRIBUTING.md.

3. Tag ↔ package integrity (M). 'v<RC>' now points at a detached
   release commit whose tree contains the rewritten package.json, so
   the tag's source archive matches the npm tarball exactly. 'main'
   stays pristine; the release commit is reachable only via the tag.

* fix(ci): surface registry errors on defensive version check; drop actions: read

- npm view <pkg>@<rc> version now distinguishes E404 (safe) from network
  failures (abort) via the same mktemp+grep pattern used for the other
  two npm view calls
- Dropped actions: read on the ci workflow_call — no sub-workflow uses
  the Actions API
2026-04-14 17:32:47 +01:00
John R. Eakin
c68d7975e6
docs: agent development framework, GitHub templates, eval refactor (#479)
* ci: E2E workflow, web typecheck job, pre-commit hook, test suite

CI:
- ci.yml consolidated to reference ci-tests.yml
- ci-quality.yml: add typecheck-web job for gitnexus-web/
- ci-e2e.yml: E2E workflow with dorny/paths-filter (web changes only)
- ci-report.yml: remove dead integration-reports references
- CI gate allows skipped E2E status
- .gitignore: playwright artifacts, eval test artifacts

Pre-commit hook:
- .githooks/pre-commit: typecheck + unit tests for both packages
- Activated via git config core.hooksPath in prepare script

Test infrastructure:
- Vitest + React Testing Library: 58 unit tests
  (graph, server-connection, mermaid, settings, constants, utils, paths)
- Playwright E2E: 5 tests + manual recording harness
- vitest.config from vitest/config, engines.node >= 20
- Playwright artifacts retain-on-failure
- wait-on in devDependencies
- vitest/coverage-v8 aligned with vitest 4.x

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: update gitnexus-web package-lock.json

Reflects devDependency additions (vitest, playwright, wait-on,
@testing-library, etc.) from package.json changes in this PR.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(e2e): add missing process-list-loaded testid, increase CI timeouts

- Add data-testid="process-list-loaded" to ProcessesPanel (E2E tests
  were waiting for an element that didn't exist)
- Increase server connect timeouts from 5s to 10s for slower CI

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): run gitnexus-web unit tests in CI, remove unused variable

- Add gitnexus-web npm ci + vitest run to ci-tests.yml so web unit
  tests are gated by the CI status check (were only running locally)
- Remove unused IS_PLAYWRIGHT_AUTOMATION variable from E2E spec

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(e2e): add process-row testid, wait for networkidle on page load

- Add data-testid="process-row" to ProcessItem component (E2E tests
  referenced it but it didn't exist in the source)
- Use waitUntil: 'networkidle' on page.goto to ensure Vite dev server
  is fully ready before interacting (fixes first-test timeout in CI)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(e2e): add process-view-button and process-highlight-button testids

E2E tests referenced these data-testid attributes but they didn't
exist in ProcessItem. All 6 E2E testids now have matching source
elements: status-ready, process-list-loaded, process-row,
process-view-button, process-highlight-button, server-url-input.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(e2e): remove networkidle — Vite HMR WebSocket prevents it from resolving

networkidle waits for zero network activity for 500ms, but Vite's HMR
WebSocket stays open permanently, causing page.goto to timeout at 60s
on all tests after the first. The explicit toBeVisible waits on UI
elements are sufficient and deterministic.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(e2e): wait for Server button visibility, add CI retry, all 5 tests pass locally

Root cause: test 1 clicked the Server button before React hydrated,
so the tab content never rendered and the input wasn't found.

Fixes:
- Wait for Server button toBeVisible before clicking
- Increase input wait to 15s
- Remove networkidle (Vite HMR WebSocket prevents it from resolving)
- Add retries: 1 in CI for transient cold-start flakiness

Verified locally: all 5 E2E tests pass, 198 unit tests pass, typecheck clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): tolerate LadybugDB native crash during analyze step

gitnexus analyze can crash with "double free or corruption" (known
issue #273) during the LadybugDB native addon shutdown. The index is
usually written successfully before the crash. The workflow now:
1. Allows analyze to exit non-zero with a warning
2. Verifies .gitnexus index was actually created
3. Only fails if no index exists (real failure)

All tests verified locally: 198 unit, 5 E2E pass, typecheck clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): fix shell quoting in analyze step, simplify to || true

The previous echo string had special characters that broke bash
quoting in GitHub Actions. Simplified to: analyze || true, then
check if .gitnexus exists.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add agent development framework, GitHub templates, eval refactor

Agent framework (layered docs for AI-assisted contributions):
- AGENTS.md: canonical instructions, impact analysis, MCP tools
- CLAUDE.md: Claude Code-specific deltas and hooks
- GUARDRAILS.md: safety boundaries, non-negotiables, escalation
- ARCHITECTURE.md: monorepo layout, data flow map
- TESTING.md: test structure, commands, categories
- RUNBOOK.md: copy-paste operations for dev/CI/MCP
- llms.txt: minimal LLM context pointer

Editor integration:
- .cursor/index.mdc + rules/100-monorepo.mdc

GitHub templates:
- PR template with areas-touched checkboxes
- Bug report + feature request issue forms

Eval harness:
- Refactored mcp_bridge, tool_registry, constants
- Error sanitization utilities
- Property-based tests via Hypothesis

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(eval): use format_exception instead of format_exc in sanitize_exception

format_exc() returns the currently handled exception traceback, which
may be unrelated if called outside an active except block. Using
format_exception(type(exc), exc, exc.__traceback__) reliably captures
the passed exception's traceback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: update CONTRIBUTING.md and TESTING.md for current CI/hook setup

- CONTRIBUTING.md: add gitnexus-web typecheck command, pre-commit hook
  checklist item
- TESTING.md: add gitnexus-web typecheck command, pre-commit hook
  section (husky), update CI integration to list actual workflow files
  (ci-quality, ci-tests, ci-e2e)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: update testing docs to reflect CI/E2E changes from PR #486

- AGENTS.md: update test counts (CLI ~2000 unit, ~1850 integration),
  add gitnexus-web testing section (198 unit, 5 E2E with commands)
- RUNBOOK.md: fix Node requirement to >=20, fix E2E local repro command
- TESTING.md: E2E uses data-testid selectors + real servers, not mocks
- .cursor/rules/100-monorepo.mdc: add web test/E2E commands

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: address context engineering review — deduplicate tokens, expand Cursor rules

- Remove ~100-line gitnexus:start block from CLAUDE.md (was duplicated from AGENTS.md)
- Fix gitnexus:start block inlined inside AGENTS.md Reference Docs bullet (doubled)
- Replace CLAUDE.md scope table with pointer to AGENTS.md (single source of truth)
- Expand .cursor/index.mdc with 5 non-negotiable safety rules for always-on context
- Add .cursor/rules/200-eval.mdc with Python/eval commands (glob-scoped to eval/**)
- Improve llms.txt with priority annotations and descriptions
- Bump version headers to 1.2.0, last-reviewed to 2026-03-24

Saves ~1,400 tokens/session with zero information loss.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
2026-03-25 06:48:41 +00:00