Commit Graph

85 Commits

Author SHA1 Message Date
sysadmin d8269fc704 chore: add #73/#76/#79/#80 docs merges to v1.1.0 release notes (#63)
Master moved during release prep: PRs #89 (#79), #90 (#80), #92 (#76), and
#93 (#73) merged. Sync the branch with master (ba6064e) and record all four
in the v1.1.0 Documentation section. CHANGELOG.md only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 15:47:32 -04:00
sysadmin 0d6d31d341 Merge remote-tracking branch 'prgs/master' into chore/issue-63-v1.1.0 2026-07-02 15:46:45 -04:00
jcwalker3 ba6064e51e Merge pull request 'docs: extend profile model for multi-service MCP boundaries (#76)' (#92) from docs/issue-76-multi-service-profile-model into master 2026-07-02 14:44:35 -05:00
jcwalker3 fac10dd6cc docs: GlitchTip read-only error/event tools design (#73) (#93)
Co-authored-by: jcwalker3 <jcwalker3@yahoo.com>
Co-committed-by: jcwalker3 <jcwalker3@yahoo.com>
2026-07-02 14:27:09 -05:00
sysadmin 0cdbf63660 chore: refresh v1.1.0 release notes with all work merged since v1.0.1 (#63)
Merge current master (4f5b732) into the release branch and expand the
CHANGELOG v1.1.0 section to cover every merge since the v1.0.1 tag:
identity/eligibility tooling (#9, #11, #13, #14), gated review/merge
workflows (#15, #16), execution profiles (#12, #19), audit logging (#18),
Retry-After backoff (#27), API pagination + failure handling (#67),
release-tag helper (#50), status:in-progress automation (#56, #58),
LLM-Agent-SHA Phase 0 (#86), provenance helper (#3), manage_labels modes
(#6), and documentation (#8, #70, #72, #77).

PRs #82 (#68 release SOP) and #84 (#69 Linux portability) were closed
without merging and are intentionally NOT listed.

No feature code changed; CHANGELOG.md only. No tag created.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 15:22:52 -04:00
sysadmin 6f9699b51a docs: extend profile model for multi-service MCP boundaries (#76) 2026-07-02 15:22:43 -04:00
sysadmin 58e4bcd157 docs: propose labels for Jenkins/GlitchTip workflow (#80) (#90)
Co-authored-by: Jason Walker <913443@dadeschools.net>
Co-committed-by: Jason Walker <913443@dadeschools.net>
2026-07-02 14:21:58 -05:00
sysadmin 0a61e8a92d docs: update safety and boundary docs for Jenkins/GlitchTip (#79) (#89)
Co-authored-by: Jason Walker <913443@dadeschools.net>
Co-committed-by: Jason Walker <913443@dadeschools.net>
2026-07-02 14:21:49 -05:00
sysadmin fd68c439b2 Merge branch 'master' into chore/issue-63-v1.1.0 2026-07-02 15:21:22 -04:00
sysadmin 4f5b732741 Merge pull request 'docs: Jenkins repo/branch/PR to job mapping design (#77)' (#91) from docs/issue-77-jenkins-job-mapping-design into master 2026-07-02 14:15:58 -05:00
sysadmin 1bc2f20623 docs: Jenkins repo/branch/PR to job mapping design (#77)
Add docs/architecture/jenkins-job-mapping-design.md: declarative versioned
mapping config (exact-match repo/branch entries, no globs, fail-closed load
on malformed/duplicate entries), resolution semantics for multibranch/
single/parameterized-view job types with URL-encoded branch and PR-<n>
addressing, branch-pinned-over-repo-wide precedence, fork PRs resolving via
base repo only, explicit machine-checkable no-match payload (never guess or
probe job names), config location in the jenkins-mcp package (no secrets,
env-overridable path), a read-only jenkins_resolve_job tool surface, and a
mocked-config/mocked-Jenkins testing strategy.

Design only; no implementation, no code behavior changed, no Jenkins write
actions introduced.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 15:09:48 -04:00
sysadmin 007b5dad14 Merge pull request 'docs: Jenkins read-only build status tools design (#72)' (#88) from docs/issue-72-jenkins-readonly-design into master 2026-07-02 14:01:36 -05:00
sysadmin 104907e311 docs: LLM-Agent-SHA opaque attribution convention, Phase 0 (#86) (#87)
Merge PR 87

Co-authored-by: Jason Walker <913443@dadeschools.net>
Co-committed-by: Jason Walker <913443@dadeschools.net>
2026-07-02 14:00:46 -05:00
sysadmin 74a7e8f792 docs: Jenkins read-only build status tools design (#72)
Add docs/architecture/jenkins-readonly-build-status-design.md:
implementation-ready design notes for the jenkins-mcp read-only tool set —
minimum tools (whoami, list_jobs, latest_build, build_status, get_build,
gated console_tail), safe return-field allowlist (url, number, timestamp,
duration, branch, result, commit), fail-closed failure behavior (unknown
job, unreachable, 5xx, auth, malformed JSON), bounded+redacted console tail
behind a distinct jenkins.console.read operation, per-service credential/
profile requirements (token by reference, fail closed), explicit exclusions
(build/deploy triggers, parameterized launches), job addressing with mapping
deferred to #77, and a mocked-Jenkins testing strategy.

Design only; no implementation, no code behavior changed, no Jenkins code
in mcp_server.py.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 14:34:18 -04:00
sysadmin 53e061bafd Merge pull request 'fix: add shared API pagination and failure handling (#67)' (#83) from fix/issue-67-api-pagination-failures into master 2026-07-02 12:42:10 -05:00
sysadmin cfe3ff6755 fix: add shared API pagination and failure handling (#67)
Harden gitea_auth.api_request: add a per-request timeout (env
GITEA_HTTP_TIMEOUT), convert timeouts and DNS/network failures
(URLError/TimeoutError) into clear RuntimeErrors, give 502/503/504 an
explicit 'upstream unavailable' message, convert malformed success JSON
into a clean error, and redact credential-like substrings from all error
text. Preserves the success path and existing 429 retry/backoff.

Add shared gitea_auth.api_get_all: page-based pagination that tolerates
missing/malformed metadata (relies on page length, not Link/X-Total-Count
headers), honors an optional overall limit, and caps pages. Wire it into
the read-only list tools gitea_list_issues, gitea_list_prs, and
gitea_list_labels (return shape unchanged).

Add tests/test_api_reliability.py (18 cases) and update the three list-tool
tests to the new call path. No auth/profile/merge/review/tracker behavior
changed. No modular #65 refactor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 13:27:06 -04:00
sysadmin 093945254d Merge pull request 'docs: add developer testing guidelines (#70)' (#81) from docs/issue-70-developer-testing-guidelines into master 2026-07-02 12:14:05 -05:00
sysadmin 625f835aa7 docs: fix invalid pytest node IDs in testing guide 2026-07-02 13:06:31 -04:00
sysadmin be4cd82c37 docs: add developer testing guidelines (#70)
Add docs/developer-testing-guidelines.md covering test commands, targeted
runs, syntax/diff checks, adding MCP tool tests, safe API/auth mocking,
profile/allowed-operation gate tests, self-review/self-merge gate tests,
no-secret regression expectations, unit vs future Docker integration tests,
and read-only vs mutating tool expectations. Link it from the README Tests
section and note the suite table is non-exhaustive.

Documentation only; no code behavior changed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 12:54:43 -04:00
sysadmin afa57fa65c chore: version bump and release notes for v1.1.0 (fixes #63) 2026-07-02 06:29:49 -04:00
sysadmin 2d5cb4bb29 Merge pull request 'refactor: split manage_labels.py into reusable modes (#6)' (#62) from feat/issue-6-manage-labels-modes into master 2026-07-02 05:24:33 -05:00
sysadmin 1441591e74 Merge pull request 'feat: add aliases for gitea_whoami identity lookup (fixes #9)' (#61) from feature/issue-9-identity-lookup into master 2026-07-02 05:23:57 -05:00
sysadmin 848a4294ba Merge pull request 'fix: document + tool macOS com.apple.provenance workaround (#3)' (#60) from fix/issue-3-provenance-python-exec into master 2026-07-02 05:23:23 -05:00
sysadmin 496e796cdd refactor: split manage_labels.py into reusable modes (#6)
Split the one-shot label backfill into reusable, mode-selected operations while
preserving the original default behavior:

- --create-labels : idempotent label creation only (create_labels()).
- --apply-mapping : one-off MAPPING labeling only (apply_mapping(); PUT replaces
  each issue's set).
- --add-label <issue> <label> : ad-hoc single-issue labeling (add_label(); POST
  appends the label, does not replace; refuses an undefined label).
- default (no mode) : create labels then apply MAPPING — identical to the prior
  behavior. --dry (and --dry-run) still print without writing.

Extracted create_labels / apply_mapping / add_label / _labels_by_name helpers;
LABELS, MAPPING, and the api() wrapper are unchanged. No auth/network behavior
change; MAPPING remains the same one-off backfill data.

Tests: extend tests/test_manage_labels.py with a TestModes suite — create-only
(no PUT), apply-only (no label creation), add-label appends (POST, not PUT),
unknown-label no-op, dry no-op, non-numeric issue exits. Existing default/dry/
mapping/constant tests unchanged and still pass.

py_compile clean; full suite 319 passed / 0 failures; git diff --check clean;
no secrets.

Closes #6.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 06:21:23 -04:00
sysadmin 642adf4705 feat: add aliases for gitea_whoami identity lookup (fixes #9)
Issue #9 requested getAuthenticatedUser and getCurrentUser in addition to whoami.
This adds the two aliased MCP tools and their corresponding unit tests.
2026-07-02 06:15:36 -04:00
sysadmin e842b60ad8 fix: document + tool the macOS com.apple.provenance workaround (#3)
Root cause: macOS Sequoia+ blocks Python.app from executing files carrying the
com.apple.provenance extended attribute. Files written by an agent/IDE terminal
get it (shell scripts and pre-session files do not). This is a macOS security
feature, not a bug in our code — so the fix is an operator workaround, not a
code change to the tools.

- scripts/clear-provenance: recursively removes ONLY com.apple.provenance under
  a path (default: repo root); tolerates files without it; leaves other xattrs
  intact; supports --dry-run. Advises running from a Full-Disk-Access terminal.
- README Troubleshooting section documenting the symptom, the helper, manual
  xattr equivalents, and the Full Disk Access alternative.

Narrow + macOS-specific; no auth/release/worktree/tracker/MCP behavior changed.

Tests: tests/test_clear_provenance.py (6 cases) — dry-run default/explicit path,
missing-path error, bad-flag/too-many-args exit 2, and that only
com.apple.provenance is targeted (not a blanket xattr clear). Dry-run only; no
real xattr mutation.

bash -n clean; py_compile mcp_server.py clean; full suite 319 passed / 0
failures; git diff --check clean; no secrets.

Closes #3.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 06:13:26 -04:00
sysadmin 3a246ab553 Merge pull request 'feat: automatically release status:in-progress on close and merge (#56)' (#57) from fix/issue-56-release-in-progress-on-close into master 2026-07-02 05:03:01 -05:00
sysadmin dbfa0fe188 fix: finalize PR 57 tracker cleanup safety 2026-07-02 06:00:06 -04:00
sysadmin b3728c54ce fix: target label delete and move helpers before entry point 2026-07-02 05:55:29 -04:00
sysadmin 4afada098c feat: automatically release status:in-progress on close and merge (#56) 2026-07-02 05:50:10 -04:00
sysadmin 6089ec724a Merge PR #53: Add release-tag automation helper
Merge reviewed PR #53 for issue #50. Validation passed and no release tags were created or pushed during review.
2026-07-02 04:40:07 -05:00
sysadmin c6c6e75af6 chore: remove deprecated recover-dirty-worktree.md (#46) 2026-07-02 05:38:29 -04:00
sysadmin 4e43347b2d feat: add scripts/release-tag automation helper (#50)
Automate the documented release-tag checklist (#48) without bypassing safety
gates.

scripts/release-tag:
- Requires a SemVer tag (vMAJOR.MINOR.PATCH); validates before any git/network.
- Fetch/prune first, then refuses: dirty worktree, non-master branch, local
  master != remote master, HEAD not on remote master, and an existing local or
  remote tag of the same name.
- Runs the full suite by default; --skip-tests is an explicit opt-out that warns.
- Creates an ANNOTATED tag (git tag -a), never lightweight.
- Safe by default: no push unless --push; --dry-run prints planned actions and
  changes nothing. Supports --notes-file <path> for the annotation message.
- Prints: commit, tag, tests_run, tag_created, tag_pushed.
- Env injection points for testing/CI: RELEASE_TAG_REMOTE, RELEASE_TAG_TEST_CMD.

tests/test_release_tag.py (14 cases): valid SemVer dry-run; invalid version;
dirty worktree; non-master; master/remote mismatch; existing tag; missing
notes-file; annotated-not-lightweight; no-push-without-flag; push-only-with-flag;
notes-file message; --skip-tests warns; default runs tests (fail blocks tag,
pass tags). Each test builds a throwaway repo with a LOCAL bare remote (cloned,
not pushed) and stubs the test command — no network, no real tags, no pushing
from the project repo.

Docs: reference scripts/release-tag from the runbook, SKILL, and the release-tag
template (script preferred; manual steps are the fallback).

Full suite 305 passed / 0 failures; bash -n clean; git diff --check clean; no
secrets.

Closes #50. Refs #48.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 05:37:41 -04:00
sysadmin ec9ddb09a7 docs: closed-not-merged PR reconciliation rules (#51)
Documents and enforces rules for closed-not-merged PR reconciliation, direct-master-push prevention, and issue label cleanup.

Rules added:
- Explicit definitions for Merged, Landed, Closed-not-merged, and Reconciled.
- A PR is done only when Gitea reports it merged or reconciliation proves content is present on master.
- Direct push to master is forbidden except as a documented recovery exception.
- PRs closed but not merged trigger the reconciliation process.
- Branch and worktree cleanup is forbidden until merge or reconciliation is confirmed.
- Final reports require PR metadata and Git content verification.

Closes #51.
2026-07-02 04:16:07 -04:00
sysadmin f18cecc998 feat: enforce issue-linked branches + document versioning/tagging policy (#48)
Formalize the branch↔issue relationship and add a release/version-tagging policy.

Branch/issue linkage:
- scripts/worktree-start now validates branch names: implementation branches
  must match (fix|feat|docs|chore)/issue-<number>-<slug>; review branches
  review/pr-<number>-<slug>. Untraceable names are rejected with a clear error
  (exit 2). New --allow-unlinked override for genuine exceptions. --dry-run
  preserved.
- Documented issue → branch → worktree → PR → cleanup traceability in the
  runbook and the portable SKILL, including the claim-comment convention and
  Closes #n / Refs #n PR-body usage.
- Noted that Gitea exposes no native issue→branch API field (only a PR head
  branch), so linkage is enforced via branch name + claim comment + PR body +
  cleanup.

Versioning / tagging policy (docs only; no release automation yet):
- SemVer vMAJOR.MINOR.PATCH (v0.x.y while unstable) with PATCH/MINOR/MAJOR bump
  rules.
- Annotated tags only, from the exact commit on remote master, only after the
  full suite passes, with release notes referencing merged PRs/issues. Never tag
  feature branches, dirty worktrees, unreviewed/self-authored work, or commits
  not on remote master.
- Release runbook in the runbook + SKILL, plus a new
  skills/llm-project-workflow/templates/release-tag.md prompt template.

Tests: worktree-start branch validation — accepts fix/feat/docs/chore/issue-*
and review/pr-*, rejects fix/random-name / my-branch / non-numeric issue,
honors --allow-unlinked, preserves --dry-run. Full suite 291 passed / 0 failures;
bash -n clean; git diff --check clean; no secrets.

Release-tag automation (a scripts/release-tag helper) intentionally deferred to a
later issue to keep this diff narrow and testable.

Closes #48. Refs #38, #39, #46.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 04:08:42 -04:00
sysadmin 00ec883014 docs: add portable llm-project-workflow skill + templates (#46)
Extract the project's operating rules into a reusable, project-agnostic skill
so any repo can adopt the same safe LLM workflow.

- skills/llm-project-workflow/SKILL.md: issue-first; isolated branch worktrees
  (main checkout = orchestration only); distinct author/reviewer identities and
  profile safety (secrets by reference only; stop if authenticated user == PR
  author); branch naming; start/review/merge/cleanup workflows; fail-closed
  cases; recovery patterns; and an "Adapting to a project" table for the
  forge-specific names.
- templates/: copy/paste prompts for start-issue, review-pr, merge-pr,
  recover-bad-state, worktree-cleanup.
- Link the skill from README.md and docs/llm-workflow-runbooks.md (the runbook
  is framed as the Gitea-specific application of the portable skill).

Docs-only; no code, no secrets, safe placeholder examples only. No change to
MCP runtime, Gitea API, credential storage, or worktree helpers.

Checks: full suite 287 passed / 0 failures; git diff --check clean; secret scan
of skills/ clean.

Closes #46. Refs #38, #39.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 04:07:14 -04:00
sysadmin 92b449f080 docs: add portable LLM workflow skill 2026-07-02 03:17:02 -04:00
sysadmin 7bcdd44fe5 fix: validate gitea_edit_pr no-fields before authentication (#43)
gitea_edit_pr called _auth() (and resolved the remote) before checking whether
any editable field was provided, so a pure validation error (no fields) surfaced
as a RuntimeError "no credentials" in environments without Gitea auth — making
test_edit_pr_no_fields_raises depend on credentials/network/env.

Move the payload build + no-fields ValueError ahead of _resolve/_auth/URL setup.
Behavior is unchanged when fields are provided (same _resolve → _auth → audited
PATCH path). No change to auth, retry/backoff, audit, config profiles, or
worktree helpers.

Tests: add test_edit_pr_no_fields_validates_before_auth asserting the no-fields
path raises ValueError and calls neither get_auth_header nor api_request (even
with auth mocked to None). Existing edit-PR tests unchanged.

Full suite passes with no Gitea credentials (287 passed, 0 failures) — the
no-fields test no longer depends on the environment.

Closes #43.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 03:08:39 -04:00
sysadmin 46db3f73e8 feat: complete isolated-worktree helpers — worktree-review, worktree-clean, tests (#39)
Finishes the isolated-worktree standard begun in #38 (which merged the
branches/ gitignore, runbook, and scripts/worktree-start). Adds the two
remaining helpers and their tests.

- scripts/worktree-review: isolated DETACHED review worktree under
  branches/review-<branch> (fetch/prune first, refuse to overwrite, print path,
  --dry-run). Detached so a reviewer cannot accidentally commit and review work
  never blocks the author's implementation folder.
- scripts/worktree-clean: the only deleting helper — removes a branches/
  worktree after merge/close, refuses a dirty worktree (no --force), optionally
  safe-deletes a merged branch (git branch -d), fetch/prune first, --dry-run.
  Deletes nothing unless explicitly invoked.
- tests/test_worktrees.py: path generation + refuse-to-overwrite for all three
  helpers via --dry-run (no real worktrees/branches/network/deletions).
- runbook: reference worktree-review / worktree-clean and the --dry-run flag.

Checks: bash -n clean on all three scripts; git diff --check clean; full suite
286 passed, 0 failures.

Closes #39. Follow-up to #38.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 02:54:50 -04:00
sysadmin 7e8256cfaf docs: standardize isolated LLM worktrees (#38) 2026-07-02 02:42:52 -04:00
sysadmin 69d4edf37d fix: single-key TTY menu UX for the Gitea config menu (#36)
Make the interactive profile menu feel like a real terminal menu, via a new
injectable MenuIO abstraction (no menu logic change, no auth/secret-storage
change).

- Single-key top-level actions in a TTY (termios/tty raw read); no Enter
  needed. Non-TTY / test runs fall back to line input.
- Enter backs out: Enter (or 0) on the main menu quits; Enter cancels any
  submenu/profile prompt and returns.
- Profile chooser: everywhere a profile is needed, show a numbered list and
  pick by key (1-9), with an explicit 'm) type a name manually' path and Enter
  to cancel. Empty config handled gracefully.
- Clear screen before redrawing the main menu and chooser — TTY only; never
  emits clear codes in non-TTY/test runs.
- Result actions (validate/test-auth/whoami/eligibility) print a concise result
  then pause for a keypress in a TTY; non-TTY never blocks.

Helpers: read_key (via default_io) / choose_menu_option / choose_profile /
clear_screen / pause_for_key, plus MenuIO(is_tty, clear_enabled). TTY detected
with sys.stdin.isatty() and sys.stdout.isatty(); stdlib only.

Safety unchanged: no tokens/passwords printed, no raw config dumps, no
.env.personal, no change to auth behavior or secret storage.

Tests: rewrote menu tests around a scripted _FakeIO (no real terminal): single-
key select + clear, main-menu Enter/0 quit, submenu Enter cancel (no change),
chooser lists/selects/no-profiles/manual/out-of-range, non-TTY line fallback,
clear-only-when-enabled, pause never hangs non-TTY, and add-flow proving the
token value never reaches disk or stdout.

Docs: runbook note on single-key nav / Enter back-out / numbered chooser.
scripts/gitea-config-menu unchanged.

Closes #36. Refs #31, #34.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 02:34:16 -04:00
sysadmin 5272e071e1 fix: add Gitea config menu wrapper (#34) 2026-07-02 01:51:53 -04:00
sysadmin 389382c2bd docs: add LLM-operated Gitea workflow runbooks (#17)
Add docs/llm-workflow-runbooks.md — the final roadmap #10 deliverable:
operational runbooks for LLM-operated Gitea workflows, built on the shipped
canonical profiles + interactive menu + gated review/merge + audit logging.

Covers:
- Principle: the profile is the role, not the LLM (task-scoped, not assigned).
- Canonical config: GITEA_MCP_CONFIG / GITEA_MCP_PROFILE, version, profiles,
  keychain + env auth references, precedence, legacy env-only fallback.
- Interactive menu (python gitea_config.py menu): create author/reviewer
  profiles, generate Claude/Gemini/Codex launcher snippets, validate auth,
  check PR reviewer eligibility.
- Thin-launcher pattern: LLM configs carry only command/args + the two
  GITEA_MCP_* vars — never raw tokens/passwords.
- Migration away from duplicated GITEA_USER_*/GITEA_PASS_*/GITEA_SITE_* blocks;
  secrets referenced by keychain id or env var name only.
- Per-workflow runbooks (create issue/children, implement+PR, review/request-
  changes/approve, merge, close-after-merge, stop-on-blocker) with safe prompts.
- Fail-closed behavior table (unknown identity/profile, self-author, moved head,
  unexpected files, detected secrets, production/deploy) and no self-review/merge.

Docs-only: no implementation code. Safe placeholder examples only (no real
tokens, passwords, usernames, or private config). README links the new runbook.

Closes #17. Refs #10.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 01:24:33 -04:00
sysadmin 835fbbf324 feat: interactive setup menu for canonical Gitea MCP profiles (#31)
Add an interactive utility so users create/edit/validate canonical runtime
profiles and generate safe LLM launcher snippets without hand-editing JSON or
pasting tokens into Claude/Gemini/Codex configs.

Run: `python gitea_config.py menu` (or `python gitea_config_menu.py`).

gitea_config.py — pure, testable authoring helpers:
- is_valid_profile_name, build_profile, keychain_auth/env_auth, empty_config
- validate_config (reports missing base_url/auth, inline token/password — never
  echoing the secret value)
- add_profile (preserves existing, rejects dup/invalid name/missing base_url),
  upsert_profile, remove_profile
- save_config: mkdir parents + atomic temp-then-os.replace, pretty JSON
- launcher_entry: thin MCP entry (command/args + GITEA_MCP_CONFIG/PROFILE only)
- keychain_set: store a token via `security add-generic-password` (token passed
  as an arg, never returned/printed/logged; injectable runner)
- `menu` __main__ dispatch

gitea_config_menu.py — interactive loop with fully injectable IO/secret/HTTP/
keychain so it is testable without a real terminal, keychain, or network:
- list / add / edit / remove / validate profiles
- test authentication + show authenticated user (calls /user only on request)
- reviewer-eligibility helper (authenticated user vs PR author, open state) —
  read-only, never approves/merges
- launcher snippets for Claude / Gemini / Codex (no secrets)

Security: tokens are never written to profiles.json, launcher snippets, logs,
or errors — only keychain ids / env var names are stored. Backwards compatible:
menu is optional; env-only mode and MCP server startup are unchanged.

Tests: tests/test_config_menu.py (21 cases) — name validation, preserve-on-add,
dup/invalid/missing-field rejection, atomic write (+ replace-failure leaves the
original intact, no temp debris), keychain_set stores-without-printing, launcher
snippets secret-free, eligibility eligible/self-author/closed, and a full menu
add→list→quit flow proving the token value never reaches disk or stdout.

Stacked on #30 (canonical profiles); base branch feat/json-runtime-profiles.
Refs #10, #19. Closes #31.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 23:32:24 -04:00
sysadmin b88ca0c929 feat: canonical shared runtime-profiles config with typed auth refs (#19)
Rework the JSON runtime-profile config from the earlier ad-hoc schema
(profiles + token_env) to the canonical single-file model in #19, so every LLM
launcher can reference one shared Gitea profiles file instead of duplicating
GITEA_USER_*/GITEA_PASS_* blocks or embedding tokens.

Canonical schema (gitea_config.py):
- top-level "version" (1) + "profiles" map.
- each profile: base_url, username, default_owner, execution_profile, and a
  typed auth reference:
    { "type": "keychain", "id": "..." }   -> macOS keychain (security(1))
    { "type": "env",      "name": "..." } -> named environment variable
- inline "token"/"password" keys are rejected (never accepted or echoed).
- select via GITEA_MCP_CONFIG (path) + GITEA_MCP_PROFILE (name).

gitea_auth integration:
- get_profile() overlays env over the selected profile (env wins; JSON fills
  the rest); profile_name <- execution_profile; token_source_name <- the
  non-secret auth reference name (env var name or "keychain:<id>"); now also
  surfaces username + default_owner.
- get_auth_header() resolves the profile's auth reference (env/keychain) as a
  token fallback after explicit env tokens; a ConfigError there fails closed.

Security / safety:
- Secrets referenced only (keychain id / env name); token values never stored
  in or returned as metadata. Errors never print file contents, tokens, or
  passwords (JSONDecodeError context suppressed).
- Missing file / invalid JSON / unsupported version / unknown-or-unset profile
  / unresolvable secret reference all raise a clear, safe ConfigError.
- No network calls during config parsing; keychain lookup is on-demand and
  injectable for tests.
- Backwards compatible: GITEA_MCP_CONFIG unset => legacy env-only mode
  (existing get_profile/get_auth_header tests unchanged).

Docs: README canonical-profile + thin-launcher (Claude/Gemini/Codex) sections
and a migration note away from duplicated GITEA_PASS_* blocks; .env.example and
gitea-mcp.example.json updated to the canonical shape (safe placeholders only).

Tests: tests/test_config.py (31 cases) — legacy env-only, JSON selection,
multiple profiles, missing/unset profile, invalid JSON, unsupported version,
env-override precedence, keychain + env auth-reference parsing and resolution,
missing-secret errors, inline token/password redaction, and no-network parse.

Refs #10. Completes the closed #19 (env-based profiles) by adding the canonical
shared-file model. Supersedes this PR's earlier simpler JSON schema.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 23:04:03 -04:00
sysadmin 3aaba73127 feat: JSON multi-profile runtime config for Gitea MCP (roadmap #10)
Let one MCP server select among named Gitea runtime profiles from a JSON file
instead of editing code or juggling many .env files:

    GITEA_MCP_CONFIG=/path/to/gitea-mcp.json
    GITEA_MCP_PROFILE=dev

- New gitea_config.py: load/validate the JSON, select the named profile, and
  resolve its token by env-var reference. Profiles supply base_url,
  profile_name, token_env, owner/repo, allowed/forbidden operations, and audit
  label.
- gitea_auth.get_profile() now overlays env over the selected JSON profile:
  explicit env vars win, the JSON profile fills only what env leaves unset.
- gitea_auth.get_auth_header() gains a JSON token_env fallback after explicit
  env tokens (env still wins).

Security / safety:
- Tokens are referenced by env-var NAME (token_env); an inline "token" is
  rejected and never echoed. The value is never stored in or returned as
  profile metadata.
- Fail-safe errors: missing file / invalid JSON / unknown or unset selected
  profile raise a clear ConfigError that never prints file contents or tokens
  (JSONDecodeError context is suppressed so the raw file text can't surface).
- No network calls during config parsing.
- Real config files are gitignored (gitea-mcp*.json), example kept.

Backwards compatible: with GITEA_MCP_CONFIG unset, behaviour is exactly the
prior env-only behaviour (all existing get_profile/get_auth_header tests pass
unchanged).

Docs: README JSON-profiles section + env table rows, .env.example placeholders,
gitea-mcp.example.json.
Tests: tests/test_config.py (22 cases) — env-only, selection, multiple
profiles, env-override precedence, missing file, invalid JSON, missing/unset
profile, inline-token rejection + redaction, and no-network-during-parse.

Refs #10. Note: issue #19 (env-based profiles) was already implemented and
closed; this JSON-file capability is adjacent new scope tracked under the
roadmap rather than reopening #19.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 22:44:31 -04:00
sysadmin d4251c5c47 Merge pull request '#18: Audit-log Gitea MCP mutating actions with execution profile metadata' (#29) from feat/issue-18-audit-log-mutating-actions into master
Reviewed-on: #29
2026-07-01 21:33:22 -05:00
sysadmin c3c48fb7c2 feat: audit-log Gitea MCP mutating actions with profile metadata (#18)
Add durable, opt-in audit logging for every mutating Gitea MCP action so an
operator can see which execution profile and authenticated Gitea user
performed (or was blocked from / failed) each mutation.

- New gitea_audit.py: pure, no-network module — recursive secret redaction
  (token/password/authorization keys; token/Basic/Bearer value runs),
  build_event (timestamp, action, result, profile, audit label, authenticated
  username, repo, issue/PR, target branch, head SHA, redacted request
  metadata), and an append-only JSON Lines sink.
- mcp_server.py: _audit helper + _audited context manager (simple mutations)
  and an _audit_pr_result decorator (gated review/merge tools, reading their
  own result dict) wired into create_issue, create_pr, edit_pr, close_issue,
  commit_files, delete_branch, create_label, set_issue_labels, mark_issue
  (label/unlabel), gitea_submit_pr_review, and gitea_merge_pr.
- Outcomes recorded as allowed/blocked/failed/succeeded; blocked and failed
  eligibility checks are logged, not just successes.

Off by default: records are written only when GITEA_AUDIT_LOG is set. When it
is unset every audit path short-circuits — no records, no extra API calls — so
existing tool behaviour and API call sequences are unchanged. Auditing never
raises; sink writes are best-effort. Tokens are never written.

Docs: README env table + audit note, .env.example placeholder.
Tests: tests/test_audit.py (19 cases) — redaction, event build, sink writes,
per-tool success/failure/blocked records, secret-free output, off-by-default
no-op, and audit-failure-never-breaks-action.

Closes #18

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 22:20:51 -04:00
sysadmin 20dd717b9c Merge pull request 'v0.3.32: Honor Retry-After on HTTP 429 with jittered exponential backoff' (#28) from fix/v0.3.32-retry-after-backoff into master
Reviewed-on: #28
2026-07-01 21:04:34 -05:00
sysadmin 1b3c961ff2 feat: honor Retry-After on HTTP 429 with jittered exponential backoff (#27)
api_request now retries HTTP 429 responses instead of failing immediately:

- Parse and honor a valid Retry-After header (seconds or HTTP-date).
- Fall back to full-jitter capped exponential backoff when the header is
  missing or invalid.
- Bound retries by max_retries and delay by max_delay (env-overridable via
  GITEA_MAX_RETRIES / GITEA_RETRY_BASE_DELAY / GITEA_RETRY_MAX_DELAY) — no
  infinite loops.
- Non-429 errors and successful responses are unchanged.

Sleep, randomness, and clock are injectable so retry timing is tested
deterministically. Adds tests/test_retry_backoff.py (23 cases).

Closes #27

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 21:28:51 -04:00