# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Package Overview

GitHub CLI tool for searching Terraform files across a GitHub organization and creating GitHub issues for offboarding cleanup.

## Running tests

```bash
cd lambda/github_cli && uv run pytest ./tests/                                        # All tests
cd lambda/github_cli && uv run pytest ./tests/unit/                                   # Unit tests only
cd lambda/github_cli && uv run pytest ./tests/integration/                            # Integration tests only
cd lambda/github_cli && uv run pytest ./tests/unit/test_github_client.py              # Single file
```

Tests are organized into two directories under `tests/`:
- `unit/` — fast, isolated tests (`test_github_client.py`, `test_github_config.py`)
- `integration/` — handler-level tests with mocked HTTP (`test_handler_github.py`, `conftest.py`)

## CLI usage

```bash
# Search for text across all Terraform files in an org
cd lambda/github_cli && uv run github-cli search-text-in-org --org theorchard --search-text "john.doe@example.com"

# With optional repo filter
cd lambda/github_cli && uv run github-cli search-text-in-org --org theorchard --search-text "john.doe" --repo-filter terraform-infra

# Create a GitHub issue for offboarding cleanup
cd lambda/github_cli && uv run github-cli --repo theorchard/terraform-infra offboard-user-with-copilot \
  --issue-description "Remove all Terraform references for this user." \
  --user-email john.doe@example.com \
  --user-full-name "John Doe"

# Create a single issue directly (debugging)
cd lambda/github_cli && uv run github-cli --repo theorchard/terraform-infra create-issue \
  --title "Cleanup" --body "Remove references" --assignees octocat
```

Global options `--repo` and `--base-branch` must come **before** the subcommand. Defaults: `theorchard/terraform-infra` / `master`.

Requires env var: `GITHUB_TOKEN` (token value locally; Secrets Manager secret name in Lambda).

## Lambda handler

`github_client/app.py` exposes a `handler(event, context)` function for AWS Lambda. It routes on `event["action"]`:

| Action | Description |
|--------|-------------|
| `search-text-in-org` | Runs a quoted search per identifier form derived from `email`/`full_name`; returns deduplicated `terraform_hits`, each with a `confidence` (`high`/`low`) |
| `offboard-user-with-copilot` | Creates a cleanup issue per affected repo summarising `auth0_results` and Terraform hits. The `operation` field (`offboard` \| `suspend`, default `offboard`) drives the issue title/body wording — `suspend` issues instruct gating access rather than deleting resources. The action name is retained for orchestration compatibility even though it now serves both operations. |

Both actions accept `dry_run: true` to skip write operations. The `search-text-in-org` action is read-only so `dry_run` has no effect there.

## Key Architecture

### Search scope
All code searches are scoped to `language:Terraform` files via the GitHub Code Search API, and the `theorchard/collab` repo is excluded from results. There is no way to search other file types through this client without modifying the query in `github_client.py`.

### Per-term quoted search & confidence
`search-text-in-org` runs **one quoted phrase search per identifier form** and merges the results, deduplicating by `html_url`. The forms are: the full email and the email local-part (tagged `confidence: high`), plus the first-initial+lastname form e.g. `jdoe` (tagged `confidence: low`). Each `TerraformHit` carries this `confidence`.

Do **not** combine terms into a single unquoted boolean-`OR` query: the legacy `/search/code` API tokenizes such a query into bare tokens and matches nothing for dotted/`@` identifiers. (The old `search_combined_in_org` method was removed for this reason.) A URL matched by any high term stays `high` even if a low term also matches it (high terms are searched first and never downgraded).

The `jdoe` form collides with unrelated resource names (e.g. it returned ~48 mostly-irrelevant hits in testing vs. 11 real ones for the email), so its hits render under a separate **"Possible matches (low confidence)"** heading in the offboarding issue body.

### repo_filter must be fully qualified
`repo_filter` must be `owner/repo` (e.g. `theorchard/terraform-infra`). A bare repo name produces `repo:<name>`, which GitHub silently matches to nothing. `_build_org_query` raises `ValueError` for a `repo_filter` without a `/`.

### Org-wide pagination
`search_text_occurrences_in_org` paginates through all results using `page`/`per_page` params (30 per page). It stops when a page returns fewer items than requested.

### Rate limit handling
After each response, the client checks `X-RateLimit-Remaining`. When it drops to 1 or 0, it sleeps until `X-RateLimit-Reset` (+ 10 seconds buffer) before retrying the current page.

### Shared CLI state
`--repo` and `--base-branch` are global options set in the `@cli.callback()` and stored in a module-level `_State` object, making them available to subcommands like `offboard-user-with-copilot`. Default repo is `theorchard/terraform-infra`.

### Issue creation
`create_issue` creates a standard GitHub issue (no automatic assignee). Engineers can pick up the issue or assign Copilot manually if a license is available.
