{"_id":"@alexsoft-hq/cascade","_rev":"4-802be9534cbc8fee236e30f252d81834","name":"@alexsoft-hq/cascade","dist-tags":{"latest":"0.8.11"},"versions":{"0.1.0":{"name":"@alexsoft-hq/cascade","version":"0.1.0","keywords":["static-analysis","impact-analysis","code-to-column","knowledge-graph","mcp","vibe-coding"],"license":"Apache-2.0","_id":"@alexsoft-hq/cascade@0.1.0","maintainers":[{"name":"alexsoft-felix","email":"contact@alexsoft.co.kr"}],"homepage":"https://alexsoft-hq.github.io/Cascade/","bugs":{"url":"https://github.com/alexsoft-hq/Cascade/issues"},"bin":{"cascade":"bin/cascade.mjs"},"dist":{"shasum":"bafc6656f0ee4ba128a3eb6e12a8d0d0cec684b2","tarball":"https://registry.npmjs.org/@alexsoft-hq/cascade/-/cascade-0.1.0.tgz","fileCount":124,"integrity":"sha512-W9ffKYG/aCuy0RXt08BAF1Zoqms7btB+8NhzqKb+yJeNiUlaiJ9DHDbFY/TjoZEXhyCP2z6Jc4HjJf+e7BVJAw==","signatures":[{"sig":"MEYCIQDbVNtjbdWbZUc5fE7g6TvmhwBq7c6OD6yvLfL8zJuE7wIhAIji7L5w0QeUcSG7Z6lebgWyoiQ18L6ZrK5EalhUrU2w","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":5069394},"type":"module","engines":{"node":">=20"},"gitHead":"0632f3543de558484383e81e939b3163d60fa8f0","scripts":{"dco":"node scripts/check-dco.mjs","test":"node --test --experimental-test-coverage test/*.test.mjs","doctor":"node bin/cascade.mjs doctor","test:quick":"node --test test/*.test.mjs"},"_npmUser":{"name":"alexsoft-felix","email":"contact@alexsoft.co.kr"},"repository":{"url":"git+https://github.com/alexsoft-hq/Cascade.git","type":"git"},"_npmVersion":"11.19.0","description":"Code-to-column change-impact knowledge graph for AI coding agents: the round trip from a screen to a database column and back, over MCP.","directories":{},"_nodeVersion":"24.20.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/cascade_0.1.0_1788751845635_0.7579311281733521","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"name":"@alexsoft-hq/cascade","version":"0.5.0","keywords":["static-analysis","impact-analysis","code-to-column","knowledge-graph","mcp","vibe-coding"],"license":"Apache-2.0","_id":"@alexsoft-hq/cascade@0.5.0","maintainers":[{"name":"alexsoft-felix","email":"contact@alexsoft.co.kr"}],"homepage":"https://alexsoft-hq.github.io/Cascade/","bugs":{"url":"https://github.com/alexsoft-hq/Cascade/issues"},"bin":{"cascade":"bin/cascade.mjs"},"dist":{"shasum":"e3936e109b61561551fe2f3e32cc2fc599061203","tarball":"https://registry.npmjs.org/@alexsoft-hq/cascade/-/cascade-0.5.0.tgz","fileCount":133,"integrity":"sha512-vEBkoEUIDxIJNRZpfr1zXAxfw/SQBVyXNS9czGYwDvHYMdyGza9T32tdLYjLFOdY20lXPD/eqQJSMw2PB1gT/w==","signatures":[{"sig":"MEYCIQD8X9mrqW6gjT4Q/UmCO0nQo6y/6qGHBXum3FLBK5ShJgIhALNTCBqnHTxLV0oFfwAsX6G3KFJBR8DI42ZKEP4cOTHg","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":5809532},"type":"module","engines":{"node":">=20"},"gitHead":"adc7e1fcfdca53ab2b0250b6e93a605cb0614469","scripts":{"dco":"node scripts/check-dco.mjs","test":"node --test --experimental-test-coverage test/*.test.mjs","doctor":"node bin/cascade.mjs doctor","test:quick":"node --test test/*.test.mjs"},"_npmUser":{"name":"alexsoft-felix","email":"contact@alexsoft.co.kr"},"repository":{"url":"git+https://github.com/alexsoft-hq/Cascade.git","type":"git"},"_npmVersion":"11.19.0","description":"Code-to-column change-impact knowledge graph for AI coding agents: the round trip from a screen to a database column and back, over MCP.","directories":{},"_nodeVersion":"24.20.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/cascade_0.5.0_1788938288904_0.027538216555751616","host":"s3://npm-registry-packages-npm-production"}},"0.8.9":{"name":"@alexsoft-hq/cascade","version":"0.8.9","keywords":["static-analysis","impact-analysis","code-to-column","knowledge-graph","mcp","vibe-coding"],"license":"Apache-2.0","_id":"@alexsoft-hq/cascade@0.8.9","maintainers":[{"name":"alexsoft-felix","email":"contact@alexsoft.co.kr"}],"homepage":"https://alexsoft-hq.github.io/Cascade/","bugs":{"url":"https://github.com/alexsoft-hq/Cascade/issues"},"bin":{"cascade":"bin/cascade.mjs"},"dist":{"shasum":"d04eb81914b3a975b53c6bd1a6b6eba16507503f","tarball":"https://registry.npmjs.org/@alexsoft-hq/cascade/-/cascade-0.8.9.tgz","fileCount":225,"integrity":"sha512-uJsjqh7ycCe2PoEldYQ5DscJHuQAB1eRSba0ZprH59VSddve6sP4dfz6BB2kzsHl8Qg6T+QXYKXqlGWYAnKJVQ==","signatures":[{"sig":"MEYCIQDN0lc6lhazeM0d1V2Zxm+kmX4Y+xDtCOYIU5J/190NsgIhAIzTdYlWiPzr0zlvC5pEtPXpZrI8S4HpNQGHazvWLkXC","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":6824727},"type":"module","engines":{"node":">=20"},"gitHead":"877df766355b9057a00f9f650af14cdbe783479b","scripts":{"dco":"node scripts/check-dco.mjs","lint":"eslint .","test":"node --test --experimental-test-coverage test/*.test.mjs","doctor":"node bin/cascade.mjs doctor","test:quick":"node --test test/*.test.mjs"},"_npmUser":{"name":"alexsoft-felix","email":"contact@alexsoft.co.kr"},"repository":{"url":"git+https://github.com/alexsoft-hq/Cascade.git","type":"git"},"_npmVersion":"11.19.0","description":"Code-to-column change-impact knowledge graph for AI coding agents: the round trip from a screen to a database column and back, over MCP.","directories":{},"_nodeVersion":"24.20.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"10.10.0","globals":"17.12.0","@eslint/js":"10.0.1"},"_npmOperationalInternal":{"tmp":"tmp/cascade_0.8.9_1789435950581_0.10088963660428663","host":"s3://npm-registry-packages-npm-production"}},"0.8.11":{"name":"@alexsoft-hq/cascade","version":"0.8.11","type":"module","description":"Code-to-column change-impact knowledge graph for AI coding agents: the round trip from a screen to a database column and back, over MCP.","license":"Apache-2.0","homepage":"https://alexsoft-hq.github.io/Cascade/","repository":{"type":"git","url":"git+https://github.com/alexsoft-hq/Cascade.git"},"bugs":{"url":"https://github.com/alexsoft-hq/Cascade/issues"},"engines":{"node":">=20"},"bin":{"cascade":"bin/cascade.mjs"},"publishConfig":{"access":"public"},"scripts":{"test":"node --test --experimental-test-coverage test/*.test.mjs","test:quick":"node --test test/*.test.mjs","lint":"eslint .","doctor":"node bin/cascade.mjs doctor","dco":"node scripts/check-dco.mjs"},"keywords":["static-analysis","impact-analysis","code-to-column","knowledge-graph","mcp","vibe-coding"],"devDependencies":{"@eslint/js":"10.0.1","eslint":"10.10.0","globals":"17.12.0"},"gitHead":"973c3467758a8d03ad9eefb1e54e27347b1cc1e6","_id":"@alexsoft-hq/cascade@0.8.11","_nodeVersion":"24.20.0","_npmVersion":"11.19.0","dist":{"integrity":"sha512-ETTyq+VyHx8o1mb4gy0eJmBkG4cFED2DI726IXGgXJoe9E3cv4nHkhL9q34zGalF+Os47qksRvOFG1rvfkSdSA==","shasum":"f5d2cf2c5833467b76779d5ebd5fef720dd1bb34","tarball":"https://registry.npmjs.org/@alexsoft-hq/cascade/-/cascade-0.8.11.tgz","fileCount":226,"unpackedSize":6905329,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCd1rUYi9TYLn3GshI3JEk9Kc/xn5Xkof6zY8ftkk7iLAIgLnQZvVhfqHUCPd4VNuggBhXtwwHt8BMteKF5AbRykwE="}]},"_npmUser":{"name":"alexsoft-felix","email":"contact@alexsoft.co.kr"},"directories":{},"maintainers":[{"name":"alexsoft-felix","email":"contact@alexsoft.co.kr"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/cascade_0.8.11_1789449325583_0.15349942667303096"},"_hasShrinkwrap":false}},"time":{"created":"2026-09-07T03:30:45.505Z","modified":"2026-09-15T05:15:25.963Z","0.1.0":"2026-09-07T03:30:45.827Z","0.5.0":"2026-09-09T07:18:09.106Z","0.8.9":"2026-09-15T01:32:30.762Z","0.8.11":"2026-09-15T05:15:25.778Z"},"bugs":{"url":"https://github.com/alexsoft-hq/Cascade/issues"},"license":"Apache-2.0","homepage":"https://alexsoft-hq.github.io/Cascade/","keywords":["static-analysis","impact-analysis","code-to-column","knowledge-graph","mcp","vibe-coding"],"repository":{"type":"git","url":"git+https://github.com/alexsoft-hq/Cascade.git"},"description":"Code-to-column change-impact knowledge graph for AI coding agents: the round trip from a screen to a database column and back, over MCP.","maintainers":[{"name":"alexsoft-felix","email":"contact@alexsoft.co.kr"}],"readme":"**English** | [한국어](README.ko.md)\n\n# <picture><source media=\"(prefers-color-scheme: dark)\" srcset=\"docs/assets/cascade-mark-dark.svg\"><img src=\"docs/assets/cascade-mark.svg\" width=\"28\" alt=\"\"></picture> Cascade\n\nA code-to-column change-impact knowledge graph for AI coding agents, and for the\npeople who work beside them. Apache-2.0.\n\n- [A picture first](#a-picture-first)\n- [What it answers](#what-it-answers)\n- [Supported stacks](#supported-stacks)\n- [Install](#install)\n- [Ten minutes on your own project](#ten-minutes-on-your-own-project)\n- [Set it up for your AI agent](#set-it-up-for-your-ai-agent)\n- [The edit loop](#the-edit-loop)\n- [Several projects, one server](#several-projects-one-server)\n- [Honesty, verify and doctor](#honesty-verify-and-doctor)\n- [The viewer](#the-viewer)\n- [Profile and framework packs](#profile-and-framework-packs)\n- [How it is measured](#how-it-is-measured)\n- [Layout of the repository](#layout-of-the-repository)\n- [Contributing, security, conduct](#contributing-security-conduct)\n- [License](#license)\n\n## A picture first\n\n![A tour of the Cascade viewer: the overview dials on litemall, then a column\nfanning out to the SQL statements and HTTP endpoints it touches, the source of\none statement, the whole-project graph, and the schema recovered from the joins\nthe SQL makes on mall](docs/assets/cascade-demo.gif)\n\nThat is one column changed and the answer read in both directions, on two real\nopen-source projects. The still shots below walk the same views one at a time.\n\nCascade answers one question in both directions: **if I change this database\ncolumn, which SQL statements, service methods, HTTP endpoints and user-facing\nscreens are affected, and if I open this screen, which column does it end at?**\nIt answers over MCP so an AI coding agent can ask before it edits, and it draws\nthe same answers in a local viewer so a person can read them.\n\nEvery edge carries its own grade, so an answer tells you exactly how far to\ntrust it: what was proved, what is a sound candidate set, what is a hint. An AI\nagent reads that grade and knows whether it can act on an answer or should check\nit first, which is what makes an impact graph safe to hand to something that\nedits code on its own. What a parser cannot see, Cascade leaves out and says so:\nruntime wiring, reflection and AOP proxies are marked absent, never guessed.\nThat calibration is the product, not a caveat.\n\n### Where it fits\n\nSAST and CodeQL look for vulnerabilities. Cascade looks for what a change\nreaches, the round trip from a screen to a database column and back, with a\ngrade on every edge and no build to run. No SAST or dependency scanner answers\nthat round trip. Where the source does not settle a call, Cascade marks it\nrather than inventing an edge, because an answer you can calibrate is one an\nagent can act on, and that is the whole point.\n\n## What it answers\n\nSix questions, and the tool that answers each. `tools/list` is the full\nsurface; these are the six worth learning first.\n\n| Question | Tool |\n|---|---|\n| I am about to change this column. Which HTTP endpoints are affected? | `endpoint_impact` |\n| Which **screens** does that column reach, through the frontend's own calls? | `screen_impact` |\n| Which SQL statements read or write it, and which of those write? | `column_impact` |\n| I am about to change this endpoint or open this screen. What does it run through, down to the tables? | `flow` |\n| I have edited these files but not committed. What is the blast radius right now? | `changed_impact` |\n| Which two API groups share data through the database without calling each other? | `coupling` |\n\n## Supported stacks\n\n| Stack | What is read | Grade ceiling | What is not read |\n|---|---|---|---|\n| Java Spring MVC controllers | `@RestController` / `@Controller` mapping annotations, parsed with the JDK's own compiler in parse-only mode. No Gradle, no Maven, no dependency classpath | `EXACT` for a mapping on a concrete controller method; `SOUND_SET` for a mapping on an interface the implementer serves | a controller assembled at run time; a handler registered programmatically |\n| MyBatis XML and annotations | `<mapper namespace>` files, `<include refid>` fragments resolved through a global index, and SQL written in `@Select` / `@Insert` / `@Update` / `@Delete` | `EXACT`: a statement id **is** the mapper interface FQN plus the method | a `${}` substitution, which is recorded as a diagnostic rather than guessed at |\n| MyBatis-Plus | `@TableName`, `@TableField`, `@TableId`, `@TableLogic`, the `BaseMapper` / `IService` / `ServiceImpl` built-ins, and condition wrappers down to the method references and literals they carry | `EXACT` where the source names the table or column; `HEURISTIC` where a naming rule had to be assumed | a wrapper whose conditions come from an HTTP query string: the table stays a fact, the columns are marked decided at run time |\n| JPA and Spring Data | `@Entity`, `@Table`, `@Column`, `@Id`, `@JoinColumn`, `@JoinTable`, `@MappedSuperclass`, derived query method names, JPQL `@Query`, native `@Query` through the SQL analyzer, and the repository built-ins a caller reached | `EXACT` where the mapping spells the name out or the naming strategy is declared, in the profile (`jpa.namingStrategy`) or in the project's own `spring.jpa.hibernate.naming.physical-strategy`; `HEURISTIC` where the strategy was assumed | `@Embedded`, `@SecondaryTable`, `@Inheritance`, `@AttributeOverride`, `@Convert`, `@ElementCollection`, named queries |\n| SQL DDL catalogs | `CREATE TABLE` and `ALTER TABLE` with types, nullability, primary keys and comments, per dialect: MySQL and MariaDB, PostgreSQL, Oracle, and H2 and HSQLDB through the ANSI parser. Each dialect brings its own identifier-case rule | `EXACT` | a dialect this engine cannot route, which is refused rather than parsed as MySQL |\n| Live catalog fetch | one read-only connection that reads tables, columns, comments and primary keys and writes a pinned snapshot | `EXACT` | anything but metadata: no table data is ever selected, and analysis itself never connects |\n| Frontends | `.js`, `.mjs`, `.cjs`, `.jsx`, `.ts`, `.tsx` and the `<script>` blocks of `.vue` single-file components; `axios` and `fetch` and `XMLHttpRequest` and the project's own wrappers traced to whichever of them sends the request; `vue-router` and `react-router` declarations composed into screens; OpenAPI 3 and Swagger 2 documents as declared routes; HAR recordings as runtime evidence | `SOUND_SET` for a call traced to a client that sends it; `EXACT` for a `RENDERS` edge onto the file a route declares | which of an imported component's functions really runs, which is a run-time question and stays `SOUND_SET` |\n\n**Not supported, and the engine says so rather than guessing.** Kotlin sources.\nAny backend this engine has no lane for, unless it publishes an OpenAPI\ndocument, in which case the routes exist and nothing below them is walked.\nAngular and Svelte routers. Runtime wiring, reflection and AOP proxies.\nGraphQL. WebSocket. A menu the server sends the frontend when the app starts,\nbeyond the routes the source itself declares.\n\n## Install\n\n```bash\nnpm install -g @alexsoft-hq/cascade     # the command is `cascade`\ncascade doctor                          # says what else, if anything, is missing\n```\n\nOr run it without installing anything: `npx @alexsoft-hq/cascade doctor`. From a\nclone of this repository the same commands are `node bin/cascade.mjs …`, and the\nrest of this page is written that way so it works before you have installed\nanything.\n\n**Node 20 or newer** for the engine itself, and nothing else. The engine and its\nservers have no npm dependencies of their own, so nothing is fetched beyond the\npackage. macOS, Linux and Windows: the suite runs on all three in CI, the\nend-to-end runs included.\n\nOn Windows, `cascade setup` builds the SQL lane's Python at `.venv\\Scripts\\python.exe`\nand that is where a run looks for it; the JDK is whatever `javac` is on `PATH`\nor under `JAVA_HOME`.\n\n**A JDK 17 or newer** for the Java lane. The lane uses the JDK's own compiler\nTree API in parse-only mode, so `javac` and `java` are all it wants:\n\n```bash\nbrew install openjdk                          # macOS\nexport JAVA_HOME=/opt/homebrew/opt/openjdk\nsudo apt-get install default-jdk              # Debian or Ubuntu\nwinget install EclipseAdoptium.Temurin.21.JDK # Windows\n```\n\n**Python 3** for the SQL lane, and one command to wire it up. The `sqlglot`\nversion is pinned in the requirements file rather than chosen at install time,\nbecause a different one parses some statements differently and would move the\npack digest, so let the tool install the pinned set:\n\n```bash\nnode bin/cascade.mjs setup\n```\n\nIt finds a `python3`, builds the virtual environment where a run looks for one,\ninstalls the pinned requirements, and proves it by importing `sqlglot` with the\ninterpreter a run will actually use. If you already have an interpreter with\n`sqlglot` in it, point `CASCADE_PYTHON` at it and skip the command.\n\n**Nothing at all for the web lane.** Its parser is vendored under\n`adapters/web/vendor/`, so reading a frontend needs no install and makes no\nnetwork call.\n\nThen let the tool tell you what is missing:\n\n```bash\nnode bin/cascade.mjs doctor\n```\n\n```\nok      node >= 20                                      v24.20.0\nok      git                                             git version 2.50.1 (Apple Git-155)\nok      python venv (SQL lane)                          .venv/bin/python: Python 3.9.6\nok      sqlglot importable (SQL lane)                   sqlglot 30.17.0\nok      JDK 17+ (java lane)                             javac 26.0.2.1 via homebrew keg (arm64)\nok      web lane parser (vendored)                      adapters/web/vendor/babel-parser.cjs loads and parses\nmissing mysql driver (optional, catalog fetch)          ModuleNotFoundError: No module named 'pymysql'\n                                                        -> .venv/bin/pip install pymysql. This is only needed for `cascade catalog fetch` against mysql\nmissing postgres driver (optional, catalog fetch)       ModuleNotFoundError: No module named 'psycopg'\n                                                        -> .venv/bin/pip install psycopg[binary]. This is only needed for `cascade catalog fetch` against postgres\nmissing oracle driver (optional, catalog fetch)         ModuleNotFoundError: No module named 'oracledb'\n                                                        -> .venv/bin/pip install oracledb. This is only needed for `cascade catalog fetch` against oracle\nok      docker (optional, live-catalog container test)  server 29.7.2\nok      project registry readable                       ~/.cascade/registry.json: 12 project(s)\nok      cache directory writable                        ~/.cache/cascade/doctor-probe\n\nall 7 required prerequisite(s) ok (9 ok, 0 warn, 3 missing)\n```\n\nOne editorial note that holds for every output excerpt on this page: an absolute\npath the engine printed is written here as the relative or `~`-prefixed path you\nwould type. Nothing else in any excerpt is edited.\n\n`doctor` exits 0 only when every **required** prerequisite is ok. The optional\nlines, the three database drivers and Docker, are reported and never fatal: the\nanalysis path never connects to a database, so a missing driver costs you\n`cascade catalog fetch` and nothing else.\n\n## Ten minutes on your own project\n\nThe run below is the one that produced every number on this page. It reads\n[macrozheng/mall](https://github.com/macrozheng/mall) (Apache-2.0, Spring Boot\nwith MyBatis XML mappers and a MySQL dump in the repository) at commit\n`0504e86b`, together with its Vue admin frontend\n[macrozheng/mall-admin-web](https://github.com/macrozheng/mall-admin-web) at\n`81fc17e5`. Point the same commands at your own tree instead.\n\n```bash\ngit clone https://github.com/macrozheng/mall ../target-examples/mall\ngit clone https://github.com/macrozheng/mall-admin-web ../target-examples/mall-admin-web\n```\n\n### 1. `init`: what is in this tree\n\n```bash\nnode bin/cascade.mjs init --root ../target-examples/mall --project mall\n```\n\n```\nproject mall at ../target-examples/mall\nrepositories (1): .@0504e86b\nfiles scanned 717: 524 java (48 spring handlers, 0 JPA entities), 104 mybatis mapper xml, 1 DDL, 0 kotlin, 0 frontend package.json\nbuild tool maven; package prefixes [com.macro.mall]; lanes [sql,java]\nwrote ../target-examples/mall/.cascade/manifest.json\nwrote ../target-examples/mall/.cascade/profile.json\nregistered mall -> ../target-examples/mall/.cascade in ~/.cascade/registry.json\ndiagnostics: none\n```\n\nIt writes three things and nothing else: `manifest.json` (each repository pinned\nto a full commit), `profile.json` (the reading convention, which you may edit),\nand a `.cascade/.gitignore` that ignores `pack/` and `catalog/` because those\ncarry your SQL text and your column comments. It also adds one line to the home\nregistry, so later commands can say `--project mall` instead of a path.\n\nA technology the engine has no lane for comes back as an\n`UNSUPPORTED_TECHNOLOGY` diagnostic rather than being quietly skipped. A second\nrun keeps your edits unless you pass `--force`, and `--json` prints the whole\ndiscovery report.\n\n### 2. `analyze`: the lanes, and one content-addressed pack\n\nWith no lane flag the inputs come from the project itself: the DDL from the\nprofile, the mapper directories and Java source roots from discovery. The run\nprints which lane got what, and from where.\n\n```bash\nnode bin/cascade.mjs analyze --root ../target-examples/mall\n```\n\n```\nlanes [sql,java]: ddl 1 file(s) (profile): ../target-examples/mall/document/sql/mall.sql; mappers 4 dir(s) (discovery); java-src 7 root(s) (discovery; 4 test root(s) excluded (the standard src/test layout; pass --java-src to include): mall-admin/src/test, mall-demo/src/test/java, mall-portal/src/test/java, mall-search/src/test/java); web none; openapi none; har none\nSQL lane: lineage (dialect mysql, identifiers fold-lower) over 904 statement(s)…\nJava lane: 246 endpoints, 9876 calls, 251 dispatch, 904 stmt-bindings (358 unresolved, 439 external, 0 mapper method(s) with no statement in this pack)\nwrote ../target-examples/mall/.cascade/pack/pack.json: 12674 nodes, 19269 edges, lanes [sql,java], digest 99141d55e969\naxes: catalog=shipped statements=shipped jpa=not-shipped mybatisPlus=not-shipped column=shipped code=shipped web=not-shipped screen=not-shipped\ncold (no previous facts-index.json beside the pack, so there is nothing to reuse): parsed 519 java file(s), 904 lineage shard(s) over 906 statement(s), pack digest 99141d55e969\n```\n\nThe **lane line** is the one to read. It names every input, where it came from,\nand what was left out: here, the four `src/test` roots an unflagged run does not\nread. The **axes line** under it is the pack's own declaration of what it can\nanswer, per axis, before anybody asks a question.\n\nmall's frontend lives in a repository of its own, so one flag adds it:\n\n```bash\nnode bin/cascade.mjs analyze --root ../target-examples/mall \\\n  --web-src ../target-examples/mall-admin-web/src\n```\n\n```\nWeb lane: 128 file(s) (83 .vue, 45 .ts/.tsx, 0 .js/.jsx), 0 parse error(s); 155 call site(s) carry a URL (89 literal, 59 template, 2 constant, 5 unresolved), 54 route declaration(s), 1 alias(es), 0 proxy rule(s)\nWeb lane: 153 call site(s), 145 resolved (145 sound, 0 heuristic), 8 unresolved (expression 4, noMatch 3, parameter 1), 3 outside-pack; prefix ../mall-admin-web: (none) (derived)\nWeb lane: 54 screen(s) from 54 route declaration(s), 54 with a component (0 unresolved), 121 exact and 64 candidate RENDERS edge(s); 301 frontend function node(s) (148 send a request, 153 lead to one), 174 CALLS edge(s) (174 exact, 0 sound, 0 heuristic; 0 of them a function handed over as a value)\nwrote ../target-examples/mall/.cascade/pack/pack.json: 13032 nodes, 19797 edges, lanes [sql,java,web], digest 7994e1a5fd22\naxes: catalog=shipped statements=shipped jpa=not-shipped mybatisPlus=not-shipped column=shipped code=shipped web=shipped screen=shipped\n```\n\nAdding a lane widened the pack, and the calibration gate stopped the run the\nfirst time because a ratio moved: see\n[Honesty, verify and doctor](#honesty-verify-and-doctor) below for the exact\nrefusal and the one override.\n\nA second `analyze` reuses what did not change. On this tree, with nothing\nedited:\n\n```\nincremental: reparsed 0 java files (519 reused, 0 dropped), reparsed 0 web file(s) (127 reused, 0 dropped), lineage recomputed 0 statements (904 reused), mapper statements reused, catalog reused, pack digest 7994e1a5fd22\n```\n\nThe digest is the same as the cold run's, which is the point: an incremental\npack must equal a cold pack of the same state byte for byte, and a test holds\nthat against randomly mutated projects.\n\n### 3. `estimate`: what it will answer, and what it will not\n\n```bash\nnode bin/cascade.mjs estimate --root ../target-examples/mall\n```\n\n```\nMEASURED. What the pack that exists actually answers:\n  pack 7994e1a5fd22 built 2026-09-06T19:32:15.901Z lanes [sql,java,web]\n  statementsWithColumnFacts       753 / 906    83.1%\n  statementsWithStringSubst       396 / 906    43.7%\n  endpointsReachingAStatement     205 / 242    84.7%\n  webCallsResolved                145 / 153    94.8%\n  mapperMethodsBound              904 / 904    100.0%\n  callsResolved                  9437 / 10234  92.2%\n  exactAnswerable                 509 / 906    56.2%\n```\n\nRun it **before** you analyze and it answers from discovery alone: what this\ntree will ship, degrade or not ship, axis by axis, with the reason on each line.\nRun it after and it adds the measured half above.\n\n### 4. `view`: look at it yourself\n\n```bash\nnode bin/cascade.mjs view --project mall\n```\n\n```\ncascade viewer at http://127.0.0.1:4319/  serving 1 project(s) [mall], budget 512 MB of pack JSON\n```\n\nThe page is described under [The viewer](#the-viewer).\n\n### 5. `mcp`: serve it to an agent\n\n```bash\nnode bin/cascade.mjs mcp --project mall\n```\n\n```\ncascade mcp: serving 1 project(s) [mall]: packs load on first use, budget 512 MB of pack JSON\n```\n\nThat is a normal MCP server speaking JSON-RPC over stdio: `initialize`, then\n`tools/list`, then `tools/call`. The next section wires it into a client.\n\n## Set it up for your AI agent\n\n```bash\ncascade agent --write                     # Claude Code: .mcp.json + CLAUDE.md\ncascade agent --client cursor --write     # Cursor: .cursor/mcp.json + a rule file\n```\n\nRun it in the project. It writes two things: the MCP server configuration, with\nthe absolute path and the project id filled in from the registry, and a short\nblock that tells the agent **when to ask** (before it edits a mapper, an entity,\na controller or a screen), which tool to ask with, and how to read the `trust`,\n`limits` and grade fields that come back. That block is the half people forget,\nand it is the half the value sits on: a server with no rule attached is a server\nthe model never calls, because nothing in its context says a question is due.\n\nThe block goes into `CLAUDE.md` (or `AGENTS.md` for Codex) between two markers,\nso a second run replaces it in place and your own text around it survives.\nWithout `--write` the command prints every file it would write instead of\nwriting one. Claude Code then needs one approval: a server that arrives in a\nproject `.mcp.json` sits at *Pending approval* until you run `claude` there once\nand approve `cascade`.\n\n### What the command writes, if you would rather do it by hand\n\nEvery configuration below runs the same command, `node <path>/bin/cascade.mjs\nmcp --project <id>`. Use an absolute path to `bin/cascade.mjs`: an MCP client\nstarts the server from a working directory you do not control.\n\n**Claude Code**, from a shell:\n\n```bash\nclaude mcp add cascade -- node /path/to/cascade/bin/cascade.mjs mcp --project mall\n```\n\n**Claude Desktop**, in `claude_desktop_config.json`:\n\n```jsonc\n{\n  \"mcpServers\": {\n    \"cascade\": {\n      \"command\": \"node\",\n      \"args\": [\"/path/to/cascade/bin/cascade.mjs\", \"mcp\", \"--project\", \"mall\"]\n    }\n  }\n}\n```\n\nDrop `--project mall` and the server serves every project in your registry, and\neach tool then takes a `project` argument. Name several instead and it serves\nexactly those: `\"args\": [\"/path/to/cascade/bin/cascade.mjs\", \"mcp\",\n\"--project\", \"mall\", \"--project\", \"shop\"]`.\n\nCursor and a generic stdio client take the same three fields.\n[`docs/setup/agents.md`](docs/setup/agents.md) has each client in full, plus\nwhat to check when a client reports the server as failed.\n\n### A worked session\n\nAn agent that has just been asked to add a discount field starts by asking what\nthe pack even is, then narrows. These are real answers from the run above,\nabridged to the fields under discussion.\n\n**1. What is in here?**\n\n```jsonc\n{\"name\": \"overview\", \"arguments\": {}}\n```\n\n```jsonc\n{ \"answer\": {\n    \"pack\": { \"project\": \"mall\", \"digest\": \"7994e1a5fd22\", \"lanes\": [\"sql\", \"java\", \"web\"] },\n    \"nodes\": [ {\"kind\": \"symbol\", \"count\": 11085}, {\"kind\": \"statement\", \"count\": 906},\n               {\"kind\": \"column\", \"count\": 669}, {\"kind\": \"endpoint\", \"count\": 242},\n               {\"kind\": \"table\", \"count\": 76}, {\"kind\": \"screen\", \"count\": 54} ],\n    \"reach\": { \"endpoints\": 239, \"statementsReached\": 208, \"tablesReached\": 49,\n               \"columnsReached\": 461, \"endpointsWithoutStatement\": 34 } },\n  \"basis\": { \"project\": \"mall\", \"buildDigest\": \"7994e1a5fd22\",\n             \"builtAt\": \"2026-09-06T19:32:15.901Z\", \"freshness\": { \"verdict\": \"unknown\" } },\n  \"trust\": { \"trustLevel\": \"UNCERTIFIED\", \"axes\": [\"overview\"],\n             \"knownGaps\": [\"no-project-golden\", \"jpa-axis-not-shipped\", \"mybatisPlus-axis-not-shipped\"] },\n  \"limits\": [ { \"scope\": \"axis:jpa\",\n                \"reason\": \"the jpa axis of this pack is not-shipped: the JPA bridge did not run, because there is no Java lane or the profile declares no jpa pack. Persistence declared by @Entity or Spring Data is absent, not empty\" } ],\n  \"truncated\": { \"any\": true, \"fields\": [ { \"field\": \"nodes\", \"shown\": 6, \"total\": 6, \"nextOffset\": null } ] } }\n```\n\nRead the four fields at the bottom before the answer at the top, because they\nare what makes the answer usable.\n\n- **`basis`** is what the answer is anchored to: which project, which pack\n  digest, when it was built, and a freshness verdict of `current`, `behind`,\n  `provisional-overlay` or `unknown`. `unknown` never reads as `current`.\n- **`trust`** is a **computed** level, never a typed-in one, and there are four\n  of them: `UNCERTIFIED` (nothing was scored, which is what `UNCERTIFIED` here\n  means: this project has no approved golden corpus, stated rather than hidden),\n  `GOLDEN_FAIL` (a check got something wrong), `RUNTIME_PASS` (the checks that\n  were scored passed, and at least one of them was labelled by a recording of the\n  program running, so the answer covered what actually ran and precision is not\n  covered) and `GOLDEN_PASS`. `knownGaps` names each axis that is degraded or was\n  never built.\n- **`limits`** is what the engine could not see, in sentences, each scoped. The\n  one above says the JPA axis is absent rather than empty, which is a different\n  claim from \"this project uses no JPA\".\n- **`truncated`** says, per list, how many rows were shown out of how many\n  exist, in what order, and where to continue. A truncated list that called\n  itself complete is the failure this field exists to prevent.\n\nThere is a fifth thing to read, and it is the shape of an empty list. `empty`\nanswers `not-shipped` (the axis was never built), `degraded` (built without\nsomething it needed) or `none` (looked, found nothing). Those are three\ndifferent answers and they never collapse into `[]`.\n\n**2. Which statements touch the column?**\n\n```jsonc\n{\"name\": \"column_impact\", \"arguments\": {\"column\": \"pms_product.price\"}}\n```\n\n```jsonc\n{ \"column\": \"pms_product.price\", \"type\": \"DECIMAL(10, 2)\",\n  \"statements\": [ { \"id\": \"com.macro.mall.mapper.PmsProductMapper.insert\", \"access\": \"write\", \"grade\": \"EXACT\" },\n                  { \"id\": \"com.macro.mall.mapper.PmsProductMapper.updateByExample\", \"access\": \"write\", \"grade\": \"EXACT\" } ] }\n```\n\n18 statements: 8 write it, 10 read it, all `EXACT`. This axis is exact because a\nMyBatis statement id **is** the mapper interface plus the method name, and the\ncolumn lineage comes from a real SQL parse against a real catalog.\n\n**3. Which endpoints does that reach?**\n\n```jsonc\n{\"name\": \"endpoint_impact\", \"arguments\": {\"column\": \"pms_product.price\"}}\n```\n\n27 endpoints, 12 of them under `/product/*`, the rest spread over `/home/*`,\n`/member/*`, `/brand/*`, `/esProduct/*`, `/cart/*`, `/productCategory/*` and\n`/flashProductRelation/*`. Every row is `SOUND_SET`, not `EXACT`, and that is\nthe honest grade: a chain is graded by its weakest link, and a parse-only lane\nresolving a call by name produces a candidate set the real target is guaranteed\nto be inside, not a proof.\n\n**4. And which screens?**\n\n```jsonc\n{\"name\": \"screen_impact\", \"arguments\": {\"column\": \"pms_product.price\"}}\n```\n\n```jsonc\n{ \"screen\": \"/pms/updateProduct\", \"grade\": \"SOUND_SET\", \"observed\": false,\n  \"endpoints\": [\"GET /product/updateInfo/{id}\", \"POST /product/create\",\n                \"POST /product/update/deleteStatus\", \"POST /product/update/{id}\"] }\n```\n\n12 screens. `observed` says whether a browser recording confirmed the call, and\n`false` here means no recording was supplied, not that the call does not happen.\n\n**5. Then walk one screen down to the tables.**\n\n```jsonc\n{\"name\": \"flow\", \"arguments\": {\"screen\": \"/pms/product\", \"direction\": \"down\"}}\n```\n\nThe walk crosses six lanes and stops at the tables: 20 frontend functions, then\n10 endpoints, 35 service methods, 7 mapper statements and 5 tables, out of 79\nlinks followed, 38 of them `EXACT` and 41 `SOUND_SET`. `walk.cut` reports\nwhatever the depth cap or the mode floor stopped, so a short answer says why it\nis short.\n\n## The edit loop\n\nYou have edited a file and not committed it. Ask what you may have touched:\n\n```bash\nnode bin/cascade.mjs impact --project mall --verbose\n```\n\n```\noverlay 4ed662d103d9 (fresh): re-parsed 1 java + 0 frontend file(s), dropped 0, provisional 2 node(s) / 2 edge(s)\ntimings ms: load-base 27 + java 133 + web 39 + sql 31 + graph 81 = 311\n  reused 645 cached java shard(s); dirty documents: mall-admin/src/main/java/com/macro/mall/controller/PmsProductController.java@0478df64\nchanged files: 1  (matched 1, unmatched 0)\ntouched: 10 symbols, 0 statements, 11 endpoints\n\nupstream endpoints affected (11):\n  GET /product/list  [EXACT]\n  GET /product/priceCheck/{id}  [EXACT]  PROVISIONAL (only in the overlay)\n  GET /product/simpleList  [EXACT]\n  ...\n\ndownstream columns affected (88):\n  ...\n\n(provisional-overlay) provisional overlay: the dirty files were RE-PARSED and this answer describes the bytes on disk. Rows marked provisional exist only in the overlay (no certified run has seen them); nothing here is published and the pack digest is unchanged.\n```\n\nThe same question over MCP is `changed_impact`, and its answer carries the same\nmachinery in fields:\n\n```jsonc\n{ \"overlay\": { \"applied\": true, \"state\": \"fresh\",\n    \"overlaySessionId\": \"4ed662d103d9ef080bd477fe9e5cde08c2222ac135497970bc66c4747c38be06\",\n    \"baseCommit\": \"0504e86b…\", \"headCommit\": \"0504e86b…\",\n    \"docVersions\": { \"mall-admin/…/PmsProductController.java\": \"0478df64…\" },\n    \"provisionalIds\": { \"symbols\": [\"symbol:com.macro.mall.controller.PmsProductController#priceCheck\"],\n                        \"endpoints\": [\"endpoint:GET /product/priceCheck/{id}\"], \"statements\": [] },\n    \"timingsMs\": { \"loadBase\": 25, \"java\": 132, \"web\": 40, \"sql\": 26, \"build\": 78, \"total\": 301 } },\n  \"basis\": { \"freshness\": { \"verdict\": \"provisional-overlay\", \"overlaySessionId\": \"4ed662d103d9…\" } } }\n```\n\n**`provisional`** is a marker, not a grade. It says this node or edge exists\nonly in the overlay and no certified run has ever seen it. The grade lattice is\nuntouched by it: the new endpoint above is `EXACT` because a mapping annotation\non a concrete controller method **is** its handler, and it is also\n`PROVISIONAL` because nothing certified has seen that method yet.\n\n**`overlaySessionId`** is the sha256 identity of this particular overlay: the\nbase commit plus the sha256 of every dirty document. Two answers with the same\nsession id describe the same bytes on disk. Two with different ones do not, even\none second apart, and an agent that caches answers should key on it.\n\n**`behind`** is what you get after you commit. The overlay is discarded rather\nthan laid onto a base that has moved:\n\n```\noverlay NOT applied (stale-commit): the pack was built at 0504e86b1f1b but HEAD is now 9477dd387ecc, so the overlay is discarded rather than laid onto a base that has moved\nlimit [overlay]: HEAD moved past the pack's base commit; the answer below is the BASE pack's, not the working tree's. Run `cascade analyze` (it is incremental) to certify the new commit\n(behind) provisional: computed from the base pack (files as last analyzed). Edited regions may add or remove connections. Re-run `cascade analyze` for a certified result.\n```\n\n**The one second rule.** This loop is only useful if it fits inside the pause\nbetween an edit and the next question, so the overlay has a one-second budget\nand the run above spent 311 ms of it on a 519-file backend plus a 128-file\nfrontend. Nothing is written while it runs: not the pack, not the fact cache.\nThe overlay may over-approximate; it may **not** omit, and that is a test rather\nthan a promise, comparing it against a full re-analysis of the same bytes.\n\n### Since a commit\n\nThe overlay answers for edits you have not committed. For the edits you have,\n`cascade diff --base-commit <rev>` compares the project with itself as it was\nat that commit: the routes, screens, tables, columns and statements that\nappeared or went away, the edges that changed grade, and the endpoints above\nany of that. The base comes from the project's own history when a certified\n`analyze` kept that build, and is otherwise analyzed now in a temporary git\nworktree, the way the current pack was, with nothing registered or sealed.\n\nIt also shows changed node attributes and edge evidence with exact before/after\nvalues, while keeping file/line-only moves apart so they do not inflate static\naffected APIs or screens. This is what the two graph packs record, not every\nsource/body change or a runtime result.\nspring-petclinic, forty commits back:\n\n```\nbase: commit b5a630b1994b, built now in a temporary worktree, the way the current pack was analyzed\nbase  60eb0629ec1b  petclinic  commit b5a630b199  built 2026-09-14T10:22:23.266Z\nhead  2e3d4b9bd778  petclinic  commit 818c4136ea  built 2026-09-14T10:22:02.018Z\nconditions: the same (lanes, identity rule, axes, workers, profile, engine, flags, roots)\nnodes: +4 -0  (statement +1, symbol +3)\nedges: +25 -2 regraded 20  (EXECUTES +3/~1, IMPLEMENTS_STMT +1, MAY_CALL +6/-2/~1, READS ~11, WRITES +15/~7)\nendpoints above the change: 15\n```\n\nThe conditions come first, because the same code read by another worker or\nwith an axis missing gives a different pack too. Two different projects are\nrefused: their difference is everything, and a list of a thousand added routes\nreads like a review while meaning nothing.\n\n## Several projects, one server\n\n`cascade init` writes one line per project into `~/.cascade/registry.json`, and\nthat file holds addresses only: `{id, dotCascadePath, source, stack,\nlastCertifiedAt}`. The analysis itself never leaves the project's own\n`.cascade/`.\n\nWith no flag, `cascade mcp` and `cascade view` serve every registered project:\n\n```bash\nnode bin/cascade.mjs mcp                                 # every registered project\nnode bin/cascade.mjs mcp --project mall --project shop   # just these two\nnode bin/cascade.mjs mcp --pack .cascade/pack            # one pack directly\nnode bin/cascade.mjs mcp --memory-budget 256             # MB of pack JSON held\n```\n\nPacks are **lazy**. Starting the server reads the registry and nothing else:\n\n```jsonc\n{\"name\": \"projects\", \"arguments\": {}}\n```\n\n```jsonc\n{ \"answer\": {\n    \"projects\": [ { \"id\": \"jpetstore\", \"stack\": [\"sql\", \"java\"], \"loaded\": false, \"bytes\": null,\n                    \"federation\": { \"index\": \"present\", \"serves\": 22, \"calls\": 0 } },\n                  { \"id\": \"mall\", \"stack\": [\"sql\", \"java\", \"web\"], \"loaded\": false, \"bytes\": null,\n                    \"federation\": { \"index\": \"present\", \"serves\": 239, \"calls\": 0 } } ],\n    \"cache\": { \"loaded\": 0, \"bytes\": 0, \"budgetBytes\": 536870912, \"evictions\": 0, \"hits\": 0, \"misses\": 0 } },\n  \"basis\": { \"project\": \"*\", \"scope\": \"server\", \"buildDigest\": null } }\n```\n\nEvery other tool takes an optional `project` argument. One served project\nanswers without being named. Several, with no `project`, is refused:\n\n```\nerror [ambiguous]: several projects are registered: jpetstore, mall. Pass \"project\"\n```\n\nThere is no \"pick the first one\" fallback, because a confident answer about the\nwrong project is exactly the failure this tool exists to prevent.\n\n**Several projects, one answer.** A microservice repository analyzes into one\npack per service, and a pack alone can only say that its code sends\n`GET /owners/{ownerId}` somewhere. When another registered project serves that\nroute, `flow` and `endpoint_impact` keep walking there: the rows from the other\npack carry `project`, `basis.siblings` names every pack the answer walked with\nits own digest, and `answer.federation` lists each crossing, each call that no\nregistered project serves (the remedy is to register it), and each project\nskipped for having no route index. The join is made when the question is\nasked, from the small `routes.json` that `analyze` writes beside each pack; the\npacks and their digests do not change. A crossing is SOUND_SET at best,\nHEURISTIC when several projects serve the same route, and never invented: a\ncall nobody serves stays \"leaves the pack\". `federate: false` asks one pack\nalone. The three whole-project pictures follow the same rule: the Overview\nlists the connected projects and the routes that go to each, the Graph draws\nwhat a request reaches in a sibling as a cluster beside this project's map,\nand the ERD frames each sibling's tables in their own cluster, joined to this\nproject by a dashed HTTP line and never by a key. Measured on\nspring-petclinic-microservices split into five projects, the gateway's\n`GET /api/gateway/owners/{ownerId}` reaches `owners` in customers-service and\n`visits` in visits-service, and the gateway's own `/owners` screen, an\nAngularJS app served from `static/` with no package.json, reaches `owners`\nin customers-service through the route table discovery read from the\ngateway's `application.yml`.\n\n**The cache budget.** `--memory-budget <MB>` bounds the pack JSON held in\nmemory, 512 MB by default, and eviction is least-recently-used. That number is a\nproxy rather than a heap measurement, because Node cannot price a live object\ngraph and measuring the real thing would mean loading the pack the budget is\nmeant to refuse. The ratio of resident graph to proxy that two independent tools\nmeasured, and the two scripts that re-measure it on your own pack, are in\n[`docs/mcp.md`](docs/mcp.md#the-memory-budget). A pack\nthat does not fit the budget **alone** is refused outright, naming both numbers,\nrather than half-loaded.\n\nThe viewer's header carries a project selector for the same set, and the choice\nrides in the URL hash.\n\n## Honesty, verify and doctor\n\n### The grade lattice\n\nEvery edge carries one of five grades, and nothing ever moves **up** the\nlattice.\n\n| Grade | What it means | Example |\n|---|---|---|\n| `EXACT` | proved unique by syntax, symbols and constants | a MyBatis statement id, which **is** the mapper interface plus the method |\n| `SOUND_SET` | a conservative candidate set the real target is guaranteed to be inside | the implementations behind an interface dispatch |\n| `HEURISTIC` | plausible from a project convention, or a recovered or partial binding | a column name derived from a field name with no declared naming strategy |\n| `RUNTIME_ONLY` | not statically decidable, and needs runtime evidence | a request seen in a browser recording |\n| `UNRESOLVED` | the analysis failed, or the shape is unsupported | a URL that resolved to a route nothing here serves |\n\n**A candidate set of one is never promoted to `EXACT`.** Narrowing is not\nproving. The lattice is computed in one file, `src/core/policy.mjs`, and the\nworkers emit evidence rather than grades. A walk is graded by its **weakest\nlink**, so one `SOUND_SET` hop makes the whole chain `SOUND_SET`. Query modes\npick a floor: `strict` uses confirmed edges only, `conservative` adds candidate\ncalls, `heuristic` also admits guessed rules, and `RUNTIME_ONLY` sits below all\nthree, which is why a recorded call is shown and never walked.\n\n### The four fields every answer carries\n\n`basis`, `trust`, `limits` and `truncated`, described in the worked session\nabove. They are stamped with a private `Symbol` in the one file allowed to build\na response, so an object that merely has the right shape is refused before it is\nserialised. Over HTTP that refusal is a 500 `contract-violation`, never a 200.\nThe long version is in [`docs/concepts.md`](docs/concepts.md).\n\n### `verify`\n\n```bash\nnode bin/cascade.mjs verify --project mall\n```\n\n```\nverified ../target-examples/mall/.cascade: 7 check(s) agreed: pack, fact index and gate state match the receipt, the running engine is the one that signed it, and it is valid until 2026-10-06T19:32:05.381Z\ngate NO_CHANGE -> GREEN\n```\n\nIt recomputes every digest in the receipt from the files on disk, checks the\nrunning engine against the one that signed the receipt, and refuses an expired\nreceipt. Exit 4 on any disagreement, never a partial pass.\n\n### `doctor`\n\nShown under [Install](#install). It exists so that a missing prerequisite is one\nreport rather than five failures discovered one command at a time.\n\n### Calibration: every run judged against the last certified one\n\nEach project keeps a sealed baseline in `.cascade/calibration/`, and every\n`analyze` is compared against it. The gate first asks **why** this run differs:\n`NO_SEAL` (no baseline yet, so this run becomes it), `NO_CHANGE` (same engine,\nsame pins), `ENGINE_MOVED` (an engine upgrade), `REPIN` (the analyzed commits\nmoved), or `BOTH_MOVED`.\n\nHere is the gate refusing a real run: the same tree, the same engine, with the\nweb lane added.\n\n```\ngate: NO_CHANGE -> RED - endpointsReachingAStatement dropped 1.28% (>0%)\n  [error] endpointsReachingAStatement: same engine and same pin, but endpointsReachingAStatement moved from 85.8% (205/239) to 84.7% (205/242). Identical inputs must produce identical measurements\n  [error] node:endpoint: same engine and same pin, but node:endpoint moved from 239 to 242\nREJECTED: the pack was written to ../target-examples/mall/.cascade/pack-rejected/pack.json and the certified pack at ../target-examples/mall/.cascade/pack/pack.json was NOT touched\n  a regression is not a new snapshot: fix it. If this drop is the intended new normal, re-run with `--accept-baseline`,\n  which re-seals the baseline from THIS run. That is the only override, and it is a human decision.\n```\n\nRead that carefully, because it is the gate working rather than misfiring. The\nnumerator did not move: 205 endpoints still reach a statement. The denominator\ngrew from 239 to 242, because the web lane found three routes the frontend calls\nthat nothing here serves. The ratio therefore fell while nothing got worse. The\ngate is **comparative**, not absolute, so it reports the finding and leaves the\njudgement to a person:\n\n```bash\nnode bin/cascade.mjs analyze --root <repo> --web-src <front/src> --accept-baseline\n```\n\nThat re-seals the baseline **from that run**, so tomorrow's comparison is\nagainst today. It is the only override there is. A `RED` run is never silently\ndiscarded: its pack goes to `<packDir>-rejected/`, the certified pack is left\nexactly where it was, and the command exits 3.\n\nThe edge counts the gate compares are per type at each grade **or stronger**\n(`edge:READS/HEURISTIC+` is every READS edge graded HEURISTIC, SOUND_SET or\nEXACT). So an edge whose grade rises, say because the project's naming\nstrategy was declared, shrinks no row and is not a drop; an edge that is gone\nstill shrinks every row it was in, and a grade that fell shrinks the stronger\nrows. A baseline sealed before 0.8.10 is summed into the same rows before it is\ncompared, so an upgrade re-seals nothing on trust.\n\nAlongside the gate, `cascade golden` keeps the project's own labelled corpus.\nThe tool **proposes** cases and a **human** approves them, a hash decides which\nare held out, and `check` scores the approved ones through the shipped MCP\ntools. The tool never approves itself, which is why the trust level on every\nanswer can mean something.\n\n### A missing axis is declared, not fatal\n\nEvery lane input is optional, and every lane can be switched off by name:\n`--no-ddl`, `--no-mappers`, `--no-java`, `--no-web`, `--no-openapi`. The run\nstill produces a valid pack, and the pack records `meta.axes` per axis. Here is\nmall with no database catalog:\n\n```bash\nnode bin/cascade.mjs analyze --root ../target-examples/mall --no-ddl \\\n  --out ../mall-no-ddl-pack\n```\n\n```\n{\"code\":\"summary\",\"columnFacts\":1248,\"defaultSchema\":null,\"diagnostics\":2,\"dialect\":\"mysql\",\"identifierCase\":\"fold-lower\",\"identifierCollisions\":0,\"joinFacts\":0,\"level\":\"info\",\"statements\":904,\"tableFacts\":948,\"unresolvedColumns\":5135,\"unresolvedJoins\":46,\"unresolvedRate\":0.8045,\"version\":\"lineage/2\"}\nwrote ../mall-no-ddl-pack/pack.json: 12598 nodes, 13479 edges, lanes [sql,java], digest f0ba9c2b4d78\naxes: catalog=not-shipped statements=shipped jpa=not-shipped mybatisPlus=not-shipped column=degraded code=shipped web=not-shipped screen=not-shipped\n```\n\nColumn facts fall from 6342 to 1248 and unresolved column references rise from\n396 to 5135. Nothing is dropped to make the answer look clean: what cannot be\nattributed without a catalog is recorded as unresolved, the `column` axis\ndeclares itself `degraded`, that declaration lands in `trust.knownGaps` on every\nanswer, and an empty list then says `degraded` rather than `none`.\n\n## The viewer\n\n`cascade view` starts one local web app over the same tool catalog the MCP\nserver uses. The page never reimplements a query: every number on it arrives in\na contract-valid answer from the engine, so the page and a model asking over MCP\ncan never be told different things. It binds `127.0.0.1` and authenticates\nnothing, because it is not meant to be reachable from anywhere else.\n\nThe masthead carries the dateline (which project answered, its digest, which\nlanes ran, the commit it was built from), the chain with a count on each step,\nthree chips for freshness, trust and the limit count, a project selector, a\nlanguage toggle and a theme toggle. Two themes sit on one set of tokens: **dark**\nis the signal room and the default, **light** is the engineering drawing that\nstill reads when printed in greyscale.\n\n### Overview\n\n![The Overview tab in the light theme: the same five dials and whole-project map\nrendered as ink on paper](docs/assets/screens/overview-light.png)\n\nFive dials across the top, from the `overview` answer's own `reach` field: how\nmany endpoints reach SQL, how many statements, tables and columns are reached,\nand how many screens reach a table. Under each number is what it leaves out, in\nthat step's own terms, because a share only means something beside its\nremainder. Beside them is the live whole-project map, asked once and shared with\nthe Graph tab. Below that: the cascade ribbon, what is in the pack, edges by\ntype and grade, the hub tables and endpoints, and a panel of what the engine\ncould not see.\n\n### Explore\n\n![The Explore tab showing a screen card: the component file it mounts, the\nfourteen frontend functions it runs, and the seven API routes those\nreach](docs/assets/screens/explore-screen-card.png)\n\nPick a table, column, statement, endpoint, method or screen and see what it\ntouches. The card above is a screen: the `.vue` file the route declares, the\nfunctions on it, each marked `leads to` or `sends`, and the routes they reach\nwith the grade on each. **My edits** asks the same question about your\nuncommitted changes.\n\n### Flow\n\n![The Flow tab: a screen on the left, then frontend functions, endpoints,\nservice methods and mapper statements in labelled hop\ncolumns](docs/assets/screens/flow-from-screen.png)\n\nOne call read left to right: the entry, the frontend functions, the endpoint,\nthe service methods it may run through, the mapper statements those reach, and\nthe tables at the end. A solid connector is a call the engine can prove, a\ndashed one a call it thinks happens but could not confirm. **by hop** groups the\nsame rows one step at a time with a census per hop.\n\n### Impact\n\n![The Impact tab: a column on the left, then mapper statements, service methods,\nendpoints and frontend functions walked\nbackwards](docs/assets/screens/impact-column.png)\n\nThe same machinery run backwards from a column, table, statement or method, up\nto the endpoints and screens that can reach it. The rail on the left gives every\ntable a caret that opens it into its own columns, so you can walk from a table\ndown to the column you are about to change.\n\n### Coupling\n\n![The Coupling tab: a writer-by-reader matrix of API groups with the shared\ncolumn counts in the cells, and the ranked list of coupled pairs\nbeside it](docs/assets/screens/coupling.png)\n\nTwo API groups can depend on each other without ever calling each other: one\nwrites a column, the other reads it. The matrix shows those pairs, writer down\nthe side and reader across the top. On mall that is 61 pairs across 32 groups,\n267 coupled columns, and 93 columns only one group touches. Click a cell for the\ncolumns the two share and the statements that carry them.\n\n### Graph\n\n![The Graph tab: the whole project as one map, API groups at the centre with\ntheir endpoints and the tables those reach around\nthem](docs/assets/screens/graph-map.png)\n\nThe whole pack as one picture. At rest it draws API groups and tables only, with\neach group-to-table line standing for every endpoint in that group that touches\nthe table; click a group and its endpoints unfold as satellites. A node's radius\nfollows the square root of its degree, line colour is what the endpoint does to\nthe table, and thickness is how many statements carry it. The map does not move\nat rest. Double-click a node for **Around \\<node\\>**: that node in the middle,\nwhat touches it on ring 1, what touches those on ring 2.\n\n### ERD\n\n![The ERD tab: the whole schema laid out by the joins the mapper SQL makes,\nwith the hub tables ranked beside it](docs/assets/screens/erd.png)\n\nThe whole schema, laid out by the joins the mapper SQL makes between tables.\nForeign keys are never read, so a relationship here is a join some statement\nactually makes. On mall that is 27 relationships over 76 tables, joining 32 of\nthem; the other 44 sit in a strip under the map, named as such rather than\ndropped.\n\n### Transactions\n\n![The Transactions tab: each @Transactional method with its write count, read\ncount and the number of tables one commit can\ntouch](docs/assets/screens/transactions.png)\n\nEvery `@Transactional` method, and what one commit can touch through it. mall\nhas 35 of them, and the largest reaches 15 tables.\n\n### Compare\n\nWhat changed in this project since an earlier build of it. Every certified\n`analyze` keeps the pack it replaces in `.cascade/history/`, the five most\nrecent, and the tab appears once the project has one. It never offers another\nproject as a base, because two codebases differ in everything. The first panel\nsays whether the two builds were analyzed the same way; read it before the\nlists, because a difference is a code change only when the analysis did not\nchange. For a commit the history does not hold, `cascade diff --base-commit`\nbuilds the base from the repository. The same report separates attribute and\nevidence changes from location-only moves, and can be printed or downloaded as\nMarkdown with its base, conditions and cut-list totals.\n\n### The source pane\n\n![The source pane docked to the right of the Flow tab, showing the component\nfile on disk with its own line numbers and the answer's lines\nmarked](docs/assets/screens/source-pane.png)\n\nThe picture is the claim; the source is the evidence. One pane shows it, docked\nto the right edge, and every row that makes a claim can open it. It reads the\nfile on disk through a local route, so it shows what is there right now rather\nthan a copy baked into the pack. It carries the file's own line numbers, marks\nthe lines the answer is about, and **Open in editor** hands the file and the line\nto VS Code or IntelliJ.\n\n### The browse rail\n\nExplore, Flow and Impact open on a **list**, not on an empty search box: the\nkinds that tab can show with their counts, a filter, a sort, and the rows with\nthe numbers you would pick by. Every row is one `browse` answer, so the page\ncounts nothing itself; typing filters the rows it already holds and sends no\nrequest. `/` puts the cursor in the filter, the arrow keys move the highlight,\nEnter picks. Under 1100px the rail becomes a drawer behind a **Browse** button.\n\nThe interface language toggle switches the **chrome only**. Grades, trust,\nlimits, empty reasons and every tool's own message stay exactly as the engine\nwrote them, because a translated grade is a grade this project invented and no\nreader could check it against the engine's own answer.\n\n![The Overview tab with the interface language set to Korean, showing that the\ngrades, trust level and limit names stay in the engine's own\nwords](docs/assets/screens/overview-ko.png)\n\n![The Flow tab in Korean: the tab names and hop labels are translated, the node\nids and grades are not](docs/assets/screens/flow-from-screen-ko.png)\n\nFull detail, including how to add a language:\n[`docs/viewer.md`](docs/viewer.md).\n\n## Profile and framework packs\n\n`cascade init` writes `.cascade/profile.json`, and you edit it. It is the\n**reading convention**: what this project's code means, in the places a parser\ncannot tell. Every key in it is either CONSUMED, meaning it changes what the\nengine does, or RECORDED and diagnosed the moment you set it. There are no dead\nkeys, and a test holds that.\n\n```json\n{\n  \"build\": { \"tool\": \"maven\", \"javaRelease\": null, \"profiles\": [] },\n  \"packagePrefixes\": [\"com.macro.mall\"],\n  \"schema\": { \"default\": null, \"propertyNames\": [], \"rewriteLayer\": null },\n  \"sqlDialects\": { \"main\": \"mysql\" },\n  \"sqlIdentifierCase\": null,\n  \"gatewayRoutes\": { \"/dev-api\": \"\" },\n  \"screenAxis\": {\n    \"enabled\": null,\n    \"nameSource\": \"route-meta\",\n    \"pathRule\": \"last-segment\",\n    \"codeRegex\": \"([A-Z]{2}\\\\d{4})\"\n  },\n  \"moduleAttribution\": { \"packageDepth\": null, \"codeLength\": 2 },\n  \"frameworkPacks\": [\"spring-mvc\", \"mybatis-xml\", \"jpa\", \"mybatis-plus\", \"web\", \"vue-router\", \"react-router\"],\n  \"jpa\": { \"namingStrategy\": \"spring-snake-case\" },\n  \"mybatisPlus\": { \"namingStrategy\": \"underscore\", \"tablePrefix\": null,\n                   \"logicDeleteValue\": \"1\", \"logicNotDeleteValue\": \"0\" },\n  \"openapi\": { \"documents\": [\"api/openapi.yaml\"] },\n  \"runtimeEvidence\": { \"har\": [\"evidence/admin-session.har\"],\n                       \"otel\": [\"evidence/checkout-smoke.json\"] },\n  \"catalog\": { \"source\": \"file\", \"connectionFrom\": \"../document/sql/mall.sql\" },\n  \"calibration\": { \"firstRun\": \"bootstrap\", \"maxRelativeDrop\": 0.05,\n                   \"maxRelativeDropOnRepin\": 0.25, \"receiptTtlDays\": 30 }\n}\n```\n\n**`frameworkPacks`** turns lanes on. `spring-mvc` reads mapping annotations,\n`mybatis-xml` reads mapper XML, `jpa` maps entities and repositories,\n`mybatis-plus` reads generic CRUD and condition wrappers, `web` reads the\nfrontend, and `vue-router` and `react-router` say which router declarations to\nrecognise. `cascade init` writes the ones it can see: an `@Entity` file gets\n`jpa`, an `extends BaseMapper<` or a `@TableName` gets `mybatis-plus`, a\n`package.json` depending on Vue or React gets `web` and its router.\n\n**`gatewayRoutes`** maps the prefix the **frontend** writes to the prefix the\n**backend** serves. `{\"/dev-api\": \"\"}` says the dev server strips it, and `\"*\"`\napplies to every call in the project. Declaring it moves the `web` axis from\n`degraded` to `shipped` and its edges from `HEURISTIC` to `SOUND_SET`, because\nthe engine no longer has to work the prefix out by counting matches.\n\n**`screenAxis`** decides whether router declarations become screens.\n`enabled` has three states: `true` and `false` are your word and are obeyed\nwhatever the run reads, and `null`, the default, means decide it from what this\nrun actually reads. That third state is what lets a backend analysed with\n`--web-src ../front/src` build screens with nothing configured. `nameSource`,\n`pathRule` and `codeRegex` shape the label and the grouping only, never the\npath.\n\n**`openapi.documents`** names OpenAPI 3 or Swagger 2 documents to read as\ndeclared routes. A route the code also serves is corroborated; a route nothing\nhere serves is added with **no handler edge**, because a declaration says a route\nexists and says nothing about what runs below it. Both drift lists, declared and\nnot served, served and not declared, are reported and neither is judged.\n\n**`runtimeEvidence.har`** names browser recordings. Every request in one that\nmatches a route this pack serves becomes a `screen` to `endpoint` edge graded\n`RUNTIME_ONLY`, which is below every mode's floor: it is **shown** and **never\nwalked**, and it never raises the grade of the static edge beside it. There is\nno discovery step for recordings, on purpose: a recording is something you made\ndeliberately, and picking one up because it happens to be in the tree would let\nan unrelated capture decide what this pack claims was observed.\n\n**`runtimeEvidence.otel`** names OpenTelemetry trace exports (OTLP/JSON). A trace\nsays which **concrete implementation** really handled a request and which\nstatement it ran, which is the one thing no reading of the source can decide: a\nmapper interface has no implementor in the source at all, and an interface call\nis a candidate set whatever the code says. A confirmed hop keeps its grade and\ngains `observed: true` beside it, an unobserved candidate is left exactly where\nit was, and a hop no static rule explains becomes a `RUNTIME_ONLY` edge that is\nshown and never walked. Nothing is promoted, no SQL text or bound parameter\nenters the pack, and there is no discovery step. The file may be an OTLP/JSON\ndocument or the application log the OpenTelemetry Java agent writes with\n`logging-otlp`, and `cascade otel-methods` prints the\n`otel.instrumentation.methods.include` line that agent needs before it emits the\ncaller spans at all. The worked recipe, with the numbers a real petclinic run\nproduced, is on\n[`docs/setup/runtime-evidence.md`](docs/setup/runtime-evidence.md).\n\nPer-lane detail: [`docs/setup/sql-lane.md`](docs/setup/sql-lane.md),\n[`docs/setup/java-lane.md`](docs/setup/java-lane.md),\n[`docs/setup/web-lane.md`](docs/setup/web-lane.md),\n[`docs/setup/db-catalog.md`](docs/setup/db-catalog.md),\n[`docs/setup/runtime-evidence.md`](docs/setup/runtime-evidence.md).\n\n## How it is measured\n\nThree mechanisms, and the full record with the commands is on\n[`docs/measured.md`](docs/measured.md), together with the list of what is **not**\nverified.\n\n### The generality gate\n\n`scripts/generality-gate.mjs` runs this engine, unchanged and with nothing\nconfigured, over a pinned corpus of real repositories nobody here wrote it for,\nand prints what it reached. `test/generality_gate.test.mjs` compares the result\nagainst `test/fixtures/generality-gate.baseline.json` and **fails when a\nrepository reaches less than it did**. A rise changes nothing until somebody runs\n`--accept`, which rewrites the baseline and prints the diff, because a number\nthat improves silently is a number nobody checked.\n\n| Repository | Endpoints reaching a statement | Tables reached | Columns reached | Frontend calls resolved | Screens reaching a table |\n|---|---|---|---|---|---|\n| jeecgboot/JeecgBoot | 744 / 969 | 73 / 177 | 836 / 2092 | 584 / 936 | 25 / 181 |\n| jishenghua/JSH_ERP | 330 / 339 | 32 / 32 | 409 / 413 | 165 / 193 | 0 / 7 |\n| apache/dolphinscheduler | 204 / 239 | 42 / 65 | 457 / 622 | 219 / 234 | 0 / 44 |\n| macrozheng/mall (+ mall-admin-web) | 205 / 239 | 49 / 76 | 461 / 669 | 145 / 151 | 44 / 54 |\n| linlinjava/litemall | 198 / 219 | 34 / 34 | 376 / 376 | 172 / 177 | 40 / 89 |\n| yangzongzhuan/RuoYi-Vue (+ RuoYi-Vue3) | 123 / 147 | 22 / 33 | 224 / 305 | 121 / 138 | 8 / 21 |\n| jeequan/jeepay | 126 / 134 | 22 / 23 | 302 / 314 | 0 / 0 | 0 / 5 |\n| xuxueli/xxl-job | 31 / 42 | 7 / 8 | 70 / 71 | 24 / 31 | 6 / 11 |\n| mybatis/jpetstore-6 | 11 / 22 | 12 / 13 | 77 / 86 | 52 / 53 | 16 / 16 |\n| spring-projects/spring-petclinic | 15 / 17 | 7 / 7 | 24 / 24 | 12 / 13 | 3 / 8 |\n| spring-petclinic-microservices | 13 / 15 | 7 / 7 | 24 / 24 | 14 / 14 | 8 / 9 |\n| eGovFramework/egovframe-enterprise-business-template | 188 / 219 | 32 / 36 | 223 / 288 | 333 / 357 | 77 / 84 |\n| eGovFramework/egovframe-common-components | 999 / 1193 | 166 / 179 | 1682 / 1862 | 2075 / 2157 | 555 / 657 |\n| eGovFramework/egovframe-msa-edu | 90 / 163 | 20 / 25 | 191 / 270 | 137 / 173 | 33 / 56 |\n| eGovFramework/egovframe-web-sample | 5 / 6 | 2 / 2 | 5 / 5 | 8 / 8 | 2 / 2 |\n| nexacro-spring/nexacro-sample-egov | 10 / 21 | 5 / 5 | 46 / 46 | 7 / 7 | 4 / 30 |\n| naver/ngrinder | 30 / 124 | 7 / 9 | 92 / 114 | 63 / 69 | 0 / 19 |\n| inswave/WRM-Public | 82 / 96 | 27 / 27 | 205 / 222 | 111 / 119 | 40 / 159 |\n| sindohmes/mes4u | 110 / 115 | 41 / 43 | 483 / 673 | 113 / 148 | 0 / 47 |\n\n```bash\nnode scripts/generality-gate.mjs --fetch     # clone every pin, then run\nnode scripts/generality-gate.mjs             # run over whatever is cloned\nnode scripts/generality-gate.mjs --accept    # rewrite the baseline, printing the diff\n```\n\nThe clones live outside this repository, under the cache directory, and every\nrun gets its own registry so the gate never writes into yours. Read the numbers\nas what they are: \"204 of 239 endpoints reach a statement\" says the engine\nconnected 204 chains, not that the other 35 are wrong.\n\n### The goldens\n\nTwo real projects are pinned to a commit and checked end to end.\n\n- **mall**, the MyBatis and Spring MVC golden: 239 endpoints, 906 mapper\n  statements, 76 tables, 669 columns and 10784 symbols, rebuilt from a fresh\n  clone at a new absolute path by the documented no-flag path, producing the\n  same pack digest each time, and `pms_product.price` reaching 8 writing plus 10\n  reading statements and 27 endpoints.\n- **jpetstore-6**, the HSQLDB and MyBatis golden: 13 tables, 86 columns, 25\n  mapper statements and 22 endpoints, of which 11 reach a statement.\n\n```bash\nnode --test test/mall_demo.test.mjs\nnode --test test/jpetstore.test.mjs\nnode --test test/petclinic.test.mjs      # the JPA golden, spring-petclinic\n```\n\nEach of these skips **out loud** without its fixture, naming the fixture and the\ncommand that produces it. CI clones all three at pinned commits and fails if any\nof those tests skips, because a permanent self-omission on a fresh clone is a\ndefect rather than a pass.\n\n### The incremental oracle\n\n`test/incremental.test.mjs` builds a synthetic Spring, MyBatis and MySQL project\nin a git repository, mutates a random subset of its files each round with a\nseeded PRNG, and requires the incremental pack to equal a cold pack **byte for\nbyte**. The correctness claim about reuse is that test, not a promise in a\ndocument.\n\n```bash\nnode --test test/incremental.test.mjs\nnode --test test/overlay_integration.test.mjs   # the overlay omits nothing a full re-analysis finds\n```\n\n## Layout of the repository\n\n```\nbin/cascade.mjs          the CLI: setup | doctor | init | agent | analyze | estimate | verify |\n                         golden | catalog discover|fetch | pack | impact | mcp | view | export | diff\nsrc/core/                the pure engine: determinism, the grade lattice, the graph, the pack,\n                         the response protocol, the working-tree overlay, the chain walk; plus\n                         the project layer (discover, init, profile, lanes, estimate, registry,\n                         resolve, paths) and the incremental core (changeset, invalidate,\n                         facts_store, incremental, worker_versions)\nsrc/mcp/                  the response contract, the query tools, the tool catalog, the stdio and\n                         HTTP servers, and the multi-project host: lazy loading, an LRU under a\n                         memory budget, and routing that refuses to guess which project a call means\nsrc/adapters/            the lane-output to graph bridges: sql_bridge, java_bridge, jpa_bridge,\n                         mp_bridge, mybatis_annotation, web_bridge, openapi_bridge, har_bridge\nsrc/viewer/              the viewer's pure logic under test: the string catalogue and lookup, the\n                         deterministic graph layout every picture is drawn from, and the source pane\nadapters/sql/            Python workers: catalog_ddl, catalog_live, mybatis_extract, lineage\nadapters/java/           the Java worker: JavaFacts, the parse-only javac Tree API pass\nadapters/web/            the frontend worker (webfacts) and its declaration packs: one JSON file per\n                         router convention and one for the HTTP client libraries\nviewer/index.html        the viewer page's markup and CSS, served by `cascade view`\nviewer/js/               the page's own code: thirteen classic scripts, one shared scope, in the\n                         order their numbers give them\nviewer/i18n/             one JSON catalogue per non-English interface language\nviewer/vendor/           the two vendored MIT browser bundles every graph picture renders with\nscripts/                 generality-gate.mjs (the pinned corpus), the memory and pack cost\n                         measurements, the java smoke check, the DCO check\ntest/                    the suite, including the goldens, the incremental oracle, the gates and\n                         the documentation drift checks\ndocs/                    the docs site: concepts, cli, mcp, viewer, measured, setup/, and ko/\n<project>/.cascade/      per-project state: manifest.json (repositories pinned to full commits),\n                         profile.json (the reading convention), pack/ and catalog/, both\n                         gitignored because they carry your SQL text and column comments, and\n                         history/, the last five certified packs, for `diff` and the Compare tab\n~/.cascade/registry.json where the tool remembers which project lives where\n$XDG_CACHE_HOME/cascade/  the regenerable fact shards, always outside your source tree. Delete it\n                         and the next run is cold\n```\n\n## Contributing, security, conduct\n\n- [`CONTRIBUTING.md`](CONTRIBUTING.md): how a round works, the three suites, the\n  gates and what each one checks, the nine invariants with the test behind each\n  and the honest state of the two that are not fully closed, the contributions\n  this project does not accept, and the DCO sign-off (`git commit -s`).\n- [`SECURITY.md`](SECURITY.md): report privately through GitHub Security\n  Advisories, never a public issue. It also spells out what is **not** a\n  vulnerability here: a wrong reachability answer is an accuracy bug and belongs\n  in the open, with the fixture that shows it.\n- [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md): Contributor Covenant 2.1.\n- [`CHANGELOG.md`](CHANGELOG.md): what each round added, and the two lists at\n  the end that separate what has been measured from what has not.\n- Docs site: [`docs/index.md`](docs/index.md).\n\n## License\n\nApache-2.0 (`LICENSE`). The engine and its servers have no npm dependencies.\nThree things are carried from somebody else and are credited in `NOTICE`:\n\n- the viewer's browser bundles under `viewer/vendor/`, `force-graph` and\n  `3d-force-graph` with `three` inside it, all MIT;\n- the web lane's parser under `adapters/web/vendor/`, `@babel/parser`, MIT,\n  pinned to the sha256 its own README publishes;\n- the viewer's three Latin web-font subsets, IBM Plex Sans and IBM Plex Mono,\n  under OFL-1.1.\n\nOptional native analysis lanes, such as a future JVM dataflow lane under an LGPL\nsolver, live in separate subprojects under their own licenses and are never\nlinked into the Apache-2.0 core. `NOTICE` carries that boundary in full.\n","readmeFilename":"README.md"}