From 0999bbfccffbe478ca5c1ff04405ac60c095e9ef Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Mon, 26 Oct 2026 13:13:05 -0400 Subject: [PATCH 01/10] feat: iris_check runs ascot over iad's IRIS connection One tool, one file: `iris_check` loads a .cls/.mac/.int/.inc from the project directory into an ascot workspace and returns its diagnostics. What the file refers to but does not define is resolved on the target namespace through ascot's host `Atelier` trait, which `tools/check.rs` implements over iad's own connection with the two requests iad already makes (`/action/query`, `/doc/{name}`), so ascot opens no connection of its own. Without a connection those references come back as hints. Power users are pointed at `ascot mcp` and `ascot lsp`. ascot 0.4.0 is the first release with the trait. It needs rmcp's `ServerConfig` (lock moves 3.1.3 -> 3.5.1, `get_info` follows the rename; a client naming protocol 2026-07-28 over `initialize` is now answered with 2025-11-25, the newest version that has one) and `if let` match guards, so the toolchain pin and the CI action pins move 1.92.0 -> 1.95.0. Clippy 1.95 flagged one sort in test_changelog.rs. Smoke tests at the three layers: a no-IRIS check (unit), the advertised contract and one stdio call (binary), a wrong `%SYSTEM.Version` member found through the connection (live). --- .github/workflows/audit.yml | 2 +- .github/workflows/ci.yml | 16 +- .github/workflows/release.yml | 4 +- Cargo.lock | 807 +++++++++++++----- README.md | 1 + coverage-floors.toml | 1 + crates/iris-agentic-dev-bin/src/cmd/tool.rs | 1 + crates/iris-agentic-dev-core/Cargo.toml | 2 + .../src/iris/server_manager.rs | 1 + .../iris-agentic-dev-core/src/tools/check.rs | 166 ++++ crates/iris-agentic-dev-core/src/tools/mod.rs | 26 +- .../src/tools/write_gate.rs | 1 + .../tests/binary/check_ascot.rs | 60 ++ .../tests/binary/main.rs | 1 + .../tests/integration/main.rs | 1 + .../integration/test_check_ascot_live.rs | 44 + .../tests/mcp_handshake.rs | 14 +- .../iris-agentic-dev-core/tests/unit/main.rs | 1 + .../tests/unit/test_changelog.rs | 2 +- .../tests/unit/test_check_ascot.rs | 20 + .../tests/unit/test_toolset.rs | 8 +- docs/tools.md | 30 +- rust-toolchain.toml | 2 +- tests/e2e/skill_eval/tool_applicability.toml | 1 + 24 files changed, 972 insertions(+), 240 deletions(-) create mode 100644 crates/iris-agentic-dev-core/src/tools/check.rs create mode 100644 crates/iris-agentic-dev-core/tests/binary/check_ascot.rs create mode 100644 crates/iris-agentic-dev-core/tests/integration/test_check_ascot_live.rs create mode 100644 crates/iris-agentic-dev-core/tests/unit/test_check_ascot.rs diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index 23c4fd3b..f7f37372 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -20,7 +20,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: dtolnay/rust-toolchain@3da603d4464771f1a8c3dd4acb41f03bc8107551 # 1.92.0 + - uses: dtolnay/rust-toolchain@035fceb3c12fb0c6a9461b52775e16473133e591 # 1.95.0 - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3 # Installed from its own lockfile so the scanner is not itself a moving target. - run: cargo install cargo-audit --locked --version 0.22.2 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5d30c52c..1bc045e6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: dtolnay/rust-toolchain@3da603d4464771f1a8c3dd4acb41f03bc8107551 # 1.92.0 + - uses: dtolnay/rust-toolchain@035fceb3c12fb0c6a9461b52775e16473133e591 # 1.95.0 with: components: clippy, rustfmt - name: Install mold linker @@ -137,7 +137,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: dtolnay/rust-toolchain@3da603d4464771f1a8c3dd4acb41f03bc8107551 # 1.92.0 + - uses: dtolnay/rust-toolchain@035fceb3c12fb0c6a9461b52775e16473133e591 # 1.95.0 - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3 - run: cargo build --locked -p iris-agentic-dev - name: MCP handshake smoke test on Windows @@ -248,7 +248,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: dtolnay/rust-toolchain@3da603d4464771f1a8c3dd4acb41f03bc8107551 # 1.92.0 + - uses: dtolnay/rust-toolchain@035fceb3c12fb0c6a9461b52775e16473133e591 # 1.95.0 - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3 - run: cargo build --locked --release -p iris-agentic-dev - uses: azure/login@a641126d1b8aa4d1fa005f4f92df94a3a4c4c906 # v3.1.0 @@ -297,7 +297,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: dtolnay/rust-toolchain@3da603d4464771f1a8c3dd4acb41f03bc8107551 # 1.92.0 + - uses: dtolnay/rust-toolchain@035fceb3c12fb0c6a9461b52775e16473133e591 # 1.95.0 - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3 with: # Separate cache per IRIS version so a binary built against one version @@ -507,7 +507,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: dtolnay/rust-toolchain@3da603d4464771f1a8c3dd4acb41f03bc8107551 # 1.92.0 + - uses: dtolnay/rust-toolchain@035fceb3c12fb0c6a9461b52775e16473133e591 # 1.95.0 with: components: llvm-tools - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3 @@ -624,7 +624,7 @@ jobs: # docs/connecting.md — where the error codes, config keys, env vars, and tool # parameters are documented — had no CI check at all, which is how a per-server # allowlist that was never implemented stayed documented for two releases. - - uses: dtolnay/rust-toolchain@3da603d4464771f1a8c3dd4acb41f03bc8107551 # 1.92.0 + - uses: dtolnay/rust-toolchain@035fceb3c12fb0c6a9461b52775e16473133e591 # 1.95.0 - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3 - name: Docs contract (docs/tools.md, docs/connecting.md, iad SKILL.md) # test_docs_contract is a module of the aggregated `unit` target now, so it needs @@ -639,7 +639,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: dtolnay/rust-toolchain@3da603d4464771f1a8c3dd4acb41f03bc8107551 # 1.92.0 + - uses: dtolnay/rust-toolchain@035fceb3c12fb0c6a9461b52775e16473133e591 # 1.95.0 with: targets: x86_64-unknown-linux-musl,aarch64-unknown-linux-musl - uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2.2.1 @@ -658,7 +658,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: dtolnay/rust-toolchain@3da603d4464771f1a8c3dd4acb41f03bc8107551 # 1.92.0 + - uses: dtolnay/rust-toolchain@035fceb3c12fb0c6a9461b52775e16473133e591 # 1.95.0 with: targets: x86_64-unknown-linux-musl - uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2.2.1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fdd3fe91..648254da 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -68,7 +68,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: dtolnay/rust-toolchain@3da603d4464771f1a8c3dd4acb41f03bc8107551 # 1.92.0 + - uses: dtolnay/rust-toolchain@035fceb3c12fb0c6a9461b52775e16473133e591 # 1.95.0 with: targets: ${{ matrix.target }} # No rust-cache here. A release build restores nothing it did not compile itself: a cache @@ -163,7 +163,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: dtolnay/rust-toolchain@3da603d4464771f1a8c3dd4acb41f03bc8107551 # 1.92.0 + - uses: dtolnay/rust-toolchain@035fceb3c12fb0c6a9461b52775e16473133e591 # 1.95.0 - name: Install cargo-cyclonedx run: cargo install cargo-cyclonedx --locked --version 0.5.9 - name: Generate the SBOM diff --git a/Cargo.lock b/Cargo.lock index 70be31b3..0a82bb84 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "aead" version = "0.5.2" @@ -134,6 +140,30 @@ dependencies = [ "security-framework", ] +[[package]] +name = "ascot" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8f8fc7e9fedc35098f78cdcd9d73f605c83c77304b25e1113130b230e4a1d8e" +dependencies = [ + "base64 0.23.1", + "clap", + "either", + "lsp-server", + "lsp-types", + "percent-encoding", + "rmcp", + "schemars", + "serde", + "serde_json", + "tokio", + "tygr", + "tygr-objectscript", + "ureq", + "url", + "wit-bindgen 0.62.0", +] + [[package]] name = "assert-json-diff" version = "2.0.2" @@ -301,9 +331,15 @@ checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" [[package]] name = "bitflags" -version = "2.11.0" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" [[package]] name = "block-buffer" @@ -465,9 +501,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.0" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946" dependencies = [ "clap_builder", "clap_derive", @@ -475,26 +511,27 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.6.0" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", + "terminal_size", ] [[package]] name = "clap_derive" -version = "4.6.0" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" +checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.5", ] [[package]] @@ -509,6 +546,16 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "combine" +version = "4.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4356eba6976435498d05a311ee9fc486da3bc81c200d3f0d7dd9703b41913f92" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -538,6 +585,7 @@ dependencies = [ "cookie", "document-features", "idna", + "indexmap", "log", "publicsuffix", "serde", @@ -581,6 +629,24 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ba106e844045ddc02c20768ac4b0b7cb45f2cc2ecbd833ac9f5dacb183c2055" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -637,7 +703,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 3.0.3", + "syn 3.0.5", ] [[package]] @@ -648,7 +714,7 @@ checksum = "68f5792fa0d41cd2325ce0ffa64f0a340eaebd4971a3a0c5e1ffd2cc488a355e" dependencies = [ "darling_core", "quote", - "syn 3.0.3", + "syn 3.0.5", ] [[package]] @@ -836,6 +902,26 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "flate2" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" +dependencies = [ + "crc32fast", + "miniz_oxide", + "zlib-rs", +] + +[[package]] +name = "fluent-uri" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17c704e9dbe1ddd863da1e6ff3567795087b1eb201ce80d8fa81162e1516500d" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "fnv" version = "1.0.7" @@ -848,6 +934,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -1053,14 +1145,17 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "foldhash", + "foldhash 0.1.5", ] [[package]] name = "hashbrown" -version = "0.16.1" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "foldhash 0.2.0", +] [[package]] name = "hashlink" @@ -1266,7 +1361,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.2", + "windows-core", ] [[package]] @@ -1394,12 +1489,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.0" +version = "2.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.1", "serde", "serde_core", ] @@ -1465,6 +1560,7 @@ dependencies = [ "aes-gcm", "anyhow", "apple-native-keyring-store", + "ascot", "base64 0.22.1", "bollard", "chrono", @@ -1513,6 +1609,55 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror 2.0.18", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.117", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.117", +] + [[package]] name = "js-sys" version = "0.3.91" @@ -1627,6 +1772,43 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "lsp-server" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee25a31f2e571e426eef2896179450cafc7e2f5be00d8a93b1c2d21c0ff7656" +dependencies = [ + "crossbeam-channel", + "log", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "lsp-types" +version = "0.97.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53353550a17c04ac46c585feb189c2db82154fc84b79c7a66c96c2c644f66071" +dependencies = [ + "bitflags 1.3.2", + "fluent-uri", + "serde", + "serde_json", + "serde_repr", +] + +[[package]] +name = "macro-string" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e7b3a5bbb2f63aaa223a671ea1bd0e7bc16bd30ce387324cf63995deaddbbd2" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + [[package]] name = "matchers" version = "0.2.0" @@ -1651,11 +1833,21 @@ dependencies = [ "autocfg", ] +[[package]] +name = "miniz_oxide" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mio" -version = "1.1.1" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "1788edb87fdc09c7e26304471e2f5be8cdefb1b6930d6e3985fc02ff53bf86ee" dependencies = [ "libc", "wasi", @@ -1930,6 +2122,16 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "prettyplease" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bfe0f4c752e450fc2faf62654f1c134747922825d5b04ca717b8874f41a40c0" +dependencies = [ + "proc-macro2", + "syn 3.0.5", +] + [[package]] name = "proc-macro-crate" version = "3.5.0" @@ -2026,7 +2228,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.60.2", + "windows-sys 0.52.0", ] [[package]] @@ -2120,7 +2322,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", + "bitflags 2.13.2", ] [[package]] @@ -2239,9 +2441,9 @@ dependencies = [ [[package]] name = "rmcp" -version = "3.1.3" +version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f17072af977b0f86f714dbd64b3d37d0715bb63064f9d13483f0a1775813374" +checksum = "ab5023804cbe0304cbe06197ac16e645a0a0d0ffa2953bf735c15c2fa0213350" dependencies = [ "async-trait", "base64 0.23.1", @@ -2271,15 +2473,15 @@ dependencies = [ [[package]] name = "rmcp-macros" -version = "3.1.3" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54817231d63a35330d5c3a5782a4a166fbe3b914a9faab440d3dd8bd37971890" +checksum = "967fa7f7935d7d6217d593df0e8e5c9307bcaf256974a5a9ee64476c97735389" dependencies = [ "darling", "proc-macro2", "quote", "serde_json", - "syn 3.0.3", + "syn 3.0.5", ] [[package]] @@ -2288,7 +2490,7 @@ version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e" dependencies = [ - "bitflags", + "bitflags 2.13.2", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -2302,17 +2504,26 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags", + "bitflags 2.13.2", "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -2321,7 +2532,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags", + "bitflags 2.13.2", "errno", "libc", "linux-raw-sys 0.12.1", @@ -2334,6 +2545,7 @@ version = "0.23.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf" dependencies = [ + "log", "once_cell", "ring", "rustls-pki-types", @@ -2364,6 +2576,33 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1167586491e2b18b8bfbb293e8180ec17c201c4f076d7cb3070ca964e7598f98" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eec689c0bc40ff2458a5977b6619cb718087084a18e02a131c599b62d05e1a5f" + [[package]] name = "rustls-webpki" version = "0.103.16" @@ -2387,6 +2626,15 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schannel" version = "0.1.29" @@ -2398,9 +2646,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" dependencies = [ "chrono", "dyn-clone", @@ -2412,14 +2660,14 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f" +checksum = "d98c67716b46af2f0b8cf752abc930f6f9aecfbf671ecfb531db8a31dbe4e2ba" dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.117", + "syn 3.0.5", ] [[package]] @@ -2453,7 +2701,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags", + "bitflags 2.13.2", "core-foundation", "core-foundation-sys", "libc", @@ -2512,13 +2760,13 @@ dependencies = [ [[package]] name = "serde_derive_internals" -version = "0.29.1" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +checksum = "f852137cce035d6a4df67ccce505ff6b3e9fd3a10e3e52b24dc71e650bb1a9bd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.5", ] [[package]] @@ -2614,6 +2862,28 @@ dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "similar" version = "2.7.0" @@ -2692,9 +2962,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.3" +version = "3.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" dependencies = [ "proc-macro2", "quote", @@ -2734,6 +3004,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "terminal_size" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" +dependencies = [ + "rustix 1.1.4", + "windows-sys 0.61.2", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -2841,9 +3121,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.50.0" +version = "1.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" dependencies = [ "bytes", "libc", @@ -2858,13 +3138,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.5", ] [[package]] @@ -3009,7 +3289,7 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags", + "bitflags 2.13.2", "bytes", "futures-util", "http", @@ -3161,6 +3441,38 @@ dependencies = [ "utf-8", ] +[[package]] +name = "tygr" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e1b0d0e8e9d559c19ab6bf14df937ad31a8894204874bc3285ba5a2976b559" +dependencies = [ + "either", + "tygr-derive", +] + +[[package]] +name = "tygr-derive" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9fd39d2505d05056a142f30fc44e75503d311bc587c61bc90c259f1a294447" +dependencies = [ + "either", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tygr-objectscript" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "491ffcdb514fd5e20eeb4bdb78d2f23afa30bbaf88bdc06db29a215070a8558f" +dependencies = [ + "either", + "tygr", +] + [[package]] name = "typenum" version = "1.20.1" @@ -3206,6 +3518,39 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde93ff05758a36a851a398a772b0b79a82fbe3c186a46f2696baaeaf84c966b" +dependencies = [ + "base64 0.23.1", + "cookie_store", + "flate2", + "log", + "percent-encoding", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "serde_json", + "ureq-proto", + "utf8-zero", + "webpki-roots", +] + +[[package]] +name = "ureq-proto" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841583fc36089d7d59419568ae157f43a5a03a2e5a81ba87c77d1596ef46e922" +dependencies = [ + "base64 0.23.1", + "http", + "httparse", + "log", +] + [[package]] name = "url" version = "2.5.8" @@ -3230,6 +3575,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf8-zero" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -3272,6 +3623,16 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -3293,7 +3654,7 @@ version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.51.0", ] [[package]] @@ -3302,7 +3663,7 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.51.0", ] [[package]] @@ -3371,7 +3732,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" dependencies = [ "leb128fmt", - "wasmparser", + "wasmparser 0.244.0", +] + +[[package]] +name = "wasm-encoder" +version = "0.259.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d0246511d901aacf25d2dc9111f0054947de5e093fe662099e709ff7530dc3" +dependencies = [ + "leb128fmt", + "wasmparser 0.259.0", ] [[package]] @@ -3382,8 +3753,20 @@ checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", "indexmap", - "wasm-encoder", - "wasmparser", + "wasm-encoder 0.244.0", + "wasmparser 0.244.0", +] + +[[package]] +name = "wasm-metadata" +version = "0.259.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7bf119eb01f4a246864c10bd87c8f26c566350abb25aa4d5273c415bbab338" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder 0.259.0", + "wasmparser 0.259.0", ] [[package]] @@ -3392,12 +3775,24 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags", + "bitflags 2.13.2", "hashbrown 0.15.5", "indexmap", "semver", ] +[[package]] +name = "wasmparser" +version = "0.259.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f7c12eac7bb587801590f6a67ff0bc84d0748513864b71108e4b311cc3df694" +dependencies = [ + "bitflags 2.13.2", + "hashbrown 0.17.1", + "indexmap", + "semver", +] + [[package]] name = "web-sys" version = "0.3.91" @@ -3418,6 +3813,24 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e49d0e65abc00c142e8f9428da991da9f1a250c39be97459901eb12b9b49f62a" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webpki-roots" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5f280bbb2b287bc8e7de7e3852945a0ff50d1bbcb5ed42b6c7a568c546df171" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "which" version = "6.0.3" @@ -3446,6 +3859,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -3458,7 +3880,7 @@ version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" dependencies = [ - "windows-core 0.58.0", + "windows-core", "windows-targets 0.52.6", ] @@ -3468,26 +3890,13 @@ version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" dependencies = [ - "windows-implement 0.58.0", - "windows-interface 0.58.0", - "windows-result 0.2.0", - "windows-strings 0.1.0", + "windows-implement", + "windows-interface", + "windows-result", + "windows-strings", "windows-targets 0.52.6", ] -[[package]] -name = "windows-core" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" -dependencies = [ - "windows-implement 0.60.2", - "windows-interface 0.59.3", - "windows-link", - "windows-result 0.4.1", - "windows-strings 0.5.1", -] - [[package]] name = "windows-implement" version = "0.58.0" @@ -3499,17 +3908,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "windows-implement" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "windows-interface" version = "0.58.0" @@ -3521,17 +3919,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "windows-interface" -version = "0.59.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "windows-link" version = "0.2.1" @@ -3560,34 +3947,16 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link", -] - [[package]] name = "windows-strings" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ - "windows-result 0.2.0", + "windows-result", "windows-targets 0.52.6", ] -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -3606,24 +3975,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -3657,30 +4008,13 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", + "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -3693,12 +4027,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -3711,12 +4039,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -3729,24 +4051,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -3759,12 +4069,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -3777,12 +4081,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -3795,12 +4093,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -3813,12 +4105,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - [[package]] name = "winnow" version = "0.7.15" @@ -3872,7 +4158,17 @@ version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" dependencies = [ - "wit-bindgen-rust-macro", + "wit-bindgen-rust-macro 0.51.0", +] + +[[package]] +name = "wit-bindgen" +version = "0.62.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53cb4b5556c3a791e86838ea287782bdafa704d55b0e68b5b81a3a16b9ea5f4b" +dependencies = [ + "bitflags 2.13.2", + "wit-bindgen-rust-macro 0.62.0", ] [[package]] @@ -3883,7 +4179,18 @@ checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" dependencies = [ "anyhow", "heck", - "wit-parser", + "wit-parser 0.244.0", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.62.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ad22a37ecbc0e1fdca34d2b670ba41368cb0920735643e00fdf39a16ee5431" +dependencies = [ + "anyhow", + "heck", + "wit-parser 0.259.0", ] [[package]] @@ -3895,11 +4202,27 @@ dependencies = [ "anyhow", "heck", "indexmap", - "prettyplease", + "prettyplease 0.2.37", "syn 2.0.117", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", + "wasm-metadata 0.244.0", + "wit-bindgen-core 0.51.0", + "wit-component 0.244.0", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.62.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84ba5213d4332c260a0d4e69e34b0f7e4b85ddae10b6ebfbac00d9c185ba628a" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease 0.3.0", + "syn 3.0.5", + "wasm-metadata 0.259.0", + "wit-bindgen-core 0.62.0", + "wit-component 0.259.0", ] [[package]] @@ -3909,12 +4232,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" dependencies = [ "anyhow", - "prettyplease", + "prettyplease 0.2.37", "proc-macro2", "quote", "syn 2.0.117", - "wit-bindgen-core", - "wit-bindgen-rust", + "wit-bindgen-core 0.51.0", + "wit-bindgen-rust 0.51.0", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.62.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f19d63da8e478a370ef8192d90ccb517965b7747d6b46cd2d831dd0ef4d2541" +dependencies = [ + "anyhow", + "macro-string", + "prettyplease 0.3.0", + "proc-macro2", + "quote", + "syn 3.0.5", + "wit-bindgen-core 0.62.0", + "wit-bindgen-rust 0.62.0", ] [[package]] @@ -3924,16 +4263,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags", + "bitflags 2.13.2", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder 0.244.0", + "wasm-metadata 0.244.0", + "wasmparser 0.244.0", + "wit-parser 0.244.0", +] + +[[package]] +name = "wit-component" +version = "0.259.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "092f783dee3253265fcde131475055f8c275ac0176a9c0396db1dc7b5544bfb4" +dependencies = [ + "anyhow", + "bitflags 2.13.2", "indexmap", "log", "serde", "serde_derive", "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", + "wasm-encoder 0.259.0", + "wasm-metadata 0.259.0", + "wasmparser 0.259.0", + "wit-parser 0.259.0", ] [[package]] @@ -3951,7 +4309,26 @@ dependencies = [ "serde_derive", "serde_json", "unicode-xid", - "wasmparser", + "wasmparser 0.244.0", +] + +[[package]] +name = "wit-parser" +version = "0.259.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6390f4f02493ce678d509c859cf1698e38929fb75c8012a6ee2f7d6b6cb66cd7" +dependencies = [ + "anyhow", + "hashbrown 0.17.1", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-ident", + "wasmparser 0.259.0", ] [[package]] @@ -4135,6 +4512,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "zlib-rs" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112" + [[package]] name = "zmij" version = "1.0.21" diff --git a/README.md b/README.md index d3e1cffd..11108997 100644 --- a/README.md +++ b/README.md @@ -597,6 +597,7 @@ blocked and how to permit it. | `iris_execute_method` | Invoke a `ClassMethod` directly by class+method+args, no boilerplate. | | `iris_query` | Execute SQL, return rows as JSON. `mode=explain\|count\|write` for plans, counts, and gated DML. | | `iris_test` | Run `%UnitTest` tests, return structured pass/fail results. | +| `iris_check` | Check one file with [ascot](https://github.com/intersystems-community/ascot) without compiling it. | | `iris_global` | Read, write, kill, or list IRIS global nodes. Patient-data and system globals are gated. | | `iris_coverage` | Measure ObjectScript line coverage via `%Monitor.System.LineByLine`. | | `iris_source_control` ✦ | Check lock status, checkout, execute SCM actions. | diff --git a/coverage-floors.toml b/coverage-floors.toml index 1b8182c6..26d555e3 100644 --- a/coverage-floors.toml +++ b/coverage-floors.toml @@ -110,6 +110,7 @@ overall = 88 # `overall` floor, which is measured with IRIS up. "src/tools/admin.rs" = 69 "src/tools/admin_tools.rs" = 12 +"src/tools/check.rs" = 4 "src/tools/comparison_tools.rs" = 11 "src/tools/coverage.rs" = 21 "src/tools/dict.rs" = 53 diff --git a/crates/iris-agentic-dev-bin/src/cmd/tool.rs b/crates/iris-agentic-dev-bin/src/cmd/tool.rs index d008d325..b62545c4 100644 --- a/crates/iris-agentic-dev-bin/src/cmd/tool.rs +++ b/crates/iris-agentic-dev-bin/src/cmd/tool.rs @@ -34,6 +34,7 @@ pub const TOOL_NAMES: &[&str] = &[ "iris_add_server", "iris_admin", "iris_business_rule_info", + "iris_check", "iris_compile", "iris_containers", "iris_coverage", diff --git a/crates/iris-agentic-dev-core/Cargo.toml b/crates/iris-agentic-dev-core/Cargo.toml index 768e5a1f..4fb490f8 100644 --- a/crates/iris-agentic-dev-core/Cargo.toml +++ b/crates/iris-agentic-dev-core/Cargo.toml @@ -44,6 +44,8 @@ futures-util = { version = "0.3", features = ["sink"] } base64 = "0.22" aes-gcm = "0.10" similar = "2" +# 0.4.0 is the first release with the host `Atelier` interface `tools/check.rs` implements. +ascot = "0.4.0" # Platform keychain store crates — already in the dep tree via keyring v4. diff --git a/crates/iris-agentic-dev-core/src/iris/server_manager.rs b/crates/iris-agentic-dev-core/src/iris/server_manager.rs index d8ea4d7b..6049ba04 100644 --- a/crates/iris-agentic-dev-core/src/iris/server_manager.rs +++ b/crates/iris-agentic-dev-core/src/iris/server_manager.rs @@ -973,6 +973,7 @@ fn tool_to_category(tool_name: &str) -> Option ToolCategory::Execute, "iris_query" => ToolCategory::Query, "iris_search" | "iris_symbols" | "iris_symbols_local" => ToolCategory::Search, + "iris_check" => ToolCategory::Docs, "docs_introspect" | "iris_doc" => ToolCategory::Docs, "iris_source_control" => ToolCategory::SourceControl, "debug_capture_packet" diff --git a/crates/iris-agentic-dev-core/src/tools/check.rs b/crates/iris-agentic-dev-core/src/tools/check.rs new file mode 100644 index 00000000..8403a8a4 --- /dev/null +++ b/crates/iris-agentic-dev-core/src/tools/check.rs @@ -0,0 +1,166 @@ +//! `iris_check`: ascot's static analysis of one file, with iad's connection answering the lookups. +//! +//! ascot asks its `Atelier` trait for what the file refers to but does not define, as two REST +//! requests iad already makes: `/action/query` and `/doc/{name}`. The trait is synchronous and the +//! connection async, so the check runs on a blocking thread and drives each request through the +//! runtime handle. The context is fresh per call: ascot caches lookups for an hour, and an agent +//! that edits a class on the server and then checks a caller of it must see the edit. + +use std::sync::Arc; + +use ascot::bindings::{Diagnostic, DiagnosticSeverity, GuestWorkspace}; +use ascot::{Atelier, AtelierContext, IrisContext, Workspace}; +use rmcp::model::CallToolResult; +use rmcp::ErrorData as McpError; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +use super::output_schemas::ToolError; +use super::{err_json, host_fs, nopws, ok_json, IrisTools}; +use crate::iris::connection::IrisConnection; + +/// Parameters of `iris_check`. +#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct CheckParams { + /// A `.cls`, `.mac`, `.int` or `.inc` file inside the project directory. + pub path: String, + /// Namespace that resolves the file's references. Omit to use the connection's. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub namespace: Option, + /// Route this call to a named registered IRIS instance. If omitted, uses the default connection. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub server: Option, +} + +#[derive(Debug, Serialize, JsonSchema)] +#[serde(untagged)] +pub enum IrisCheckResponse { + Ok(CheckOk), + Err(ToolError), +} + +#[derive(Debug, Serialize, JsonSchema)] +pub struct CheckOk { + pub success: bool, + pub path: String, + /// Whether a connection resolved the file's references; without one they are hints. + pub iris: bool, + pub namespace: String, + pub diagnostics: Vec, + pub errors: usize, + pub warnings: usize, + pub hints: usize, +} + +/// iad's connection as the two requests ascot asks for. +pub struct IadAtelier { + conn: Arc, + client: reqwest::Client, + namespace: String, + handle: tokio::runtime::Handle, +} + +impl Atelier for IadAtelier { + fn query(&self, sql: &str, params: &[&str]) -> Result { + let params = params.iter().map(|p| Value::from(*p)).collect(); + let body = self + .handle + .block_on(self.conn.query(sql, params, &self.namespace, &self.client)) + .map_err(|e| format!("query in {}: {e}", self.namespace))?; + match body.pointer("/result/content") { + Some(rows) if rows.is_array() => Ok(rows.to_string()), + _ => Err(format!("query answered with no rows: {}", body["status"])), + } + } + + fn get_doc(&self, name: &str) -> Result, String> { + let path = format!("/doc/{}", urlencoding::encode(name)); + let url = self.conn.versioned_ns_url(&self.namespace, &path); + let err = |e: &dyn std::fmt::Display| format!("GET {url}: {e}"); + self.handle.block_on(async { + let resp = self + .client + .get(&url) + .basic_auth(&self.conn.username, Some(&self.conn.password)) + .send() + .await + .map_err(|e| err(&e))?; + if resp.status() == reqwest::StatusCode::NOT_FOUND { + return Ok(None); + } + let body: Value = + crate::iris::http::capped_json(resp.error_for_status().map_err(|e| err(&e))?) + .await + .map_err(|e| err(&e))?; + let lines: Vec<&str> = body["result"]["content"] + .as_array() + .map(|a| a.iter().filter_map(Value::as_str).collect()) + .unwrap_or_default(); + Ok((!lines.is_empty()).then(|| lines.join("\n"))) + }) + } +} + +/// The diagnostics of one document, `atelier` resolving what it does not define. +pub fn check(uri: &str, text: &str, atelier: Option) -> Vec { + let iris = atelier.map(|a| Box::new(AtelierContext::from(a)) as Box); + let workspace = Workspace::native(iris, None); + workspace.open(uri.to_string(), text.to_string()); + workspace.diagnostics(uri.to_string()) +} + +pub(crate) async fn handle_iris_check( + tools: &IrisTools, + path: &str, + namespace: Option<&str>, +) -> Result { + // Judged before it is read: inside the project directory, with one of the compile extensions. + let file = { + let (root, cwd) = tools.host_path_context(); + let exts = host_fs::COMPILE_EXTENSIONS; + match host_fs::confine_file(root.as_deref(), &cwd, path, exts) { + Ok(canonical) => canonical, + Err(host_fs::PathRefusal::NotFound) => { + return err_json("FILE_NOT_FOUND", &format!("Local file not found: {path}")) + } + Err(why) => return tools.refuse(host_fs::refusal_body("iris_check", path, &why, exts)), + } + }; + let namespace = tools.target_ns(namespace)?; + // Lookups go through the target connection when it speaks Atelier (a docker-only one cannot). + let atelier = tools + .target_conn_opt()? + .filter(|conn| !nopws::atelier_unavailable(conn)) + .map(|conn| IadAtelier { + conn, + client: tools.http_client().clone(), + namespace: namespace.clone(), + handle: tokio::runtime::Handle::current(), + }); + let iris = atelier.is_some(); + // ascot is synchronous and its lookups block on the runtime, so it runs off the worker threads. + let diagnostics = match tokio::task::spawn_blocking(move || { + std::fs::read_to_string(&file).map(|text| check(&file.to_string_lossy(), &text, atelier)) + }) + .await + .map_err(|e| McpError::internal_error(format!("check: {e}"), None))? + { + Ok(diagnostics) => diagnostics, + Err(e) => return err_json("FILE_READ_FAILED", &format!("cannot read {path}: {e}")), + }; + use DiagnosticSeverity as S; + let n = |s: S| diagnostics.iter().filter(|d| d.severity == s).count(); + let ok = CheckOk { + success: true, + path: path.to_string(), + iris, + namespace, + errors: n(S::Error), + warnings: n(S::Warning), + hints: n(S::Hint), + diagnostics, + }; + ok_json(serde_json::to_value(ok).unwrap_or(Value::Null)) +} diff --git a/crates/iris-agentic-dev-core/src/tools/mod.rs b/crates/iris-agentic-dev-core/src/tools/mod.rs index 87602066..8f484bc1 100644 --- a/crates/iris-agentic-dev-core/src/tools/mod.rs +++ b/crates/iris-agentic-dev-core/src/tools/mod.rs @@ -95,6 +95,7 @@ pub use crate::iris::connection::{mcp_peer, MCP_PEER}; pub mod admin; pub mod admin_tools; +pub mod check; pub mod comparison_tools; pub mod coverage; pub mod dict; @@ -140,16 +141,16 @@ pub use scm::{ScmAction, ScmParams}; /// happened and how it's now derived instead of hand-maintained.) #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Toolset { - /// 84 tools — 93 total `#[tool]` methods minus the 9 that are Merged-tier-only + /// 85 tools — 94 total `#[tool]` methods minus the 9 that are Merged-tier-only /// dispatchers (iris_admin, iris_debug, iris_containers, iris_get_log, iris_global, /// iris_execute_method, iris_message_body, iris_business_rule_info, /// iris_production_diff — added by later specs and deliberately scoped to Merged /// rather than the original tool surface). Default when `IRIS_TOOLSET` is unset. Baseline, - /// 80 tools — Baseline (84) minus the 4 stub tools (skill_propose/skill_optimize/ + /// 81 tools — Baseline (85) minus the 4 stub tools (skill_propose/skill_optimize/ /// skill_share/skill_community_install). Nostub, - /// 81 tools — 93 total minus the 4 stubs minus 8 tools replaced by 2 consolidated + /// 82 tools — 94 total minus the 4 stubs minus 8 tools replaced by 2 consolidated /// dispatchers (4 debug_* tools → iris_debug; agent_info/iris_list_containers/ /// iris_select_container/iris_start_sandbox → iris_containers). The 9 /// Merged-tier-only dispatchers from Baseline's note above are present here, which @@ -6069,6 +6070,20 @@ impl IrisTools { })) } + #[tool( + description = "Check one ObjectScript file with intersystems-community/ascot, without saving or compiling it. ascot (https://github.com/intersystems-community/ascot) is a static analyzer and type checker: undefined labels and macros, unknown classes, methods and properties, wrong argument counts, unreachable code. path: a .cls, .mac, .int or .inc file inside the project directory. What the file refers to but does not define (superclasses, called classes and routines, included .inc files) is resolved against the target namespace through this connection with read-only Atelier requests; no code runs on IRIS. Without a connection those references are reported as hints, not checked. Returns LSP-style diagnostics: zero-based range, severity (Error, Warning, Information, Hint), a fixed code and its docs.rs page. Skill: objectscript-tdd. `server` (optional): name of a registered IRIS instance. If omitted, uses the default connection. Use `iris_servers` to list available instances.", + annotations(read_only_hint = true), + output_schema = output_schemas::oneof_output_schema::() + )] + async fn iris_check( + &self, + Parameters(p): Parameters, + ) -> Result { + let result = check::handle_iris_check(self, &p.path, p.namespace.as_deref()).await; + self.record_call("iris_check", result.is_ok()); + result + } + #[tool( description = "Introspect an ObjectScript class — returns methods, properties, and type information. Methods include FormalSpec as a structured array of {name, type, byref, output, default} objects and a ReturnType field. For BPL and DTL classes, an xdata_flow field describes the process steps (BPL: kind=bpl, steps array with Call/Code/If/Other entries, has_dynamic_dispatch flag; DTL: kind=dtl, source_class, target_class, subtransforms, assign_count). Skill: objectscript-navigation. `server` (optional): name of a registered IRIS instance. If omitted, uses the default connection. Use `iris_servers` to list available instances.", annotations(read_only_hint = true), @@ -9915,10 +9930,10 @@ impl ServerHandler for IrisTools { .await } - fn get_info(&self) -> ServerInfo { + fn get_info(&self) -> ServerConfig { // Cap to 2025-11-25: the 2026-07-28 draft requires per-tool cache metadata that // clients using that version will reject when absent. Pin until we opt into caching. - ServerInfo::new(ServerCapabilities::builder().enable_tools().build()) + ServerConfig::new(ServerCapabilities::builder().enable_tools().build()) .with_protocol_version(ProtocolVersion::V_2025_11_25) .with_server_info(Implementation::new( "iris-agentic-dev".to_string(), @@ -11756,6 +11771,7 @@ impl IrisTools { }; } dispatch!("iris_compile", CompileParams, iris_compile); + dispatch!("iris_check", check::CheckParams, iris_check); dispatch!("iris_execute", ExecuteParams, iris_execute); dispatch!("iris_test", TestParams, iris_test); dispatch!("iris_query", QueryParams, iris_query); diff --git a/crates/iris-agentic-dev-core/src/tools/write_gate.rs b/crates/iris-agentic-dev-core/src/tools/write_gate.rs index 55dc0f16..35135de4 100644 --- a/crates/iris-agentic-dev-core/src/tools/write_gate.rs +++ b/crates/iris-agentic-dev-core/src/tools/write_gate.rs @@ -685,6 +685,7 @@ pub const CLASSIFICATION: &[ToolClass] = &[ ro("iris_servers").local_default(), ro("iris_symbols"), ro("iris_symbols_local").local(), + ro("iris_check"), mixed( "iris_system_performance", &[ diff --git a/crates/iris-agentic-dev-core/tests/binary/check_ascot.rs b/crates/iris-agentic-dev-core/tests/binary/check_ascot.rs new file mode 100644 index 00000000..64687838 --- /dev/null +++ b/crates/iris-agentic-dev-core/tests/binary/check_ascot.rs @@ -0,0 +1,60 @@ +//! `iris_check` over stdio: its advertised contract, and one check of a file in the project +//! directory with no IRIS configured. + +use iris_agentic_dev_core::testing::{ + assert_advertised_contracts, assert_source_contracts, require_iad_binary, McpSession, + ParamContract, +}; + +const CONTRACT: &[ParamContract] = &[ParamContract { + tool: "iris_check", + params: &[ + ("path", "string"), + ("namespace", "string"), + ("server", "string"), + ], + required: &["path"], +}]; + +#[test] +#[ignore = "spawns the built binary; run with --include-ignored"] +fn iris_check_advertises_its_contract() { + let Some(_bin) = require_iad_binary() else { + return; + }; + assert_advertised_contracts(CONTRACT); +} + +#[test] +fn iris_check_reads_what_it_advertises() { + assert_source_contracts(CONTRACT); +} + +#[test] +#[ignore = "spawns the built binary; run with --include-ignored"] +fn a_file_in_the_project_is_checked_without_iris() { + let Some(_bin) = require_iad_binary() else { + return; + }; + let dir = tempfile::tempdir().expect("tempdir"); + std::fs::write( + dir.path().join("Test.mac"), + "ROUTINE Test [Type=MAC]\n Do Missing\n Quit\n", + ) + .unwrap(); + let env = [( + "OBJECTSCRIPT_WORKSPACE".to_string(), + dir.path().to_string_lossy().into_owned(), + )]; + let answer = McpSession::start_in(Some(dir.path()), &env) + .call("iris_check", &serde_json::json!({"path": "Test.mac"})); + let text = answer["result"]["content"][0]["text"] + .as_str() + .unwrap_or_else(|| panic!("no text content: {answer}")); + let b: serde_json::Value = serde_json::from_str(text).expect("JSON body"); + assert_eq!(b["success"], true, "{b}"); + assert_eq!(b["iris"], false, "{b}"); + assert_eq!(b["errors"], 1, "{b}"); + assert_eq!(b["diagnostics"][0]["severity"], "Error", "{b}"); + assert_eq!(b["diagnostics"][0]["range"]["start"]["line"], 1, "{b}"); +} diff --git a/crates/iris-agentic-dev-core/tests/binary/main.rs b/crates/iris-agentic-dev-core/tests/binary/main.rs index 4b97703f..f84beacb 100644 --- a/crates/iris-agentic-dev-core/tests/binary/main.rs +++ b/crates/iris-agentic-dev-core/tests/binary/main.rs @@ -14,6 +14,7 @@ fn isolate_the_test_process() { iris_agentic_dev_core::testing::isolate_process(); } +mod check_ascot; mod cli_discovery; mod coding_pack_129; mod gate_normalize_163; diff --git a/crates/iris-agentic-dev-core/tests/integration/main.rs b/crates/iris-agentic-dev-core/tests/integration/main.rs index 1a522afc..f12dcd1f 100644 --- a/crates/iris-agentic-dev-core/tests/integration/main.rs +++ b/crates/iris-agentic-dev-core/tests/integration/main.rs @@ -33,6 +33,7 @@ mod test_admin_e2e; mod test_aihub_139_live; mod test_attribution_live; mod test_benchmark_live; +mod test_check_ascot_live; mod test_cmd_live; mod test_codemode_gate_live; mod test_comparison_e2e; diff --git a/crates/iris-agentic-dev-core/tests/integration/test_check_ascot_live.rs b/crates/iris-agentic-dev-core/tests/integration/test_check_ascot_live.rs new file mode 100644 index 00000000..65fd38de --- /dev/null +++ b/crates/iris-agentic-dev-core/tests/integration/test_check_ascot_live.rs @@ -0,0 +1,44 @@ +//! `iris_check` against iris-dev-iris: a name the file does not define is resolved through iad's +//! connection, so a wrong member of a system class is an error rather than a hint. +//! +//! Run: `IRIS_HOST=localhost IRIS_WEB_PORT=52780 IRIS_USERNAME=_SYSTEM IRIS_PASSWORD=SYS +//! IAD_BINARY=$PWD/target/debug/iris-agentic-dev cargo test --features testing --test integration +//! test_check_ascot -- --include-ignored --test-threads=1` + +use iris_agentic_dev_core::testing::{live_env, McpSession}; + +#[test] +#[ignore = "requires live IRIS"] +fn a_wrong_member_of_a_system_class_is_an_error() { + let dir = tempfile::tempdir().expect("tempdir"); + std::fs::write( + dir.path().join("Live.CheckProbe.cls"), + "Class Live.CheckProbe\n{\nClassMethod Run()\n{\n Write ##class(%SYSTEM.Version).GetVersion()\n Write ##class(%SYSTEM.Version).NoSuchMethod()\n}\n}\n", + ) + .unwrap(); + let mut env = live_env(); + env.retain(|(k, _)| k != "IRIS_CONTAINER"); + env.push(( + "OBJECTSCRIPT_WORKSPACE".to_string(), + dir.path().to_string_lossy().into_owned(), + )); + let answer = McpSession::start_in(Some(dir.path()), &env).call( + "iris_check", + &serde_json::json!({"path": "Live.CheckProbe.cls", "namespace": "USER"}), + ); + let text = answer["result"]["content"][0]["text"] + .as_str() + .unwrap_or_else(|| panic!("no text content: {answer}")); + let b: serde_json::Value = serde_json::from_str(text).expect("JSON body"); + assert_eq!(b["success"], true, "{b}"); + assert_eq!(b["iris"], true, "{b}"); + assert_eq!(b["errors"], 1, "{b}"); + assert_eq!(b["hints"], 0, "{b}"); + assert!( + b["diagnostics"][0]["message"] + .as_str() + .unwrap_or("") + .contains("NoSuchMethod"), + "{b}" + ); +} diff --git a/crates/iris-agentic-dev-core/tests/mcp_handshake.rs b/crates/iris-agentic-dev-core/tests/mcp_handshake.rs index d97f5ed1..a6578639 100644 --- a/crates/iris-agentic-dev-core/tests/mcp_handshake.rs +++ b/crates/iris-agentic-dev-core/tests/mcp_handshake.rs @@ -444,8 +444,14 @@ fn mcp_server_negotiates_all_known_protocol_versions() { return; }; - // All known protocol versions should be echoed back - for client_version in &["2026-07-28", "2025-11-25", "2024-11-05"] { + // Every known version with an `initialize` handshake is echoed back. 2026-07-28 moved the + // lifecycle into per-request `_meta`, so a client naming it over `initialize` is answered with + // the newest version that still has one (rmcp's `LATEST_WITH_INITIALIZE`). + for (client_version, expected) in &[ + ("2026-07-28", "2025-11-25"), + ("2025-11-25", "2025-11-25"), + ("2024-11-05", "2024-11-05"), + ] { let mut child = iris_agentic_dev_core::testing::clean_mcp_command(&bin) .env("IRIS_HOST", "127.0.0.1") .env("IRIS_WEB_PORT", "9") @@ -471,8 +477,8 @@ fn mcp_server_negotiates_all_known_protocol_versions() { .unwrap_or("missing"); assert_eq!( - server_version, *client_version, - "client sent {client_version}, server replied {server_version} — should echo known versions" + server_version, *expected, + "client sent {client_version}, server replied {server_version}, expected {expected}" ); iris_agentic_dev_core::testing::stop_server(&mut child); diff --git a/crates/iris-agentic-dev-core/tests/unit/main.rs b/crates/iris-agentic-dev-core/tests/unit/main.rs index 1d29451a..54d88a18 100644 --- a/crates/iris-agentic-dev-core/tests/unit/main.rs +++ b/crates/iris-agentic-dev-core/tests/unit/main.rs @@ -36,6 +36,7 @@ mod test_benchmark_task_loading; mod test_bundled_skills; mod test_cargo_config; mod test_changelog; +mod test_check_ascot; mod test_check_config_source_values_141; mod test_clean_command_home_141; mod test_cli_dispatch_helpers; diff --git a/crates/iris-agentic-dev-core/tests/unit/test_changelog.rs b/crates/iris-agentic-dev-core/tests/unit/test_changelog.rs index 4e2de059..0920041f 100644 --- a/crates/iris-agentic-dev-core/tests/unit/test_changelog.rs +++ b/crates/iris-agentic-dev-core/tests/unit/test_changelog.rs @@ -44,7 +44,7 @@ fn release_notes() -> Vec<(String, String)> { Some((key, version, read(&format!("docs/release-notes/{name}")))) }) .collect(); - notes.sort_by(|a, b| b.0.cmp(&a.0)); + notes.sort_by_key(|n| std::cmp::Reverse(n.0)); notes.into_iter().map(|(_, v, text)| (v, text)).collect() } diff --git a/crates/iris-agentic-dev-core/tests/unit/test_check_ascot.rs b/crates/iris-agentic-dev-core/tests/unit/test_check_ascot.rs new file mode 100644 index 00000000..02b2b854 --- /dev/null +++ b/crates/iris-agentic-dev-core/tests/unit/test_check_ascot.rs @@ -0,0 +1,20 @@ +//! `iris_check` without IRIS: a routine checked on its own. The live half is +//! `tests/integration/test_check_ascot_live.rs`. + +use ascot::bindings::DiagnosticSeverity; +use iris_agentic_dev_core::tools::check::check; + +#[test] +fn a_routine_calling_a_label_it_lacks_is_an_error_without_iris() { + let diagnostics = check( + "/w/Test.mac", + "ROUTINE Test [Type=MAC]\n Do Missing\n Quit\n", + None, + ); + assert_eq!(diagnostics.len(), 1, "{diagnostics:?}"); + assert_eq!(diagnostics[0].severity, DiagnosticSeverity::Error); + assert!( + diagnostics[0].message.contains("Missing"), + "{diagnostics:?}" + ); +} diff --git a/crates/iris-agentic-dev-core/tests/unit/test_toolset.rs b/crates/iris-agentic-dev-core/tests/unit/test_toolset.rs index 759bfafd..3c891949 100644 --- a/crates/iris-agentic-dev-core/tests/unit/test_toolset.rs +++ b/crates/iris-agentic-dev-core/tests/unit/test_toolset.rs @@ -133,8 +133,8 @@ fn test_baseline_tool_count() { let tools = IrisTools::new_with_toolset(None, Toolset::Baseline).expect("IrisTools::new"); let count = tools.registered_tool_names().len(); assert_eq!( - count, 84, - "Baseline toolset must have exactly 84 tools (93 total #[tool] methods - 9 \ + count, 85, + "Baseline toolset must have exactly 85 tools (94 total #[tool] methods - 9 \ Merged-tier-only dispatchers), got {}. If this changed on purpose, update this \ number — do not just silence the assertion.", count @@ -266,8 +266,8 @@ fn test_merged_tool_count() { let tools = IrisTools::new_with_toolset(None, Toolset::Merged).expect("IrisTools::new"); let count = tools.registered_tool_names().len(); assert_eq!( - count, 81, - "Merged toolset must have exactly 81 tools (93 total #[tool] methods - stubs 4 - \ + count, 82, + "Merged toolset must have exactly 82 tools (94 total #[tool] methods - stubs 4 - \ replaced-by-dispatcher 8), got {}. If this changed on purpose (a tool was added, \ removed, or moved tiers), update this number — do not just silence the assertion.", count diff --git a/docs/tools.md b/docs/tools.md index ae125a03..8875e20d 100644 --- a/docs/tools.md +++ b/docs/tools.md @@ -24,6 +24,7 @@ unless there is something non-obvious to say about it. | [`iris_import_servers`](#iris_import_servers) | Server Management | | [`iris_doc`](#iris_doc) | Code | | [`iris_compile`](#iris_compile) | Code | +| [`iris_check`](#iris_check) | Code | | [`iris_execute`](#iris_execute) | Code | | [`iris_execute_method`](#iris_execute_method) | Code | | [`iris_query`](#iris_query) | Code | @@ -141,7 +142,7 @@ forced call is judged by what it actually runs. ## Tool catalog size -This server exposes 80 to 84 tools depending on toolset (`IRIS_TOOLSET=baseline|nostub|merged`), +This server exposes 81 to 85 tools depending on toolset (`IRIS_TOOLSET=baseline|nostub|merged`), with full schemas and descriptions: 94 KB to 107 KB of JSON, roughly 26K to 30K tokens, if a client loads the whole catalog on every connection. Two independent ways to avoid paying that cost, and you don't have to pick just one: @@ -467,6 +468,31 @@ iris_compile(target="MyApp.*.cls", flags="cukd") --- +### `iris_check` + +Check one ObjectScript file with +[intersystems-community/ascot](https://github.com/intersystems-community/ascot) without saving or +compiling it. + +| Parameter | Type | Default | Notes | +| ----------- | ------ | ------- | -------------------------------------------------------------------------------------------------------- | +| `path` | string | — | **Required.** A `.cls`, `.mac`, `.int` or `.inc` file inside the [project directory](#project-directory) | +| `namespace` | string | — | Namespace that resolves the file's references; defaults to the connection's | +| `server` | string | — | Named server; omit for default | + +Classes, routines and `.inc` files the file refers to but does not define are read from the target +server with read-only Atelier requests; nothing runs on IRIS. Without a connection they are +reported as hints. A path outside the project directory is refused with `HOST_PATH_BLOCKED`. + +Returns LSP-style diagnostics (zero-based `range`, `severity`, `code`, `code_description`) with +`errors`, `warnings` and `hints` counts, and `iris` saying whether a connection answered the lookups. + +```text +iris_check(path="src/MyApp/Service.cls") +``` + +--- + ### `iris_execute` Run arbitrary ObjectScript and return the output. @@ -2260,7 +2286,7 @@ can act on before calling the tool. | Annotation | Set on | What it means | | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | -| `read_only_hint` | 52 tools — all query, inspect, list, history, and comparison tools | The tool makes no changes to IRIS state | +| `read_only_hint` | 53 tools — all query, inspect, list, history, and comparison tools | The tool makes no changes to IRIS state | | `destructive_hint` | 7 tools — `global_kill`, `iris_admin`, `iris_credential_manage`, `iris_lookup_manage`, `iris_namespace_create`, `iris_remove_server`, `skill_forget` | The tool can irreversibly delete or overwrite data | MCP clients that respect `read_only_hint` can run read-only tools in parallel or in diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 711b3a48..38ab2c6b 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.92.0" +channel = "1.95.0" components = ["clippy", "rustfmt"] diff --git a/tests/e2e/skill_eval/tool_applicability.toml b/tests/e2e/skill_eval/tool_applicability.toml index 35dfb049..7f7504e0 100644 --- a/tests/e2e/skill_eval/tool_applicability.toml +++ b/tests/e2e/skill_eval/tool_applicability.toml @@ -83,6 +83,7 @@ iris_system_performance = "instance-admin" iris_table_info = "sql" iris_test = "testing" iris_test_server = "configuration" +iris_check = "objectscript-code" iris_ws_close = "interactive-session" iris_ws_exec = "interactive-session" iris_ws_open = "interactive-session" From f77bbc72ef36213d84228e499800fb69778ece93 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Tue, 27 Oct 2026 14:04:52 -0400 Subject: [PATCH 02/10] build: stop recompiling core on every build The build script's `rerun-if-changed` named `.git/HEAD` and `.git/refs` relative to the crate directory, where no `.git` exists. Cargo treats a rerun path that does not exist as always changed, so the script and its `rustc-env` reran on every invocation and `cargo run -- tool` recompiled `iris-agentic-dev-core` and the binary each time. The paths now come from `git rev-parse --absolute-git-dir`. --- crates/iris-agentic-dev-core/build.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/crates/iris-agentic-dev-core/build.rs b/crates/iris-agentic-dev-core/build.rs index 969a6b94..7372af0d 100644 --- a/crates/iris-agentic-dev-core/build.rs +++ b/crates/iris-agentic-dev-core/build.rs @@ -40,6 +40,17 @@ fn main() { }; println!("cargo:rustc-env=IRIS_AGENTIC_DEV_BUILD_SUFFIX={suffix}"); - println!("cargo:rerun-if-changed=.git/HEAD"); - println!("cargo:rerun-if-changed=.git/refs"); + // Resolved through git: `.git` is two directories up from this crate, and a rerun path that + // does not exist counts as always changed, which recompiled the crate on every build. + let git_dir = Command::new("git") + .args(["rev-parse", "--absolute-git-dir"]) + .current_dir(&manifest_dir) + .output() + .ok() + .filter(|o| o.status.success()) + .and_then(|o| String::from_utf8(o.stdout).ok()) + .map(|s| s.trim().to_string()) + .unwrap_or_else(|| ".git".to_string()); + println!("cargo:rerun-if-changed={git_dir}/HEAD"); + println!("cargo:rerun-if-changed={git_dir}/refs"); } From 5856c2a67cd3344161380152922710f196c8639e Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Wed, 28 Oct 2026 13:52:05 -0400 Subject: [PATCH 03/10] docs: iris_check leads with when to use it and who powers it --- crates/iris-agentic-dev-core/src/tools/mod.rs | 2 +- docs/tools.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/iris-agentic-dev-core/src/tools/mod.rs b/crates/iris-agentic-dev-core/src/tools/mod.rs index 8f484bc1..52e53948 100644 --- a/crates/iris-agentic-dev-core/src/tools/mod.rs +++ b/crates/iris-agentic-dev-core/src/tools/mod.rs @@ -6071,7 +6071,7 @@ impl IrisTools { } #[tool( - description = "Check one ObjectScript file with intersystems-community/ascot, without saving or compiling it. ascot (https://github.com/intersystems-community/ascot) is a static analyzer and type checker: undefined labels and macros, unknown classes, methods and properties, wrong argument counts, unreachable code. path: a .cls, .mac, .int or .inc file inside the project directory. What the file refers to but does not define (superclasses, called classes and routines, included .inc files) is resolved against the target namespace through this connection with read-only Atelier requests; no code runs on IRIS. Without a connection those references are reported as hints, not checked. Returns LSP-style diagnostics: zero-based range, severity (Error, Warning, Information, Hint), a fixed code and its docs.rs page. Skill: objectscript-tdd. `server` (optional): name of a registered IRIS instance. If omitted, uses the default connection. Use `iris_servers` to list available instances.", + description = "Check an ObjectScript file before compilation, powered by intersystems-community/ascot. ascot (https://github.com/intersystems-community/ascot) is a static analyzer and flow-sensitive type checker; it reports undefined labels and macros, unknown classes, methods and properties, wrong argument counts, unreachable code. path: a .cls, .mac, .int or .inc file inside the project directory. What the file refers to but does not define (superclasses, called classes and routines, included .inc files) is resolved against the target namespace through this connection with read-only Atelier requests; no code runs on IRIS. Without a connection those references are reported as hints, not checked. Returns LSP-style diagnostics: zero-based range, severity (Error, Warning, Information, Hint), a fixed code and its docs.rs page. Skill: objectscript-tdd. `server` (optional): name of a registered IRIS instance. If omitted, uses the default connection. Use `iris_servers` to list available instances.", annotations(read_only_hint = true), output_schema = output_schemas::oneof_output_schema::() )] diff --git a/docs/tools.md b/docs/tools.md index 8875e20d..8ad12c3f 100644 --- a/docs/tools.md +++ b/docs/tools.md @@ -470,9 +470,9 @@ iris_compile(target="MyApp.*.cls", flags="cukd") ### `iris_check` -Check one ObjectScript file with -[intersystems-community/ascot](https://github.com/intersystems-community/ascot) without saving or -compiling it. +Check an ObjectScript file before compilation, powered by +[intersystems-community/ascot](https://github.com/intersystems-community/ascot), a static analyzer +and flow-sensitive type checker. | Parameter | Type | Default | Notes | | ----------- | ------ | ------- | -------------------------------------------------------------------------------------------------------- | From 38cff93892b9ac03753742bdae2b9e35464f8e74 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Thu, 29 Oct 2026 10:04:45 -0400 Subject: [PATCH 04/10] deps: ascot 0.4.2 --- Cargo.lock | 9 +++++---- crates/iris-agentic-dev-core/Cargo.toml | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0a82bb84..02e1290c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -142,9 +142,9 @@ dependencies = [ [[package]] name = "ascot" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8f8fc7e9fedc35098f78cdcd9d73f605c83c77304b25e1113130b230e4a1d8e" +checksum = "2d5e2a69f218843b9c5e0cbb18be6bcefe66725f8a1c55c4dcd3e0f7a7d16258" dependencies = [ "base64 0.23.1", "clap", @@ -3465,9 +3465,9 @@ dependencies = [ [[package]] name = "tygr-objectscript" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "491ffcdb514fd5e20eeb4bdb78d2f23afa30bbaf88bdc06db29a215070a8558f" +checksum = "d699c80c6d8d0801b812b22dd6a15ba4568b48665f7d23f049d403c8758f302d" dependencies = [ "either", "tygr", @@ -3535,6 +3535,7 @@ dependencies = [ "serde", "serde_json", "ureq-proto", + "url", "utf8-zero", "webpki-roots", ] diff --git a/crates/iris-agentic-dev-core/Cargo.toml b/crates/iris-agentic-dev-core/Cargo.toml index 4fb490f8..d3dd8987 100644 --- a/crates/iris-agentic-dev-core/Cargo.toml +++ b/crates/iris-agentic-dev-core/Cargo.toml @@ -45,7 +45,7 @@ base64 = "0.22" aes-gcm = "0.10" similar = "2" # 0.4.0 is the first release with the host `Atelier` interface `tools/check.rs` implements. -ascot = "0.4.0" +ascot = "0.4.2" # Platform keychain store crates — already in the dep tree via keyring v4. From b6f4f2580387785b3efd10a05e5281921683b08d Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Thu, 29 Oct 2026 14:31:26 -0400 Subject: [PATCH 05/10] e2e: fourteen corpus tasks whose defect the compiler accepts CORPUS-43 to CORPUS-56 each plant a bug IRIS compiles clean and trips on at runtime: a variable set on one path, an accumulator set inside a loop that may not run, `$DATA` answering 10, `Quit` with a value inside `For`, a `%Status` method quitting with no value, one argument too many, a utility declared `Method` and called through `##class`, a property typo through an object variable, a misspelled `%SYSTEM.Version` method, JSON text returned for a `%DynamicObject`, a collection property subscripted, `$CASE` with no default, an `Output` parameter never assigned, and a `$ORDER` loop with its empty check inverted. The kinds come from the runtime-proven tests of objectscript-typecheck-corpus; none involves a routine. Every fixture compiles on IRIS 2026.2, every check fails on the fixture and passes on the reference, and `iris_check` reports the planted line on all fourteen fixtures and nothing on any reference. All fourteen are holdout from the day they were written. --- .../e2e/tasks/benchmark/corpus/CORPUS-43.yaml | 42 +++++++++++ .../e2e/tasks/benchmark/corpus/CORPUS-44.yaml | 42 +++++++++++ .../e2e/tasks/benchmark/corpus/CORPUS-45.yaml | 56 ++++++++++++++ .../e2e/tasks/benchmark/corpus/CORPUS-46.yaml | 47 ++++++++++++ .../e2e/tasks/benchmark/corpus/CORPUS-47.yaml | 41 ++++++++++ .../e2e/tasks/benchmark/corpus/CORPUS-48.yaml | 45 +++++++++++ .../e2e/tasks/benchmark/corpus/CORPUS-49.yaml | 64 ++++++++++++++++ .../e2e/tasks/benchmark/corpus/CORPUS-50.yaml | 64 ++++++++++++++++ .../e2e/tasks/benchmark/corpus/CORPUS-51.yaml | 35 +++++++++ .../e2e/tasks/benchmark/corpus/CORPUS-52.yaml | 45 +++++++++++ .../e2e/tasks/benchmark/corpus/CORPUS-53.yaml | 49 ++++++++++++ .../e2e/tasks/benchmark/corpus/CORPUS-54.yaml | 35 +++++++++ .../e2e/tasks/benchmark/corpus/CORPUS-55.yaml | 56 ++++++++++++++ .../e2e/tasks/benchmark/corpus/CORPUS-56.yaml | 74 +++++++++++++++++++ tests/e2e/tasks/benchmark/split.toml | 19 +++++ 15 files changed, 714 insertions(+) create mode 100644 tests/e2e/tasks/benchmark/corpus/CORPUS-43.yaml create mode 100644 tests/e2e/tasks/benchmark/corpus/CORPUS-44.yaml create mode 100644 tests/e2e/tasks/benchmark/corpus/CORPUS-45.yaml create mode 100644 tests/e2e/tasks/benchmark/corpus/CORPUS-46.yaml create mode 100644 tests/e2e/tasks/benchmark/corpus/CORPUS-47.yaml create mode 100644 tests/e2e/tasks/benchmark/corpus/CORPUS-48.yaml create mode 100644 tests/e2e/tasks/benchmark/corpus/CORPUS-49.yaml create mode 100644 tests/e2e/tasks/benchmark/corpus/CORPUS-50.yaml create mode 100644 tests/e2e/tasks/benchmark/corpus/CORPUS-51.yaml create mode 100644 tests/e2e/tasks/benchmark/corpus/CORPUS-52.yaml create mode 100644 tests/e2e/tasks/benchmark/corpus/CORPUS-53.yaml create mode 100644 tests/e2e/tasks/benchmark/corpus/CORPUS-54.yaml create mode 100644 tests/e2e/tasks/benchmark/corpus/CORPUS-55.yaml create mode 100644 tests/e2e/tasks/benchmark/corpus/CORPUS-56.yaml diff --git a/tests/e2e/tasks/benchmark/corpus/CORPUS-43.yaml b/tests/e2e/tasks/benchmark/corpus/CORPUS-43.yaml new file mode 100644 index 00000000..96507b95 --- /dev/null +++ b/tests/e2e/tasks/benchmark/corpus/CORPUS-43.yaml @@ -0,0 +1,42 @@ +id: CORPUS-43 +prompt: | + Bench.Label.Describe(pScore) in the BENCHMARK namespace should return "pass" for a score of 50 or + more and "fail" for anything lower. It raises for every score below 50. Fix it and + leave the class compiled on the server. + + IRIS: host localhost, web port 52780, username _SYSTEM, password SYS, namespace BENCHMARK. + The instance runs in the Docker container iris-dev-iris. +fixtures: + - name: Bench.Label + content: | + Class Bench.Label Extends %RegisteredObject + { + + ClassMethod Describe(pScore As %Integer) As %String + { + If pScore >= 50 { + Set tLabel = "pass" + } + Quit tLabel + } + + } +check: | + try { set ok=((##class(Bench.Label).Describe(70)="pass")&&(##class(Bench.Label).Describe(20)="fail")) } catch { set ok=0 } write $select(ok:"PASS",1:"FAIL") +solution: + - name: Bench.Label + content: | + Class Bench.Label Extends %RegisteredObject + { + + ClassMethod Describe(pScore As %Integer) As %String + { + Set tLabel = "fail" + If pScore >= 50 { + Set tLabel = "pass" + } + Quit tLabel + } + + } +tags: [undefined, branches] diff --git a/tests/e2e/tasks/benchmark/corpus/CORPUS-44.yaml b/tests/e2e/tasks/benchmark/corpus/CORPUS-44.yaml new file mode 100644 index 00000000..0f839fc9 --- /dev/null +++ b/tests/e2e/tasks/benchmark/corpus/CORPUS-44.yaml @@ -0,0 +1,42 @@ +id: CORPUS-44 +prompt: | + Bench.Sum.Total(pList) in the BENCHMARK namespace should return the sum of the numbers in the + %List pList, and 0 for an empty list. It raises for an empty list. Fix it and leave + the class compiled on the server. + + IRIS: host localhost, web port 52780, username _SYSTEM, password SYS, namespace BENCHMARK. + The instance runs in the Docker container iris-dev-iris. +fixtures: + - name: Bench.Sum + content: | + Class Bench.Sum Extends %RegisteredObject + { + + ClassMethod Total(pList As %List) As %Integer + { + For i = 1:1:$ListLength(pList) { + Set tTotal = $Get(tTotal, 0) + $List(pList, i) + } + Quit tTotal + } + + } +check: | + try { set ok=((##class(Bench.Sum).Total($ListBuild(1,2,3))=6)&&(##class(Bench.Sum).Total("")=0)) } catch { set ok=0 } write $select(ok:"PASS",1:"FAIL") +solution: + - name: Bench.Sum + content: | + Class Bench.Sum Extends %RegisteredObject + { + + ClassMethod Total(pList As %List) As %Integer + { + Set tTotal = 0 + For i = 1:1:$ListLength(pList) { + Set tTotal = tTotal + $List(pList, i) + } + Quit tTotal + } + + } +tags: [undefined, loops] diff --git a/tests/e2e/tasks/benchmark/corpus/CORPUS-45.yaml b/tests/e2e/tasks/benchmark/corpus/CORPUS-45.yaml new file mode 100644 index 00000000..0c208907 --- /dev/null +++ b/tests/e2e/tasks/benchmark/corpus/CORPUS-45.yaml @@ -0,0 +1,56 @@ +id: CORPUS-45 +prompt: | + Bench.Tree.ValueOf(pKey) in the BENCHMARK namespace should return the value stored at + ^Bench.Tree(pKey), or an empty string when that node holds no value of its own, which includes a + node that only has child nodes. Setup() stores a value at "a" and a child under "b". ValueOf("b") + raises . Fix it and leave the class compiled on the server. + + IRIS: host localhost, web port 52780, username _SYSTEM, password SYS, namespace BENCHMARK. + The instance runs in the Docker container iris-dev-iris. +fixtures: + - name: Bench.Tree + content: | + Class Bench.Tree Extends %RegisteredObject + { + + ClassMethod Setup() + { + Kill ^Bench.Tree + Set ^Bench.Tree("a") = 1 + Set ^Bench.Tree("b", "x") = 2 + } + + ClassMethod ValueOf(pKey As %String) As %String + { + If $Data(^Bench.Tree(pKey), tValue) { + Quit tValue + } + Quit "" + } + + } +check: | + try { do ##class(Bench.Tree).Setup() set ok=((##class(Bench.Tree).ValueOf("a")=1)&&(##class(Bench.Tree).ValueOf("b")="")&&(##class(Bench.Tree).ValueOf("zz")="")) } catch { set ok=0 } write $select(ok:"PASS",1:"FAIL") +solution: + - name: Bench.Tree + content: | + Class Bench.Tree Extends %RegisteredObject + { + + ClassMethod Setup() + { + Kill ^Bench.Tree + Set ^Bench.Tree("a") = 1 + Set ^Bench.Tree("b", "x") = 2 + } + + ClassMethod ValueOf(pKey As %String) As %String + { + If $Data(^Bench.Tree(pKey), tValue) # 2 { + Quit tValue + } + Quit "" + } + + } +tags: [globals, undefined] diff --git a/tests/e2e/tasks/benchmark/corpus/CORPUS-46.yaml b/tests/e2e/tasks/benchmark/corpus/CORPUS-46.yaml new file mode 100644 index 00000000..422178db --- /dev/null +++ b/tests/e2e/tasks/benchmark/corpus/CORPUS-46.yaml @@ -0,0 +1,47 @@ +id: CORPUS-46 +prompt: | + Bench.Find.FirstOver(pList, pMin) in the BENCHMARK namespace should return the first number in + the %List pList that is greater than pMin, and an empty string when there is none. It raises + as soon as it finds one. Fix it and leave the class compiled on the server. + + IRIS: host localhost, web port 52780, username _SYSTEM, password SYS, namespace BENCHMARK. + The instance runs in the Docker container iris-dev-iris. +fixtures: + - name: Bench.Find + content: | + Class Bench.Find Extends %RegisteredObject + { + + ClassMethod FirstOver(pList As %List, pMin As %Integer) As %Integer + { + For i = 1:1:$ListLength(pList) { + Set tItem = $List(pList, i) + If tItem > pMin { + Quit tItem + } + } + Quit "" + } + + } +check: | + try { set ok=((##class(Bench.Find).FirstOver($ListBuild(1,5,9),3)=5)&&(##class(Bench.Find).FirstOver($ListBuild(1,2),3)="")) } catch { set ok=0 } write $select(ok:"PASS",1:"FAIL") +solution: + - name: Bench.Find + content: | + Class Bench.Find Extends %RegisteredObject + { + + ClassMethod FirstOver(pList As %List, pMin As %Integer) As %Integer + { + For i = 1:1:$ListLength(pList) { + Set tItem = $List(pList, i) + If tItem > pMin { + Return tItem + } + } + Quit "" + } + + } +tags: [loops, control-flow] diff --git a/tests/e2e/tasks/benchmark/corpus/CORPUS-47.yaml b/tests/e2e/tasks/benchmark/corpus/CORPUS-47.yaml new file mode 100644 index 00000000..bb08bd8e --- /dev/null +++ b/tests/e2e/tasks/benchmark/corpus/CORPUS-47.yaml @@ -0,0 +1,41 @@ +id: CORPUS-47 +prompt: | + Bench.Check.Validate(pAge) in the BENCHMARK namespace should return an error %Status for a + negative age and $$$OK otherwise. Set sc = ##class(Bench.Check).Validate(5) raises . Fix + it and leave the class compiled on the server. + + IRIS: host localhost, web port 52780, username _SYSTEM, password SYS, namespace BENCHMARK. + The instance runs in the Docker container iris-dev-iris. +fixtures: + - name: Bench.Check + content: | + Class Bench.Check Extends %RegisteredObject + { + + ClassMethod Validate(pAge As %Integer) As %Status + { + If pAge < 0 { + Quit $$$ERROR($$$GeneralError, "age must not be negative") + } + Quit + } + + } +check: | + try { set ok=($system.Status.IsOK(##class(Bench.Check).Validate(5))&&$system.Status.IsError(##class(Bench.Check).Validate(-1))) } catch { set ok=0 } write $select(ok:"PASS",1:"FAIL") +solution: + - name: Bench.Check + content: | + Class Bench.Check Extends %RegisteredObject + { + + ClassMethod Validate(pAge As %Integer) As %Status + { + If pAge < 0 { + Quit $$$ERROR($$$GeneralError, "age must not be negative") + } + Quit $$$OK + } + + } +tags: [status-handling, return-value] diff --git a/tests/e2e/tasks/benchmark/corpus/CORPUS-48.yaml b/tests/e2e/tasks/benchmark/corpus/CORPUS-48.yaml new file mode 100644 index 00000000..9538ba91 --- /dev/null +++ b/tests/e2e/tasks/benchmark/corpus/CORPUS-48.yaml @@ -0,0 +1,45 @@ +id: CORPUS-48 +prompt: | + Bench.Price.Net(pGross) in the BENCHMARK namespace should return pGross with a 20% discount + applied, so Net(100) is 80. It raises . Fix it and leave the class compiled on the + server. + + IRIS: host localhost, web port 52780, username _SYSTEM, password SYS, namespace BENCHMARK. + The instance runs in the Docker container iris-dev-iris. +fixtures: + - name: Bench.Price + content: | + Class Bench.Price Extends %RegisteredObject + { + + ClassMethod Net(pGross As %Numeric) As %Numeric + { + Quit ..Apply(pGross, 0.2, 1) + } + + ClassMethod Apply(pAmount As %Numeric, pRate As %Numeric) As %Numeric + { + Quit pAmount * (1 - pRate) + } + + } +check: | + try { set ok=(##class(Bench.Price).Net(100)=80) } catch { set ok=0 } write $select(ok:"PASS",1:"FAIL") +solution: + - name: Bench.Price + content: | + Class Bench.Price Extends %RegisteredObject + { + + ClassMethod Net(pGross As %Numeric) As %Numeric + { + Quit ..Apply(pGross, 0.2) + } + + ClassMethod Apply(pAmount As %Numeric, pRate As %Numeric) As %Numeric + { + Quit pAmount * (1 - pRate) + } + + } +tags: [method-calls, arguments] diff --git a/tests/e2e/tasks/benchmark/corpus/CORPUS-49.yaml b/tests/e2e/tasks/benchmark/corpus/CORPUS-49.yaml new file mode 100644 index 00000000..850038a6 --- /dev/null +++ b/tests/e2e/tasks/benchmark/corpus/CORPUS-49.yaml @@ -0,0 +1,64 @@ +id: CORPUS-49 +prompt: | + Bench.Temp in the BENCHMARK namespace converts Celsius to Fahrenheit. ToFahrenheit(pCelsius) + converts a value and needs nothing from an instance; Fahrenheit() converts an instance's Celsius + property by calling it. Boiling() should return the Fahrenheit value of 100 degrees Celsius, which + is 212. It raises . Fix it and leave the class compiled on the server. + + IRIS: host localhost, web port 52780, username _SYSTEM, password SYS, namespace BENCHMARK. + The instance runs in the Docker container iris-dev-iris. +fixtures: + - name: Bench.Temp + content: | + Class Bench.Temp Extends %RegisteredObject + { + + Property Celsius As %Numeric; + + /// The Fahrenheit reading of this instance's Celsius. + Method Fahrenheit() As %Numeric + { + Quit ..ToFahrenheit(..Celsius) + } + + /// The Fahrenheit value of a Celsius reading. + Method ToFahrenheit(pCelsius As %Numeric) As %Numeric + { + Quit (pCelsius * 9 / 5) + 32 + } + + ClassMethod Boiling() As %Numeric + { + Quit ##class(Bench.Temp).ToFahrenheit(100) + } + + } +check: | + try { set t=##class(Bench.Temp).%New() set t.Celsius=0 set ok=((##class(Bench.Temp).Boiling()=212)&&(t.Fahrenheit()=32)) } catch { set ok=0 } write $select(ok:"PASS",1:"FAIL") +solution: + - name: Bench.Temp + content: | + Class Bench.Temp Extends %RegisteredObject + { + + Property Celsius As %Numeric; + + /// The Fahrenheit reading of this instance's Celsius. + Method Fahrenheit() As %Numeric + { + Quit ..ToFahrenheit(..Celsius) + } + + /// The Fahrenheit value of a Celsius reading. + ClassMethod ToFahrenheit(pCelsius As %Numeric) As %Numeric + { + Quit (pCelsius * 9 / 5) + 32 + } + + ClassMethod Boiling() As %Numeric + { + Quit ##class(Bench.Temp).ToFahrenheit(100) + } + + } +tags: [method-calls, instance-vs-class] diff --git a/tests/e2e/tasks/benchmark/corpus/CORPUS-50.yaml b/tests/e2e/tasks/benchmark/corpus/CORPUS-50.yaml new file mode 100644 index 00000000..db17635a --- /dev/null +++ b/tests/e2e/tasks/benchmark/corpus/CORPUS-50.yaml @@ -0,0 +1,64 @@ +id: CORPUS-50 +prompt: | + Bench.Card.Example(pTitle, pOwner) in the BENCHMARK namespace builds a card and returns its + Summary(), the title followed by the owner in parentheses, so Example("Dune", "Ann") is + "Dune (Ann)". It raises . Fix it and leave the class compiled on the + server. + + IRIS: host localhost, web port 52780, username _SYSTEM, password SYS, namespace BENCHMARK. + The instance runs in the Docker container iris-dev-iris. +fixtures: + - name: Bench.Card + content: | + Class Bench.Card Extends %RegisteredObject + { + + Property Title As %String; + + Property Owner As %String; + + /// The title followed by the owner in parentheses. + Method Summary() As %String + { + Quit ..Title_" ("_..Owner_")" + } + + /// The summary of a card titled pTitle and owned by pOwner. + ClassMethod Example(pTitle As %String, pOwner As %String) As %String + { + Set tCard = ..%New() + Set tCard.Turtle = pTitle + Set tCard.Owner = pOwner + Quit tCard.Summary() + } + + } +check: | + try { set ok=(##class(Bench.Card).Example("Dune","Ann")="Dune (Ann)") } catch { set ok=0 } write $select(ok:"PASS",1:"FAIL") +solution: + - name: Bench.Card + content: | + Class Bench.Card Extends %RegisteredObject + { + + Property Title As %String; + + Property Owner As %String; + + /// The title followed by the owner in parentheses. + Method Summary() As %String + { + Quit ..Title_" ("_..Owner_")" + } + + /// The summary of a card titled pTitle and owned by pOwner. + ClassMethod Example(pTitle As %String, pOwner As %String) As %String + { + Set tCard = ..%New() + Set tCard.Title = pTitle + Set tCard.Owner = pOwner + Quit tCard.Summary() + } + + } +tags: [properties, wrong-member] diff --git a/tests/e2e/tasks/benchmark/corpus/CORPUS-51.yaml b/tests/e2e/tasks/benchmark/corpus/CORPUS-51.yaml new file mode 100644 index 00000000..9c86e23b --- /dev/null +++ b/tests/e2e/tasks/benchmark/corpus/CORPUS-51.yaml @@ -0,0 +1,35 @@ +id: CORPUS-51 +prompt: | + Bench.Info.Major() in the BENCHMARK namespace should return the major version number of the + running IRIS instance, as the %SYSTEM.Version class reports it. It raises . + Fix it and leave the class compiled on the server. + + IRIS: host localhost, web port 52780, username _SYSTEM, password SYS, namespace BENCHMARK. + The instance runs in the Docker container iris-dev-iris. +fixtures: + - name: Bench.Info + content: | + Class Bench.Info Extends %RegisteredObject + { + + ClassMethod Major() As %Integer + { + Quit $System.Version.GetMajorVersion() + } + + } +check: | + try { set ok=(##class(Bench.Info).Major()=$System.Version.GetMajor()) } catch { set ok=0 } write $select(ok:"PASS",1:"FAIL") +solution: + - name: Bench.Info + content: | + Class Bench.Info Extends %RegisteredObject + { + + ClassMethod Major() As %Integer + { + Quit $System.Version.GetMajor() + } + + } +tags: [system-classes, wrong-member] diff --git a/tests/e2e/tasks/benchmark/corpus/CORPUS-52.yaml b/tests/e2e/tasks/benchmark/corpus/CORPUS-52.yaml new file mode 100644 index 00000000..41323e40 --- /dev/null +++ b/tests/e2e/tasks/benchmark/corpus/CORPUS-52.yaml @@ -0,0 +1,45 @@ +id: CORPUS-52 +prompt: | + Bench.Payload.Build(pName, pQty) in the BENCHMARK namespace should return a %DynamicObject with + a name member and a qty member, so that Qty(pName, pQty) can read qty back. Qty("x", 3) raises + . Fix it and leave the class compiled on the server. + + IRIS: host localhost, web port 52780, username _SYSTEM, password SYS, namespace BENCHMARK. + The instance runs in the Docker container iris-dev-iris. +fixtures: + - name: Bench.Payload + content: | + Class Bench.Payload Extends %RegisteredObject + { + + ClassMethod Build(pName As %String, pQty As %Integer) As %DynamicObject + { + Quit "{""name"":"""_pName_""",""qty"":"_pQty_"}" + } + + ClassMethod Qty(pName As %String, pQty As %Integer) As %Integer + { + Quit ..Build(pName, pQty).%Get("qty") + } + + } +check: | + try { set ok=((##class(Bench.Payload).Qty("x",3)=3)&&(##class(Bench.Payload).Build("x",3).%Get("name")="x")) } catch { set ok=0 } write $select(ok:"PASS",1:"FAIL") +solution: + - name: Bench.Payload + content: | + Class Bench.Payload Extends %RegisteredObject + { + + ClassMethod Build(pName As %String, pQty As %Integer) As %DynamicObject + { + Quit {"name": (pName), "qty": (pQty)} + } + + ClassMethod Qty(pName As %String, pQty As %Integer) As %Integer + { + Quit ..Build(pName, pQty).%Get("qty") + } + + } +tags: [json, dynamic-objects, types] diff --git a/tests/e2e/tasks/benchmark/corpus/CORPUS-53.yaml b/tests/e2e/tasks/benchmark/corpus/CORPUS-53.yaml new file mode 100644 index 00000000..b2cad916 --- /dev/null +++ b/tests/e2e/tasks/benchmark/corpus/CORPUS-53.yaml @@ -0,0 +1,49 @@ +id: CORPUS-53 +prompt: | + Bench.Basket in the BENCHMARK namespace keeps its items in a list collection. Add(pItem) appends + one, and First() should return the first item added, or an empty string when nothing has been + added. First() raises . Fix it and leave the class compiled on the server. + + IRIS: host localhost, web port 52780, username _SYSTEM, password SYS, namespace BENCHMARK. + The instance runs in the Docker container iris-dev-iris. +fixtures: + - name: Bench.Basket + content: | + Class Bench.Basket Extends %RegisteredObject + { + + Property Items As list Of %String; + + Method Add(pItem As %String) As %Status + { + Quit ..Items.Insert(pItem) + } + + Method First() As %String + { + Quit ..Items(1) + } + + } +check: | + try { set b=##class(Bench.Basket).%New() do b.Add("pear") do b.Add("fig") set ok=((b.First()="pear")&&(##class(Bench.Basket).%New().First()="")) } catch { set ok=0 } write $select(ok:"PASS",1:"FAIL") +solution: + - name: Bench.Basket + content: | + Class Bench.Basket Extends %RegisteredObject + { + + Property Items As list Of %String; + + Method Add(pItem As %String) As %Status + { + Quit ..Items.Insert(pItem) + } + + Method First() As %String + { + Quit ..Items.GetAt(1) + } + + } +tags: [collections, properties] diff --git a/tests/e2e/tasks/benchmark/corpus/CORPUS-54.yaml b/tests/e2e/tasks/benchmark/corpus/CORPUS-54.yaml new file mode 100644 index 00000000..06d97a53 --- /dev/null +++ b/tests/e2e/tasks/benchmark/corpus/CORPUS-54.yaml @@ -0,0 +1,35 @@ +id: CORPUS-54 +prompt: | + Bench.Word.Of(pCode) in the BENCHMARK namespace should return "one", "two" or "three" for the + codes 1 to 3, and "other" for any other code. Of(7) raises . Fix it and leave the + class compiled on the server. + + IRIS: host localhost, web port 52780, username _SYSTEM, password SYS, namespace BENCHMARK. + The instance runs in the Docker container iris-dev-iris. +fixtures: + - name: Bench.Word + content: | + Class Bench.Word Extends %RegisteredObject + { + + ClassMethod Of(pCode As %Integer) As %String + { + Quit $Case(pCode, 1:"one", 2:"two", 3:"three") + } + + } +check: | + try { set ok=((##class(Bench.Word).Of(2)="two")&&(##class(Bench.Word).Of(7)="other")) } catch { set ok=0 } write $select(ok:"PASS",1:"FAIL") +solution: + - name: Bench.Word + content: | + Class Bench.Word Extends %RegisteredObject + { + + ClassMethod Of(pCode As %Integer) As %String + { + Quit $Case(pCode, 1:"one", 2:"two", 3:"three", :"other") + } + + } +tags: [functions, case] diff --git a/tests/e2e/tasks/benchmark/corpus/CORPUS-55.yaml b/tests/e2e/tasks/benchmark/corpus/CORPUS-55.yaml new file mode 100644 index 00000000..26eb19ca --- /dev/null +++ b/tests/e2e/tasks/benchmark/corpus/CORPUS-55.yaml @@ -0,0 +1,56 @@ +id: CORPUS-55 +prompt: | + Bench.Pieces.Count(pText) in the BENCHMARK namespace should return how many comma-separated + pieces pText has, so Count("a,b,c") is 3. It calls Split(pText, Output pCount), which is meant to + hand the count back through pCount. Count("a,b,c") raises . Fix it and leave the class + compiled on the server. + + IRIS: host localhost, web port 52780, username _SYSTEM, password SYS, namespace BENCHMARK. + The instance runs in the Docker container iris-dev-iris. +fixtures: + - name: Bench.Pieces + content: | + Class Bench.Pieces Extends %RegisteredObject + { + + ClassMethod Split(pText As %String, Output pCount As %Integer) As %Status + { + Set tCount = $Length(pText, ",") + Quit $$$OK + } + + ClassMethod Count(pText As %String) As %Integer + { + Set tSC = ..Split(pText, .tCount) + If $$$ISERR(tSC) { + Quit 0 + } + Quit tCount + } + + } +check: | + try { set ok=((##class(Bench.Pieces).Count("a,b,c")=3)&&(##class(Bench.Pieces).Count("x")=1)) } catch { set ok=0 } write $select(ok:"PASS",1:"FAIL") +solution: + - name: Bench.Pieces + content: | + Class Bench.Pieces Extends %RegisteredObject + { + + ClassMethod Split(pText As %String, Output pCount As %Integer) As %Status + { + Set pCount = $Length(pText, ",") + Quit $$$OK + } + + ClassMethod Count(pText As %String) As %Integer + { + Set tSC = ..Split(pText, .tCount) + If $$$ISERR(tSC) { + Quit 0 + } + Quit tCount + } + + } +tags: [output-parameters, undefined] diff --git a/tests/e2e/tasks/benchmark/corpus/CORPUS-56.yaml b/tests/e2e/tasks/benchmark/corpus/CORPUS-56.yaml new file mode 100644 index 00000000..e5d07e95 --- /dev/null +++ b/tests/e2e/tasks/benchmark/corpus/CORPUS-56.yaml @@ -0,0 +1,74 @@ +id: CORPUS-56 +prompt: | + Bench.Stock.Total() in the BENCHMARK namespace should return the sum of the quantities stored + under ^Bench.Stock, and 0 when the global is empty. After Setup() stores two quantities, Total() + returns 0, and after Clear() empties the global it raises . Fix it and leave the class + compiled on the server. + + IRIS: host localhost, web port 52780, username _SYSTEM, password SYS, namespace BENCHMARK. + The instance runs in the Docker container iris-dev-iris. +fixtures: + - name: Bench.Stock + content: | + Class Bench.Stock Extends %RegisteredObject + { + + ClassMethod Setup() + { + Kill ^Bench.Stock + Set ^Bench.Stock("apple") = 3 + Set ^Bench.Stock("pear") = 5 + } + + ClassMethod Clear() + { + Kill ^Bench.Stock + } + + ClassMethod Total() As %Integer + { + Set tTotal = 0 + Set tKey = "" + For { + Set tKey = $Order(^Bench.Stock(tKey), 1, tQty) + Quit:tKey'="" + Set tTotal = tTotal + tQty + } + Quit tTotal + } + + } +check: | + try { do ##class(Bench.Stock).Setup() set ok=(##class(Bench.Stock).Total()=8) do ##class(Bench.Stock).Clear() set ok=ok&&(##class(Bench.Stock).Total()=0) } catch { set ok=0 } write $select(ok:"PASS",1:"FAIL") +solution: + - name: Bench.Stock + content: | + Class Bench.Stock Extends %RegisteredObject + { + + ClassMethod Setup() + { + Kill ^Bench.Stock + Set ^Bench.Stock("apple") = 3 + Set ^Bench.Stock("pear") = 5 + } + + ClassMethod Clear() + { + Kill ^Bench.Stock + } + + ClassMethod Total() As %Integer + { + Set tTotal = 0 + Set tKey = "" + For { + Set tKey = $Order(^Bench.Stock(tKey), 1, tQty) + Quit:tKey="" + Set tTotal = tTotal + tQty + } + Quit tTotal + } + + } +tags: [globals, order-traversal, undefined] diff --git a/tests/e2e/tasks/benchmark/split.toml b/tests/e2e/tasks/benchmark/split.toml index f0a2eee9..eac063f5 100644 --- a/tests/e2e/tasks/benchmark/split.toml +++ b/tests/e2e/tasks/benchmark/split.toml @@ -86,6 +86,11 @@ # class, a ToolSet query's parameters and `MaxRows`, and `AddDocument` with and without a `source`. None # makes an LLM call. # +# - **CORPUS-43 to CORPUS-56 are holdout from the day they were written (2026-10-29).** Each plants +# a defect the IRIS compiler accepts and the program trips on at runtime, drawn from the +# runtime-proven tests of `objectscript-typecheck-corpus`. Written to measure `iris_check` (PR +# #166); none involves a routine. +# # One caveat that the split cannot fix and the report has to carry instead: I wrote the 42 corpus # tasks knowing what iad's 81 tools can do. Authoring is not tuning — no description, prompt or skill # was changed while looking at a result from these tasks — but a corpus written by the tool author is @@ -156,6 +161,20 @@ holdout = [ "CORPUS-39", "CORPUS-40", "CORPUS-41", + "CORPUS-43", + "CORPUS-44", + "CORPUS-45", + "CORPUS-46", + "CORPUS-47", + "CORPUS-48", + "CORPUS-49", + "CORPUS-50", + "CORPUS-51", + "CORPUS-52", + "CORPUS-53", + "CORPUS-54", + "CORPUS-55", + "CORPUS-56", "SKILL-01", "SKILL-04", "SKILL-06", From 0ae3ca7698357c577b88a1cb626486911996f918 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Thu, 29 Oct 2026 15:16:43 -0400 Subject: [PATCH 06/10] e2e: CORPUS-57, a misspelled macro the compiler also catches The one control among the fifteen new tasks: `$$$ISERROR` where `$$$ISERR` was meant. The compiler rejects it with MPP5610 and two cascade errors at routine offsets; ascot reports one error at the class line with `ISERR` as the first suggestion. Holdout from the day it was written. --- .../e2e/tasks/benchmark/corpus/CORPUS-57.yaml | 59 +++++++++++++++++++ tests/e2e/tasks/benchmark/split.toml | 8 ++- 2 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 tests/e2e/tasks/benchmark/corpus/CORPUS-57.yaml diff --git a/tests/e2e/tasks/benchmark/corpus/CORPUS-57.yaml b/tests/e2e/tasks/benchmark/corpus/CORPUS-57.yaml new file mode 100644 index 00000000..49d44b76 --- /dev/null +++ b/tests/e2e/tasks/benchmark/corpus/CORPUS-57.yaml @@ -0,0 +1,59 @@ +id: CORPUS-57 +prompt: | + Bench.Guard in the BENCHMARK namespace does not compile. Save(pName) should return the error + %Status from Validate(pName) when there is one, so Save("") is an error, and $$$OK otherwise, so + Save("x") succeeds. Find the error, fix it, and leave the class compiled on the server. + + IRIS: host localhost, web port 52780, username _SYSTEM, password SYS, namespace BENCHMARK. + The instance runs in the Docker container iris-dev-iris. +fixtures: + - name: Bench.Guard + content: | + Class Bench.Guard Extends %RegisteredObject + { + + ClassMethod Save(pName As %String) As %Status + { + Set tSC = ..Validate(pName) + If $$$ISERROR(tSC) { + Quit tSC + } + Quit $$$OK + } + + ClassMethod Validate(pName As %String) As %Status + { + If pName = "" { + Quit $$$ERROR($$$GeneralError, "name required") + } + Quit $$$OK + } + + } +check: | + try { set ok=($system.Status.IsError(##class(Bench.Guard).Save(""))&&$system.Status.IsOK(##class(Bench.Guard).Save("x"))) } catch { set ok=0 } write $select(ok:"PASS",1:"FAIL") +solution: + - name: Bench.Guard + content: | + Class Bench.Guard Extends %RegisteredObject + { + + ClassMethod Save(pName As %String) As %Status + { + Set tSC = ..Validate(pName) + If $$$ISERR(tSC) { + Quit tSC + } + Quit $$$OK + } + + ClassMethod Validate(pName As %String) As %Status + { + If pName = "" { + Quit $$$ERROR($$$GeneralError, "name required") + } + Quit $$$OK + } + + } +tags: [macros, compile-error, status-handling] diff --git a/tests/e2e/tasks/benchmark/split.toml b/tests/e2e/tasks/benchmark/split.toml index eac063f5..3b1bdf57 100644 --- a/tests/e2e/tasks/benchmark/split.toml +++ b/tests/e2e/tasks/benchmark/split.toml @@ -86,10 +86,11 @@ # class, a ToolSet query's parameters and `MaxRows`, and `AddDocument` with and without a `source`. None # makes an LLM call. # -# - **CORPUS-43 to CORPUS-56 are holdout from the day they were written (2026-10-29).** Each plants +# - **CORPUS-43 to CORPUS-57 are holdout from the day they were written (2026-10-29).** Each plants # a defect the IRIS compiler accepts and the program trips on at runtime, drawn from the -# runtime-proven tests of `objectscript-typecheck-corpus`. Written to measure `iris_check` (PR -# #166); none involves a routine. +# runtime-proven tests of `objectscript-typecheck-corpus`; CORPUS-57 is the one control the +# compiler also catches, a misspelled macro. Written to measure `iris_check` (PR #166); none +# involves a routine. # # One caveat that the split cannot fix and the report has to carry instead: I wrote the 42 corpus # tasks knowing what iad's 81 tools can do. Authoring is not tuning — no description, prompt or skill @@ -175,6 +176,7 @@ holdout = [ "CORPUS-54", "CORPUS-55", "CORPUS-56", + "CORPUS-57", "SKILL-01", "SKILL-04", "SKILL-06", From 250bda814a7b7e4ef95923e529adfd4c115bf403 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Thu, 29 Oct 2026 15:30:54 -0400 Subject: [PATCH 07/10] e2e: split note says why CORPUS-57 is in the batch --- tests/e2e/tasks/benchmark/split.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/e2e/tasks/benchmark/split.toml b/tests/e2e/tasks/benchmark/split.toml index 3b1bdf57..2c0122ff 100644 --- a/tests/e2e/tasks/benchmark/split.toml +++ b/tests/e2e/tasks/benchmark/split.toml @@ -88,9 +88,9 @@ # # - **CORPUS-43 to CORPUS-57 are holdout from the day they were written (2026-10-29).** Each plants # a defect the IRIS compiler accepts and the program trips on at runtime, drawn from the -# runtime-proven tests of `objectscript-typecheck-corpus`; CORPUS-57 is the one control the -# compiler also catches, a misspelled macro. Written to measure `iris_check` (PR #166); none -# involves a routine. +# runtime-proven tests of `objectscript-typecheck-corpus`; CORPUS-57, a misspelled macro the +# compiler also catches, was asked for in the PR review. Written to measure `iris_check` (PR #166); +# none involves a routine. # # One caveat that the split cannot fix and the report has to carry instead: I wrote the 42 corpus # tasks knowing what iad's 81 tools can do. Authoring is not tuning — no description, prompt or skill From d95455bffe361aefc30795e9c469e3ab19527e62 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Thu, 29 Oct 2026 17:17:50 -0400 Subject: [PATCH 08/10] iris_check takes a document name, with the text optional `name` replaces `path`: the document with its extension, read from the target namespace through the same `/doc` request the bridge already makes for lookups, or taken from `source` when the caller holds text it has not saved yet. An MCP client no longer needs a file inside the project directory, which is where the earlier shape failed: the classes a task is about live on the server, and the code an agent wants checked is in its hands before it is written anywhere. Without a connection the response now carries a `note` saying that external references were not checked; without a connection and without `source` the call is refused with IRIS_UNREACHABLE like the other IRIS tools, and a `name` the server lacks answers DOCUMENT_NOT_FOUND. --- README.md | 2 +- .../iris-agentic-dev-core/src/tools/check.rs | 75 +++++++++++++------ crates/iris-agentic-dev-core/src/tools/mod.rs | 7 +- .../tests/binary/check_ascot.rs | 34 ++++----- .../integration/test_check_ascot_live.rs | 48 +++++++----- docs/tools.md | 24 +++--- 6 files changed, 119 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index 11108997..384c9a0d 100644 --- a/README.md +++ b/README.md @@ -597,7 +597,7 @@ blocked and how to permit it. | `iris_execute_method` | Invoke a `ClassMethod` directly by class+method+args, no boilerplate. | | `iris_query` | Execute SQL, return rows as JSON. `mode=explain\|count\|write` for plans, counts, and gated DML. | | `iris_test` | Run `%UnitTest` tests, return structured pass/fail results. | -| `iris_check` | Check one file with [ascot](https://github.com/intersystems-community/ascot) without compiling it. | +| `iris_check` | Check one document with [ascot](https://github.com/intersystems-community/ascot), no compile. | | `iris_global` | Read, write, kill, or list IRIS global nodes. Patient-data and system globals are gated. | | `iris_coverage` | Measure ObjectScript line coverage via `%Monitor.System.LineByLine`. | | `iris_source_control` ✦ | Check lock status, checkout, execute SCM actions. | diff --git a/crates/iris-agentic-dev-core/src/tools/check.rs b/crates/iris-agentic-dev-core/src/tools/check.rs index 8403a8a4..d01b053d 100644 --- a/crates/iris-agentic-dev-core/src/tools/check.rs +++ b/crates/iris-agentic-dev-core/src/tools/check.rs @@ -17,16 +17,20 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use super::output_schemas::ToolError; -use super::{err_json, host_fs, nopws, ok_json, IrisTools}; +use super::{err_json, iris_unreachable, nopws, ok_json, IrisTools}; use crate::iris::connection::IrisConnection; /// Parameters of `iris_check`. #[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct CheckParams { - /// A `.cls`, `.mac`, `.int` or `.inc` file inside the project directory. - pub path: String, - /// Namespace that resolves the file's references. Omit to use the connection's. + /// The document to check, with its extension: `Pkg.Class.cls`, `Routine.mac`, `.int` or `.inc`. + pub name: String, + /// The document's text, to check code before saving it. Omit to check the document as stored + /// in the target namespace. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub source: Option, + /// Namespace that resolves the document's references. Omit to use the connection's. #[serde(default, skip_serializing_if = "Option::is_none")] pub namespace: Option, /// Route this call to a named registered IRIS instance. If omitted, uses the default connection. @@ -44,9 +48,12 @@ pub enum IrisCheckResponse { #[derive(Debug, Serialize, JsonSchema)] pub struct CheckOk { pub success: bool, - pub path: String, - /// Whether a connection resolved the file's references; without one they are hints. + pub name: String, + /// Whether a connection resolved the document's references; without one they are hints. pub iris: bool, + /// Present when `iris` is false: what was not checked, and how to connect. + #[serde(skip_serializing_if = "Option::is_none")] + pub note: Option, pub namespace: String, pub diagnostics: Vec, pub errors: usize, @@ -113,21 +120,18 @@ pub fn check(uri: &str, text: &str, atelier: Option) -> Vec, namespace: Option<&str>, ) -> Result { - // Judged before it is read: inside the project directory, with one of the compile extensions. - let file = { - let (root, cwd) = tools.host_path_context(); - let exts = host_fs::COMPILE_EXTENSIONS; - match host_fs::confine_file(root.as_deref(), &cwd, path, exts) { - Ok(canonical) => canonical, - Err(host_fs::PathRefusal::NotFound) => { - return err_json("FILE_NOT_FOUND", &format!("Local file not found: {path}")) - } - Err(why) => return tools.refuse(host_fs::refusal_body("iris_check", path, &why, exts)), - } - }; + // The extension decides how ascot parses the document, so an unknown one is refused up front. + let ext = name.rsplit_once('.').map(|(_, e)| e.to_ascii_lowercase()); + if !matches!(ext.as_deref(), Some("cls" | "mac" | "int" | "inc")) { + return err_json( + "INVALID_PARAMS", + &format!("name must end in .cls, .mac, .int or .inc, got {name}"), + ); + } let namespace = tools.target_ns(namespace)?; // Lookups go through the target connection when it speaks Atelier (a docker-only one cannot). let atelier = tools @@ -140,22 +144,47 @@ pub(crate) async fn handle_iris_check( handle: tokio::runtime::Handle::current(), }); let iris = atelier.is_some(); + if source.is_none() && !iris { + return Err(iris_unreachable()); + } + let (doc, source) = (name.to_string(), source.map(str::to_string)); // ascot is synchronous and its lookups block on the runtime, so it runs off the worker threads. let diagnostics = match tokio::task::spawn_blocking(move || { - std::fs::read_to_string(&file).map(|text| check(&file.to_string_lossy(), &text, atelier)) + // Without `source` the document comes from the server, through the same request ascot uses. + let text = match source { + Some(text) => Some(text), + None => match atelier.as_ref().map(|a| a.get_doc(&doc)) { + Some(Ok(text)) => text, + Some(Err(e)) => return Err(e), + None => None, + }, + }; + Ok(text.map(|text| check(&doc, &text, atelier))) }) .await .map_err(|e| McpError::internal_error(format!("check: {e}"), None))? { - Ok(diagnostics) => diagnostics, - Err(e) => return err_json("FILE_READ_FAILED", &format!("cannot read {path}: {e}")), + Ok(Some(diagnostics)) => diagnostics, + Ok(None) => { + return err_json( + "DOCUMENT_NOT_FOUND", + &format!("no document {name} in {namespace}"), + ) + } + Err(e) => return err_json("IRIS_UNREACHABLE", &e), }; use DiagnosticSeverity as S; let n = |s: S| diagnostics.iter().filter(|d| d.severity == s).count(); let ok = CheckOk { success: true, - path: path.to_string(), + name: name.to_string(), iris, + note: (!iris).then(|| { + "No IRIS connection, so what this document refers to but does not define was not \ + checked and appears as hints. Set IRIS_HOST and IRIS_WEB_PORT (or a project \ + .iris-agentic-dev.toml) to check it." + .to_string() + }), namespace, errors: n(S::Error), warnings: n(S::Warning), diff --git a/crates/iris-agentic-dev-core/src/tools/mod.rs b/crates/iris-agentic-dev-core/src/tools/mod.rs index 52e53948..288185ec 100644 --- a/crates/iris-agentic-dev-core/src/tools/mod.rs +++ b/crates/iris-agentic-dev-core/src/tools/mod.rs @@ -6071,7 +6071,8 @@ impl IrisTools { } #[tool( - description = "Check an ObjectScript file before compilation, powered by intersystems-community/ascot. ascot (https://github.com/intersystems-community/ascot) is a static analyzer and flow-sensitive type checker; it reports undefined labels and macros, unknown classes, methods and properties, wrong argument counts, unreachable code. path: a .cls, .mac, .int or .inc file inside the project directory. What the file refers to but does not define (superclasses, called classes and routines, included .inc files) is resolved against the target namespace through this connection with read-only Atelier requests; no code runs on IRIS. Without a connection those references are reported as hints, not checked. Returns LSP-style diagnostics: zero-based range, severity (Error, Warning, Information, Hint), a fixed code and its docs.rs page. Skill: objectscript-tdd. `server` (optional): name of a registered IRIS instance. If omitted, uses the default connection. Use `iris_servers` to list available instances.", + description = "Check an ObjectScript document before compilation, powered by intersystems-community/ascot. ascot (https://github.com/intersystems-community/ascot) is a static analyzer and flow-sensitive type checker; it reports undefined labels and macros, unknown classes, methods and properties, wrong argument counts, unreachable code. name: the document with its extension (Pkg.Class.cls, Routine.mac, .int, .inc). source: the document's text, to check code before saving it; omitted, the document is read as stored in the target namespace. What the document refers to but does not define (superclasses, called classes and routines, included .inc files) is resolved against the target namespace through this connection with read-only Atelier requests; no code runs on IRIS. Without a connection those references are reported as hints, not checked, and the response says so in `note`. Returns LSP-style diagnostics: zero-based range, severity (Error, Warning, Information, Hint), a fixed code and its docs.rs page. Skill: objectscript-tdd. `server` (optional): name of a registered IRIS instance. If omitted, uses the default connection. Use `iris_servers` to list available instances.", + annotations(read_only_hint = true), output_schema = output_schemas::oneof_output_schema::() )] @@ -6079,7 +6080,9 @@ impl IrisTools { &self, Parameters(p): Parameters, ) -> Result { - let result = check::handle_iris_check(self, &p.path, p.namespace.as_deref()).await; + let result = + check::handle_iris_check(self, &p.name, p.source.as_deref(), p.namespace.as_deref()) + .await; self.record_call("iris_check", result.is_ok()); result } diff --git a/crates/iris-agentic-dev-core/tests/binary/check_ascot.rs b/crates/iris-agentic-dev-core/tests/binary/check_ascot.rs index 64687838..46ab3b79 100644 --- a/crates/iris-agentic-dev-core/tests/binary/check_ascot.rs +++ b/crates/iris-agentic-dev-core/tests/binary/check_ascot.rs @@ -1,5 +1,5 @@ -//! `iris_check` over stdio: its advertised contract, and one check of a file in the project -//! directory with no IRIS configured. +//! `iris_check` over stdio: its advertised contract, and one check of inline source with no IRIS +//! configured. use iris_agentic_dev_core::testing::{ assert_advertised_contracts, assert_source_contracts, require_iad_binary, McpSession, @@ -9,11 +9,12 @@ use iris_agentic_dev_core::testing::{ const CONTRACT: &[ParamContract] = &[ParamContract { tool: "iris_check", params: &[ - ("path", "string"), + ("name", "string"), + ("source", "string"), ("namespace", "string"), ("server", "string"), ], - required: &["path"], + required: &["name"], }]; #[test] @@ -32,28 +33,27 @@ fn iris_check_reads_what_it_advertises() { #[test] #[ignore = "spawns the built binary; run with --include-ignored"] -fn a_file_in_the_project_is_checked_without_iris() { +fn inline_source_is_checked_without_iris() { let Some(_bin) = require_iad_binary() else { return; }; - let dir = tempfile::tempdir().expect("tempdir"); - std::fs::write( - dir.path().join("Test.mac"), - "ROUTINE Test [Type=MAC]\n Do Missing\n Quit\n", - ) - .unwrap(); - let env = [( - "OBJECTSCRIPT_WORKSPACE".to_string(), - dir.path().to_string_lossy().into_owned(), - )]; - let answer = McpSession::start_in(Some(dir.path()), &env) - .call("iris_check", &serde_json::json!({"path": "Test.mac"})); + let answer = McpSession::start(&[]).call( + "iris_check", + &serde_json::json!({"name": "Test.mac", "source": "ROUTINE Test [Type=MAC]\n Do Missing\n Quit\n"}), + ); let text = answer["result"]["content"][0]["text"] .as_str() .unwrap_or_else(|| panic!("no text content: {answer}")); let b: serde_json::Value = serde_json::from_str(text).expect("JSON body"); assert_eq!(b["success"], true, "{b}"); assert_eq!(b["iris"], false, "{b}"); + assert!( + b["note"] + .as_str() + .unwrap_or("") + .contains("No IRIS connection"), + "{b}" + ); assert_eq!(b["errors"], 1, "{b}"); assert_eq!(b["diagnostics"][0]["severity"], "Error", "{b}"); assert_eq!(b["diagnostics"][0]["range"]["start"]["line"], 1, "{b}"); diff --git a/crates/iris-agentic-dev-core/tests/integration/test_check_ascot_live.rs b/crates/iris-agentic-dev-core/tests/integration/test_check_ascot_live.rs index 65fd38de..9f481c75 100644 --- a/crates/iris-agentic-dev-core/tests/integration/test_check_ascot_live.rs +++ b/crates/iris-agentic-dev-core/tests/integration/test_check_ascot_live.rs @@ -1,5 +1,6 @@ -//! `iris_check` against iris-dev-iris: a name the file does not define is resolved through iad's -//! connection, so a wrong member of a system class is an error rather than a hint. +//! `iris_check` against iris-dev-iris: a document stored on the server is fetched and checked, and +//! a name it does not define is resolved through iad's connection, so a wrong member of a system +//! class is an error rather than a hint. //! //! Run: `IRIS_HOST=localhost IRIS_WEB_PORT=52780 IRIS_USERNAME=_SYSTEM IRIS_PASSWORD=SYS //! IAD_BINARY=$PWD/target/debug/iris-agentic-dev cargo test --features testing --test integration @@ -7,31 +8,37 @@ use iris_agentic_dev_core::testing::{live_env, McpSession}; +fn body(answer: &serde_json::Value) -> serde_json::Value { + let text = answer["result"]["content"][0]["text"] + .as_str() + .unwrap_or_else(|| panic!("no text content: {answer}")); + serde_json::from_str(text).unwrap_or_else(|e| panic!("not JSON ({e}): {text}")) +} + #[test] #[ignore = "requires live IRIS"] -fn a_wrong_member_of_a_system_class_is_an_error() { - let dir = tempfile::tempdir().expect("tempdir"); - std::fs::write( - dir.path().join("Live.CheckProbe.cls"), - "Class Live.CheckProbe\n{\nClassMethod Run()\n{\n Write ##class(%SYSTEM.Version).GetVersion()\n Write ##class(%SYSTEM.Version).NoSuchMethod()\n}\n}\n", - ) - .unwrap(); +fn a_stored_document_is_fetched_and_a_wrong_system_member_is_an_error() { let mut env = live_env(); env.retain(|(k, _)| k != "IRIS_CONTAINER"); - env.push(( - "OBJECTSCRIPT_WORKSPACE".to_string(), - dir.path().to_string_lossy().into_owned(), + let mut s = McpSession::start(&env); + let name = "Live.CheckProbe.cls"; + let source = "Class Live.CheckProbe\n{\nClassMethod Run()\n{\n Write ##class(%SYSTEM.Version).GetVersion()\n Write ##class(%SYSTEM.Version).NoSuchMethod()\n}\n}\n"; + let put = body(&s.call( + "iris_doc", + &serde_json::json!({"mode": "put", "name": name, "content": source, "namespace": "USER"}), )); - let answer = McpSession::start_in(Some(dir.path()), &env).call( + assert_eq!(put["success"], true, "{put}"); + let b = body(&s.call( "iris_check", - &serde_json::json!({"path": "Live.CheckProbe.cls", "namespace": "USER"}), + &serde_json::json!({"name": name, "namespace": "USER"}), + )); + s.call( + "iris_doc", + &serde_json::json!({"mode": "delete", "name": name, "namespace": "USER"}), ); - let text = answer["result"]["content"][0]["text"] - .as_str() - .unwrap_or_else(|| panic!("no text content: {answer}")); - let b: serde_json::Value = serde_json::from_str(text).expect("JSON body"); assert_eq!(b["success"], true, "{b}"); assert_eq!(b["iris"], true, "{b}"); + assert!(b["note"].is_null(), "{b}"); assert_eq!(b["errors"], 1, "{b}"); assert_eq!(b["hints"], 0, "{b}"); assert!( @@ -41,4 +48,9 @@ fn a_wrong_member_of_a_system_class_is_an_error() { .contains("NoSuchMethod"), "{b}" ); + let missing = body(&s.call( + "iris_check", + &serde_json::json!({"name": "Live.NoSuchDoc.cls", "namespace": "USER"}), + )); + assert_eq!(missing["error_code"], "DOCUMENT_NOT_FOUND", "{missing}"); } diff --git a/docs/tools.md b/docs/tools.md index 8ad12c3f..95ba8493 100644 --- a/docs/tools.md +++ b/docs/tools.md @@ -470,25 +470,29 @@ iris_compile(target="MyApp.*.cls", flags="cukd") ### `iris_check` -Check an ObjectScript file before compilation, powered by +Check an ObjectScript document before compilation, powered by [intersystems-community/ascot](https://github.com/intersystems-community/ascot), a static analyzer and flow-sensitive type checker. -| Parameter | Type | Default | Notes | -| ----------- | ------ | ------- | -------------------------------------------------------------------------------------------------------- | -| `path` | string | — | **Required.** A `.cls`, `.mac`, `.int` or `.inc` file inside the [project directory](#project-directory) | -| `namespace` | string | — | Namespace that resolves the file's references; defaults to the connection's | -| `server` | string | — | Named server; omit for default | +| Parameter | Type | Default | Notes | +| ----------- | ------ | ------- | --------------------------------------------------------------------------------------------- | +| `name` | string | — | **Required.** The document with its extension: `Pkg.Class.cls`, `Routine.mac`, `.int`, `.inc` | +| `source` | string | — | The document's text, to check code before saving it; omit to check it as stored on the server | +| `namespace` | string | — | Namespace that resolves the document's references; defaults to the connection's | +| `server` | string | — | Named server; omit for default | -Classes, routines and `.inc` files the file refers to but does not define are read from the target -server with read-only Atelier requests; nothing runs on IRIS. Without a connection they are -reported as hints. A path outside the project directory is refused with `HOST_PATH_BLOCKED`. +Classes, routines and `.inc` files the document refers to but does not define are read from the +target server with read-only Atelier requests; nothing runs on IRIS. Without a connection they are +reported as hints and the response carries a `note` saying so; without a connection and without +`source` there is nothing to check, and the call is refused with `IRIS_UNREACHABLE`. A `name` the +server does not hold answers `DOCUMENT_NOT_FOUND`. Returns LSP-style diagnostics (zero-based `range`, `severity`, `code`, `code_description`) with `errors`, `warnings` and `hints` counts, and `iris` saying whether a connection answered the lookups. ```text -iris_check(path="src/MyApp/Service.cls") +iris_check(name="MyApp.Service.cls") +iris_check(name="MyApp.Service.cls", source="Class MyApp.Service ...") ``` --- From 4916d0a11da8426c50f75923da24818a4bff79cb Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Thu, 29 Oct 2026 17:45:33 -0400 Subject: [PATCH 09/10] gates: call iris_unreachable path-qualified in check.rs The tool-name gate reads an imported `iris_unreachable` in a `use` list as a tool reference; every other caller writes it as a call, which the gate exempts. --- crates/iris-agentic-dev-core/src/tools/check.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/iris-agentic-dev-core/src/tools/check.rs b/crates/iris-agentic-dev-core/src/tools/check.rs index d01b053d..12ecef2b 100644 --- a/crates/iris-agentic-dev-core/src/tools/check.rs +++ b/crates/iris-agentic-dev-core/src/tools/check.rs @@ -17,7 +17,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use super::output_schemas::ToolError; -use super::{err_json, iris_unreachable, nopws, ok_json, IrisTools}; +use super::{err_json, nopws, ok_json, IrisTools}; use crate::iris::connection::IrisConnection; /// Parameters of `iris_check`. @@ -145,7 +145,7 @@ pub(crate) async fn handle_iris_check( }); let iris = atelier.is_some(); if source.is_none() && !iris { - return Err(iris_unreachable()); + return Err(super::iris_unreachable()); } let (doc, source) = (name.to_string(), source.map(str::to_string)); // ascot is synchronous and its lookups block on the runtime, so it runs off the worker threads. From 4edbe8584a5247ff12f53a9e74e4421c5d625b45 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Mon, 2 Nov 2026 12:12:01 -0500 Subject: [PATCH 10/10] fix: satisfy three guards upstream added since the branch forked The startup-client scan matches the text `self.client`, so the bridge's field is `http`: the client it holds is the target's own, taken from `http_client()`. The no-connection note no longer tells the agent to edit the project file; it names the environment variables and `iris_add_server`. And ascot's clap turns on `wrap_help` for the whole build, which rewrapped the `trust` line of `--help` and gave the subcommand scan a phantom command; `term_width = 0` keeps the help in source formatting whatever a dependency enables. --- crates/iris-agentic-dev-bin/src/main.rs | 2 ++ crates/iris-agentic-dev-core/src/tools/check.rs | 12 ++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/iris-agentic-dev-bin/src/main.rs b/crates/iris-agentic-dev-bin/src/main.rs index 834330d4..a34fbbe6 100644 --- a/crates/iris-agentic-dev-bin/src/main.rs +++ b/crates/iris-agentic-dev-bin/src/main.rs @@ -10,6 +10,8 @@ use tracing_subscriber::EnvFilter; version, about = "CLI and package manager for InterSystems IRIS developer ecosystem", long_about = None, + // Source formatting: a dependency turning on clap's `wrap_help` must not rewrap this help. + term_width = 0, )] struct Cli { #[command(subcommand)] diff --git a/crates/iris-agentic-dev-core/src/tools/check.rs b/crates/iris-agentic-dev-core/src/tools/check.rs index 3ac1cbf3..8c5290ba 100644 --- a/crates/iris-agentic-dev-core/src/tools/check.rs +++ b/crates/iris-agentic-dev-core/src/tools/check.rs @@ -64,7 +64,7 @@ pub struct CheckOk { /// iad's connection as the two requests ascot asks for. pub struct IadAtelier { conn: Arc, - client: reqwest::Client, + http: reqwest::Client, namespace: String, handle: tokio::runtime::Handle, } @@ -74,7 +74,7 @@ impl Atelier for IadAtelier { let params = params.iter().map(|p| Value::from(*p)).collect(); let body = self .handle - .block_on(self.conn.query(sql, params, &self.namespace, &self.client)) + .block_on(self.conn.query(sql, params, &self.namespace, &self.http)) .map_err(|e| format!("query in {}: {e}", self.namespace))?; match body.pointer("/result/content") { Some(rows) if rows.is_array() => Ok(rows.to_string()), @@ -88,7 +88,7 @@ impl Atelier for IadAtelier { let err = |e: &dyn std::fmt::Display| format!("GET {url}: {e}"); self.handle.block_on(async { let resp = self - .client + .http .get(&url) .basic_auth(&self.conn.username, Some(&self.conn.password)) .send() @@ -140,7 +140,7 @@ pub(crate) async fn handle_iris_check( { Some(conn) => Some(IadAtelier { conn, - client: tools.http_client()?.clone(), + http: tools.http_client()?.clone(), namespace: namespace.clone(), handle: tokio::runtime::Handle::current(), }), @@ -184,8 +184,8 @@ pub(crate) async fn handle_iris_check( iris, note: (!iris).then(|| { "No IRIS connection, so what this document refers to but does not define was not \ - checked and appears as hints. Set IRIS_HOST and IRIS_WEB_PORT (or a project \ - .iris-agentic-dev.toml) to check it." + checked and appears as hints. Set the IRIS_HOST and IRIS_WEB_PORT environment \ + variables, or register a server with iris_add_server, to check it." .to_string() }), namespace,