# inclusive-language Administrator & Developer Guide

Architecture, configuration, and developer reference for
`cisco-open/inclusive-language`. The repository is a small collection of
**rule files and helper scripts** that drive third-party linters; it is not
an executable on its own. This guide covers what each artifact does, how to
deploy them across an organization, and how to extend or modify them.

The User Guide explains how to scan files. This guide explains how to operate
the tooling at scale, run security and capacity reviews on it, and contribute
new rules or scripts.

---

## 1. Architecture Overview

`inclusive-language` is a 17-file, single-folder repository (`small`
complexity tier, 10,963 source characters total). Every file lives at the
project root — there are no subdirectories. The repository ships **three
independent components** that share only a name and a goal:

```text
                              ┌────────────────────────┐
   project source tree   ────►│ alex (Node, npm)       │  prose linter
   (Markdown, code, docs)     │ + alex-race-rules.yaml │  warnings/CI gate
                              │ + .alexrc.js           │
                              │ + .alexignore          │
                              └────────────────────────┘

                              ┌────────────────────────┐
   project source tree   ────►│ woke (Go binary)       │  source-code linter
   (any text, any line)       │ + cisco-rules.yaml     │  warnings/CI gate
                              └────────────────────────┘

                              ┌────────────────────────┐
   GitHub org (any repo) ────►│ gh_search.py           │  CSV findings to
                              │ gh_default_branch.py   │  stdout + log file
                              │ + config.py            │
                              └────────────────────────┘
                                       ▲
                                       │
                              ┌────────────────────────┐
                              │  github.com REST API   │
                              │  (or Enterprise URL)   │
                              └────────────────────────┘
```

> [!NOTE]
> The three components are completely decoupled. You can adopt the alex rule
> pack without ever touching woke, run the Python helpers without installing
> alex or woke, or use only `cisco-rules.yaml` against a fresh woke install.
> The repository is structured as an "à la carte" inclusive-language
> toolkit, not a unified product.

### 1.1 Component inventory

| Component | Path | Role |
| --- | --- | --- |
| alex rule pack | `alex-race-rules.yaml` | 13 term-pair entries consumed by alex |
| alex runtime config | `.alexrc.js` | `allow` list + `noBinary: true` flag |
| alex ignore list | `.alexignore` | gitignore-syntax path exclusions |
| woke rule pack | `cisco-rules.yaml` | 4 rule entries consumed by woke |
| GitHub keyword search | `gh_search.py` | PyGitHub-based code search (Enterprise) |
| Default-branch audit | `gh_default_branch.py` | Lists repos with `master` default branch |
| Helper script config template | `exampleconfig.py` | Sample of `config.py` for the helpers |
| Python dependency manifests | `Pipfile`, `requirements.txt` | Pin pygithub, requests, cffi, etc. |
| Node dependency manifest | `package.json` | Pins `alex@^11.0.1`, defines `lint-language` script |
| Project metadata | `LICENSE`, `NOTICE`, `CODE_OF_CONDUCT.md`, `SECURITY.md`, `README.md`, `bash-script-inventory.md` | Standard OSS hygiene files |

### 1.2 Technology stack

| Layer | Technology | Version pin |
| --- | --- | --- |
| Prose linter | alex | `^11.0.1` (npm) |
| Source-code linter | woke | unpinned (binary, install latest) |
| Helper scripting language | Python | `3.9.6` (Pipfile) |
| GitHub API client | PyGitHub | `1.59.0` |
| Network stack | requests | `2.31.0` |
| TLS | certifi | `2023.7.22` |
| C extension shim | cffi + pycparser | `1.15.1` / `2.21` |
| Crypto | pynacl, pyjwt | `1.5.0` / `2.8.0` |
| Deprecation handling | deprecated, wrapt | `1.2.14` / `1.15.0` |
| Encoding | charset-normalizer, idna, urllib3 | `3.2.0` / `3.4` / `2.0.4` |

### 1.3 Data flow

The three components have three distinct flows:

```text
alex flow:
  developer types `npx alex .`
    └─► alex resolves `.alexrc.js` from CWD
          └─► alex reads `.alexignore` for excluded paths
                └─► alex walks remaining files, applies built-in
                    retext-equality rules + `alex-race-rules.yaml`
                      └─► writes warnings to stdout, exits 0 or 1

woke flow:
  developer types `woke --rule-in cisco-rules.yaml .`
    └─► woke loads its default rules + cisco-rules.yaml (merged by name)
          └─► woke walks files, scans every line
                └─► writes findings to stdout, exits 0 or 1

helper-script flow:
  developer types `python gh_search.py`
    └─► script imports config.py for token/endpoint/orgname
          └─► constructs Github(token) client (PyGitHub)
                └─► reads rate limit, sleeps if exhausted
                      └─► issues `search_code` query: "<keyword>" org:<org>
                            └─► iterates results (with 2s sleep), prints CSV lines
                                  └─► tee'd to log file via stdlib logging
```

