{"id":49594063,"url":"https://github.com/zer0contextlost/axiom","last_synced_at":"2026-05-04T03:05:59.155Z","repository":{"id":355486315,"uuid":"1228249437","full_name":"zer0contextlost/axiom","owner":"zer0contextlost","description":"Find the invariants your codebase assumes but never tests.","archived":false,"fork":false,"pushed_at":"2026-05-03T21:41:22.000Z","size":92,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-05-03T22:25:19.673Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zer0contextlost.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-03T19:40:05.000Z","updated_at":"2026-05-03T21:41:26.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/zer0contextlost/axiom","commit_stats":null,"previous_names":["zer0contextlost/axiom"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/zer0contextlost/axiom","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zer0contextlost%2Faxiom","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zer0contextlost%2Faxiom/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zer0contextlost%2Faxiom/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zer0contextlost%2Faxiom/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zer0contextlost","download_url":"https://codeload.github.com/zer0contextlost/axiom/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zer0contextlost%2Faxiom/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32590492,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-03T22:12:39.696Z","status":"ssl_error","status_checked_at":"2026-05-03T22:09:10.534Z","response_time":103,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-04T03:05:58.431Z","updated_at":"2026-05-04T03:05:59.133Z","avatar_url":"https://github.com/zer0contextlost.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# AXIOM\n\n**Find the invariants your codebase assumes but never tests.**\n\n85% line coverage doesn't mean your code is safe. It means 85% of your lines ran during tests — not that the *assumptions* those lines make have ever been challenged.\n\nEvery non-trivial codebase is full of implicit invariants:\n\n- `user.subscription` is never `null` before `getBillingPlan()` is called\n- `initDB()` always runs before `query()`\n- `req.user` is always populated by auth middleware before route handlers execute\n- `users.find()` will always return something before you access its properties\n\nThese assumptions are true *by convention*, not by contract. Nobody wrote them down. Nobody tests them. When they break — due to a refactor, a new code path, a missing middleware — production breaks and the test suite stays green.\n\n**AXIOM reads your code statically, infers what it assumes to be true, diffs that against your test suite, and hands you a ranked list of the bets you're making on code that's never been verified.**\n\n---\n\n## Demo\n\n```\n$ axiom scan ./src\n\nAXIOM  v0.9.0  scanned 847 files in 1.2s\n\n━━━ UNTESTED INVARIANTS (ranked by blast radius) ━━━\n\n  ● CRITICAL  billing.ts:47\n    user.subscription assumed non-null\n    ├─ relied on by 14 call sites\n    ├─ nearest public entry: POST /checkout  (2 hops)\n    └─ tests covering null path: 0\n\n  ● CRITICAL  auth.ts:203\n    session.userId assumed UUID format\n    ├─ relied on by 37 call sites\n    ├─ nearest public entry: GET /profile  (1 hop)\n    └─ tests covering non-UUID: 0\n\n  ◐ HIGH  users.ts:31\n    user.email assumed non-null (.find() can return undefined)\n    ├─ relied on by 8 call sites\n    ├─ nearest public entry: GET /users/:id  (1 hop)\n    └─ tests covering undefined path: 0\n\n  ◐ HIGH  queue.ts:88\n    processJob() assumes initQueue() ran first\n    ├─ relied on by 6 call sites\n    ├─ nearest public entry: worker entrypoint  (3 hops)\n    └─ tests covering out-of-order: 0\n\nSummary: 3 critical · 8 high · 14 medium · 22 low\n         Run `axiom explain \u003cfile:line\u003e` for remediation hints\n```\n\n---\n\n## Install\n\n```bash\nnpm install -g axiom-scan\n```\n\nOr run without installing:\n\n```bash\nnpx --package=axiom-scan axiom scan ./src\n```\n\n---\n\n## Usage\n\n```bash\n# Scan current directory\naxiom scan\n\n# Scan a specific path\naxiom scan ./src\n\n# JSON output for CI pipelines\naxiom scan --json \u003e axiom-report.json\n\n# Only show critical and high severity\naxiom scan --min-severity high\n\n# Exit with code 1 if any critical invariants found (for CI gates)\naxiom scan --fail-on critical\n\n# Ignore paths\naxiom scan --ignore \"**/*.generated.ts\" --ignore \"src/migrations/**\"\n\n# SARIF output for GitHub code scanning\naxiom scan --sarif \u003e results.sarif\n\n# Only scan files changed since a branch or commit\naxiom scan --since main\naxiom scan --since HEAD~5\n\n# Explain a specific invariant with remediation hints\naxiom explain billing.ts:47\n```\n\n### Inline suppressions\n\nTo suppress a specific finding, add `axiom-ignore` on the flagged line or the line above:\n\n```typescript\n// axiom-ignore\nconst plan = user.subscription.plan;\n\nconst plan = user.subscription.plan; // axiom-ignore\n```\n\nWorks in all supported languages using that language's comment syntax.\n\n---\n\n## How it works\n\nAXIOM operates in four phases:\n\n```\n1. CRAWL   Walk the source tree, collect TypeScript/JavaScript/Python/Go/Ruby files\n2. INFER   Extract implicit invariant assumptions from the AST\n3. DIFF    Map which invariants are exercised by the test suite\n4. RANK    Score gaps by blast radius, emit report\n```\n\n### Languages supported\n\n| Language | Parser | Invariant types |\n|----------|--------|-----------------|\n| TypeScript / JavaScript | `@typescript-eslint/typescript-estree` | null, ordering, shape |\n| Python | `tree-sitter-python` (WASM) | null, ordering, shape |\n| Go | `tree-sitter-go` (WASM) | null, ordering, shape |\n| Ruby | `tree-sitter-ruby` (WASM) | null, ordering, shape |\n| Java | `tree-sitter-java` (WASM) | null, ordering, shape |\n| Rust | `tree-sitter-rust` (WASM) | null, ordering, shape |\n| C# | `tree-sitter-c_sharp` (WASM) | null, ordering, shape |\n\n### Invariant types detected\n\n**Type 1 — Null / Nil Assumptions**\n\nProperty accesses on values that could be null, undefined, or nil, where no guard exists.\n\n*TypeScript — nullable parameter:*\n```typescript\nfunction getBillingPlan(user: User | null) {\n  return user.subscription.plan;  // user could be null\n}\n```\n\n*Python — dict.get() without None check:*\n```python\ndef process(data: dict):\n    val = data.get(\"key\")\n    return val.strip()  # val can be None\n```\n\n*Go — map lookup or call with discarded ok/error:*\n```go\nfunc handle(m map[string]*User) {\n    user, _ := m[\"key\"]\n    fmt.Println(user.Name)  // user might be nil\n}\n```\n\n*Ruby — .find() or hash subscript without nil guard:*\n```ruby\ndef process(users)\n  user = users.find { |u| u.admin? }\n  user.email  # user could be nil if none found\nend\n```\n\n**Type 2 — Ordering Invariants**\n\nFunction B reads state written by function A, but nothing guarantees A ran first.\n\n*TypeScript:*\n```typescript\nlet db: Database;\nexport function initDB(url: string) { db = new Database(url); }\nexport function query(sql: string)  { return db.run(sql); }  // assumes initDB ran\n```\n\n*Go — nil map write before make():*\n```go\nfunc build() {\n    var index map[string]int\n    index[\"key\"] = 1  // panic: assignment to entry in nil map\n}\n```\n\n**Type 3 — Type Shape Assumptions**\n\nValues used as if they match a specific type or format without validation.\n\n*TypeScript — UUID assumed without validation:*\n```typescript\nfunction createUser(id: string) {\n  await db.insert({ id, ... });  // id used as UUID in 8 places, never validated\n}\n```\n\n*Go — unsafe type assertion:*\n```go\nfunc render(v interface{}) {\n    msg := v.(proto.Message)  // panics if v is the wrong type\n}\n```\n\n*Python — int() without isdigit() check:*\n```python\ndef set_limit(n: str):\n    limit = int(n)  # ValueError if n is not numeric\n```\n\n*Ruby — Integer() without rescue:*\n```ruby\ndef set_limit(n)\n  limit = Integer(n)  # ArgumentError if n is not numeric\nend\n```\n\n*Rust — `.parse().unwrap()` without error handling:*\n```rust\nfn set_limit(n: \u0026str) {\n    let limit: u32 = n.parse().unwrap();  // panics if n is not numeric\n}\n```\n\n*Rust — truncating cast on function parameter:*\n```rust\nfn set_backlog(backlog: usize) {\n    setsockopt(fd, backlog as i32);  // silently truncates if backlog \u003e i32::MAX\n}\n```\n\n**C# examples:**\n\n*Nullable `.Value` without `.HasValue` check:*\n```csharp\nvoid Process(int? count) {\n    int val = count.Value;  // throws InvalidOperationException if null\n}\n```\n\n*LINQ `FirstOrDefault()` result accessed without null guard:*\n```csharp\nvar user = users.FirstOrDefault(u =\u003e u.Name == name);\nreturn user.Email;  // NullReferenceException if no match\n```\n\n*`int.Parse()` without try-catch:*\n```csharp\nvoid SetLimit(string s) {\n    int limit = int.Parse(s);  // FormatException if s is not numeric\n}\n```\n\n### Blast radius score\n\n```\nscore = (call_site_count × 2)\n      + (public_entrypoint_proximity)   // closer to HTTP handler = higher\n      + (type_severity)                  // null \u003e ordering \u003e shape\n      - (partial_test_coverage_discount)\n```\n\nBuckets: **CRITICAL** (\u003e20) · **HIGH** (10–20) · **MEDIUM** (5–10) · **LOW** (\u003c5)\n\n### Call graph\n\nAXIOM builds a cross-file call graph and traces invariants back to their nearest public entrypoint. It recognizes:\n\n- Named route/handler/controller/middleware functions\n- Express-style route callbacks — `router.get('/users', (req, res) =\u003e {...})` is labeled `GET /users`\n- JSX component usage — `\u003cUserCard /\u003e` in a parent component counts as a call site\n\n---\n\n## JSON output\n\n```json\n{\n  \"scanned\": 847,\n  \"duration_ms\": 1204,\n  \"invariants\": [\n    {\n      \"id\": \"billing.ts:47\",\n      \"type\": \"null\",\n      \"description\": \"user.subscription assumed non-null\",\n      \"severity\": \"critical\",\n      \"score\": 34,\n      \"call_sites\": 14,\n      \"nearest_entrypoint\": { \"route\": \"POST /checkout\", \"hops\": 2 },\n      \"test_coverage\": 0\n    }\n  ]\n}\n```\n\n---\n\n## Configuration\n\nCreate `.axiomrc.json` in your project root:\n\n```json\n{\n  \"ignore\": [\n    \"**/*.generated.ts\",\n    \"src/migrations/**\"\n  ],\n  \"minScore\": 5,\n  \"maxResults\": 50\n}\n```\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `ignore` | `string[]` | Glob patterns to exclude (merged with built-in ignores) |\n| `minScore` | `number` | Minimum blast-radius score to include in output |\n| `maxResults` | `number` | Cap total results (sorted by score, highest first) |\n\nConfig is merged with CLI flags — CLI flags take precedence.\n\n---\n\n## CI / GitHub Actions\n\nAdd to your workflow to surface invariants without blocking:\n\n```yaml\n- name: Run AXIOM scan\n  run: axiom scan . --min-severity high --json \u003e axiom-report.json\n  continue-on-error: true\n\n- name: Upload AXIOM report\n  uses: actions/upload-artifact@v4\n  with:\n    name: axiom-report\n    path: axiom-report.json\n\n- name: Gate on critical invariants\n  run: axiom scan . --min-severity high --fail-on critical\n```\n\n`--fail-on critical` exits with code 1 if any critical invariants are found, failing the job.\n\n### GitHub Code Scanning (SARIF)\n\nUpload results directly to GitHub's Security tab:\n\n```yaml\n- name: Run AXIOM scan\n  run: axiom scan . --sarif \u003e axiom.sarif\n  continue-on-error: true\n\n- name: Upload SARIF to GitHub\n  uses: github/codeql-action/upload-sarif@v3\n  with:\n    sarif_file: axiom.sarif\n```\n\n### Diff-only mode for PRs\n\nOnly scan files changed in a pull request — much faster in CI:\n\n```yaml\n- name: AXIOM diff scan\n  run: axiom scan . --since origin/main --fail-on critical\n```\n\n---\n\n## Roadmap\n\n| Version | Status | Scope |\n|---------|--------|-------|\n| **v0.1** | ✅ Shipped | Null/undefined, ordering, and type-shape invariants; cross-file call graph; blast-radius ranking; terminal + JSON output |\n| **v0.2** | ✅ Shipped | `axiom explain` with remediation hints, `.axiomrc.json` config, GitHub Actions example |\n| **v0.3** | ✅ Shipped | JSX/React component call tracking, Express route naming (`GET /path`), `find()`/`get()`/index-access undefined detection, `--fail-on` CI gate, false-positive reduction |\n| **v0.4** | ✅ Shipped | Python support (null, ordering, shape via tree-sitter), watch mode, VS Code extension |\n| **v0.5** | ✅ Shipped | Go support (nil map writes, unsafe type assertions, strconv without error handling) |\n| **v0.6** | ✅ Shipped | Ruby support, SARIF output for GitHub code scanning, `--since` flag for diff-only mode |\n| **v0.7** | ✅ Shipped | Java support (null, ordering, shape); ordering deduplication for reactive/DI frameworks |\n| **v0.8** | ✅ Shipped | Rust support (`unwrap`/`expect` without guards, `.parse().unwrap()`, truncating casts); inline `axiom-ignore` suppressions for all languages |\n| **v0.9** | ✅ Shipped | C# support (nullable `.Value`, LINQ null dereference, `int.Parse()`/`Convert.To*()` without try-catch, uninitialized field ordering) |\n\n---\n\n## Why not...\n\n| Tool | Gap |\n|------|-----|\n| Istanbul / V8 coverage | Measures line execution, not behavioral assumptions |\n| Mutation testing (Stryker) | Slow, noisy, you need tests for it to work |\n| TypeScript strict mode | Catches declared-type nulls only, not behavioral invariants |\n| ESLint | Rule-based; you write the rules. AXIOM infers them |\n| Semgrep | Pattern matching; you write the patterns. AXIOM infers them |\n\nAXIOM's position: **nobody infers invariants from code behavior and diffs them against test coverage.** The gap is clear and unoccupied.\n\n---\n\n## Contributing\n\n```bash\ngit clone https://github.com/zer0contextlost/axiom\ncd axiom\nnpm install\nnpm run dev   # watch mode\nnpm test\n```\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for details.\n\n---\n\n## License\n\nMIT — see [LICENSE](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzer0contextlost%2Faxiom","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzer0contextlost%2Faxiom","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzer0contextlost%2Faxiom/lists"}