diff --git a/.github/labeler.yml b/.github/labeler.yml
index 2ed28a5..3485170 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -1,62 +1,35 @@
-# Auto-label PRs by changed files
-'feat':
- - src/**/*.rs
- - Cargo.toml
-
-'fix':
- - src/**/*.rs
-
-'docs':
- - '**/*.md'
- - '**/docs/**'
-
-'ci':
- - .github/workflows/**/*
- - .github/**/*
-
-'config':
- - 'opencode.json'
- - 'opencode.jsonc'
- - '**/opencode*'
-
-'test':
- - tests/**/*
-
-size/XS:
- - all:
- - '**/*'
- any:
- - count: '**/*.rs'
- min: 1
- max: 3
-
-size/S:
- - all:
- - '**/*'
- any:
- - count: '**/*.rs'
- min: 4
- max: 10
-
-size/M:
- - all:
- - '**/*'
- any:
- - count: '**/*.rs'
- min: 11
- max: 30
-
-size/L:
- - all:
- - '**/*'
- any:
- - count: '**/*.rs'
- min: 31
- max: 100
-
-size/XL:
- - all:
- - '**/*'
- any:
- - count: '**/*.rs'
- min: 101
+# Auto-label PRs by changed files (actions/labeler v5 syntax)
+feat:
+- changed-files:
+ - any-glob-to-any-file:
+ - src/**/*.rs
+ - Cargo.toml
+
+fix:
+- changed-files:
+ - any-glob-to-any-file:
+ - src/**/*.rs
+
+docs:
+- changed-files:
+ - any-glob-to-any-file:
+ - '**/*.md'
+ - '**/docs/**'
+
+ci:
+- changed-files:
+ - any-glob-to-any-file:
+ - .github/workflows/**/*
+ - .github/**/*
+
+config:
+- changed-files:
+ - any-glob-to-any-file:
+ - 'opencode.json'
+ - 'opencode.jsonc'
+ - '**/opencode*'
+
+test:
+- changed-files:
+ - any-glob-to-any-file:
+ - tests/**/*
\ No newline at end of file
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index fff9e50..9d7c431 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -7,6 +7,7 @@ on:
env:
CARGO_TERM_COLOR: always
+ RUST_BACKTRACE: full
jobs:
test:
@@ -20,7 +21,19 @@ jobs:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- - run: cargo test
+
+ - name: Compile check
+ run: cargo check 2>&1
+
+ - name: Run tests (verbose)
+ run: cargo test -- --nocapture --test-threads=1 2>&1
+
+ - name: Test summary
+ if: always()
+ run: |
+ echo "### Test Results (${{ matrix.os }})" >> $GITHUB_STEP_SUMMARY
+ echo "- OS: ${{ matrix.os }}" >> $GITHUB_STEP_SUMMARY
+ echo "- Status: ${{ job.status }}" >> $GITHUB_STEP_SUMMARY
fmt:
name: Format
@@ -41,8 +54,7 @@ jobs:
with:
components: clippy
- uses: Swatinem/rust-cache@v2
- - run: cargo clippy --all-targets
- continue-on-error: true
+ - run: cargo clippy --all-targets -- -D warnings
msrv:
name: MSRV (1.86.0)
diff --git a/Cargo.lock b/Cargo.lock
index d5c196c..7fdc93d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -22,9 +22,9 @@ dependencies = [
[[package]]
name = "anyhow"
-version = "1.0.102"
+version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
+checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
[[package]]
name = "async-trait"
@@ -49,6 +49,29 @@ version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
+[[package]]
+name = "aws-lc-rs"
+version = "1.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4342d8937fc7e5dd9b1c60292261c0670c882a2cd1719cfc11b1af41731e32ad"
+dependencies = [
+ "aws-lc-sys",
+ "zeroize",
+]
+
+[[package]]
+name = "aws-lc-sys"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d9ceb1da931507a12f4fccea479dccd00da1943e1b4ae72d8e502d707361444"
+dependencies = [
+ "cc",
+ "cmake",
+ "dunce",
+ "fs_extra",
+ "pkg-config",
+]
+
[[package]]
name = "base64"
version = "0.22.1"
@@ -69,17 +92,19 @@ checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
[[package]]
name = "bytes"
-version = "1.11.1"
+version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
+checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
[[package]]
name = "cc"
-version = "1.2.64"
+version = "1.2.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dad887fd958be91b5098c0248def011f4523ab786cd411be668777e55063501f"
+checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38"
dependencies = [
"find-msvc-tools",
+ "jobserver",
+ "libc",
"shlex",
]
@@ -95,6 +120,17 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
+[[package]]
+name = "chacha20"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "rand_core",
+]
+
[[package]]
name = "chrono"
version = "0.4.45"
@@ -109,12 +145,50 @@ dependencies = [
"windows-link",
]
+[[package]]
+name = "cmake"
+version = "0.1.58"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "combine"
+version = "4.6.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
+dependencies = [
+ "bytes",
+ "memchr",
+]
+
+[[package]]
+name = "core-foundation"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+[[package]]
+name = "cpufeatures"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "dirs"
version = "5.0.1"
@@ -147,6 +221,12 @@ dependencies = [
"syn",
]
+[[package]]
+name = "dunce"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
+
[[package]]
name = "equivalent"
version = "1.0.2"
@@ -163,6 +243,18 @@ dependencies = [
"windows-sys 0.61.2",
]
+[[package]]
+name = "fastbloom"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef975e30683b2d965054bb0a836f8973857c4ebf6acf274fe46617cd285060d8"
+dependencies = [
+ "foldhash",
+ "libm",
+ "portable-atomic",
+ "siphasher",
+]
+
[[package]]
name = "fastrand"
version = "2.4.1"
@@ -177,9 +269,9 @@ checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "foldhash"
-version = "0.1.5"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
+checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
[[package]]
name = "form_urlencoded"
@@ -190,6 +282,12 @@ dependencies = [
"percent-encoding",
]
+[[package]]
+name = "fs_extra"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
+
[[package]]
name = "futures-channel"
version = "0.3.32"
@@ -254,52 +352,24 @@ dependencies = [
[[package]]
name = "getrandom"
-version = "0.3.4"
+version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
+checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
dependencies = [
"cfg-if",
"js-sys",
"libc",
- "r-efi 5.3.0",
- "wasip2",
+ "r-efi",
+ "rand_core",
"wasm-bindgen",
]
-[[package]]
-name = "getrandom"
-version = "0.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
-dependencies = [
- "cfg-if",
- "libc",
- "r-efi 6.0.0",
- "wasip2",
- "wasip3",
-]
-
-[[package]]
-name = "hashbrown"
-version = "0.15.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
-dependencies = [
- "foldhash",
-]
-
[[package]]
name = "hashbrown"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
-[[package]]
-name = "heck"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
-
[[package]]
name = "http"
version = "1.4.2"
@@ -312,9 +382,9 @@ dependencies = [
[[package]]
name = "http-body"
-version = "1.0.1"
+version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
+checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c"
dependencies = [
"bytes",
"http",
@@ -322,9 +392,9 @@ dependencies = [
[[package]]
name = "http-body-util"
-version = "0.1.3"
+version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
+checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2"
dependencies = [
"bytes",
"futures-core",
@@ -504,12 +574,6 @@ dependencies = [
"zerovec",
]
-[[package]]
-name = "id-arena"
-version = "2.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
-
[[package]]
name = "idna"
version = "1.1.0"
@@ -538,9 +602,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [
"equivalent",
- "hashbrown 0.17.1",
- "serde",
- "serde_core",
+ "hashbrown",
]
[[package]]
@@ -555,11 +617,70 @@ 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",
+]
+
+[[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",
+]
+
+[[package]]
+name = "jobserver"
+version = "0.1.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3"
+dependencies = [
+ "getrandom 0.4.3",
+ "libc",
+]
+
[[package]]
name = "js-sys"
-version = "0.3.102"
+version = "0.3.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31"
+checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102"
dependencies = [
"cfg-if",
"futures-util",
@@ -572,23 +693,23 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
-[[package]]
-name = "leb128fmt"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
-
[[package]]
name = "libc"
version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
+[[package]]
+name = "libm"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
+
[[package]]
name = "libredox"
-version = "0.1.17"
+version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3"
+checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652"
dependencies = [
"libc",
]
@@ -605,20 +726,11 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
-[[package]]
-name = "lock_api"
-version = "0.4.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
-dependencies = [
- "scopeguard",
-]
-
[[package]]
name = "log"
-version = "0.4.32"
+version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a"
+checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
[[package]]
name = "lru-slab"
@@ -637,15 +749,15 @@ dependencies = [
[[package]]
name = "memchr"
-version = "2.8.2"
+version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
+checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
name = "mio"
-version = "1.2.1"
+version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda"
+checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427"
dependencies = [
"libc",
"wasi",
@@ -676,6 +788,12 @@ version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
+[[package]]
+name = "openssl-probe"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
+
[[package]]
name = "option-ext"
version = "0.2.0"
@@ -684,50 +802,29 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "oura"
-version = "0.2.0"
+version = "0.3.0"
dependencies = [
"anyhow",
"async-trait",
- "base64",
"chrono",
"dirs",
+ "quinn",
"regex",
"reqwest",
+ "rustls",
+ "rustls-pemfile",
"serde",
"serde_json",
- "serde_yaml",
"tempfile",
"thiserror 1.0.69",
"tokio",
"toml",
"tracing",
"tracing-subscriber",
+ "url",
"uuid",
]
-[[package]]
-name = "parking_lot"
-version = "0.12.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
-dependencies = [
- "lock_api",
- "parking_lot_core",
-]
-
-[[package]]
-name = "parking_lot_core"
-version = "0.9.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
-dependencies = [
- "cfg-if",
- "libc",
- "redox_syscall",
- "smallvec",
- "windows-link",
-]
-
[[package]]
name = "percent-encoding"
version = "2.3.2"
@@ -741,31 +838,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
-name = "potential_utf"
-version = "0.1.5"
+name = "pkg-config"
+version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"
-dependencies = [
- "zerovec",
-]
+checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
[[package]]
-name = "ppv-lite86"
-version = "0.2.21"
+name = "portable-atomic"
+version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
-dependencies = [
- "zerocopy",
-]
+checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
[[package]]
-name = "prettyplease"
-version = "0.2.37"
+name = "potential_utf"
+version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
+checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"
dependencies = [
- "proc-macro2",
- "syn",
+ "zerovec",
]
[[package]]
@@ -779,9 +869,9 @@ dependencies = [
[[package]]
name = "quinn"
-version = "0.11.9"
+version = "0.11.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20"
+checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8"
dependencies = [
"bytes",
"cfg_aliases",
@@ -799,18 +889,21 @@ dependencies = [
[[package]]
name = "quinn-proto"
-version = "0.11.14"
+version = "0.11.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
+checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560"
dependencies = [
"bytes",
- "getrandom 0.3.4",
+ "fastbloom",
+ "getrandom 0.4.3",
"lru-slab",
"rand",
+ "rand_pcg",
"ring",
"rustc-hash",
"rustls",
"rustls-pki-types",
+ "rustls-platform-verifier",
"slab",
"thiserror 2.0.18",
"tinyvec",
@@ -820,33 +913,27 @@ dependencies = [
[[package]]
name = "quinn-udp"
-version = "0.5.14"
+version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
+checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694"
dependencies = [
"cfg_aliases",
"libc",
"once_cell",
"socket2",
"tracing",
- "windows-sys 0.60.2",
+ "windows-sys 0.61.2",
]
[[package]]
name = "quote"
-version = "1.0.45"
+version = "1.0.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
+checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
dependencies = [
"proc-macro2",
]
-[[package]]
-name = "r-efi"
-version = "5.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
-
[[package]]
name = "r-efi"
version = "6.0.0"
@@ -855,40 +942,28 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "rand"
-version = "0.9.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
-dependencies = [
- "rand_chacha",
- "rand_core",
-]
-
-[[package]]
-name = "rand_chacha"
-version = "0.9.0"
+version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
+checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
dependencies = [
- "ppv-lite86",
+ "chacha20",
+ "getrandom 0.4.3",
"rand_core",
]
[[package]]
name = "rand_core"
-version = "0.9.5"
+version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
-dependencies = [
- "getrandom 0.3.4",
-]
+checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
[[package]]
-name = "redox_syscall"
-version = "0.5.18"
+name = "rand_pcg"
+version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
+checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a"
dependencies = [
- "bitflags",
+ "rand_core",
]
[[package]]
@@ -904,9 +979,9 @@ dependencies = [
[[package]]
name = "regex"
-version = "1.12.4"
+version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba"
+checksum = "2a0e75113e14dc5acb068cd0786884f214f1312650a3d36d269f5c4f3cdee8a2"
dependencies = [
"aho-corasick",
"memchr",
@@ -916,9 +991,9 @@ dependencies = [
[[package]]
name = "regex-automata"
-version = "0.4.14"
+version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
+checksum = "1f388202e4b80542a0921078cc23b6333bcf1409c1e3f86404cae4766a6131db"
dependencies = [
"aho-corasick",
"memchr",
@@ -987,9 +1062,18 @@ dependencies = [
[[package]]
name = "rustc-hash"
-version = "2.1.2"
+version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
+checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
+
+[[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"
@@ -1006,10 +1090,12 @@ dependencies = [
[[package]]
name = "rustls"
-version = "0.23.40"
+version = "0.23.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b"
+checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138"
dependencies = [
+ "aws-lc-rs",
+ "log",
"once_cell",
"ring",
"rustls-pki-types",
@@ -1018,22 +1104,71 @@ dependencies = [
"zeroize",
]
+[[package]]
+name = "rustls-native-certs"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d"
+dependencies = [
+ "openssl-probe",
+ "rustls-pki-types",
+ "schannel",
+ "security-framework",
+]
+
+[[package]]
+name = "rustls-pemfile"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
+dependencies = [
+ "rustls-pki-types",
+]
+
[[package]]
name = "rustls-pki-types"
-version = "1.14.1"
+version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
+checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046"
dependencies = [
"web-time",
"zeroize",
]
+[[package]]
+name = "rustls-platform-verifier"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0"
+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.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
+
[[package]]
name = "rustls-webpki"
version = "0.103.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
dependencies = [
+ "aws-lc-rs",
"ring",
"rustls-pki-types",
"untrusted",
@@ -1041,9 +1176,9 @@ dependencies = [
[[package]]
name = "rustversion"
-version = "1.0.22"
+version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
+checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
[[package]]
name = "ryu"
@@ -1052,10 +1187,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
[[package]]
-name = "scopeguard"
-version = "1.2.0"
+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"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "security-framework"
+version = "3.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
+dependencies = [
+ "bitflags",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
[[package]]
name = "semver"
@@ -1127,19 +1297,6 @@ dependencies = [
"serde",
]
-[[package]]
-name = "serde_yaml"
-version = "0.9.34+deprecated"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
-dependencies = [
- "indexmap",
- "itoa",
- "ryu",
- "serde",
- "unsafe-libyaml",
-]
-
[[package]]
name = "sharded-slab"
version = "0.1.7"
@@ -1165,6 +1322,28 @@ dependencies = [
"libc",
]
+[[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 = "siphasher"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
+
[[package]]
name = "slab"
version = "0.4.12"
@@ -1179,9 +1358,9 @@ checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
[[package]]
name = "socket2"
-version = "0.6.4"
+version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51"
+checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
dependencies = [
"libc",
"windows-sys 0.61.2",
@@ -1201,9 +1380,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
-version = "2.0.117"
+version = "2.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
+checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
dependencies = [
"proc-macro2",
"quote",
@@ -1237,7 +1416,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
dependencies = [
"fastrand",
- "getrandom 0.4.2",
+ "getrandom 0.4.3",
"once_cell",
"rustix",
"windows-sys 0.61.2",
@@ -1285,9 +1464,9 @@ dependencies = [
[[package]]
name = "thread_local"
-version = "1.1.9"
+version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
+checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070"
dependencies = [
"cfg-if",
]
@@ -1304,9 +1483,9 @@ dependencies = [
[[package]]
name = "tinyvec"
-version = "1.11.0"
+version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3"
+checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f"
dependencies = [
"tinyvec_macros",
]
@@ -1326,7 +1505,6 @@ dependencies = [
"bytes",
"libc",
"mio",
- "parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
@@ -1447,6 +1625,7 @@ version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
dependencies = [
+ "log",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
@@ -1514,18 +1693,6 @@ version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
-[[package]]
-name = "unicode-xid"
-version = "0.2.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
-
-[[package]]
-name = "unsafe-libyaml"
-version = "0.2.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
-
[[package]]
name = "untrusted"
version = "0.9.0"
@@ -1552,11 +1719,11 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "uuid"
-version = "1.23.3"
+version = "1.23.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7"
+checksum = "ea5fab0d6c3c01ae70085a09cb03d4c7a1d6314e2b3e075392783396d724ca0a"
dependencies = [
- "getrandom 0.4.2",
+ "getrandom 0.4.3",
"js-sys",
"wasm-bindgen",
]
@@ -1567,6 +1734,16 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
+[[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"
@@ -1582,29 +1759,11 @@ version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
-[[package]]
-name = "wasip2"
-version = "1.0.4+wasi-0.2.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
-dependencies = [
- "wit-bindgen 0.57.1",
-]
-
-[[package]]
-name = "wasip3"
-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 0.51.0",
-]
-
[[package]]
name = "wasm-bindgen"
-version = "0.2.125"
+version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a"
+checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4"
dependencies = [
"cfg-if",
"once_cell",
@@ -1615,9 +1774,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-futures"
-version = "0.4.75"
+version = "0.4.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "503b14d284f2c8dac03b819967e155ea753f573586193b2b2c95990cb5d69280"
+checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -1625,9 +1784,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.125"
+version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d"
+checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -1635,9 +1794,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.125"
+version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd"
+checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e"
dependencies = [
"bumpalo",
"proc-macro2",
@@ -1648,52 +1807,18 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.125"
+version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f"
+checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24"
dependencies = [
"unicode-ident",
]
-[[package]]
-name = "wasm-encoder"
-version = "0.244.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
-dependencies = [
- "leb128fmt",
- "wasmparser",
-]
-
-[[package]]
-name = "wasm-metadata"
-version = "0.244.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
-dependencies = [
- "anyhow",
- "indexmap",
- "wasm-encoder",
- "wasmparser",
-]
-
-[[package]]
-name = "wasmparser"
-version = "0.244.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
-dependencies = [
- "bitflags",
- "hashbrown 0.15.5",
- "indexmap",
- "semver",
-]
-
[[package]]
name = "web-sys"
-version = "0.3.102"
+version = "0.3.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a6430a72df5eb332242960fe84b3002a241163998241eb596d4f739b9757061d"
+checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -1709,15 +1834,33 @@ dependencies = [
"wasm-bindgen",
]
+[[package]]
+name = "webpki-root-certs"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d46a5a140e6f7afeccd8eae97eff335163939eac8b929834875168b29b3d267"
+dependencies = [
+ "rustls-pki-types",
+]
+
[[package]]
name = "webpki-roots"
-version = "1.0.7"
+version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d"
+checksum = "bf85cb06032201fa7c6f829d7db5a7e5aa45bcc0655327713065f6f0576731bf"
dependencies = [
"rustls-pki-types",
]
+[[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 = "windows-core"
version = "0.62.2"
@@ -1795,15 +1938,6 @@ 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"
@@ -1837,30 +1971,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"
@@ -1873,12 +1990,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"
@@ -1891,12 +2002,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"
@@ -1909,24 +2014,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"
@@ -1939,12 +2032,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"
@@ -1957,12 +2044,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"
@@ -1975,12 +2056,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"
@@ -1993,12 +2068,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"
@@ -2008,100 +2077,6 @@ dependencies = [
"memchr",
]
-[[package]]
-name = "wit-bindgen"
-version = "0.51.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
-dependencies = [
- "wit-bindgen-rust-macro",
-]
-
-[[package]]
-name = "wit-bindgen"
-version = "0.57.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
-
-[[package]]
-name = "wit-bindgen-core"
-version = "0.51.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
-dependencies = [
- "anyhow",
- "heck",
- "wit-parser",
-]
-
-[[package]]
-name = "wit-bindgen-rust"
-version = "0.51.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
-dependencies = [
- "anyhow",
- "heck",
- "indexmap",
- "prettyplease",
- "syn",
- "wasm-metadata",
- "wit-bindgen-core",
- "wit-component",
-]
-
-[[package]]
-name = "wit-bindgen-rust-macro"
-version = "0.51.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
-dependencies = [
- "anyhow",
- "prettyplease",
- "proc-macro2",
- "quote",
- "syn",
- "wit-bindgen-core",
- "wit-bindgen-rust",
-]
-
-[[package]]
-name = "wit-component"
-version = "0.244.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
-dependencies = [
- "anyhow",
- "bitflags",
- "indexmap",
- "log",
- "serde",
- "serde_derive",
- "serde_json",
- "wasm-encoder",
- "wasm-metadata",
- "wasmparser",
- "wit-parser",
-]
-
-[[package]]
-name = "wit-parser"
-version = "0.244.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
-dependencies = [
- "anyhow",
- "id-arena",
- "indexmap",
- "log",
- "semver",
- "serde",
- "serde_derive",
- "serde_json",
- "unicode-xid",
- "wasmparser",
-]
-
[[package]]
name = "writeable"
version = "0.6.3"
@@ -2131,26 +2106,6 @@ dependencies = [
"synstructure",
]
-[[package]]
-name = "zerocopy"
-version = "0.8.52"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f"
-dependencies = [
- "zerocopy-derive",
-]
-
-[[package]]
-name = "zerocopy-derive"
-version = "0.8.52"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
[[package]]
name = "zerofrom"
version = "0.1.8"
@@ -2213,6 +2168,6 @@ dependencies = [
[[package]]
name = "zmij"
-version = "1.0.21"
+version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
+checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
diff --git a/Cargo.toml b/Cargo.toml
index 0423131..6459479 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,26 +1,29 @@
[package]
name = "oura"
-version = "0.2.0"
+version = "0.3.0"
description = "Oura — MCP server for intelligent iterative looping. The serpent that refines code through endless improvement cycles."
+rust-version = "1.86"
edition = "2021"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
-serde_yaml = "0.9"
toml = "0.8"
-tokio = { version = "1", features = ["full"] }
+tokio = { version = "1", features = ["rt-multi-thread", "sync", "macros", "process", "io-util", "io-std", "net"] }
uuid = { version = "1.0", features = ["v4"] }
chrono = { version = "0.4", features = ["serde"] }
anyhow = "1.0"
thiserror = "1.0"
reqwest = { version = "0.12", features = ["json", "rustls-tls", "blocking"], default-features = false }
+quinn = "0.11"
+rustls = "0.23"
+rustls-pemfile = "2"
regex = "1"
dirs = "5.0"
-base64 = "0.22"
async-trait = "0.1"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
+url = "2"
[dev-dependencies]
tempfile = "3.0"
diff --git a/README.md b/README.md
index 0d2a4ea..2a9d451 100644
--- a/README.md
+++ b/README.md
@@ -12,20 +12,15 @@
-
-
-
-
+
+
## Características
-- **Loop Engine**: iteraciones automáticas con detección de convergencia
+- **Loop Engine**: iteraciones automáticas o manuales con detección de convergencia
- **Feedback multi-fuente**: tests, lint, typecheck, custom
- **Sub-agentes**: Security Auditor, Refactor Engine, Anti-deletion Guard, Code Optimizer
-- **Integración GitHub**: PRs, workflows, actions, auto-commit, multi-repo
-- **Plugin system**: hooks extensibles para eventos del loop
-- **Synapsis bridge**: persistencia en Synapsis memory + task orchestration
- **Config**: TOML + env vars (`OURA_*`)
## Arquitectura
@@ -34,7 +29,7 @@
-Oura está compuesto por **14 módulos** organizados en capas:
+Oura está compuesto por **13 módulos** organizados en capas:
| Capa | Módulos | Responsabilidad |
|------|---------|-----------------|
@@ -63,6 +58,11 @@ export OURA_GITHUB_TOKEN=ghp_xxx
export OURA_GITHUB_OWNER=MethodWhite
export OURA_GITHUB_REPO=my-project
export OURA_MAX_ITERATIONS=50
+export OURA_CONVERGENCE_THRESHOLD=90.0
+export OURA_GITHUB_ENABLED=true
+export OURA_WORKING_DIR=/path/to/project
+export OURA_CONFIG=/custom/path/config.toml
+export OURA_QUIET=1
```
### Config file (`~/.config/oura/config.toml`)
@@ -74,15 +74,18 @@ convergence_threshold = 90.0
feedback_sources = ["test", "lint"]
[github]
-enabled = true
+enabled = false
default_owner = "MethodWhite"
default_repo = "my-project"
-auto_commit = true
-auto_pr = true
-
-[synapsis]
-enabled = true
-endpoint = "http://localhost:7438"
+auto_commit = false
+auto_pr = false
+
+# External MCP connector — call tools on other MCP servers during iteration
+# [connector]
+# enabled = true
+# server_url = "http://localhost:7438"
+# tools = ["synapsis_mem_search"]
+# auto_call = true
```
## Uso con MCP
@@ -104,13 +107,14 @@ Añade a `opencode.json` / `claude-code.json` / `cursor.json`:
| Tool | Descripción |
|------|-------------|
-| `oura_start_loop` | Inicia loop de iteración con goal |
+| `oura_start_loop` | Inicia loop de iteración con goal (manual=true para modo manual con oura_iterate) |
| `oura_iterate` | Ejecuta un paso manual |
| `oura_loop_status` | Estado del loop actual |
| `oura_loop_stop` | Detiene el loop |
| `oura_results` | Resultados acumulados |
| `oura_configure` | Actualiza configuración (maxIterations, threshold, workingDirectory) |
-| `oura_working_dir` | Cambia el directorio de trabajo |
+| `oura_working_dir` | Set the working directory for Oura commands |
+| `oura_connector` | Call a tool on an external MCP server (e.g. Synapsis). Manual or auto via `[connector]` config |
| `oura_plugin_load` | Carga un plugin |
| `oura_plugin_list` | Lista plugins cargados |
| `oura_analyze_security` | Auditoría de seguridad (9 patrones) |
@@ -123,25 +127,25 @@ Añade a `opencode.json` / `claude-code.json` / `cursor.json`:
| `oura_update` | Actualiza Oura desde git + cargo build |
| `oura_profile` | Detecta perfil del proyecto |
| `oura_verify` | Verifica licencias y dependencias |
-| `mcp_call` | Llama a tools de otros servidores MCP |
+| `mcp_call` | HTTP call to another MCP server (low-level; SSRF protected) |
+
## Calidad
| Métrica | Valor |
|---------|-------|
| Tests | 58 pasando |
-| Cobertura clippy | 0 warnings |
+| Clippy | 0 warnings |
| `unwrap()` en producción | 0 |
-| Deuda técnica corregida | ~55 issues |
-| Lenguaje | Rust 1.86+ |
-| Licencia | MIT |
+| Lenguaje | Rust |
+| Licencia | BSL 1.1 |
## Licencia
-MIT — ver [LICENSE](LICENSE).
+BSL 1.1 — ver [LICENSE](LICENSE).
---
- Hecho con 🐍 por MethodWhite
+ Hecho con 🦀 por MethodWhite
diff --git a/build.rs b/build.rs
index c656aa9..4c118b2 100644
--- a/build.rs
+++ b/build.rs
@@ -18,4 +18,9 @@ fn main() {
println!("cargo:rustc-env=GIT_HEAD={}", git_head);
println!("cargo:rerun-if-changed=.git/HEAD");
+ if let Ok(head) = std::fs::read_to_string(".git/HEAD") {
+ if let Some(ref_path) = head.strip_prefix("ref: ") {
+ println!("cargo:rerun-if-changed=.git/{}", ref_path.trim());
+ }
+ }
}
diff --git a/install.ps1 b/install.ps1
new file mode 100644
index 0000000..ca73efa
--- /dev/null
+++ b/install.ps1
@@ -0,0 +1,114 @@
+#!/usr/bin/env pwsh
+#requires -Version 5.1
+
+param(
+ [string]$Version = "latest",
+ [string]$InstallDir = "${env:ProgramFiles}\Oura",
+ [string]$ConfigDir = "${env:USERPROFILE}\.config\oura"
+)
+
+$Repo = "MethodWhite/oura"
+$Green = "Green"
+$Yellow = "Yellow"
+$Red = "Red"
+$Cyan = "Cyan"
+
+function Log { Write-Host "[✓] $args" -ForegroundColor $Green }
+function Warn { Write-Host "[!] $args" -ForegroundColor $Yellow }
+function Err { Write-Host "[✗] $args" -ForegroundColor $Red; exit 1 }
+function Info { Write-Host "[i] $args" -ForegroundColor $Cyan }
+
+function Detect-Arch {
+ $arch = if ([Environment]::Is64BitOperatingSystem) { "x86_64" } else { "i686" }
+ $env = [Environment]::GetEnvironmentVariable("PROCESSOR_IDENTIFIER")
+ if ($env -match "ARM|AArch64") { $arch = "aarch64" }
+ return $arch
+}
+
+function Get-ReleaseUrl {
+ param([string]$OsArch)
+ if ($Version -eq "latest") {
+ return "https://github.com/$Repo/releases/latest/download/oura-${OsArch}.exe"
+ }
+ return "https://github.com/$Repo/releases/download/$Version/oura-${OsArch}.exe"
+}
+
+function Main {
+ Write-Host ""
+ Write-Host "╔══════════════════════════════════════╗" -ForegroundColor $Cyan
+ Write-Host "║ Oura Installer — 0XFFRice ║" -ForegroundColor $Cyan
+ Write-Host "╚══════════════════════════════════════╝" -ForegroundColor $Cyan
+ Write-Host ""
+
+ $elevated = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
+ if (-not $elevated) {
+ Warn "Not running as Administrator. Some installs may fail."
+ Warn "Restart with: Start-Process pwsh -Verb RunAs -ArgumentList '-File install.ps1'"
+ }
+
+ $arch = Detect-Arch
+ $osArch = "${arch}-pc-windows-msvc"
+ $url = Get-ReleaseUrl -OsArch $osArch
+
+ Info "Detected: $osArch"
+ Info "Download: $url"
+
+ $tmpdir = "$env:TEMP\oura-install"
+ New-Item -ItemType Directory -Force -Path $tmpdir | Out-Null
+ $outFile = "$tmpdir\oura.exe"
+
+ try {
+ Invoke-WebRequest -Uri $url -OutFile $outFile -UseBasicParsing -ErrorAction Stop
+ } catch {
+ Err "Download failed: $_"
+ }
+
+ try {
+ $version = & $outFile --version 2>&1
+ if (-not $?) { Err "Binary validation failed" }
+ } catch {
+ Err "Binary validation failed: $_"
+ }
+
+ New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null
+ Copy-Item $outFile "$InstallDir\oura.exe" -Force
+ Log "Installed: $InstallDir\oura.exe"
+
+ $userPath = [Environment]::GetEnvironmentVariable("PATH", "User")
+ if ($userPath -notlike "*$InstallDir*") {
+ [Environment]::SetEnvironmentVariable("PATH", "$InstallDir;$userPath", "User")
+ Log "Added to PATH (user scope)"
+ }
+
+ New-Item -ItemType Directory -Force -Path $ConfigDir | Out-Null
+ $configFile = "$ConfigDir\config.toml"
+ if (-not (Test-Path $configFile)) {
+ @"
+[loop_engine]
+max_iterations = 20
+convergence_threshold = 90.0
+feedback_sources = ["test", "lint"]
+
+[github]
+enabled = true
+default_owner = "MethodWhite"
+default_repo = "my-project"
+auto_commit = true
+auto_pr = true
+
+# [synapsis] # Optional: uncomment for Synapsis integration (separate project)
+# enabled = true
+# endpoint = "http://localhost:7438"
+"@ | Out-File -FilePath $configFile -Encoding utf8
+ Log "Config created: $configFile"
+ } else {
+ Warn "Config exists at $configFile — skipping"
+ }
+
+ ""
+ Log "Oura installed successfully!"
+ & "$InstallDir\oura.exe" version 2>$null
+ ""
+}
+
+Main
diff --git a/install.sh b/install.sh
new file mode 100755
index 0000000..a226b7d
--- /dev/null
+++ b/install.sh
@@ -0,0 +1,132 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+REPO="MethodWhite/oura"
+VERSION="${1:-latest}"
+INSTALL_DIR="${OURA_INSTALL_DIR:-/usr/local/bin}"
+CONFIG_DIR="${OURA_CONFIG_DIR:-$HOME/.config/oura}"
+
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+RED='\033[0;31m'
+CYAN='\033[0;36m'
+NC='\033[0m'
+
+log() { printf "${GREEN}[✓]${NC} %s\n" "$*"; }
+warn() { printf "${YELLOW}[!]${NC} %s\n" "$*"; }
+err() { printf "${RED}[✗]${NC} %s\n" "$*"; exit 1; }
+info() { printf "${CYAN}[i]${NC} %s\n" "$*"; }
+
+detect_arch() {
+ local arch
+ arch=$(uname -m)
+ case "$arch" in
+ x86_64|amd64) echo "x86_64" ;;
+ aarch64|arm64) echo "aarch64" ;;
+ *) err "Unsupported architecture: $arch" ;;
+ esac
+}
+
+detect_os() {
+ local os
+ os=$(uname -s)
+ case "$os" in
+ Linux) echo "unknown-linux-gnu" ;;
+ Darwin) echo "apple-darwin" ;;
+ *) err "Unsupported OS: $os" ;;
+ esac
+}
+
+get_release_url() {
+ local os_arch="$1"
+ if [ "$VERSION" = "latest" ]; then
+ echo "https://github.com/$REPO/releases/latest/download/oura-${os_arch}"
+ else
+ echo "https://github.com/$REPO/releases/download/$VERSION/oura-${os_arch}"
+ fi
+}
+
+main() {
+ echo ""
+ printf "${CYAN}╔══════════════════════════════════════╗${NC}\n"
+ printf "${CYAN}║ Oura Installer — 0XFFRice ║${NC}\n"
+ printf "${CYAN}╚══════════════════════════════════════╝${NC}\n"
+ echo ""
+
+ if [ "$(id -u)" -eq 0 ]; then
+ warn "Running as root — installing system-wide to $INSTALL_DIR"
+ else
+ info "Running as user — installing to $INSTALL_DIR"
+ if ! mkdir -p "$INSTALL_DIR" 2>/dev/null; then
+ info "Need sudo to write to $INSTALL_DIR"
+ exec sudo "$0" "$@"
+ fi
+ fi
+
+ local arch vendor os_arch
+ arch=$(detect_arch)
+ vendor=$(detect_os)
+ os_arch="${arch}-${vendor}"
+ local url
+ url=$(get_release_url "$os_arch")
+
+ info "Detected: $os_arch"
+ info "Download: $url"
+
+ local tmpdir
+ tmpdir=$(mktemp -d)
+ trap 'rm -rf "$tmpdir"' EXIT
+
+ if command -v curl &>/dev/null; then
+ curl -fsSL "$url" -o "$tmpdir/oura" || err "Download failed"
+ elif command -v wget &>/dev/null; then
+ wget -q "$url" -O "$tmpdir/oura" || err "Download failed"
+ else
+ err "Need curl or wget"
+ fi
+
+ chmod +x "$tmpdir/oura"
+ "$tmpdir/oura" --version &>/dev/null || err "Binary validation failed"
+
+ mkdir -p "$INSTALL_DIR"
+ cp "$tmpdir/oura" "$INSTALL_DIR/oura"
+ log "Installed: $INSTALL_DIR/oura"
+
+ mkdir -p "$CONFIG_DIR"
+ if [ ! -f "$CONFIG_DIR/config.toml" ]; then
+ cat > "$CONFIG_DIR/config.toml" << 'TOML'
+[loop_engine]
+max_iterations = 20
+convergence_threshold = 90.0
+feedback_sources = ["test", "lint"]
+
+[github]
+enabled = true
+default_owner = "MethodWhite"
+default_repo = "my-project"
+auto_commit = true
+auto_pr = true
+
+# [synapsis] # Optional: uncomment for Synapsis integration (separate project)
+# enabled = true
+# endpoint = "http://localhost:7438"
+TOML
+ log "Config created: $CONFIG_DIR/config.toml"
+ else
+ warn "Config exists at $CONFIG_DIR/config.toml — skipping"
+ fi
+
+ if echo "$PATH" | tr ':' '\n' | grep -qx "$INSTALL_DIR"; then
+ log "Ready to use. Run: oura --help"
+ else
+ warn "$INSTALL_DIR is not in PATH. Add it:"
+ warn " export PATH=\"\$PATH:$INSTALL_DIR\""
+ fi
+
+ echo ""
+ log "Oura installed successfully!"
+ "$INSTALL_DIR/oura" version 2>/dev/null || true
+ echo ""
+}
+
+main "$@"
diff --git a/mcp-config/cursor.json b/mcp-config/cursor.json
index 0d58f9b..0018fc0 100644
--- a/mcp-config/cursor.json
+++ b/mcp-config/cursor.json
@@ -1,6 +1,7 @@
{
- "mcp": {
+ "mcpServers": {
"oura": {
+ "type": "stdio",
"command": "/home/methodwhite/Proyectos/oura/target/release/oura"
}
}
diff --git a/mcp-config/opencode.json b/mcp-config/opencode.json
index 7c10f1d..0018fc0 100644
--- a/mcp-config/opencode.json
+++ b/mcp-config/opencode.json
@@ -2,10 +2,7 @@
"mcpServers": {
"oura": {
"type": "stdio",
- "command": "/home/methodwhite/Proyectos/oura/target/release/oura",
- "env": {
- "SYNAPSIS_ENDPOINT": "http://localhost:7438"
- }
+ "command": "/home/methodwhite/Proyectos/oura/target/release/oura"
}
}
}
diff --git a/src/agents.rs b/src/agents.rs
index 201381f..6b45e70 100644
--- a/src/agents.rs
+++ b/src/agents.rs
@@ -88,6 +88,45 @@ const DANGEROUS_PATTERNS: &[DangerousPattern] = &[
),
];
+struct CompiledPattern {
+ type_: &'static str,
+ severity: &'static str,
+ regex: regex::Regex,
+ description: &'static str,
+ recommendation: &'static str,
+ langs: &'static [&'static str],
+}
+
+fn compiled_patterns() -> &'static [CompiledPattern] {
+ static PATTERNS: std::sync::OnceLock> = std::sync::OnceLock::new();
+ PATTERNS.get_or_init(|| {
+ DANGEROUS_PATTERNS
+ .iter()
+ .filter_map(
+ |(type_, severity, pattern, description, recommendation, langs)| {
+ match regex::Regex::new(pattern) {
+ Ok(regex) => Some(CompiledPattern {
+ type_,
+ severity,
+ regex,
+ description,
+ recommendation,
+ langs,
+ }),
+ Err(e) => {
+ eprintln!(
+ "[Oura] Warning: failed to compile security pattern '{}': {}",
+ type_, e
+ );
+ None
+ }
+ }
+ },
+ )
+ .collect()
+ })
+}
+
impl SecurityAuditor {
pub fn new() -> Self {
Self
@@ -95,9 +134,17 @@ impl SecurityAuditor {
pub fn audit(&self, files: &[String]) -> Vec {
let mut entries = vec![];
+ let compiled = compiled_patterns();
for file in files {
- let ext = std::path::Path::new(file)
+ let path = std::path::Path::new(file);
+ if let Ok(meta) = path.metadata() {
+ if meta.len() > 10_000_000 {
+ continue;
+ }
+ }
+
+ let ext = path
.extension()
.and_then(|e| e.to_str())
.unwrap_or("")
@@ -105,12 +152,25 @@ impl SecurityAuditor {
let content = match fs::read_to_string(file) {
Ok(c) => c,
- Err(_) => continue,
+ Err(e) => {
+ eprintln!(
+ "[Oura] SecurityAuditor: skipping unreadable file {}: {}",
+ file, e
+ );
+ continue;
+ }
};
let lines: Vec<&str> = content.lines().collect();
- for (type_, severity, pattern, description, recommendation, langs) in DANGEROUS_PATTERNS
+ for CompiledPattern {
+ type_,
+ severity,
+ regex,
+ description,
+ recommendation,
+ langs,
+ } in compiled
{
// Skip pattern if its language list doesn't match the file extension
let ext_match = |ext: &str, langs: &[&str]| -> bool {
@@ -124,22 +184,17 @@ impl SecurityAuditor {
"php" => "php",
"sql" => "sql",
"html" | "htm" | "xhtml" => "html",
- _ => "",
+ _ => return false,
};
- mapped.is_empty() || langs.contains(&mapped)
+ langs.contains(&mapped)
};
if !ext_match(&ext, langs) {
continue;
}
- let re = match Regex::new(pattern) {
- Ok(r) => r,
- Err(_) => continue,
- };
-
for (i, line) in lines.iter().enumerate() {
- if re.is_match(line) {
+ if regex.is_match(line) {
entries.push(SecurityAuditEntry {
type_: type_.to_string(),
severity: severity.to_string(),
@@ -161,7 +216,7 @@ pub struct RefactorEngine;
const CLEAN_CODE_PATTERNS: &[(&str, &str, &str)] = &[
(
- "catch\\s*\\([^)]*\\)\\s*\\{[^}]*\\}",
+ "catch\\s*\\([^)]*\\)\\s*\\{",
"Generic catch clause",
"Type the error or add specific error handling",
),
diff --git a/src/config.rs b/src/config.rs
index bb43681..b45c9f8 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -10,17 +10,24 @@ pub struct Config {
#[serde(default)]
pub github: GitHubConfig,
#[serde(default)]
- pub synapsis: SynapsisConfig,
- #[serde(default)]
pub logging: LoggingConfig,
+ #[serde(default)]
+ pub connector: ConnectorConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(deny_unknown_fields)]
pub struct GeneralConfig {
+ #[serde(default)]
pub data_dir: Option,
+ #[serde(default = "default_profile")]
pub profile: String,
}
+fn default_profile() -> String {
+ "default".into()
+}
+
impl Default for GeneralConfig {
fn default() -> Self {
Self {
@@ -30,12 +37,44 @@ impl Default for GeneralConfig {
}
}
+fn default_threshold() -> f64 {
+ 90.0
+}
+fn default_feedback_sources() -> Vec {
+ vec!["test".into(), "lint".into()]
+}
+fn default_max_runtime() -> u64 {
+ 3600
+}
+fn default_max_iterations() -> u32 {
+ 20
+}
+fn default_pr_prefix() -> String {
+ "[Oura] ".into()
+}
+fn default_logging_level() -> String {
+ "info".into()
+}
+fn default_logging_format() -> String {
+ "text".into()
+}
+fn default_logging_output() -> String {
+ "stderr".into()
+}
+
#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(deny_unknown_fields)]
pub struct LoopEngineConfig {
+ #[serde(default = "default_max_iterations")]
pub max_iterations: u32,
+ #[serde(default = "default_threshold")]
pub convergence_threshold: f64,
+ #[serde(default = "default_feedback_sources")]
pub feedback_sources: Vec,
+ #[serde(default)]
pub working_directory: Option,
+ #[serde(default = "default_max_runtime")]
+ pub max_runtime_secs: u64,
}
impl Default for LoopEngineConfig {
@@ -45,21 +84,43 @@ impl Default for LoopEngineConfig {
convergence_threshold: 90.0,
feedback_sources: vec!["test".into(), "lint".into()],
working_directory: None,
+ max_runtime_secs: 3600,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(deny_unknown_fields)]
pub struct GitHubConfig {
+ #[serde(default)]
pub enabled: bool,
+ #[serde(default)]
pub default_owner: String,
+ #[serde(default)]
pub default_repo: String,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
pub token: Option,
+ #[serde(default)]
pub auto_commit: bool,
+ #[serde(default)]
pub auto_pr: bool,
+ #[serde(default = "default_pr_prefix")]
pub pr_title_prefix: String,
+ #[serde(default)]
pub workflows_enabled: bool,
- pub repos: Vec,
+}
+
+#[allow(dead_code)]
+impl GitHubConfig {
+ pub fn masked_token(&self) -> Option {
+ self.token.as_ref().map(|t| {
+ if t.len() > 8 {
+ format!("{}…{}", &t[..4], &t[t.len() - 4..])
+ } else {
+ "********".into()
+ }
+ })
+ }
}
impl Default for GitHubConfig {
@@ -73,55 +134,73 @@ impl Default for GitHubConfig {
auto_pr: false,
pr_title_prefix: "[Oura] ".into(),
workflows_enabled: true,
- repos: vec![],
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct RepoConfig {
- pub owner: String,
- pub repo: String,
- pub branch: String,
- pub base_branch: String,
- pub auto_sync: bool,
- pub workflows: Vec,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct SynapsisConfig {
- pub enabled: bool,
- pub endpoint: String,
- pub mcp_command: String,
+#[serde(deny_unknown_fields)]
+pub struct LoggingConfig {
+ #[serde(default = "default_logging_level")]
+ pub level: String,
+ #[serde(default = "default_logging_format")]
+ pub format: String,
+ #[serde(default = "default_logging_output")]
+ pub output: String,
}
-impl Default for SynapsisConfig {
+impl Default for LoggingConfig {
fn default() -> Self {
Self {
- enabled: true,
- endpoint: "http://localhost:7438".into(),
- mcp_command: "synapsis-mcp".into(),
+ level: "info".into(),
+ format: "text".into(),
+ output: "stderr".into(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct LoggingConfig {
- pub level: String,
- pub format: String,
- pub output: String,
+#[serde(deny_unknown_fields)]
+pub struct ConnectorConfig {
+ #[serde(default)]
+ pub enabled: bool,
+ #[serde(default)]
+ pub transport: String,
+ #[serde(default)]
+ pub server_url: String,
+ #[serde(default)]
+ pub host: String,
+ #[serde(default)]
+ pub port: u16,
+ #[serde(default)]
+ pub endpoint: String,
+ #[serde(default)]
+ pub tools: Vec,
+ #[serde(default)]
+ pub auto_call: bool,
}
-impl Default for LoggingConfig {
+impl Default for ConnectorConfig {
fn default() -> Self {
Self {
- level: "info".into(),
- format: "text".into(),
- output: "stderr".into(),
+ enabled: false,
+ transport: "http".into(),
+ server_url: "http://localhost:7438".into(),
+ host: "127.0.0.1".into(),
+ port: 7439,
+ endpoint: "/message".into(),
+ tools: vec![],
+ auto_call: false,
}
}
}
+fn quiet_eprint(msg: &str) {
+ if std::env::var("OURA_QUIET").is_err() {
+ eprintln!("{}", msg);
+ }
+}
+
impl Config {
pub fn load() -> Self {
let config_paths = vec![
@@ -132,26 +211,35 @@ impl Config {
for path in config_paths.into_iter().flatten() {
if path.exists() {
- let content = std::fs::read_to_string(&path).unwrap_or_default();
+ let content = match std::fs::read_to_string(&path) {
+ Ok(c) => c,
+ Err(e) => {
+ quiet_eprint(&format!(
+ "[Oura] Warning: couldn't read config at {}: {}",
+ path.display(),
+ e
+ ));
+ quiet_eprint("[Oura] Using default configuration");
+ return Self::apply_env_overrides(Config::default());
+ }
+ };
match toml::from_str(&content) {
Ok(config) => {
- if std::env::var("OURA_QUIET").is_err() && std::env::var("QUIET").is_err() {
- eprintln!("[Oura] Loaded config from: {}", path.display());
- }
+ quiet_eprint(&format!("[Oura] Loaded config from: {}", path.display()));
return Self::apply_env_overrides(config);
}
Err(e) => {
- eprintln!(
+ quiet_eprint(&format!(
"[Oura] Warning: failed to parse config at {}: {}",
path.display(),
e
- );
+ ));
}
}
}
}
- eprintln!("[Oura] Using default configuration");
+ quiet_eprint("[Oura] Using default configuration");
Self::apply_env_overrides(Config::default())
}
@@ -162,8 +250,10 @@ impl Config {
}
}
if let Ok(val) = std::env::var("OURA_CONVERGENCE_THRESHOLD") {
- if let Ok(n) = val.parse() {
- config.loop_engine.convergence_threshold = n;
+ if let Ok(n) = val.parse::() {
+ if n.is_finite() && (0.0..=100.0).contains(&n) {
+ config.loop_engine.convergence_threshold = n;
+ }
}
}
if let Ok(val) = std::env::var("OURA_GITHUB_TOKEN") {
@@ -178,9 +268,6 @@ impl Config {
if let Ok(val) = std::env::var("OURA_GITHUB_ENABLED") {
config.github.enabled = val == "true" || val == "1";
}
- if let Ok(val) = std::env::var("OURA_SYNAPSIS_ENDPOINT") {
- config.synapsis.endpoint = val;
- }
if let Ok(val) = std::env::var("OURA_WORKING_DIR") {
config.loop_engine.working_directory = Some(val);
}
@@ -194,7 +281,10 @@ impl Config {
let config = Config::default();
let toml_str = toml::to_string_pretty(&config)?;
std::fs::write(path, toml_str)?;
- eprintln!("[Oura] Default config written to: {}", path.display());
+ quiet_eprint(&format!(
+ "[Oura] Default config written to: {}",
+ path.display()
+ ));
Ok(())
}
@@ -222,7 +312,6 @@ mod tests {
assert_eq!(config.loop_engine.max_iterations, 20);
assert_eq!(config.loop_engine.convergence_threshold, 90.0);
assert!(!config.github.enabled);
- assert!(config.synapsis.enabled);
}
#[test]
@@ -238,6 +327,7 @@ mod tests {
assert_eq!(config.max_iterations, 20);
assert_eq!(config.convergence_threshold, 90.0);
assert_eq!(config.feedback_sources, vec!["test", "lint"]);
+ assert_eq!(config.max_runtime_secs, 3600);
}
#[test]
@@ -249,13 +339,6 @@ mod tests {
assert_eq!(config.pr_title_prefix, "[Oura] ");
}
- #[test]
- fn test_synapsis_config_default() {
- let config = SynapsisConfig::default();
- assert!(config.enabled);
- assert_eq!(config.endpoint, "http://localhost:7438");
- }
-
#[test]
fn test_logging_config_default() {
let config = LoggingConfig::default();
@@ -277,6 +360,47 @@ mod tests {
);
}
+ #[test]
+ fn test_config_partial_section() {
+ let toml_str = "[loop_engine]\nmax_iterations = 5\n".to_string();
+ let config: Config = toml::from_str(&toml_str).unwrap();
+ assert_eq!(config.loop_engine.max_iterations, 5);
+ assert_eq!(config.loop_engine.convergence_threshold, 90.0);
+ assert_eq!(config.loop_engine.max_runtime_secs, 3600);
+ }
+
+ #[test]
+ fn test_config_unknown_key_rejected() {
+ let toml_str = "[loop_engine]\nunknown_key = true\nmax_iterations = 5\n".to_string();
+ let result: Result = toml::from_str(&toml_str);
+ assert!(result.is_err());
+ }
+
+ #[test]
+ fn test_convergence_threshold_env_nan_rejected() {
+ std::env::set_var("OURA_CONVERGENCE_THRESHOLD", "nan");
+ let config = Config::apply_env_overrides(Config::default());
+ // nan should be rejected, default remains
+ assert_eq!(config.loop_engine.convergence_threshold, 90.0);
+ std::env::remove_var("OURA_CONVERGENCE_THRESHOLD");
+ }
+
+ #[test]
+ fn test_convergence_threshold_env_out_of_range_rejected() {
+ std::env::set_var("OURA_CONVERGENCE_THRESHOLD", "200");
+ let config = Config::apply_env_overrides(Config::default());
+ assert_eq!(config.loop_engine.convergence_threshold, 90.0);
+ std::env::remove_var("OURA_CONVERGENCE_THRESHOLD");
+ }
+
+ #[test]
+ fn test_convergence_threshold_env_valid() {
+ std::env::set_var("OURA_CONVERGENCE_THRESHOLD", "85.5");
+ let config = Config::apply_env_overrides(Config::default());
+ assert_eq!(config.loop_engine.convergence_threshold, 85.5);
+ std::env::remove_var("OURA_CONVERGENCE_THRESHOLD");
+ }
+
#[test]
fn test_apply_env_overrides() {
std::env::set_var("OURA_MAX_ITERATIONS", "50");
diff --git a/src/connector.rs b/src/connector.rs
new file mode 100644
index 0000000..9910ac2
--- /dev/null
+++ b/src/connector.rs
@@ -0,0 +1,126 @@
+use anyhow::{Context, Result};
+use quinn::{ClientConfig, Endpoint, EndpointConfig, TransportConfig};
+use std::sync::Arc;
+use std::time::Duration;
+
+pub struct QuicConnector {
+ endpoint: Endpoint,
+}
+
+impl QuicConnector {
+ pub fn new() -> Result {
+ let mut transport = TransportConfig::default();
+ transport.max_idle_timeout(Some(Duration::from_secs(30).try_into().unwrap()));
+ transport.keep_alive_interval(Some(Duration::from_secs(5)));
+
+ let crypto = rustls::ClientConfig::builder()
+ .dangerous()
+ .with_custom_certificate_verifier(Arc::new(SkipVerification))
+ .with_no_client_auth();
+
+ let quic_config = quinn::crypto::rustls::QuicClientConfig::try_from(crypto)?;
+ let mut client_config = ClientConfig::new(Arc::new(quic_config));
+ client_config.transport_config(Arc::new(transport));
+
+ let socket = std::net::UdpSocket::bind("0.0.0.0:0")?;
+ let mut endpoint = Endpoint::new(
+ EndpointConfig::default(),
+ None::,
+ socket,
+ Arc::new(quinn::TokioRuntime),
+ )?;
+ endpoint.set_default_client_config(client_config);
+
+ Ok(Self { endpoint })
+ }
+
+ pub async fn connect(&self, host: &str, port: u16) -> Result {
+ let addr: std::net::SocketAddr = format!("{}:{}", host, port)
+ .parse()
+ .context("Invalid host:port")?;
+ Ok(self.endpoint.connect(addr, host)?.await?)
+ }
+
+ pub async fn call_tool(
+ &self,
+ host: &str,
+ port: u16,
+ tool_name: &str,
+ arguments: &serde_json::Value,
+ ) -> Result {
+ let conn = self
+ .connect(host, port)
+ .await
+ .context("QUIC connection failed")?;
+ let (mut send, mut recv) = conn.open_bi().await.context("Failed to open QUIC stream")?;
+
+ let request = serde_json::json!({
+ "jsonrpc": "2.0", "id": "1", "method": "tools/call",
+ "params": { "name": tool_name, "arguments": arguments }
+ });
+ let body = serde_json::to_vec(&request)?;
+
+ let len = body.len() as u32;
+ send.write_all(&len.to_be_bytes()).await?;
+ send.write_all(&body).await?;
+ let _ = send.finish();
+
+ let mut len_buf = [0u8; 4];
+ recv.read_exact(&mut len_buf).await?;
+ let resp_len = u32::from_be_bytes(len_buf) as usize;
+
+ let mut resp_buf = vec![0u8; resp_len];
+ recv.read_exact(&mut resp_buf).await?;
+
+ Ok(String::from_utf8(resp_buf)?)
+ }
+}
+
+#[derive(Debug)]
+struct SkipVerification;
+
+impl rustls::client::danger::ServerCertVerifier for SkipVerification {
+ fn verify_server_cert(
+ &self,
+ _end_entity: &rustls::pki_types::CertificateDer<'_>,
+ _intermediates: &[rustls::pki_types::CertificateDer<'_>],
+ _server_name: &rustls::pki_types::ServerName<'_>,
+ _ocsp_response: &[u8],
+ _now: rustls::pki_types::UnixTime,
+ ) -> Result {
+ Ok(rustls::client::danger::ServerCertVerified::assertion())
+ }
+
+ fn verify_tls12_signature(
+ &self,
+ _message: &[u8],
+ _cert: &rustls::pki_types::CertificateDer<'_>,
+ _dss: &rustls::DigitallySignedStruct,
+ ) -> Result {
+ Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
+ }
+
+ fn verify_tls13_signature(
+ &self,
+ _message: &[u8],
+ _cert: &rustls::pki_types::CertificateDer<'_>,
+ _dss: &rustls::DigitallySignedStruct,
+ ) -> Result {
+ Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
+ }
+
+ fn supported_verify_schemes(&self) -> Vec {
+ vec![
+ rustls::SignatureScheme::RSA_PKCS1_SHA256,
+ rustls::SignatureScheme::ECDSA_NISTP256_SHA256,
+ rustls::SignatureScheme::RSA_PSS_SHA256,
+ rustls::SignatureScheme::RSA_PKCS1_SHA384,
+ rustls::SignatureScheme::ECDSA_NISTP384_SHA384,
+ rustls::SignatureScheme::RSA_PSS_SHA384,
+ rustls::SignatureScheme::RSA_PKCS1_SHA512,
+ rustls::SignatureScheme::ECDSA_NISTP521_SHA512,
+ rustls::SignatureScheme::RSA_PSS_SHA512,
+ rustls::SignatureScheme::ED25519,
+ ]
+ }
+}
diff --git a/src/engine.rs b/src/engine.rs
index 174f5d4..90613dc 100644
--- a/src/engine.rs
+++ b/src/engine.rs
@@ -1,7 +1,13 @@
+use crate::config::{ConnectorConfig, LoopEngineConfig};
use crate::error::{OuraError, Result};
use crate::events::{EventBus, OuraEvent};
-use crate::feedback::{ClippyFeedbackCollector, ProfileFeedbackCollector, TestFeedbackCollector};
-use crate::traits::{CompositeFeedbackCollector, DefaultCommandRunner, FeedbackCollector};
+use crate::feedback::{
+ ClippyFeedbackCollector, ConnectorFeedbackCollector, ProfileFeedbackCollector,
+ TestFeedbackCollector,
+};
+use crate::traits::{
+ CommandRunner, CompositeFeedbackCollector, DefaultCommandRunner, FeedbackCollector,
+};
use crate::types::*;
use chrono::Utc;
use std::sync::{
@@ -11,6 +17,20 @@ use std::sync::{
use tokio::sync::Notify;
use uuid::Uuid;
+fn lock_state(
+ state: &Arc>>,
+) -> Result>> {
+ state
+ .lock()
+ .map_err(|e| OuraError::Internal(format!("Lock poisoned: {}", e)))
+}
+
+fn lock_value(mutex: &Arc>) -> Result> {
+ mutex
+ .lock()
+ .map_err(|e| OuraError::Internal(format!("Lock poisoned: {}", e)))
+}
+
pub struct LoopEngine {
state: Arc>>,
max_iterations: Arc>,
@@ -27,31 +47,63 @@ impl Drop for LoopEngine {
fn drop(&mut self) {
self.stop_flag.store(true, Ordering::SeqCst);
self.stop_notify.notify_waiters();
+ }
+}
+
+impl LoopEngine {
+ #[allow(dead_code)]
+ pub async fn shutdown(&mut self) {
+ self.stop_flag.store(true, Ordering::SeqCst);
+ self.stop_notify.notify_waiters();
if let Some(handle) = self.loop_handle.take() {
- handle.abort();
+ tokio::select! {
+ _ = handle => {},
+ _ = tokio::time::sleep(std::time::Duration::from_secs(3)) => {},
+ }
}
}
}
impl LoopEngine {
- pub fn new(max_iterations: u32, convergence_threshold: f64) -> Self {
- let command_runner = Box::new(DefaultCommandRunner);
+ pub fn new(config: &LoopEngineConfig, connector_config: &ConnectorConfig) -> Self {
let mut composite = CompositeFeedbackCollector::new();
- composite.add(Box::new(TestFeedbackCollector::new(
- Box::new(DefaultCommandRunner),
- "cargo test 2>&1".to_string(),
- )));
- composite.add(Box::new(ClippyFeedbackCollector::new(
- Box::new(DefaultCommandRunner),
- "cargo clippy 2>&1".to_string(),
- )));
- composite.add(Box::new(ProfileFeedbackCollector));
+ let make_runner = |dir: &Option| -> Box {
+ match dir {
+ Some(d) => Box::new(DefaultCommandRunner::new_with_dir(
+ std::path::PathBuf::from(d),
+ )),
+ None => Box::new(DefaultCommandRunner::new()),
+ }
+ };
+ let runner = make_runner(&config.working_directory);
+ if config.feedback_sources.contains(&"test".to_string()) {
+ composite.add(Box::new(TestFeedbackCollector::new(runner)));
+ }
+ let runner = make_runner(&config.working_directory);
+ if config.feedback_sources.contains(&"lint".to_string()) {
+ composite.add(Box::new(ClippyFeedbackCollector::new(runner)));
+ }
+ if config.feedback_sources.contains(&"profile".to_string()) {
+ let collector = match &config.working_directory {
+ Some(d) => ProfileFeedbackCollector::new_with_dir(std::path::PathBuf::from(d)),
+ None => ProfileFeedbackCollector::new(),
+ };
+ composite.add(Box::new(collector));
+ }
+ if connector_config.enabled
+ && !connector_config.server_url.is_empty()
+ && !connector_config.tools.is_empty()
+ {
+ composite.add(Box::new(ConnectorFeedbackCollector::new(
+ connector_config.clone(),
+ )));
+ }
Self {
state: Arc::new(Mutex::new(None)),
- max_iterations: Arc::new(Mutex::new(max_iterations)),
- convergence_threshold: Arc::new(Mutex::new(convergence_threshold)),
- max_runtime_secs: Arc::new(Mutex::new(3600)),
+ max_iterations: Arc::new(Mutex::new(config.max_iterations)),
+ convergence_threshold: Arc::new(Mutex::new(config.convergence_threshold)),
+ max_runtime_secs: Arc::new(Mutex::new(config.max_runtime_secs)),
stop_flag: Arc::new(AtomicBool::new(false)),
stop_notify: Arc::new(Notify::new()),
feedback_collector: Arc::new(composite),
@@ -60,6 +112,7 @@ impl LoopEngine {
}
}
+ #[allow(dead_code)]
pub fn with_feedback_collector(
max_iterations: u32,
convergence_threshold: f64,
@@ -82,7 +135,12 @@ impl LoopEngine {
&self.event_bus
}
- pub async fn start(&mut self, goal: &str) -> Result {
+ pub async fn start(
+ &mut self,
+ goal: &str,
+ manual: bool,
+ max_iterations: Option,
+ ) -> Result {
if let Some(ref handle) = self.loop_handle {
if !handle.is_finished() {
return Err(OuraError::LoopAlreadyRunning);
@@ -92,7 +150,7 @@ impl LoopEngine {
self.stop_flag.store(false, Ordering::SeqCst);
{
- let state_guard = self.state.lock().unwrap();
+ let state_guard = lock_state(&self.state)?;
if let Some(ref state) = *state_guard {
if state.status == "running" {
return Err(OuraError::LoopAlreadyRunning);
@@ -100,10 +158,18 @@ impl LoopEngine {
}
}
+ let max_iter = max_iterations.unwrap_or(*lock_value(&self.max_iterations)?);
+ let threshold = *lock_value(&self.convergence_threshold)?;
+ let runtime = *lock_value(&self.max_runtime_secs)?;
let initial_state = LoopState {
id: Uuid::new_v4().to_string(),
goal: goal.into(),
- config: OuraConfig::default(),
+ config: OuraConfig {
+ max_iterations: max_iter,
+ convergence_threshold: threshold,
+ max_runtime_secs: runtime,
+ ..OuraConfig::default()
+ },
current_iteration: 0,
history: vec![],
status: "running".into(),
@@ -111,7 +177,7 @@ impl LoopEngine {
};
{
- let mut state_guard = self.state.lock().unwrap();
+ let mut state_guard = lock_state(&self.state)?;
*state_guard = Some(initial_state.clone());
}
@@ -121,8 +187,12 @@ impl LoopEngine {
timestamp: Utc::now().to_rfc3339(),
});
+ if manual {
+ return Ok(initial_state);
+ }
+
let state_clone = self.state.clone();
- let max_iter_clone = self.max_iterations.clone();
+ let max_iter_limit = max_iter;
let threshold_clone = self.convergence_threshold.clone();
let runtime_clone = self.max_runtime_secs.clone();
let stop_flag_clone = self.stop_flag.clone();
@@ -133,11 +203,23 @@ impl LoopEngine {
let handle = tokio::spawn(async move {
let loop_start = std::time::Instant::now();
- let max_runtime = *runtime_clone.lock().unwrap();
+ let max_runtime = match runtime_clone.lock() {
+ Ok(guard) => *guard,
+ Err(_) => {
+ tracing::error!("Lock poisoned in runtime");
+ return;
+ }
+ };
loop {
if stop_flag_clone.load(Ordering::SeqCst) {
- let mut state_guard = state_clone.lock().unwrap();
+ let mut state_guard = match state_clone.lock() {
+ Ok(g) => g,
+ Err(_) => {
+ tracing::error!("Lock poisoned");
+ return;
+ }
+ };
if let Some(ref mut st) = *state_guard {
st.status = "stopped".into();
}
@@ -153,7 +235,13 @@ impl LoopEngine {
}
if max_runtime > 0 && loop_start.elapsed().as_secs() > max_runtime {
- let mut state_guard = state_clone.lock().unwrap();
+ let mut state_guard = match state_clone.lock() {
+ Ok(g) => g,
+ Err(_) => {
+ tracing::error!("Lock poisoned");
+ return;
+ }
+ };
if let Some(ref mut st) = *state_guard {
st.status = "failed".into();
}
@@ -166,7 +254,13 @@ impl LoopEngine {
}
let iteration_num = {
- let mut state_guard = state_clone.lock().unwrap();
+ let mut state_guard = match state_clone.lock() {
+ Ok(g) => g,
+ Err(_) => {
+ tracing::error!("Lock poisoned");
+ return;
+ }
+ };
let st = match state_guard.as_mut() {
Some(s) => s,
None => break,
@@ -174,7 +268,7 @@ impl LoopEngine {
if st.status != "running" {
break;
}
- st.current_iteration += 1;
+ st.current_iteration = st.current_iteration.saturating_add(1);
st.current_iteration
};
@@ -184,10 +278,45 @@ impl LoopEngine {
timestamp: Utc::now().to_rfc3339(),
});
- let max_iter = *max_iter_clone.lock().unwrap();
- let threshold = *threshold_clone.lock().unwrap();
+ let threshold = match threshold_clone.lock() {
+ Ok(g) => *g,
+ Err(_) => {
+ tracing::error!("Lock poisoned in threshold");
+ return;
+ }
+ };
- let feedback_entries = feedback_collector.collect().await;
+ let feedback_entries = {
+ let collect = feedback_collector.collect();
+ tokio::select! {
+ biased;
+ _ = stop_notify_clone.notified() => {
+ let mut state_guard = match state_clone.lock() {
+ Ok(g) => g,
+ Err(_) => { tracing::error!("Lock poisoned"); return; }
+ };
+ if let Some(ref mut st) = *state_guard {
+ st.status = "stopped".into();
+ }
+ event_bus.publish(OuraEvent::LoopStopped {
+ loop_id: loop_id.clone(),
+ iterations: iteration_num,
+ timestamp: Utc::now().to_rfc3339(),
+ });
+ break;
+ }
+ _ = tokio::time::sleep(std::time::Duration::from_secs(600)) => {
+ tracing::error!("Feedback collection timed out after 600s");
+ event_bus.publish(OuraEvent::Error {
+ loop_id: Some(loop_id.clone()),
+ message: "Feedback collection timed out".to_string(),
+ timestamp: Utc::now().to_rfc3339(),
+ });
+ Vec::new()
+ }
+ result = collect => result,
+ }
+ };
event_bus.publish(OuraEvent::FeedbackCollected {
loop_id: loop_id.clone(),
@@ -208,13 +337,11 @@ impl LoopEngine {
let mut score = 100.0 - (error_count * 15.0) - (warning_count * 5.0);
score = score.clamp(0.0, 100.0);
- let has_feedback = !feedback_entries.is_empty();
- let converged = has_feedback
- && (score >= threshold || (error_count == 0.0 && warning_count == 0.0));
+ let converged = score >= threshold;
let status = if converged {
- "converged"
- } else if iteration_num >= max_iter {
+ "completed"
+ } else if iteration_num >= max_iter_limit {
"failed"
} else {
"running"
@@ -239,7 +366,13 @@ impl LoopEngine {
});
{
- let mut state_guard = state_clone.lock().unwrap();
+ let mut state_guard = match state_clone.lock() {
+ Ok(g) => g,
+ Err(_) => {
+ tracing::error!("Lock poisoned in state update");
+ return;
+ }
+ };
let st = match state_guard.as_mut() {
Some(s) => s,
None => break,
@@ -252,7 +385,7 @@ impl LoopEngine {
final_score: score,
timestamp: Utc::now().to_rfc3339(),
});
- } else if iteration_num >= max_iter {
+ } else if iteration_num >= max_iter_limit {
st.status = "failed".into();
}
st.history.push(result);
@@ -265,7 +398,10 @@ impl LoopEngine {
tokio::select! {
_ = tokio::time::sleep(std::time::Duration::from_millis(100)) => {}
_ = stop_notify_clone.notified() => {
- let mut state_guard = state_clone.lock().unwrap();
+ let mut state_guard = match state_clone.lock() {
+ Ok(g) => g,
+ Err(_) => { tracing::error!("Lock poisoned in stop notify"); return; }
+ };
if let Some(ref mut st) = *state_guard {
st.status = "stopped".into();
}
@@ -291,20 +427,30 @@ impl LoopEngine {
}
}
- let mut state_guard = self
- .state
- .lock()
- .map_err(|_| OuraError::Internal("Lock poisoned".to_string()))?;
- let state = state_guard.as_mut().ok_or(OuraError::NoActiveLoop)?;
+ let iteration_num = {
+ let mut state_guard = self
+ .state
+ .lock()
+ .map_err(|_| OuraError::Internal("Lock poisoned".to_string()))?;
+ let state = state_guard.as_mut().ok_or(OuraError::NoActiveLoop)?;
- if state.status != "running" {
- return Err(OuraError::LoopNotRunning(state.status.clone()));
- }
+ if state.status != "running" {
+ return Err(OuraError::LoopNotRunning(state.status.clone()));
+ }
- state.current_iteration += 1;
- let iteration_num = state.current_iteration;
+ state.current_iteration = state.current_iteration.saturating_add(1);
+ state.current_iteration
+ };
- let feedback_entries = self.feedback_collector.collect().await;
+ let feedback_entries = tokio::time::timeout(
+ std::time::Duration::from_secs(600),
+ self.feedback_collector.collect(),
+ )
+ .await
+ .unwrap_or_else(|_| {
+ tracing::error!("Feedback collection timed out during manual iteration");
+ vec![]
+ });
let error_count = feedback_entries
.iter()
@@ -318,15 +464,13 @@ impl LoopEngine {
let mut score = 100.0 - (error_count * 15.0) - (warning_count * 5.0);
score = score.clamp(0.0, 100.0);
- let has_feedback = !feedback_entries.is_empty();
- let converged = has_feedback
- && (score >= *self.convergence_threshold.lock().unwrap()
- || (error_count == 0.0 && warning_count == 0.0));
+ let threshold = *lock_value(&self.convergence_threshold)?;
+ let converged = score >= threshold;
- let max_iter = *self.max_iterations.lock().unwrap();
+ let max_iter = *lock_value(&self.max_iterations)?;
let status = if converged {
- "converged"
+ "completed"
} else if iteration_num >= max_iter {
"failed"
} else {
@@ -343,6 +487,12 @@ impl LoopEngine {
score,
};
+ let mut state_guard = self
+ .state
+ .lock()
+ .map_err(|_| OuraError::Internal("Lock poisoned".to_string()))?;
+ let state = state_guard.as_mut().ok_or(OuraError::NoActiveLoop)?;
+
if converged {
state.status = "completed".into();
} else if iteration_num >= max_iter {
@@ -353,85 +503,102 @@ impl LoopEngine {
Ok(result)
}
- pub fn stop(&mut self) -> u32 {
+ pub fn stop(&mut self) -> Result {
self.stop_flag.store(true, Ordering::SeqCst);
self.stop_notify.notify_waiters();
- let state_guard = self.state.lock().unwrap();
- state_guard
- .as_ref()
- .map(|s| s.current_iteration)
- .unwrap_or(0)
+ let mut guard = self
+ .state
+ .lock()
+ .map_err(|e| OuraError::Internal(format!("Lock poisoned: {}", e)))?;
+ if let Some(ref mut st) = *guard {
+ st.status = "stopped".into();
+ Ok(st.current_iteration)
+ } else {
+ Ok(0)
+ }
}
- pub fn get_state(&self) -> Option {
- self.state.lock().unwrap().clone()
+ pub fn get_state(&self) -> Result