{"id":50321347,"url":"https://github.com/kritoke/catseye","last_synced_at":"2026-05-29T03:32:16.353Z","repository":{"id":358663098,"uuid":"1231458775","full_name":"kritoke/catseye","owner":"kritoke","description":"All around code quality and security scanner, finds problems in particular in ai generated code. ","archived":false,"fork":false,"pushed_at":"2026-05-25T14:26:26.000Z","size":5292,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-25T14:26:32.233Z","etag":null,"topics":["code-review"],"latest_commit_sha":null,"homepage":"","language":"OCaml","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kritoke.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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-07T01:32:53.000Z","updated_at":"2026-05-25T14:26:30.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/kritoke/catseye","commit_stats":null,"previous_names":["kritoke/catseye"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/kritoke/catseye","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kritoke%2Fcatseye","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kritoke%2Fcatseye/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kritoke%2Fcatseye/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kritoke%2Fcatseye/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kritoke","download_url":"https://codeload.github.com/kritoke/catseye/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kritoke%2Fcatseye/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33635961,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-29T02:00:06.066Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["code-review"],"created_at":"2026-05-29T03:32:15.796Z","updated_at":"2026-05-29T03:32:16.347Z","avatar_url":"https://github.com/kritoke.png","language":"OCaml","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Catseye\n\n**Multi-language static security analysis with taint tracking, code smell detection, and AI antipattern linting.**\n\nSupports **Crystal, Gleam, JavaScript, TypeScript, Svelte, OCaml, and Rust** — with language-specific security rules and antipattern databases for each.\n\n\u003e **v0.4.4** - OCaml idiomatic rules, updated Crystal/Gleam/Svelte detectors, OCaml verbose-option detection\n\n## Installation\n\n### Binary Releases (Linux \u0026 macOS)\n\nDownload pre-built binaries from the [Releases](https://github.com/kritoke/catseye/releases) page:\n\n```bash\n# Linux x86_64\ncurl -L https://github.com/kritoke/catseye/releases/download/v0.4.3/catseye-linux-x86_64.tar.gz | tar xz\n\n# Linux ARM64 (aarch64)\ncurl -L https://github.com/kritoke/catseye/releases/download/v0.4.3/catseye-linux-aarch64.tar.gz | tar xz\n\n# macOS Apple Silicon (ARM64)\ncurl -L https://github.com/kritoke/catseye/releases/download/v0.4.3/catseye-macos-aarch64.tar.gz | tar xz\n```\n\n\u003e **Note:** macOS Intel (x86_64) builds have been discontinued. Use macOS ARM64 for Apple Silicon Macs.\n\nAfter extraction, run `./install-grammars.sh` to install tree-sitter grammars:\n\n### Nix (All Platforms)\n\n```nix\n# In your project\ncat \u003e flake.nix \u003c\u003c 'EOF'\n{\n  inputs.catseye.url = \"github:kritoke/catseye\";\n  outputs = { self, nixpkgs, catseye }: {\n    devShells.x86_64-linux.default = nixpkgs.legacyPackages.x86_64-linux.mkShell {\n      buildInputs = [ catseye.packages.x86_64-linux.default ];\n    };\n  };\n}\nEOF\n```\n\n### Build from Source\n\n**Requirements:**\n\n- **OCaml** 5.x + **Dune** 3.x\n- **tree-sitter** CLI + language grammars (JS, TS, Svelte, OCaml, Gleam, Rust)\n- **Crystal** 1.x (optional — needed only for native Crystal extractor)\n- OCaml libs: yojson, cmdliner, bos, rresult, logs, fmt, toml, kdl, ocamlgraph\n\nFor detailed instructions on installing dependencies without Nix, see [install.md](install.md).\n\n```bash\n# Clone the repo\ngit clone https://github.com/kritoke/catseye.git\ncd catseye\n\n# Build (uses tree-sitter grammars from nix by default)\njust build\n\n# Run tests\njust test\n```\n\n## Quick Start\n\n```bash\n# Scan a project (auto-detects all languages)\njust scan path/to/project/src\n\n# Scan specific languages only\ncatseye-ocaml --lang javascript,typescript path/to/project/\n\n# Scan with all checks\njust scan-full path/to/project/src\n\n# JSON output\njust scan-json path/to/project/src\n```\n\n## Language Support\n\n| Language   | Extensions                 | Security Rules |        AI Lint        |   Code Smells   | Extractor                      |\n| ---------- | -------------------------- | :------------: | :-------------------: | :-------------: | ------------------------------ |\n| Crystal    | `.cr`                      |  ✅ 12 rules   |    ✅ 45 detectors    | ✅ 16 detectors | Crystal extractor + AST bridge |\n| Gleam      | `.gleam`                   |  ✅ 12 rules   |    ✅ 36 detectors    | ✅ 16 detectors | tree-sitter                    |\n| JavaScript | `.js` `.jsx` `.mjs` `.cjs` |  ✅ 10 rules   | ✅ 60+ hallucinations | ✅ 16 detectors | tree-sitter                    |\n| TypeScript | `.ts` `.tsx`               |  ✅ 10 rules   | ✅ (shares JS rules)  | ✅ 16 detectors | tree-sitter                    |\n| Svelte     | `.svelte`                  |  ✅ XSS/SSRF   |      ✅ 12 rules      | ✅ 16 detectors | tree-sitter (two-pass)         |\n| OCaml      | `.ml` `.mli`               |    ✅ Basic    |      ✅ 18 rules      | ✅ 16 detectors | tree-sitter                    |\n| Rust       | `.rs`                      |    ✅ Basic    |    ✅ 3 detectors     | ✅ 16 detectors | tree-sitter (native)           |\n\n## CLI Reference\n\n```\ncatseye [options] \u003cdirectory\u003e\n\n  -f, --format \u003cfmt\u003e         terminal (default), json, sarif, markdown, dot\n  -o, --output \u003cpath\u003e        write results to file\n  -r, --rules \u003cpath\u003e         rules directory (default: ~/.local/lib/catseye/rules/)\n  --config \u003cpath\u003e            config file path (default: .catseye.toml in target or parents)\n  --lang \u003clang\u003e              all (default), or comma-separated: crystal,gleam,javascript,typescript,svelte,ocaml,rust\n  --no-color                 disable colored output\n  --no-cache                 disable extraction cache\n  --clear-cache              clear cache and run full scan\n  --cache-dir \u003cpath\u003e         cache directory (default: .catseye)\n  --cfg                      use IL/CFG-based taint engine (more sensitive)\n  --no-cfg                   use flat taint engine (default, fewer findings)\n  --analysis-timeout \u003cms\u003e    timeout for analysis phase (0 = disabled)\n  --cfg-max-blocks \u003cn\u003e       max blocks per function CFG (default: 500)\n  --cfg-timeout-ms \u003cms\u003e      timeout per function CFG build (default: 5000)\n  --predator-vision          enable reachability analysis (live/dormant/safe)\n  --crows-nest               enable supply chain audit (Crystal shard.yml + Gleam gleam.toml only; very limited CVE data)\n  --claws                    enable code smell detection\n  --ai-lint                  enable AI antipattern detection (Crystal, Gleam, Svelte, OCaml, Rust)\n  --suppress \u003crules\u003e         comma-separated rule IDs to suppress (e.g., unused-let,InsecureRandom)\n  --include-deps             include shard dependencies in scan (Crystal only)\n  --no-recurse               don't recurse into subdirectories (applies to all languages)\n  -p, --parallelism \u003cn\u003e      parallel workers (0 = auto)\n  -v, --version              show version\n  -h, --help                 show help\n```\n\n## What It Detects\n\n\u003e **Full rule reference:** See [RULES.md](RULES.md) for complete tables of all security rules, code smells, and AI antipatterns.\n\n### Security Rules (taint-based)\n\nRules are KDL files — different rule sets per language, all using the same taint engine.\n\n| Rule                   | Severity |           Crystal/Gleam           |              JS/TS               |         Svelte         |          Rust           |\n| ---------------------- | -------- | :-------------------------------: | :------------------------------: | :--------------------: | :---------------------: |\n| **SSRF**               | Critical | `HTTP::Client.get`, `hackney.get` |         `$fetch`, `$get`         |        `$fetch`        |            —            |\n| **CommandInjection**   | Critical |      `system`, `Process.run`      |      `child_process.$exec`       |           —            | `std::process::Command` |\n| **PathTraversal**      | High     |     `File.read`, `File.write`     |    `$readFile`, `$writeFile`     |           —            |            —            |\n| **SQLInjection**       | Critical |       `db.exec`, `db.query`       |                —                 |           —            |            —            |\n| **XSS**                | Critical |                 —                 |  `innerHTML`, `document.write`   | `{@html}`, `innerHTML` |            —            |\n| **UnsafeBlock**        | High     |                 —                 |                —                 |           —            |       `unsafe {}`       |\n| **OpenRedirect**       | Medium   |           `redirect_to`           |  `$redirect`, `location.assign`  |           —            |\n| **PrototypePollution** | High     |                 —                 |    `$merge`, `Object.assign`     |           —            |\n| **EvalInjection**      | Critical |                 —                 | `eval`, `Function`, `setTimeout` |           —            |\n| **EnvInjection**       | High     |             `ENV[]=`              |                —                 |           —            |\n| **LDAPInjection**      | High     |           `LDAP.query`            |                —                 |           —            |\n| **ScentLeakage**       | High     |        `puts`, `Log.info`         |          `console.log`           |           —            |\n| **ReDoS**              | Medium   |            `Regex.new`            |           `new RegExp`           |           —            |\n| **WeakCryptography**   | Medium   |           `Digest::MD5`           |       `createHash('md5')`        |           —            |\n| **HardcodedSecrets**   | Medium   |            `password=`            |            `api_key=`            |           —            |\n\nRules are KDL files in `src/ocaml/rules/` — add your own by creating a `.kdl` file.\n\n### AI Antipattern Detection (`--ai-lint`)\n\nCatches patterns common in AI-generated code: hallucinated method calls, framework confusion, security antipatterns, and best practice violations.\n\n#### JavaScript / TypeScript (60+ rules)\n\n| Category                 | Examples                                                                                                                           |\n| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- |\n| **Hallucinated methods** | `strip()` → `.trim()`, `len()` → `.length`, `append()` → `.push()`, `print()` → `console.log()`                                    |\n| **Framework confusion**  | Python (`dict`, `range`, `enumerate`), Ruby (`puts`, `select`, `compact`), Java (`System.out.println`), PHP (`var_dump`, `strlen`) |\n| **Security**             | `eval()`, `new Function()`, `child_process.exec()`, prototype pollution (`__proto__`), `Math.random()` for security                |\n| **Best practices**       | `alert()`, `debugger`, `console.log` left in code, `document.write()` deprecated                                                   |\n| **Code quality**         | `==` instead of `===`, deep `.then()` chains (4+), `escape()`/`unescape()` deprecated, incomplete `.replace()` sanitization        |\n\n#### Svelte (12 rules)\n\n| Category                     | Examples                                                                                                        |\n| ---------------------------- | --------------------------------------------------------------------------------------------------------------- |\n| **Svelte 4→5 migration**     | `createEventDispatcher` → callback props, `beforeUpdate`/`afterUpdate` → `$effect()`, Svelte 4 stores → runes   |\n| **Svelte 5 Rune Validation** | `$state()` without init, `$effect` without cleanup (setInterval), `$derived` reassignment                       |\n| **Framework confusion**      | React hooks (`useState`, `useEffect`), Vue directives (`v-if`, `v-for`, `v-model`), Angular (`ngModel`, `ngIf`) |\n| **XSS**                      | `{@html}` with dynamic content, `innerHTML`, `document.write`                                                   |\n\n#### OCaml (18 rules)\n\n| Category                   | Rule ID                    | What it catches                                          |\n| -------------------------- | -------------------------- | -------------------------------------------------------- |\n| **Hallucinated functions** | `hallucinated-method`      | Haskell/Scala/Python APIs (`foldl`, `putStrLn`, `range`) |\n| **Unsafe operations**      | `unsafe-obj-magic`         | `Obj.magic` — unsafe type coercion                       |\n|                            | `unsafe-deserialization`   | `Marshal.from_channel`, `Marshal.from_string`            |\n|                            | `command-injection`        | `Sys.command`, `Unix.exec*` with untrusted input         |\n| **Partial functions**      | `partial-function`         | `List.hd`, `List.tl`, `List.assoc`, `Option.get`         |\n| **Best practices**         | `ocaml-verbose-option`     | Nested `match` on options → use `let*`                   |\n|                            | `ocaml-non-tail-recursive` | Recursive functions without tail optimization            |\n|                            | `ocaml-redundant-if-bool`  | `if x then true else false` → just `x`                   |\n|                            | `unused-binding`           | `let` bindings that are never used                       |\n|                            | `hardcoded-secrets`        | API key patterns in source code                          |\n\n#### Crystal \u0026 Gleam\n\n| Rule                      | Languages | What it catches                                       |\n| ------------------------- | --------- | ----------------------------------------------------- |\n| `hallucinated-stdlib`     | Crystal   | Calls to methods that don't exist (45-entry database) |\n| `hardcoded-secrets`       | Both      | API key patterns (Stripe, GitHub, AWS, JWT, Slack)    |\n| `hardcoded-urls`          | Crystal   | Hardcoded http:// and IP addresses                    |\n| `deprecated-syntax`       | Crystal   | `puts`, `p`, `pp` in production code                  |\n| `sequential-blocking`     | Crystal   | 3+ sequential HTTP/DB/File blocking calls             |\n| `string-concat-loop`      | Crystal   | String concatenation inside iterators                 |\n| `nilable-ivar-access`     | Crystal   | Instance variable accesses that may need nil checks   |\n| `panic-call`              | Gleam     | `panic` used instead of `Result`                      |\n| `list-wrap-unnecessary`   | Gleam     | `List.wrap` on collections                            |\n| `debug-in-library`        | Gleam     | `io.debug` in non-example/test code                   |\n| `result-in-map`           | Gleam     | `list.map` on Result values                           |\n| `pipeline-steps-overload` | Gleam     | 5+ step pipelines                                     |\n| `use-candidate`           | Gleam     | 3+ nested anonymous functions — suggest `use`         |\n\n#### Rust (3 detectors)\n\n| Rule                | What it catches                                                |\n| ------------------- | -------------------------------------------------------------- |\n| `RustHallucination` | Python/Ruby/Go APIs in Rust (`len()`, `range()`, `dict.get()`) |\n| `UnsafePanic`       | `unwrap()`, `expect()`, `panic!()` without error handling      |\n| `RustInefficiency`  | Unnecessary clones, `String::from(\u0026var)`                       |\n\n### Code Smells (`--claws`)\n\nAll 16 code smell detectors use **AST-native analysis** via `CatseyeAST.t` — they work across all supported languages.\n\n| Detector              | Rule ID               | Threshold                          |\n| --------------------- | --------------------- | ---------------------------------- |\n| Cyclomatic complexity | `HighComplexity`      | M ≥ 10                             |\n| Long parameter list   | `LongParameterList`   | ≥ 5 params                         |\n| Deep nesting          | `DeepNesting`         | ≥ 4 levels                         |\n| God objects           | `GodObject`           | ≥ 20 defs/file                     |\n| DRY violations        | `DRYViolation`        | 4+ duplicates                      |\n| Long method           | `LongMethod`          | ≥ 30 nodes                         |\n| Message chain         | `MessageChain`        | ≥ 5 links                          |\n| Data class            | `DataClass`           | 2+ props, no behavior              |\n| Data clump            | `DataClump`           | 3+ params always together          |\n| Flag argument         | `FlagArgument`        | bool params                        |\n| Complex match         | `ComplexMatch`        | ≥ 5 branches                       |\n| Dead code             | `DeadCode`            | unreachable code                   |\n| Feature envy          | `FeatureEnvy`         | excessive cross-class calls        |\n| Orphaned spawn        | `OrphanedSpawn`       | `spawn`/`go` without rescue/ensure |\n| Muted pack            | `MutedPack`           | `Channel.send` without receive     |\n| Dead letter           | `DeadLetter`          | `Channel.close` before receive     |\n| Spaghetti code        | `SpaghettiCode`       | ≥ 60 body nodes                    |\n| Large class           | `LargeClass`          | \u003e 500 LOC                          |\n| Blob                  | `Blob`                | large + data clumps                |\n| Lazy class            | `LazyClass`           | \u003c 3 methods                        |\n| Hub-like module       | `HubLikeModule`       | \u003e 12 dependencies                  |\n| Shotgun surgery       | `ShotgunSurgery`      | 5+ calls to same module            |\n| Parallel inheritance  | `ParallelInheritance` | same-prefix class hierarchies      |\n\n### Supply Chain Audit (`--crows-nest`)\n\n\u003e ⚠️ **Very limited.** Only supports Crystal `shard.yml` and Gleam `gleam.toml`. No JavaScript/TypeScript (npm/pnpm/yarn), Python, Ruby, Rust, Go, or other ecosystems. CVE data via [OSV.dev](https://osv.dev) has **very limited coverage** — most packages return no vulnerabilities even when known issues exist. Use dedicated tools like `npm audit`, `cargo audit`, or `safety` for real supply chain auditing.\n\nWhat it does:\n\n- Parses `shard.yml` → Crystal Shards dependencies (with versions from GitHub)\n- Parses `gleam.toml` → Gleam Hex dependencies\n- Queries OSV.dev for known CVEs (limited data coverage)\n- Checks GitHub repo activity for staleness (Crystal shards with `github:` fields)\n- Results cached in SQLite (24h TTL)\n\nWhat it doesn't do:\n\n- Parse `package.json`, `Cargo.toml`, `requirements.txt`, `Gemfile`, etc.\n- Run ecosystem-native audit tools (`pnpm audit`, `cargo audit`, etc.)\n- Provide comprehensive vulnerability coverage\n- Check lockfiles for exact installed versions\n\n## Example Output\n\n```\n  Catseye v0.4.3\n  Target:   ./src\n  Files:    72 Crystal, 8 JavaScript, 5 TypeScript, 4 Svelte\n\n  → Running analysis engine (7367 nodes)...\n\n  🔴 Error  SSRF  src/controllers/proxy_controller.cr:32\n       Potential SSRF via HTTP::Client.get with tainted argument(s): url.\n      ← Source: params (proxy_controller.cr:28)\n\n  🔴 Error  XSS  frontend/src/routes/+page.svelte:15\n       {@html} with dynamic content is an XSS risk — ensure input is sanitized\n\n  [ai:hallucinated-method] scripts/utils.js:42 - 'strip()' doesn't exist in JS — use .trim()\n\n  ⚠️ Warning  PathTraversal  src/file_handler.cr:45\n       Path traversal via File.read — but path.starts_with?() validation detected, suppressing.\n\n  Found 6 Error(s), 0 Warning(s) across 89 files.\n  Review the findings above.\n```\n\n## How It Works\n\n```\nSource files\n    │\n    ├─ Crystal (.cr) ──→ Crystal extractor (AST → JSON) ─┐\n    ├─ Gleam (.gleam) ─→ tree-sitter (CST → XML → AST) ─┤\n    ├─ JS/TS (.js .ts) ─→ tree-sitter (CST → XML → AST) ┤\n    ├─ Svelte (.svelte) ─→ tree-sitter two-pass ─────────┤\n    └─ OCaml (.ml) ─→ tree-sitter (CST → XML → AST) ────┤\n                                                          │\n                              CatseyeAST.t (unified) ◄────┘\n                                    │\n                   ┌────────────────┼────────────────┐\n                   ▼                ▼                ▼\n             Security Nodes    AI Linter       Code Smells\n             (taint engine)   (AST rules)    (Claws)\n                   │                │                │\n                   └────────────────┼────────────────┘\n                                    ▼\n                          KDL Rule Interpreter\n                                    │\n                          Terminal / JSON / SARIF / Markdown / DOT\n```\n\n**Taint pipeline:** seed → propagate → returns → interproc → propagate → cross-file → guards → rules\n\n1. **Seed** — Params named like taint sources (`url`, `request`, `params`) are marked tainted\n2. **Propagate** — Fixed-point; taint flows through assignments, call chains, and **property access** (e.g., `uri.request_target` inherits taint from `uri`)\n3. **Returns** — Functions with tainted bodies return tainted data\n4. **Inter-procedural** — Taint crosses function boundaries\n5. **Guards** — `unless path.starts_with?(\"/safe/\")` suppresses taint (**path sensitivity**)\n6. **Rules** — KDL rules match sinks against tainted variables, with `arg=N` position matching\n\n**Path sensitivity** reduces false positives by tracking validation guards:\n\n- `starts_with?`, `end_with?` → suppress path traversal\n- `valid_url?`, `check_*`, `sanitize_*` → suppress SSRF\n- Validation scope: 50 lines or to next function boundary\n\n**CFG engine** (`--cfg`) converts CatseyeAST.t → IL → basic block CFG → forward dataflow taint analysis. Branch-aware: taint does not flow across dead branches. Dominator-based sanitizer suppression.\n\n### Adding a Security Rule\n\nCreate `src/ocaml/rules/my_rule.kdl`:\n\n```kdl\nrule \"MyRule\" severity=\"Medium\" {\n    sinks {\n        sink \"Dangerous.call\" arg=0 {\n            sanitizer \"Safe.wrapper\"\n        }\n    }\n    sources {\n        source \"params\"\n        source \"url\"\n    }\n    message \"My rule: {sink} with tainted argument(s): {tainted_vars}.\"\n}\n```\n\n`arg=0` means only flag when tainted data is in the first argument. Omit for any-arg matching.\n`$var` metavariables match any receiver prefix: `sink \"$client.get\"` matches `http.get`, `conn.get`, `my_client.get`.\n\nRebuild with `just build` and test.\n\n### Extraction Strategy\n\n**Crystal** uses a dedicated Crystal extractor (compiled at build time). All other languages use **tree-sitter** with language-specific CST → CatseyeAST mappers.\n\nFor Crystal projects with `shard.yml`, the `lib/` directory is automatically excluded to skip shard dependencies and avoid symlink loops.\n\n**Svelte** uses a two-pass strategy: first parse with tree-sitter-svelte to extract `\u003cscript\u003e` blocks, then parse the script content with the JS/TS grammar.\n\n## Configuration\n\nOptional `.catseye.toml` in your project root (walked up from the target directory):\n\n```toml\n[scan]\nexclude = [\"node_modules\", \".git\", \"vendor\", \"spec\"]\n\n[analysis]\nextra_sources = [\"user_input\", \"raw_params\"]\nextra_sanitizers = [\"sanitize_path\", \"escape_shell\"]\nparallelism = 4\n\n[claws]\ncomplexity_warning = 10\nmax_params = 5\n\n# Suppress code smell rules by file glob\n[claws.suppress]\nDataClump = [\"**\"]\nLongParameterList = [\"**/repositories/**\"]\n\n# Suppress security/taint findings by file glob\n[taint.suppress]\nSSRF = [\"**/validated_http_client.cr\"]\nPathTraversal = [\"**/safe_io.cr\"]\n\n# Suppress specific rules by ID (CLI --suppress flag)\n[suppress]\n# unused-let: Gleam OTP bindings appear unused but are used by runtime\nunused-let = true\nguard-after-wildcard = true\n```\n\n### CLI Suppress Flag\n\nUse `--suppress` to disable specific rules without a config file:\n\n```bash\ncatseye ./src --suppress unused-let,guard-after-wildcard\n\n# Suppress security rules\ncatseye ./src --suppress InsecureRandom,WeakCryptography\n```\n\nThis suppresses rules in both the taint/security engine and AI lint detectors.\n\n### Glob Patterns\n\n- `*` matches any characters except `/`\n- `**` matches any characters including `/` (cross-directory)\n- `?` matches a single character\n\n## Justfile Recipes\n\n```\njust build               Build the engine\njust test                Unit tests + E2E\njust scan \u003cdir\u003e          Scan with terminal output\njust scan-full \u003cdir\u003e     Scan with all checks enabled\njust scan-json \u003cdir\u003e     Scan with JSON output\njust scan-ai \u003cdir\u003e       AI antipattern detection only\njust scan-reports \u003cdir\u003e  Generate JSON + SARIF + Markdown reports\njust fmt                 Format OCaml code\njust lint                Check formatting\njust clean               Clean build artifacts\njust extract \u003cfile\u003e      Run Crystal extractor on a single file (debug)\n```\n\n## Project Structure\n\n```\ncatseye/\n├── src/\n│   ├── ocaml/\n│   │   ├── bin/main.ml                 # CLI entry point\n│   │   ├── lib/\n│   │   │   ├── catseye_engine/          # Flat taint analysis + propagation, extractor registry\n│   │   │   ├── catseye_il/              # IL types, CFG builder (ocamlgraph), dominator analysis\n│   │   │   ├── catseye_ast/             # Unified AST + language mappers + plugin registry\n│   │   │   │   ├── crystal_mapper.ml         # Crystal JSON → AST\n│   │   │   │   ├── gleam_mapper.ml           # Gleam tree-sitter → AST\n│   │   │   │   ├── javascript_mapper.ml      # JS tree-sitter → AST\n│   │   │   │   ├── typescript_mapper.ml      # TS (extends JS mapper)\n│   │   │   │   ├── svelte_mapper.ml          # Svelte two-pass → AST\n│   │   │   │   ├── ocaml_mapper.ml           # OCaml tree-sitter → AST\n│   │   │   │   ├── language_plugin.ml        # Plugin interface\n│   │   │   │   └── plugin_registry.ml        # Plugin discovery\n│   │   │   ├── ai_linter/              # AI antipattern rules\n│   │   │   │   ├── crystal_rules.ml          # Crystal hallucination DB (37 entries)\n│   │   │   │   ├── gleam_rules.ml            # Gleam antipatterns\n│   │   │   │   ├── javascript_rules.ml       # JS/TS hallucinations + antipatterns (60+)\n│   │   │   │   ├── svelte_rules.ml           # Svelte 4→5 + framework confusion (40+)\n│   │   │   │   └── ocaml_rules.ml            # OCaml hallucinations + unsafe ops (55+)\n│   │   │   ├── catseye_claws/           # Code smell detection (AST-native, 16 detectors)\n│   │   │   ├── catseye_crowsnest/       # Supply chain audit\n│   │   │   ├── catseye_rules/           # KDL rule interpreter (arg, $var, fix templates)\n│   │   │   ├── catseye_cli/             # CLI, orchestrator, output formats\n│   │   │   └── catseye_types/           # Shared types\n│   │   └── rules/                       # KDL rule files\n│   │       ├── crystal/*.kdl                  # Crystal security rules\n│   │       ├── javascript.kdl                 # JS/TS security rules\n│   │       └── gleam/*.kdl                    # Gleam security rules\n│   └── extractor/extractor.cr           # Crystal AST extractor\n├── test/samples/                        # Test corpus (Crystal, JS, Svelte)\n├── flake.nix                            # Nix dev shell (all grammars)\n└── justfile                             # Build tasks\n```\n\n## Performance\n\n| Scan                      | Files                        | Extraction | Analysis |\n| ------------------------- | ---------------------------- | ---------- | -------- |\n| Crystal only (72 files)   | 72                           | ~0.12s     | ~0.06s   |\n| Multi-language (89 files) | 72 Crystal + 17 JS/TS/Svelte | ~0.25s     | ~6s      |\n| OCaml self-scan           | 84                           | ~0.19s     | ~0.15s   |\n| Gleam project (144 files) | 115 Gleam + 29 TS/JS         | ~0.73s     | ~0.14s   |\n\n**CFG engine** scales linearly: 500 sequential branches in 0.09ms, 10,000 nodes in 2.4ms, 500-block taint analysis in 0.75ms.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkritoke%2Fcatseye","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkritoke%2Fcatseye","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkritoke%2Fcatseye/lists"}