Architecture¶
How taken is put together. This is a map for contributors: where the
decision happens, where the data comes from, and which invariants must not
break.
The pipeline¶
fetch -> decide -> present
taken answers one question about a GitHub issue: is it already taken?
The pipeline has three stages, kept deliberately separate:
- Fetch (
taken/checks.py, ortaken/graphql.py): gather evidence from the GitHub API into a plainfindingsdict. - Decide (
taken/verdict.py): a pure function overfindingsreturning(verdict, reasons). No network, no I/O. - Present (
taken/cli.py): format the verdict for humans or as JSON.
The findings dict is the contract between fetch and decide. Anything that
gathers evidence must produce the same shape, so verdict logic can never
drift between fetch paths.
Modules¶
taken/cli.py- argument parsing, output formatting (human and--json), and the run modes: single issue, batch (multiple targets),--discover,--clear-cache. Entry point for thetakencommand.taken/checks.py- the REST fetch layer (the largest module). It wrapsgh api, owns the cache, paces search requests, retries on rate limits, and builds findings:check_issue,check_timeline,fetch_comments/find_claimant_hits,check_ai_policy/classify_policy,check_repo_health/count_recent_contributors.run_checksruns them all for one issue.taken/verdict.py-decide(findings). Pure logic, fully unit-tested. Precedence is fixed: TAKEN wins over CAUTION wins over GO. Rules live here: linked PRs, assignees, claimant comments, AI policy, truncation (a scan that hit the page cap downgrades to CAUTION rather than risk a GO on partial evidence), repo health, and difficulty-fit (a beginner label next to a long thread or a design-level label).taken/graphql.py- opt-in fetch path (--graphql). One GraphQL query per issue instead of ~10 REST calls, then maps every result back into the REST findings shape.--persistent-sessionreuses one HTTPS keep-alive connection. Because both paths feed the samedecide(), the verdict cannot differ by transport.taken/discover.py---discovermode. Builds one OR-combined search query across beginner labels, verifies each candidate with the full single-issue pipeline in parallel workers, and ranks by maintainer responsiveness (score_candidate).taken/mcp_server.py- thetaken-mcpstdio server. Three tools:check_issue,scan_repo,discover_candidates. Thin wrappers over the same functions the CLI uses.taken/budget.py- API budget tiers (anonymous vs authenticated), paging caps, and worker sizing.taken/health.py- the--healthmaintainer report: unattended claims, stale PRs, untouched beginner issues, and untriaged issues.
Invariants¶
These are load-bearing. Do not break them without discussion.
- Read-only against GitHub.
takenonly issues GETs. It never creates, edits, or closes anything. There is no write path to remove. - No tokens in-process. API calls shell out to
gh apiwith the invoker's own auth;takennever sees a token. The one exception is--persistent-session, which holds the token fromgh auth tokenin memory only, never on disk or in logs. - Fail closed. A scan that stopped early (page cap reached), a cache that cannot be read, or missing evidence downgrades toward CAUTION, never toward GO on thinner proof.
- Cache is a performance layer, not a truth layer. API responses are
cached for 1 hour on disk (
~/.cache/taken, overridable withTAKEN_CACHE_DIR) plus an in-memory layer.--no-cachebypasses it. A stale cache can hide fresh state; that is documented, not hidden. - Vendored console copies.
docs/console/py/checks.py,docs/console/py/verdict.py,docs/console/py/budget.pyanddocs/console/py/transport.pyare byte-identical copies oftaken/for the in-browser Pyodide console. CI fails if they drift. Re-copy after any change to those modules:cp taken/checks.py docs/console/py/checks.py && cp taken/verdict.py docs/console/py/verdict.py && cp taken/budget.py docs/console/py/budget.py && cp taken/transport.py docs/console/py/transport.py. - Version strings move together.
pyproject.toml,_taken_pkg.__version__and thetaken --versionstring indocs/console/py/webshim.pymust match; CI checks both console values, including the version used by the transport's User-Agent.
Data flow: one issue¶
cli.pyparses the target (a URL orowner/repo#number).run_checks(REST) orrun_checks_graphql(GraphQL) buildsfindings:issue(state, labels, assignees),linked_prs(from the timeline, not just comments),claimants(comments matching claim patterns),ai_policy(CONTRIBUTING/README classification),repo_health(recent pushes and merges), and comment totals.decide(findings)returns the verdict with cited reasons.cli.pyprints it, or--jsonfor machines.
Data flow: --discover¶
build_querymakes one search across beginner-label variants._collect_candidatespages the search results._verify_candidateruns the full single-issue pipeline per candidate in worker threads (this is the expensive part by design: ~4-6 API calls each, paced for the search rate limit).score_candidateranks by maintainer engagement signals; results print ranked.
Testing¶
tests/mirrors the modules. Verdict rules get focused unit tests with constructed findings (no network). Fetch-layer tests mockgh api.- New or changed decision logic needs focused coverage; the bar is meaningful cases (boundaries, precedence, no-fire cases), not a repository-wide percentage.