> [!NOTE]
> All flows are **stateless**. None of the three components persists data
> between runs. The Python helpers' rate-limit awareness lives entirely in
> the live `Github` client object — once the script exits, the rate-limit
> bookkeeping is forgotten and the next run starts fresh.

---

## 2. System Requirements

### 2.1 Per-component requirements

| Component | OS | Runtime | RAM | Network |
| --- | --- | --- | --- | --- |
| alex | Linux, macOS, Windows | Node.js 18+ | 256 MB | none after install |
| woke | Linux, macOS, Windows | Go binary (no runtime) | 128 MB | none |
| `gh_*.py` helpers | Linux, macOS, Windows | Python 3.9.6 (per Pipfile) | 256 MB | HTTPS to GitHub API |

### 2.2 Network reachability for the helper scripts

The Python helpers issue HTTPS requests to one of:

- `https://api.github.com/` — public GitHub
- A self-hosted enterprise URL such as `https://github.example.com/api/v3/`

Both flow through `pygithub` → `requests` → `urllib3`. Air-gapped
environments that only proxy GitHub Enterprise traffic must whitelist (sorry —
allowlist) the configured endpoint and provide outbound HTTPS on port 443.

### 2.3 Authentication

| Component | Auth required? | Mechanism |
| --- | --- | --- |
| alex | No | Operates on local files |
| woke | No | Operates on local files |
| `gh_search.py` | Yes | GitHub PAT, read scope sufficient |
| `gh_default_branch.py` | Yes | GitHub PAT, read scope sufficient |

### 2.4 Disk

The repository itself is < 1 MB on disk after a fresh clone. After
`pip install -r requirements.txt` and `npm install`, expect:

- Python venv: ~25 MB (pygithub + dependencies)
- Node `node_modules/`: ~12 MB (alex + retext-equality + dependencies)
- woke binary: ~11 MB (single Go binary)

---

## 3. Configuration Reference

Every setting that any of the three components supports.

### 3.1 `.alexrc.js` — alex runtime configuration

Located at the project root. CommonJS export.

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `allow` | `string[]` | `[]` | Rule IDs to skip globally. Cisco ships `["cisco-rules"]` as a placeholder. |
| `noBinary` | `boolean` | `false` | When `true`, suppresses warnings about gendered binary pairs (`he/she`, `him/her`). Cisco ships `true`. |

