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:
┌────────────────────────┐
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.yamlagainst 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:
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
Githubclient 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.
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 onlytypeCisco ships). inconsiderateandconsiderateare required; both must be lists, even if a single value.noteis 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 optionalnoteper rule. Cisco's shipped file uses neither — every rule defaults to woke's default severity (warning).
3.5 package.json — Node dependency
{
"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 topip 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:
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:
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:
- 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:
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:
- 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.
- Shard by repo prefix. Run
gh_default_branch.pyagainst multiplefilterkeyinputs in parallel terminals — each call only iterates repos whosefull_namematches the filter substring. - Replace with a bulk clone + woke pass. For exhaustive coverage, clone every repo overnight and run
wokelocally; 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 callinginput().
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:
# 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.yamlandcisco-rules.yamlin 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 sametype: basicschema. 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 ("Investigate the status of the woke linter") tracks the question of whether woke is still actively maintained. If woke goes unmaintained, migrating
cisco-rules.yamlconsumers 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:
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).
# 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.yamlis 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 == 0and 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.alexignoreand woke's.woke.yamlignore list. Otherwise the file path leaks even though the contents do not.
7.3 Recommended allowlist patterns for sensitive paths
# .alexignore
secrets/
.env
**/*.key
**/*.pem
config/credentials/
node_modules/
.git/
For woke, create .woke.yaml at the project root:
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, ordelete_reposcope. The scripts only read; any wider scope is excess privilege that could be abused ifconfig.pyleaks.
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
.gitignorebefore any commit. - Never logged, never shipped in error messages, never echoed.
- Rotated when an employee with access leaves.
# 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.
# 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
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
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:
- Read
gh_orgnameandgh_api_keyfromconfig. - Construct
Github(ACCESS_TOKEN)client. - Search every repo in the org via
g.search_repositories(query='org:<org>'). - 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_namedoes not contain the user-suppliedfilterkey. - Fetch the full repo object and read
default_branch. - Print a line if the default branch is
'master'.
- Check the core rate limit (
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:
g = Github(ACCESS_TOKEN) # public github.com
eg = Github(base_url=API_ENDPOINT, login_or_token=ACCESS_TOKEN) # enterprise
Both functions:
- Configure stdlib logging to a file (
githubsearchresultsinfo.logfor public,enterprisesearchresultsinfo.logfor Enterprise). - Read the search rate limit and sleep if exhausted.
- If
filetype == 'any', query"{keyword}" org:{org}; otherwise query"{keyword}" org:{org} in:file extension:{filetype}. - Iterate
searchresults, sleeping 2 seconds between each, printing CSV-shaped lines. - Catch and
printany per-entry exception so the loop doesn't abort.
The __main__ block:
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 theifis a known bug. The branch forsearch_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 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.pyis imported at module top, andACCESS_TOKEN = config.gh_api_keyis read immediately. Failure to import or missing constants crashes the script before any function runs.- Functions take primitive arguments (
stronly), returnNone, and side-effect viaprint()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 ("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 thePipfileecosystem). - Mock
pygithub.Githubcalls — the live GitHub API is not test-friendly. - For YAML rule changes, add a smoke test that runs
npx alexagainst 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.pyis 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 ingh_search.py(Section 8.2.2). - Convert helper scripts to
argparseCLI 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.txtto 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(orerror) explicitly incisco-rules.yamlso 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:20and thenpx alex .step. - Jenkins — call
npx alexfrom a Node-shell stage; capture exit code. - Pre-commit framework — alex has community hooks at https://github.com/get-alex/alex. Use the
pre-commitrepo entry pointed at this rule pack.
9.5 Editor integrations
- VS Code — the vscode-alex extension surfaces alex warnings inline. Point it at your project's
.alexrc.jsandalex-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:
- 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.