Commit Graph

16 Commits

Author SHA1 Message Date
sysadmin 4dee03b2aa fix: close ungated CLI merge bypasses in review_pr.py and merge_pr.py (#16)
Reviewer found the MCP merge surface is gated but two local CLI scripts remain
ungated merge paths that LLM automations in this project have been using.
Close them (Option B — minimal safe fix; full gated CLI merge left to a
follow-up):

- review_pr.py: `--merge` now fails closed BEFORE any API call with a clear
  message directing callers to the gated `gitea_merge_pr` MCP workflow. The
  review-only path is unchanged. The merge execution block was removed.
- merge_pr.py: main() is now a fail-closed no-op — reads no credentials and
  makes no merge API call; prints that merge is only available via the gated
  workflow.
- README: the `review_pr.py` row and Quick Examples no longer advertise a CLI
  `--merge` path; added an audit-logging clarification that #16 returns
  structured gate/merge results but does not add durable audit logging, which
  is tracked by #18.

Tests updated/added:
- test_review_pr.py: `--merge` fails closed with no API call; message points to
  the gated workflow.
- test_merge_pr.py: merge fails closed with no API call, even with
  --force/--do/--title/--message; message points to the gated workflow.
- test_mcp_server.py: README no longer advertises the ungated CLI merge example.

The gated MCP `gitea_merge_pr` is unchanged and still gated; `gitea_review_pr`
still fails closed on merge=True; `gitea_submit_pr_review` still cannot merge.
No secrets, auth headers, raw env, or credential paths are exposed. No
Jenkins/Ops/GlitchTip/Release/deploy/CI behavior added. #17/#18 not started.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 15:40:34 -04:00
sysadmin f04cf44975 feat: gate gitea_merge_pr behind identity/profile/eligibility + confirmation (#16)
Replace the ungated gitea_merge_pr with a gated merge workflow. This is now
the only merge path the MCP server exposes; the merge API is called only
after every safety gate passes.

Gates (fail-closed at each step):
  1. Merge method is merge | squash | rebase.
  2. Explicit confirmation: confirmation must equal "MERGE PR <n>" (without it,
     zero API calls are made).
  3. Reuse gitea_check_pr_eligibility (#14) with action 'merge': proves the
     authenticated identity, the active profile (and that it allows merge), the
     PR author, blocks self-merge, requires the PR open, and fails closed when
     the PR is not mergeable or mergeability is unknown.
  4. Optional expected_head_sha: refuse if the PR head moved.
  5. Optional expected_changed_files: refuse if the PR's changed file set differs.
  6. Redundant self-merge block (auth user == PR author).

The force/ignore-checks option was removed — Gitea's own mergeable signal
(which reflects branch-protection required reviews/checks) must be positive,
so required approval/check state is honoured, never bypassed.

Output reports performed, authenticated user, profile name, PR author, PR
number, head SHA checked, merge method, gates passed/blocked, and merge
result / merge commit — never a token, auth header, or credential. Error text
is scrubbed via _redact.

Surface audit: no ungated merge path remains. The /merge endpoint appears only
inside gitea_merge_pr; gitea_review_pr fails closed on merge=True before any
API call; gitea_submit_pr_review has no merge parameter and 'merge' is not a
reviewable action. Tests assert all three.

Tests cover: merge succeeds only when all gates pass; self-author blocked;
unknown identity/profile blocked; profile without merge permission blocked;
missing/wrong confirmation blocked (no API call); head-SHA mismatch blocked;
changed-files mismatch blocked; closed PR blocked; non-mergeable blocked;
unknown mergeability fail-closed; no merge call when gates fail; invalid merge
method rejected; output and error redaction; and the no-ungated-merge-path audit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 15:03:49 -04:00
sysadmin 6c992a4fa7 fix: close gitea_review_pr ungated bypass (#15) 2026-07-01 14:42:00 -04:00
sysadmin f05e58c847 feat: add gated gitea_submit_pr_review review actions (#15)
Add gitea_submit_pr_review, the only tool that submits a Gitea PR review.
It performs a review mutation (comment / approve / request_changes) only
after every safety gate passes, and never merges.

Gates (fail-closed at each step):
  1. Validate action is comment | approve | request_changes.
  2. Reuse gitea_check_pr_eligibility (#14) for authenticated-user lookup,
     active-profile lookup, PR-author lookup, self-approval block, and the
     profile-allowed-operation check. approve requires 'approve' eligibility,
     request_changes requires 'request_changes', comment requires 'review'.
  3. Redundant self-approval block (auth user == PR author).
  4. Optional expected_head_sha: refuse if the PR head has moved.
  5. Only then POST /repos/{owner}/{repo}/pulls/{n}/reviews (formal review
     endpoint, so approvals/change-requests carry real review state).

Output reports action, whether performed, authenticated user, profile name,
PR author, PR number, head SHA checked, and reasons — never a token, auth
header, or credential. Error text is scrubbed via _redact as defence in depth.

Merge is intentionally not implemented (belongs to #16).

Tests cover: self-author approve blocked, approve/request_changes/comment
succeed only when eligible, unknown identity fail-closed, disallowed profile
op blocked, head-SHA mismatch blocked, no mutation when gates fail, invalid
action rejected, and secret redaction in output and error paths.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 14:31:34 -04:00
sysadmin baf4eae30b test: cover self-approve block and unknown-mergeability fail-closed (#14)
Add two explicit eligibility tests requested in review of PR #24:
- self-author blocked from 'approve' (eligible=false, reason
  "authenticated user is PR author").
- 'merge' fails closed when Gitea reports mergeable=None (eligible=false,
  reason "PR mergeability unknown").

Tests only; no implementation change. Behavior already enforced by
gitea_check_pr_eligibility.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 14:02:48 -04:00
sysadmin fbbbd5359e feat: add read-only gitea_check_pr_eligibility (#14)
Add a read-only MCP tool that decides whether the current authenticated
identity + active runtime profile is eligible to review, approve,
request_changes, or merge a specific PR. Evaluation only — it never
reviews, approves, requests changes, merges, or mutates anything.

Inspects: authenticated username (/user), active profile metadata
(allowed/forbidden operations), and PR facts (author, state, head SHA,
mergeability). Returns {eligible, requested_action, authenticated_user,
profile_name, pr_author, pr_state, head_sha, mergeable, reasons}.

Fail-closed rules:
- unknown action / unknown remote -> not eligible
- action not in allowed ops, or in forbidden ops -> not eligible
- identity undetermined -> not eligible
- authenticated user == PR author -> cannot approve/merge
- PR not open -> not eligible
- merge requires a positive mergeable signal

No token/auth-header exposure. No review/approve/request-changes
mutation. No merge mutation. No multi-token switching. No
Jenkins/Ops/GlitchTip/Release/deploy behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 13:54:49 -04:00
sysadmin 38c96d5815 feat: add read-only gitea_get_profile discovery tool (#13)
Add a read-only MCP tool that reports the active runtime execution
profile so an LLM can inspect what the current process is configured to
do before deciding whether to attempt an action later.

- gitea_get_profile: returns profile_name, allowed/forbidden operation
  categories, audit_label, token_source_name (a NAME, never a value),
  base_url, remote, resolved server, and — optionally — the verified
  authenticated username. Identity resolution fails soft and marks
  identity_status (verified/unknown/unavailable/not_resolved); the
  profile config is always returned. Never mutates Gitea.
- gitea_auth.get_profile(): extended with forbidden_operations,
  audit_label, token_source_name from env (non-secret metadata).
- .env.example / README: document the new optional metadata vars and
  the discovery tool.
- tests: metadata parsing, verified/unavailable/unknown identity paths,
  skip-identity, and secret-redaction.

Read-only. No token exposure. No multi-token switching. No PR
eligibility, review, or merge workflow. No Jenkins/Ops/GlitchTip/
Release/deploy behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 13:41:14 -04:00
sysadmin e31612027d feat: support separate Gitea MCP runtime profiles via env config (#19)
Allow the same MCP server to run as separate MCP entries, each with its
own token and profile name, so roles stay task-scoped (the profile is
the role, not the LLM).

- gitea_auth.get_profile(): reads GITEA_PROFILE_NAME,
  GITEA_ALLOWED_OPERATIONS, GITEA_BASE_URL as non-secret metadata.
  Never reads/returns/logs the token.
- gitea_whoami now surfaces the safe profile metadata (name + allowed
  operations) alongside identity; token still never exposed.
- .env.example: placeholder-only template for a runtime profile.
- .gitignore: track .env.example while keeping real .env* ignored.
- README: document multiple env-configured MCP entries.
- tests: profile defaults/parsing, token-never-included, whoami surfaces
  profile without leaking token.

One token + one profile per process. No multi-token switching in a
single runtime. No approve/merge/eligibility workflow. No
Jenkins/Ops/GlitchTip/Release/deploy behavior. No real secrets.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 13:21:59 -04:00
sysadmin 03e28c159e feat: add read-only gitea_whoami authenticated-user lookup (#11)
Add a read-only MCP tool that calls Gitea's authenticated-user
endpoint (GET /api/v1/user) and returns safe identity metadata only:
username, display name, user id, email, server, and remote.

This lets future review/merge workflows prove which Gitea account the
MCP server is authenticated as, so self-review/self-merge can be
detected before acting — the blocker discovered during PR #8 dogfooding.

- Never returns the token, Authorization header, password, or secrets.
- Fails closed with a clear error if identity cannot be determined.
- No mutation; no profile switching; no review/approve/merge behavior.

Tests: identity mapping, secret-redaction, fail-closed, unknown-remote.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 12:42:37 -04:00
sysadmin 989856a007 feat: add PR review and edit tools to CLI and MCP server 2026-06-26 06:24:19 -04:00
sysadmin 82fcd5a4bc feat: expand MCP server tools for PR and label management, add helper CLI scripts
Closes #7
2026-06-24 00:14:47 -04:00
sysadmin 80a71f2020 feat: bypass Cloudflare block & convert close/mark issue shell scripts to Python 2026-06-21 22:32:37 -04:00
sysadmin 51296c88a3 refactor: rename auth.py to gitea_auth.py and ignore env files 2026-06-21 22:27:40 -04:00
sysadmin b7e195e426 feat: add MCP server + shared auth module (#7, #1)
- New: mcp_server.py — FastMCP stdio server exposing 7 tools:
  gitea_create_issue, gitea_create_pr, gitea_close_issue,
  gitea_list_issues, gitea_view_issue, gitea_mark_issue,
  gitea_mirror_refs
- New: auth.py — shared authentication and API helpers
  (get_credentials, get_auth_header, api_request, repo_api_url)
- Refactored: create_pr.py, create_issue.py, manage_labels.py
  to use shared auth module (eliminates credential duplication)
- New: tests/test_mcp_server.py — 17 tests for all MCP tools
- Updated: tests/test_credentials.py — now tests auth.py directly
- Updated: tests/test_create_issue.py — adapted for refactored imports
- New: requirements.txt — frozen venv deps (mcp[cli], pytest)
- Updated: README.md — MCP server as primary interface
- Config: added gitea-tools to mcp_config.json

Closes #1. Resolves #2, #5. Relates to #7.
2026-06-21 20:08:07 -04:00
sysadmin dd6f1308c1 feat: add mirror_refs.sh for bidirectional ref syncing
- mirror_refs.sh: additive branch+tag mirroring between dadeschools (HTTPS)
  and prgs (SSH:2222). Dry-run default, --apply to execute, --force for
  diverged branches. Uses bare repo cache for isolation.
- test_mirror_refs.py: flag parsing, safety defaults, brace-delimited refspec
  validation, and local bare-repo integration tests (FF detection, branch/tag
  comparison).
- README.md: document mirror_refs.sh, test suite, and multi-instance auth.
2026-06-21 18:08:53 -04:00
sysadmin c4c9993039 test: add comprehensive test suite
- test_create_issue.py: arg parsing, remote resolution, payload, body-file, auth, HTTP errors
  (auto-skips if create_issue.py is inaccessible due to macOS sandbox)
- test_create_pr.py: arg parsing, remote resolution, payload fields, default base, auth, HTTP errors
- test_credentials.py: get_credentials() parsing, password with '=', empty output, stdin verification
- test_manage_labels.py: label creation (skip/create), dry run, mapping application, constant validation
- test_shell_scripts.py: close_issue.sh and mark_issue.sh arg validation and error messages

28 passed, 12 skipped (macOS sandbox on create_issue.py).
2026-06-21 17:26:18 -04:00