Other keys alex supports (not used by Cisco's shipped config) include
`profanitySublevel` (number, 0/1/2 — strictness of the profanity rule),
`reset` (boolean — start from no enabled rules), and `deny` (string[] — force
a rule on regardless of defaults). These appear in the upstream
[alex documentation](https://github.com/get-alex/alex#configuration).

### 3.2 `.alexignore` — alex path exclusions

Plain-text gitignore-style patterns. One pattern per line. `#` for comments.
Matched relative to the directory containing `.alexignore`.

### 3.3 `alex-race-rules.yaml` — full term-pair reference

The complete shipped mapping (13 entries):

| Inconsiderate | Considerate alternatives | Note |
| --- | --- | --- |
| `master` | `primary`, `lead`, `hub`, `reference` | Avoid using the term `master`; these suggestions are for the computer term |
| `masters` | `primaries`, `hubs`, `references` | Avoid using the term `master`; these suggestions are for the computer term |
| `slave` | `secondary`, `worker`, `replica`, `node` | Avoid using the term `slave`; these suggestions are for the computer term |
| `slaves` | `secondaries`, `workers`, `replicas`, `nodes` | Avoid using the term `slave`; these suggestions are for the computer term |
| `blackhat` | `unethical hacker`, `malicious actor` | (no note) |
| `whitehat` | `ethical hacker`, `security researcher` | (no note) |
| `nonwhite`, `non white` | `person of color`, `people of color` | (no note) |
| `blacklist`, `black list` | `blocklist`, `wronglist`, `banlist`, `deny list` | Replace racially-charged language |
| `blacklisted` | `blocklisted`, `wronglisted`, `banlisted`, `deny-listed` | Replace racially-charged language |
| `blacklisting` | `blocklisting`, `wronglisting`, `banlisting`, `deny-listing` | Replace racially-charged language |
| `whitelist`, `white list` | `passlist`, `alrightlist`, `safelist`, `allow list` | Replace racially-charged language |
| `whitelisted` | `passlisted`, `alrightlisted`, `safelisted`, `allow-listed` | Replace racially-charged language |
| `whitelisting` | `passlisting`, `alrightlisting`, `safelisting`, `allow-listing` | Replace racially-charged language |

Schema constraints:

- Every entry must declare `type: basic` (the only `type` Cisco ships).
- `inconsiderate` and `considerate` are required; both must be lists, even if a single value.
- `note` is optional free text. When present, alex includes it in the warning output.

### 3.4 `cisco-rules.yaml` — full woke rule reference

The four shipped rules:

| `name` | `terms` | `alternatives` |
| --- | --- | --- |
| `whitelist` | `whitelist` | `permit(list)`, `allow(list)` |
| `blacklist` | `blacklist` | `ban(list)`, `block(list)` |
| `master` | `master` | `primary`, `control`, `active`, `source`, `parent`, `main` |
| `slave` | `slave` | `secondary`, `subordinate`, `data`, `standby`, `subsidiary`, `replica`, `worker`, `child`, `helper`, `follower` |

Schema constraints (from woke's documentation):

- Top-level key is `rules:` (a list).
- Each rule has `name` (string), `terms` (list of strings), `alternatives` (list of strings).
- woke supports an optional `severity` (`error` | `warning` | `info`) and an optional `note` per rule. Cisco's shipped file uses neither — every rule defaults to woke's default severity (warning).

### 3.5 `package.json` — Node dependency

```json
{
  "devDependencies": {
    "alex": "^11.0.1"
  },
  "scripts": {
    "lint-language": "alex"
  }
}
```

| Key | Value | Purpose |
| --- | --- | --- |
| `devDependencies.alex` | `^11.0.1` | The only npm dep. `^` allows 11.x but not 12.0. |
| `scripts.lint-language` | `alex` | `npm run lint-language` invokes `node_modules/.bin/alex`. |

### 3.6 `Pipfile` / `requirements.txt` — Python dependencies

The Pipfile is authoritative; `requirements.txt` is the autogenerated sibling
(per the comment header). Both pin the same versions:

| Package | Version | Used for |
| --- | --- | --- |
| `pygithub` | `1.59.0` | GitHub API client |
| `requests` | `2.31.0` | HTTP transport |
| `urllib3` | `2.0.4` | HTTP/connection pool |
| `certifi` | `2023.7.22` | TLS root certs |
| `idna` | `3.4` | Internationalized domain names |
| `charset-normalizer` | `3.2.0` | Encoding detection |
| `cffi` | `1.15.1` | C bindings (transitive) |
| `pycparser` | `2.21` | cffi parser |
| `pynacl` | `1.5.0` | NaCl crypto bindings |
| `pyjwt` | `2.8.0` | JWT token handling |
| `deprecated` | `1.2.14` | Deprecation decorator |
| `wrapt` | `1.15.0` | Function-wrapping helper |

Python version: `python_version = "3.9.6"`. Newer Python releases work but
are not the tested target.

> [!IMPORTANT]
> The requirements.txt file uses `=` instead of `==` (e.g.,
> `cffi = "==1.15.1"`). This is the **Pipfile-style** syntax left in by the
> autogeneration step — pip itself accepts `==1.15.1` (note the doubled `=`)
> in regular requirements files. If you copy the file into a different
> tooling chain, normalize the assignment operator before piping it to
> `pip install -r`.

### 3.7 `config.py` — helper-script credentials

| Constant | Type | Read by | Required? |
| --- | --- | --- | --- |
| `gh_api_key` | `str` | `gh_default_branch.py`, `gh_search.py` | Yes |
| `gh_api_endpoint` | `str` | `gh_search.py` (Enterprise mode) | Required when running Enterprise mode |
| `gh_orgname` | `str` | `gh_default_branch.py`, `gh_search.py` | Yes |

Loaded via `import config`. Both scripts read `config.gh_api_key` at module
import time; if `config.py` is absent or any of the constants is missing, the
scripts fail with `ImportError` or `AttributeError` immediately at startup.

---

## 4. Deployment & Scaling

### 4.1 Single-repo deployment (most common)

Drop the four alex artifacts into the repo's root, install alex as a
dev-dependency, wire up CI:

```bash
cp /path/to/inclusive-language/.alexrc.js .
cp /path/to/inclusive-language/.alexignore .
cp /path/to/inclusive-language/alex-race-rules.yaml .
cp /path/to/inclusive-language/package.json .   # or merge with existing
npm install
```

For woke, copy `cisco-rules.yaml` into a `.woke/` subdirectory and reference
it in CI:

```bash
mkdir -p .woke
cp /path/to/inclusive-language/cisco-rules.yaml .woke/
```

### 4.2 Multi-repo deployment (org-wide rollout)

Two patterns work well at scale.

**Pattern A — Vendor the rules into every repo.**
Each repo carries its own copy of the YAML rule files. Updates propagate via
a script that opens PRs across all repos. Pros: each repo is self-contained;
no runtime dependency on a central service. Cons: rule changes require N
PRs.

**Pattern B — Centralized rule pack on a CDN or shared registry.**
Publish the YAML files to a known URL (internal artifact server, GitHub
Pages, S3) and have CI pipelines fetch them at runtime:

```yaml
- name: Fetch Cisco inclusive rules
  run: curl -sSfL https://internal.example.com/inclusive/alex-race-rules.yaml -o alex-race-rules.yaml
- name: Run alex
  run: npx alex .
```

Pros: one place to change rules. Cons: CI fails open if the CDN goes down;
auditing what rules ran for a historical CI build is harder.

> [!TIP]
> For most orgs, **Pattern A** is the right answer. Run a single
> `gh search ... | xargs -I {} gh pr create ...` script monthly to update
> any drifted copies, and gate every PR on the same CI workflow that runs
> alex.

### 4.3 GitHub Actions deployment template

Reusable workflow with both linters:

```yaml
name: Inclusive Language
on:
  pull_request:
    branches: [main]

jobs:
  alex:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npx alex .

  woke:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install woke
        run: |
          curl -sSfL https://git.io/getwoke | bash -s -- -b /usr/local/bin
      - name: Run woke
        run: woke --rule-in .woke/cisco-rules.yaml .
```

### 4.4 Scaling the GitHub helper scripts

The helper scripts are intentionally synchronous and rate-limit-aware. They
do not parallelize. For large orgs (1000+ repos), you have three knobs:

1. **Increase rate limits.** Authenticated GitHub search is 30 requests/minute. A GitHub App with installation-level auth bumps this; rewrite the helper to use a JWT-based App auth flow if you need more throughput.
2. **Shard by repo prefix.** Run `gh_default_branch.py` against multiple `filterkey` inputs in parallel terminals — each call only iterates repos whose `full_name` matches the filter substring.
3. **Replace with a bulk clone + woke pass.** For exhaustive coverage, clone every repo overnight and run `woke` locally; one woke invocation can scan thousands of repos in minutes once the local checkout exists.

> [!WARNING]
> The shipped helper scripts run **interactively** — they prompt via
> `input()`. They are not CI-friendly out of the box. To run them
> non-interactively, refactor `__main__` to read environment variables or
> CLI arguments instead of calling `input()`.

---

## 5. Operations

Operating this tooling at scale is mostly about keeping rule files in sync
across repos and watching for upstream changes in alex and woke.

### 5.1 Monitoring rule drift across repos

If you adopted Pattern A (vendored rules), drift is the main risk. Detect it
with a scheduled job:

```bash
# Compare every repo's alex-race-rules.yaml against the canonical version
gh repo list YOUR-ORG --json name --limit 1000 \
  | jq -r '.[].name' \
  | while read repo; do
      gh api "repos/YOUR-ORG/$repo/contents/alex-race-rules.yaml" 2>/dev/null \
        | jq -r '.content' \
        | base64 -d > "/tmp/$repo.yaml"
      diff -q canonical-alex-race-rules.yaml "/tmp/$repo.yaml" || echo "DRIFT: $repo"
    done
```

Run weekly via a GitHub Actions cron workflow.

### 5.2 Updating rules

When you decide to add or change a term:

- [ ] Edit `alex-race-rules.yaml` and `cisco-rules.yaml` in the canonical repo.
- [ ] Bump nothing — there is no version field. The git commit hash is the implicit version.
- [ ] Run alex and woke against your reference corpus to confirm no surprise regressions.
- [ ] Open PRs to the consuming repos (or wait for the next CDN sync if you use Pattern B).
- [ ] Update the rollout tracker for any teams whose CI now fails on legitimate historical content.

> [!TIP]
> Add a rule against terms like `dummy`, `sanity check`, or other ableist
> language by appending entries with the same `type: basic` schema. The alex
> rule pack is intentionally extensible.

### 5.3 Watching upstream

The repository pins `alex@^11.0.1`. alex follows semantic versioning, so 11.x
patches are safe automatic updates and 12.0 needs manual review. For woke,
the binary is unpinned — install whichever version you trust.

> [!NOTE]
> GitHub Issue [#142](https://github.com/cisco-open/inclusive-language/issues/142)
> ("Investigate the status of the woke linter") tracks the question of
> whether woke is still actively maintained. If woke goes unmaintained,
> migrating `cisco-rules.yaml` consumers to alex (or to a successor linter)
> is the contingency plan. Subscribe to that issue for status.

### 5.4 Audit reports

`gh_search.py` produces an audit log when run. Capture the output for
periodic review:

```bash
python gh_search.py 2>&1 | tee inclusive-audit-$(date +%Y%m).log
```

The script also writes `enterprisesearchresultsinfo.log` (or
`githubsearchresultsinfo.log`) automatically — different file names depending
on which helper function was called.

### 5.5 Rotating helper-script credentials

Rotate the GitHub PAT in `config.py` every 90 days (or whenever your
organization's credential policy demands).

```bash
# 1. Generate new PAT in GitHub UI
# 2. Update config.py
# 3. Smoke test
python gh_default_branch.py < /dev/null
# 4. Revoke the old PAT in GitHub UI
```

> [!CAUTION]
> The helper scripts log API call rates to a logfile. Do **not** redirect
> stdout to a logfile that gets committed — the CSV output includes
> repository names that may be confidential in private orgs.

---

## 6. Performance

### 6.1 alex performance characteristics

alex's runtime is approximately linear in total prose bytes scanned. Typical
numbers from a mid-size repo (~200 markdown files, ~2 MB total prose):

- Cold first invocation (`npx`, includes download): 4-8 seconds.
- Subsequent invocations: 0.8-2 seconds.
- Per-file overhead: ~5-15 ms.

Files alex skips entirely (binary, unrecognized extension, listed in
`.alexignore`) cost effectively zero.

### 6.2 woke performance characteristics

woke is a Go binary with concurrent file walking. It scans every line of
every file regardless of language:

- Single-machine throughput: 50-200 MB/s of source text.
- Per-file startup overhead: ~1 ms.
- Memory: O(rule count); 4 rules from `cisco-rules.yaml` is negligible.

### 6.3 Helper script throughput

The Python helpers are bottlenecked by GitHub's API rate limits:

| Mode | Rate limit | Effective scan rate |
| --- | --- | --- |
| `gh_search.py` (search API) | 30 req/min auth'd | ~30 keyword/extension combos per minute |
| `gh_default_branch.py` (search + repos) | 5,000 req/hr core | ~1 repo per second after initial query |

The 2-second `time.sleep(2)` between result iterations in `gh_search.py` is
deliberate padding to avoid burst-rate-limit errors on the per-result `path`
fetches.

> [!NOTE]
> Both helpers detect `rate.remaining == 0` and sleep until the reset window
> automatically. The sleep can be **multiple minutes**. If a CI job needs a
> hard deadline, wrap the helper invocation in a timeout.

### 6.4 Scaling alex/woke in CI

For PR-only scanning, full-repo runs are usually fast enough. For
mono-repos with millions of lines:

- Use `lint-staged` (Section 5.3 of User Guide) to scan only the staged diff in pre-commit.
- In CI, scan only files changed in the PR: `git diff --name-only origin/main...HEAD | xargs -r alex`.
- For nightly full-repo runs, accept the multi-minute runtime.

---

## 7. Security

This tooling is **read-only by design** — the linters never modify files,
and the helper scripts only read public or authenticated GitHub data. The
attack surface is small but not zero.

### 7.1 Threat model summary

| Threat | Likelihood | Impact | Mitigation |
| --- | --- | --- | --- |
| Leaked GitHub PAT in `config.py` | Medium (developer error) | High (org-wide read access) | `.gitignore` `config.py`, fine-scope tokens, rotate quarterly |
| Malicious YAML in `alex-race-rules.yaml` | Low | Low (alex parses safely) | Review YAML PRs |
| Malicious JS in `.alexrc.js` | Low | High (arbitrary Node code at lint time) | Review `.alexrc.js` PRs as carefully as application code |
| alex/woke supply-chain compromise | Low | High | Pin `alex@^11.0.1`; verify woke binary checksums |
| Sensitive content scanned by woke | Medium (repos with secrets) | Medium (rule names visible in CI logs) | Use `.alexignore`/woke ignore for sensitive paths |

### 7.2 Scanning content with sensitive material

Both alex and woke read source files into memory and emit warnings citing
**file path, line number, and the matching word**. They do not reproduce the
surrounding line by default — the scanner output is designed to be safe to
share. However:

- **woke's verbose output** (default) includes the matching token but **not** the surrounding line. Logs can be safely captured.
- **alex's output** also includes only the matching word and surrounding line numbers, not the line content itself.

> [!WARNING]
> If you ship CI logs to a third-party log aggregator, the aggregator will
> see file paths. For repositories whose **directory structure** is itself
> sensitive (e.g., `secrets/production/api-key.env`), exclude those paths
> from scanning via `.alexignore` and woke's `.woke.yaml` ignore list.
> Otherwise the file path leaks even though the contents do not.

### 7.3 Recommended allowlist patterns for sensitive paths

```text
# .alexignore
secrets/
.env
**/*.key
**/*.pem
config/credentials/
node_modules/
.git/
```

For woke, create `.woke.yaml` at the project root:

```yaml
ignore_files:
  - secrets/
  - .env
  - "**/*.key"
  - "**/*.pem"
  - config/credentials/
```

### 7.4 Helper-script auth scope

The helper scripts use a personal access token. The minimum scope:

| Target | Required scope |
| --- | --- |
| Public github.com repos | None (unauthenticated reads work, but rate-limited at 60/hr) |
| Authenticated github.com (higher rate) | No scopes — just create a classic token with no boxes checked |
| Private repos in your own org | Fine-grained token with `Contents: Read-only` for the target org |
| GitHub Enterprise repos | Whatever your enterprise admin requires; usually `repo` scope |

> [!CAUTION]
> Do not grant `repo` (write), `admin:org`, or `delete_repo` scope. The
> scripts only read; any wider scope is excess privilege that could be
> abused if `config.py` leaks.

### 7.5 Secrets handling

The shipped `exampleconfig.py` contains an obvious placeholder string. The
**real** `config.py` you create with a working token must be:

- Listed in `.gitignore` before any commit.
- Never logged, never shipped in error messages, never echoed.
- Rotated when an employee with access leaves.

```bash
# Add to .gitignore in any repo where you check out inclusive-language
echo 'config.py' >> .gitignore
git add .gitignore
git commit -m "chore: ignore inclusive-language config.py"
```

### 7.6 Container/sandboxing recommendations

For organizations with strict scanner-isolation policies:

- alex runs fine inside Docker with **no network** after the initial `npm install`.
- woke is a single static binary with no network requirement.
- The Python helpers **must** have HTTPS egress to GitHub's API.

```bash
# Network-isolated alex run
docker run --rm --network none -v "$PWD":/work -w /work node:20-alpine \
  sh -c 'npm install --no-audit alex@^11.0.1 && npx alex .'
```

> [!IMPORTANT]
> alex's plugins (loaded transitively by retext-equality) are JavaScript
> code that runs in the same Node process. If you treat `node_modules/` as
> trust-equivalent to your application code (which you should), no
> additional sandboxing is required beyond what your normal Node CI runs do.

---

## 8. Developer Internals

For contributors and anyone modifying this repository.

### 8.1 Repository structure

```text
inclusive-language/
├── .alexignore               # alex path exclusions (gitignore syntax)
├── .alexrc.js                # alex runtime config (CommonJS)
├── .gitignore                # standard
├── alex-race-rules.yaml      # 13 alex term-pair entries
├── bash-script-inventory.md  # internal inventory doc
├── cisco-rules.yaml          # 4 woke rule entries
├── CODE_OF_CONDUCT.md        # standard OSS doc
├── exampleconfig.py          # template config.py for the helpers
├── gh_default_branch.py      # default-branch audit script
├── gh_search.py              # keyword-search script
├── LICENSE                   # Apache 2.0 (per Cisco Open standard)
├── NOTICE                    # third-party attributions
├── package.json              # node deps: alex@^11.0.1
├── Pipfile                   # python deps (authoritative)
├── README.md                 # project description
├── requirements.txt          # python deps (autogenerated mirror)
└── SECURITY.md               # security disclosure policy
```

Every file lives at the project root; there are **no subdirectories**. This
is unusual for a Python+Node hybrid but reflects the project's small scope.

### 8.2 The two helper scripts in detail

#### 8.2.1 `gh_default_branch.py`

```python
def repo_default_branch(filterkey):
    orgname = config.gh_orgname
    repositories = g.search_repositories(query='org:{}'.format(orgname))
    for repo in repositories:
        # rate-limit check + sleep
        ...
        if repo.private == False:
            if filterkey in repo.full_name:
                repo = g.get_repo(repo.full_name)
                defaultbranch = repo.default_branch
                if defaultbranch == 'master':
                    print('Repo: ', repo.full_name, 'has a default branch named master.')
```

Flow:

1. Read `gh_orgname` and `gh_api_key` from `config`.
2. Construct `Github(ACCESS_TOKEN)` client.
3. Search every repo in the org via `g.search_repositories(query='org:<org>')`.
4. For each repo:
   - Check the **core** rate limit (`rate_limit.core`); sleep until reset if exhausted.
   - Skip private repos (`if repo.private == False`).
   - Skip repos whose `full_name` does not contain the user-supplied `filterkey`.
   - Fetch the full repo object and read `default_branch`.
   - Print a line if the default branch is `'master'`.

The function returns no value — output is via `print()`.

The `__main__` block prompts for `filterkey` via `input()` and calls
`repo_default_branch(filterkey)`.

> [!NOTE]
> The script also contains a commented-out `repo.create_issue(...)` call
> that would file a tracking issue against each offending repo. Uncomment
> with care — automated issue creation is hard to undo. Test on a
> single throwaway repository first.

#### 8.2.2 `gh_search.py`

Two near-identical functions: `search_github(keyword, filetype)` and
`search_enterprise_github(keyword, filetype)`. The only difference is which
`Github` client object they use:

```python
g  = Github(ACCESS_TOKEN)                              # public github.com
eg = Github(base_url=API_ENDPOINT, login_or_token=ACCESS_TOKEN)  # enterprise
```

Both functions:

1. Configure stdlib logging to a file (`githubsearchresultsinfo.log` for public, `enterprisesearchresultsinfo.log` for Enterprise).
2. Read the **search** rate limit and sleep if exhausted.
3. If `filetype == 'any'`, query `"{keyword}" org:{org}`; otherwise query `"{keyword}" org:{org} in:file extension:{filetype}`.
4. Iterate `searchresults`, sleeping 2 seconds between each, printing CSV-shaped lines.
5. Catch and `print` any per-entry exception so the loop doesn't abort.

The `__main__` block:

```python
which_github = input(...)
keyword      = input(...)
filetype     = input(...)
which_github = 'Enterprise'   # <-- This line overrides the user's input
if which_github == "Enterprise":
    search_enterprise_github(keyword, filetype)
else:
    search_github(keyword, filetype)
```

> [!CAUTION]
> The `which_github = 'Enterprise'` reassignment immediately before the
> `if` is a known bug. The branch for `search_github` (public) is dead
> code as shipped. To run public-GitHub searches, comment the override
> line out. This is consistent with what GitHub Issue
> [#30](https://github.com/cisco-open/inclusive-language/issues/30) is
> trying to surface — there is no test/support matrix to catch this.

### 8.3 Code patterns and conventions

The Python code in this repository is written in a **procedural, top-of-file
configuration** style:

- `config.py` is imported at module top, and `ACCESS_TOKEN = config.gh_api_key` is read immediately. Failure to import or missing constants crashes the script before any function runs.
- Functions take primitive arguments (`str` only), return `None`, and side-effect via `print()` and stdlib logging.
- Exception handling is broad (`except Exception as e: print(...)`) — every per-iteration error is swallowed so the search loop continues.
- No type hints, no docstrings, no unit tests are present in the repository.

### 8.4 Testing strategy

There are **no tests** in the repository. Issue
[#30](https://github.com/cisco-open/inclusive-language/issues/30) ("Build up
a test and support matrix") tracks this gap. If you contribute code:

- Add a `tests/` directory at the project root.
- Use `pytest` (already a transitive dep via the `Pipfile` ecosystem).
- Mock `pygithub.Github` calls — the live GitHub API is not test-friendly.
- For YAML rule changes, add a smoke test that runs `npx alex` against a fixture file and asserts the expected warnings.

### 8.5 Contribution checklist

Before opening a PR:

- [ ] YAML edits validated with `python -c "import yaml; yaml.safe_load(open('cisco-rules.yaml'))"`.
- [ ] alex run on any sample text changed.
- [ ] Python edits checked with `python -m py_compile gh_search.py gh_default_branch.py`.
- [ ] No real PAT in `exampleconfig.py` (placeholder string only).
- [ ] LICENSE and NOTICE unchanged unless adding a new third-party dependency.
- [ ] Reference any related issue number in the PR description.

> [!TIP]
> The placeholder PAT in `exampleconfig.py` is intentionally an obvious
> sentinel string (`"ghp_TotallyaFaKeTok3nIprom1seYouS0d0ntTrYit"`). When
> reviewing PRs, treat any non-sentinel string in this file as a leaked
> credential and reject the PR.

### 8.6 Potential improvements

- **Fix the hard-coded `which_github = 'Enterprise'` bug** in `gh_search.py` (Section 8.2.2).
- **Convert helper scripts to `argparse` CLI** so they run non-interactively in CI.
- **Add a test/support matrix** (Issue #30) covering the YAML schemas and the helper scripts.
- **Pin woke** in a sibling shell script with a checksum, the same way alex is pinned via `package.json`.
- **Migrate `requirements.txt`** to use `==` instead of `=` so it's directly pip-consumable.
- **Document woke's status** based on Issue #142 outcome — either confirm woke is supported or pivot the rule pack to a successor linter.
- **Rule severity** — add `severity: warning` (or `error`) explicitly in `cisco-rules.yaml` so woke output is predictable across woke versions.

---

## 9. Integration Points

### 9.1 Inputs

| Surface | Provider | Format |
| --- | --- | --- |
| Files to scan | The consuming repository | Any text file (alex), any file (woke) |
| Configuration | `.alexrc.js`, `.alexignore`, `cisco-rules.yaml`, `config.py` | CommonJS, gitignore, YAML, Python module |
| Credentials | GitHub PAT in `config.py` | String |
| API endpoint | `gh_api_endpoint` in `config.py` | URL |
| User input (helpers) | stdin via `input()` | Plain text |

### 9.2 Outputs

| Channel | Producer | Consumer |
| --- | --- | --- |
| Stdout warnings | alex, woke | Developer terminal, CI logs |
| Exit code | alex, woke | CI gate |
| Stdout CSV | `gh_search.py` | Audit pipelines, spreadsheet ingest |
| Stdout text | `gh_default_branch.py` | Audit pipelines |
| Log file | `gh_search.py` | `githubsearchresultsinfo.log` or `enterprisesearchresultsinfo.log` |

### 9.3 External dependencies

| Service | Component | Failure mode |
| --- | --- | --- |
| npm registry | alex install | Install fails → fall back to `npx --offline` if a cached copy exists |
| woke release server | woke install | Install fails → use a vendored binary |
| GitHub REST API | helper scripts | Timeout/network → script crashes; rate limit → script sleeps until reset |
| PyPI | Python deps | Install fails → use a vendored wheelhouse |

### 9.4 CI integrations

Already-known good integrations:

- **GitHub Actions** — sample workflow in §4.3.
- **GitLab CI** — same pattern; use `node:20` and the `npx alex .` step.
- **Jenkins** — call `npx alex` from a Node-shell stage; capture exit code.
- **Pre-commit framework** — alex has community hooks at <https://github.com/get-alex/alex>. Use the `pre-commit` repo entry pointed at this rule pack.

### 9.5 Editor integrations

- **VS Code** — the [vscode-alex](https://marketplace.visualstudio.com/items?itemName=tlahmann.alex-linter) extension surfaces alex warnings inline. Point it at your project's `.alexrc.js` and `alex-race-rules.yaml`.
- **Sublime Text / Atom** — alex has community plugins for both, though Atom is end-of-life.
- **JetBrains IDEs** — no first-party plugin; teams typically run alex via the integrated terminal as a watch task.

### 9.6 Reporting integrations

woke can emit SARIF-format output that GitHub's code-scanning UI consumes
natively. Wire it up in your CI:

```yaml
- name: Run woke (SARIF)
  run: woke --output sarif --rule-in cisco-rules.yaml . > woke.sarif

- uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: woke.sarif
```

This surfaces every finding in the GitHub Security tab of the repository.
alex does not support SARIF natively as of 11.x; for alex findings, the
common pattern is to parse stdout into a JSON Step Summary instead.

---

_Generated April 2026 from cisco-open/inclusive-language at 0486454. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
