diff --git a/.gitignore b/.gitignore index ad67955..0728338 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,8 @@ target # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ + + +# Added by cargo + +/target diff --git a/.skry.toml b/.skry.toml new file mode 100644 index 0000000..eb6ef33 --- /dev/null +++ b/.skry.toml @@ -0,0 +1,40 @@ +# Example skry configuration file +# Place this file in your project root as .skry.toml + +[general] +# Maximum number of tokens to include in the context +token_budget = 4096 + +# Pruning strategy for dependencies +# Options: aggressive, bodies, full +pruning_strategy = "aggressive" + +[lsp] +# Map file extensions to LSP server commands +# These are the default values; customize as needed + +# Rust +rs = "rust-analyzer" + +# C++ +cpp = "clangd" +cc = "clangd" +cxx = "clangd" +h = "clangd" +hpp = "clangd" + +# TypeScript/JavaScript +ts = "typescript-language-server --stdio" +tsx = "typescript-language-server --stdio" +js = "typescript-language-server --stdio" +jsx = "typescript-language-server --stdio" + +# Python (requires pyright or pylsp) +# py = "pyright-langserver --stdio" +# py = "pylsp" + +# Go (requires gopls) +# go = "gopls" + +# Java (requires jdtls) +# java = "jdtls" diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..e819d2c --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,805 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "clap" +version = "4.5.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[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 = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.179" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f" + +[[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.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lsp-server" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d6ada348dbc2703cbe7637b2dda05cff84d3da2819c24abcb305dd613e0ba2e" +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 = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[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 = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "proc-macro2" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +dependencies = [ + "serde_core", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "skry" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "lsp-server", + "lsp-types", + "serde", + "serde_json", + "thiserror", + "tokio", + "toml", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tokio" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "toml" +version = "0.9.10+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[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", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[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.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[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.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[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.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[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.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" + +[[package]] +name = "zmij" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..53569e6 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "skry" +version = "0.1.0" +edition = "2021" +authors = ["David Young"] +description = "A deterministic, LSP-powered context assembler for LLMs" +license = "MIT" +repository = "https://github.com/davidbits/skry" +readme = "README.md" + +[dependencies] +# CLI argument parsing +clap = { version = "4.5", features = ["derive"] } + +# Configuration parsing (TOML) +toml = "0.9.10" +serde = { version = "1.0", features = ["derive"] } + +# LSP and JSON-RPC +lsp-types = "0.97.0" +lsp-server = "0.7" +serde_json = "1.0" + +# Async runtime for managing LSP subprocesses +tokio = { version = "1.35", features = ["full"] } + +# Error handling +anyhow = "1.0" +thiserror = "2.0.17" + +# Logging +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } + +[lib] +name = "skry" +path = "src/lib.rs" + +[[bin]] +name = "skry" +path = "src/main.rs" diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 0000000..d0d691a --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,182 @@ +# Development Guide + +This document provides information about the project structure and how to work with the codebase. + +## Project Structure + +``` +skry/ +├── src/ +│ ├── lib.rs # Library entry point, exports public modules +│ ├── main.rs # CLI entry point with argument parsing +│ ├── config.rs # Configuration parsing (.skry.toml) +│ ├── lsp.rs # LSP client implementation +│ ├── pipeline.rs # Intent parsing and dependency traversal +│ ├── assembly.rs # Context assembly and token budgeting +│ └── standby.rs # Daemon mode implementation +├── .skry.toml # Example configuration file +├── Cargo.toml # Rust dependencies and project metadata +└── README.md # User-facing documentation +``` + +## Module Overview + +### `config` Module +Handles loading and parsing `.skry.toml` configuration files. Supports: +- Token budget settings +- Pruning strategies (aggressive, bodies, full) +- LSP server mappings for different file extensions + +### `lsp` Module +LSP client implementation that: +- Manages language server subprocesses +- Communicates via JSON-RPC +- Supports lifecycle management (start, stop) +- Placeholder for textDocument/definition and textDocument/references + +### `pipeline` Module +Core processing pipeline that: +- Parses user intent (symbol resolution or search) +- Resolves entry points for LSP queries +- Traverses dependency graphs +- Currently contains placeholder implementations + +### `assembly` Module +Context assembly engine that: +- Stitches code snippets into prompts +- Applies pruning strategies +- Enforces token budget limits +- Estimates token usage (rough approximation: 1 token ≈ 4 characters) + +### `standby` Module +Daemon mode implementation for: +- Keeping LSP servers running between queries +- Reducing startup latency for large projects +- Handling background query processing +- Currently contains placeholder implementations + +## Building and Testing + +### Build the Project +```bash +# Debug build +cargo build + +# Release build +cargo build --release +``` + +### Run Tests +```bash +# Run all tests +cargo test + +# Run with output +cargo test -- --nocapture + +# Run specific test +cargo test test_name +``` + +### Generate Documentation +```bash +# Generate and open documentation +cargo doc --open --no-deps +``` + +### Run the CLI +```bash +# Show help +cargo run -- --help + +# Generate context (one-shot mode) +cargo run -- generate --search "query" --depth 2 + +# Start daemon +cargo run -- standby + +# Query daemon (in another terminal) +cargo run -- query --symbol "function_name" +``` + +## Development Status + +This is the **initial scaffolding** for the project. The following areas need further implementation: + +### High Priority +1. **LSP Communication**: Complete JSON-RPC implementation in `lsp.rs` + - Initialize request/response + - textDocument/definition + - textDocument/references + - Proper message framing and parsing + +2. **Entry Point Resolution**: Implement actual file parsing and symbol search in `pipeline.rs` + - Use tree-sitter or regex for symbol finding + - Implement fuzzy search for natural language queries + +3. **Graph Traversal**: Implement recursive dependency walking in `pipeline.rs` + - Breadth-first or depth-first traversal + - Cycle detection + - Depth limiting + +4. **Daemon IPC**: Implement inter-process communication for standby mode + - Unix socket or named pipe + - Message protocol + - State persistence + +### Medium Priority +5. **Smart Pruning**: Improve content pruning strategies in `assembly.rs` + - Better AST-aware pruning + - Signature extraction + - Comment handling + +6. **Token Estimation**: More accurate token counting + - Integration with tiktoken or similar + - Model-specific tokenization + +### Nice to Have +7. **Language Support**: Add support for more languages + - Python (pyright/pylsp) + - Go (gopls) + - Java (jdtls) + +8. **Configuration**: Enhanced configuration options + - Per-language settings + - Custom pruning rules + - Output formatting + +## Code Style + +- Follow Rust standard naming conventions +- Add documentation comments (`///`) for public items +- Write unit tests for new functionality +- Use `tracing` for logging, not `println!` +- Handle errors with `Result` and `anyhow`/`thiserror` + +## Dependencies + +Key dependencies used: +- **clap**: CLI argument parsing with derive macros +- **tokio**: Async runtime for subprocess management +- **lsp-types**: LSP protocol types +- **serde**: Serialization/deserialization +- **toml**: Configuration file parsing +- **anyhow/thiserror**: Error handling +- **tracing**: Structured logging + +## Testing Philosophy + +- Unit tests for individual functions and modules +- Integration tests for end-to-end workflows (to be added) +- Test both success and error cases +- Mock external dependencies (LSP servers) + +## Contributing + +When adding new features: +1. Update relevant module documentation +2. Add unit tests +3. Update this development guide if architecture changes +4. Ensure `cargo test` passes +5. Run `cargo clippy` for linting suggestions +6. Format code with `cargo fmt` diff --git a/src/assembly.rs b/src/assembly.rs new file mode 100644 index 0000000..f18bd27 --- /dev/null +++ b/src/assembly.rs @@ -0,0 +1,152 @@ +//! Context assembly and token budgeting +//! +//! Stitches code snippets into final prompts while respecting token limits. + +use crate::config::PruningStrategy; +use crate::pipeline::DependencyGraph; +use anyhow::Result; +use tracing::debug; + +/// Context assembler that builds prompts from dependency graphs +pub struct Assembler { + token_budget: usize, + pruning_strategy: PruningStrategy, +} + +impl Assembler { + /// Create a new assembler with a token budget and pruning strategy + pub fn new(token_budget: usize, pruning_strategy: PruningStrategy) -> Self { + Self { + token_budget, + pruning_strategy, + } + } + + /// Assemble a context prompt from a dependency graph + pub fn assemble(&self, graph: &DependencyGraph, intent: &str) -> Result { + debug!( + "Assembling context (budget: {} tokens, strategy: {:?})", + self.token_budget, self.pruning_strategy + ); + + let mut output = String::new(); + + // Add the user's intent/query + output.push_str("# User Intent\n\n"); + output.push_str(intent); + output.push_str("\n\n"); + + // Add context information + output.push_str("# Context\n\n"); + output.push_str( + "The following code snippets were retrieved by traversing the dependency graph.\n", + ); + output.push_str(&format!("Graph depth: {}\n", graph.depth)); + output.push_str(&format!("Total nodes: {}\n\n", graph.nodes.len())); + + // Add code snippets + output.push_str("# Code Snippets\n\n"); + + for (i, node) in graph.nodes.iter().enumerate() { + output.push_str(&format!("## {} - {}\n\n", i + 1, node.symbol)); + output.push_str(&format!("Location: {}\n\n", node.location.as_str())); + output.push_str("```\n"); + + // Apply pruning strategy + let content = self.prune_content(&node.content); + output.push_str(&content); + + output.push_str("\n```\n\n"); + + // Check if we're approaching the token budget + if self.estimate_tokens(&output) > self.token_budget { + debug!("Token budget exceeded, truncating output"); + output.push_str("... [output truncated due to token budget] ...\n"); + break; + } + } + + Ok(output) + } + + /// Prune content according to the configured strategy + fn prune_content(&self, content: &str) -> String { + match self.pruning_strategy { + PruningStrategy::Aggressive => { + // TODO: Implement proper language-specific pruning logic + // Requires dynamic logic detection based on file extension/language + // Should use AST parsing or language-specific patterns for each supported language + // For now, returns full content as placeholder + content.to_string() + } + PruningStrategy::Bodies => { + // TODO: Implement proper function body removal + // Should parse the AST and extract only signatures + // For now, returns full content as placeholder + content.to_string() + } + PruningStrategy::Full => { + // Keep everything + content.to_string() + } + } + } + + /// Estimate the number of tokens in the text + /// Uses a rough approximation: 1 token ≈ 4 characters + fn estimate_tokens(&self, text: &str) -> usize { + text.len() / 4 + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::pipeline::{DependencyGraph, DependencyNode}; + use lsp_types::Uri; + use std::str::FromStr; + + #[test] + fn test_assembler_creation() { + let assembler = Assembler::new(4096, PruningStrategy::Aggressive); + assert_eq!(assembler.token_budget, 4096); + } + + #[test] + fn test_token_estimation() { + let assembler = Assembler::new(4096, PruningStrategy::Full); + let text = "a".repeat(400); + assert_eq!(assembler.estimate_tokens(&text), 100); + } + + #[test] + fn test_assemble_empty_graph() { + let assembler = Assembler::new(4096, PruningStrategy::Full); + let graph = DependencyGraph { + nodes: Vec::new(), + depth: 0, + }; + + let result = assembler.assemble(&graph, "Test intent").unwrap(); + assert!(result.contains("Test intent")); + assert!(result.contains("Total nodes: 0")); + } + + #[test] + fn test_assemble_with_nodes() { + let assembler = Assembler::new(4096, PruningStrategy::Full); + let graph = DependencyGraph { + nodes: vec![DependencyNode { + symbol: "test_function".to_string(), + location: Uri::from_str("file:///test.rs").unwrap(), + content: "fn test_function() {}".to_string(), + references: Vec::new(), + }], + depth: 1, + }; + + let result = assembler.assemble(&graph, "Find test function").unwrap(); + assert!(result.contains("test_function")); + assert!(result.contains("file:///test.rs")); + } +} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..c7d499c --- /dev/null +++ b/src/config.rs @@ -0,0 +1,135 @@ +//! Configuration parsing and management +//! +//! Handles loading and parsing `.skry.toml` configuration files. + +use anyhow::{Context, Result}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::fs; +use std::path::Path; + +/// Main configuration structure for skry +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct Config { + #[serde(default)] + pub general: GeneralConfig, + #[serde(default)] + pub lsp: LspConfig, +} + +/// General configuration settings +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct GeneralConfig { + /// Maximum number of tokens to include in the context + #[serde(default = "default_token_budget")] + pub token_budget: usize, + + /// Pruning strategy for dependencies + #[serde(default = "default_pruning_strategy")] + pub pruning_strategy: PruningStrategy, +} + +/// Strategy for pruning dependency trees +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum PruningStrategy { + /// Aggressively prune deep dependencies + Aggressive, + /// Only include function signatures, not bodies + Bodies, + /// Include full implementations + Full, +} + +/// LSP configuration mapping file extensions to language server executables +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct LspConfig { + /// Map of file extensions to LSP server commands + #[serde(flatten)] + pub servers: HashMap, +} + +impl Default for GeneralConfig { + fn default() -> Self { + Self { + token_budget: default_token_budget(), + pruning_strategy: default_pruning_strategy(), + } + } +} + +impl Default for LspConfig { + fn default() -> Self { + let mut servers = HashMap::new(); + servers.insert("rs".to_string(), "rust-analyzer".to_string()); + servers.insert("cpp".to_string(), "clangd".to_string()); + servers.insert( + "ts".to_string(), + "typescript-language-server --stdio".to_string(), + ); + Self { servers } + } +} + +impl Config { + /// Load configuration from a file + pub fn load>(path: P) -> Result { + let content = fs::read_to_string(path.as_ref()) + .with_context(|| format!("Failed to read config file: {:?}", path.as_ref()))?; + + let config: Config = + toml::from_str(&content).with_context(|| "Failed to parse TOML configuration")?; + + Ok(config) + } + + /// Try to load config from default locations, or return default config + pub fn load_or_default() -> Self { + Self::try_load_default().unwrap_or_default() + } + + /// Try to load config from default location (.skry.toml in current directory) + fn try_load_default() -> Option { + let config_path = Path::new(".skry.toml"); + if config_path.exists() { + Self::load(config_path).ok() + } else { + None + } + } + + /// Get the LSP server command for a given file extension + pub fn get_lsp_command(&self, extension: &str) -> Option<&str> { + self.lsp.servers.get(extension).map(|s| s.as_str()) + } +} + +fn default_token_budget() -> usize { + 4096 +} + +fn default_pruning_strategy() -> PruningStrategy { + PruningStrategy::Aggressive +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_default_config() { + let config = Config::default(); + assert_eq!(config.general.token_budget, 4096); + assert!(matches!( + config.general.pruning_strategy, + PruningStrategy::Aggressive + )); + } + + #[test] + fn test_lsp_command_lookup() { + let config = Config::default(); + assert_eq!(config.get_lsp_command("rs"), Some("rust-analyzer")); + assert_eq!(config.get_lsp_command("cpp"), Some("clangd")); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..61e7aef --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,15 @@ +//! # skry +//! +//! A deterministic, LSP-powered context assembler for LLMs. +//! +//! `skry` uses the Language Server Protocol (LSP) to navigate code the way a compiler does, +//! building context prompts by traversing the Abstract Syntax Tree (AST), resolving symbol +//! definitions, and walking dependency graphs. + +pub mod assembly; +pub mod config; +pub mod lsp; +pub mod pipeline; +pub mod standby; + +pub use config::Config; diff --git a/src/lsp.rs b/src/lsp.rs new file mode 100644 index 0000000..cd0ef3d --- /dev/null +++ b/src/lsp.rs @@ -0,0 +1,138 @@ +//! LSP client implementation +//! +//! Manages communication with Language Server Protocol servers via JSON-RPC. + +use anyhow::{Context, Result}; +use lsp_types::*; +use std::process::{Child, Command, Stdio}; +use std::sync::Arc; +use tokio::sync::Mutex; +use tracing::{debug, info}; + +/// LSP client that manages a language server subprocess +pub struct LspClient { + /// The running LSP server process + process: Arc>>, + /// Root URI of the workspace + #[allow(dead_code)] + root_uri: Uri, + /// Command used to start the server + command: String, +} + +impl LspClient { + /// Create a new LSP client + pub fn new(command: String, root_uri: Uri) -> Self { + Self { + process: Arc::new(Mutex::new(None)), + root_uri, + command, + } + } + + /// Start the LSP server process + pub async fn start(&self) -> Result<()> { + let mut process = self.process.lock().await; + + let parts: Vec<&str> = self.command.split_whitespace().collect(); + let (cmd, args) = parts.split_first().context("Invalid LSP command")?; + + info!("Starting LSP server: {}", self.command); + + let child = Command::new(cmd) + .args(args) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .with_context(|| format!("Failed to start LSP server: {}", self.command))?; + + *process = Some(child); + + debug!("LSP server started successfully"); + Ok(()) + } + + /// Stop the LSP server process + pub async fn stop(&self) -> Result<()> { + let mut process = self.process.lock().await; + + if let Some(mut child) = process.take() { + info!("Stopping LSP server"); + child.kill().context("Failed to kill LSP server process")?; + child + .wait() + .context("Failed to wait for LSP server process")?; + } + + Ok(()) + } + + /// Send an initialization request to the LSP server + pub async fn initialize(&self) -> Result { + debug!("Sending initialize request to LSP server"); + + // Placeholder for actual JSON-RPC implementation + // In a full implementation, this would send the initialize request + // and parse the response + + Ok(InitializeResult { + capabilities: ServerCapabilities::default(), + server_info: None, + }) + } + + /// Request the definition of a symbol + pub async fn goto_definition( + &self, + _uri: Uri, + _position: Position, + ) -> Result> { + debug!("Requesting definition"); + + // Placeholder for actual JSON-RPC implementation + // Would send textDocument/definition request + + Ok(None) + } + + /// Request references to a symbol + pub async fn find_references( + &self, + _uri: Uri, + _position: Position, + _include_declaration: bool, + ) -> Result>> { + debug!("Requesting references"); + + // Placeholder for actual JSON-RPC implementation + // Would send textDocument/references request + + Ok(None) + } +} + +impl Drop for LspClient { + fn drop(&mut self) { + // Ensure the process is cleaned up + if let Ok(mut process) = self.process.try_lock() { + if let Some(mut child) = process.take() { + let _ = child.kill(); + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::str::FromStr; + + #[test] + fn test_lsp_client_creation() { + let root_uri = Uri::from_str("file:///tmp/test").unwrap(); + let client = LspClient::new("rust-analyzer".to_string(), root_uri.clone()); + assert_eq!(client.command, "rust-analyzer"); + assert_eq!(client.root_uri, root_uri); + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..9527936 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,190 @@ +//! Main entry point for the skry CLI + +use anyhow::Result; +use clap::{Parser, Subcommand}; +use skry::{assembly, pipeline, standby, Config}; +use std::path::PathBuf; +use tracing::info; + +/// skry - A deterministic, LSP-powered context assembler for LLMs +#[derive(Parser)] +#[command(author, version, about, long_about = None)] +struct Cli { + /// Path to the project root (defaults to current directory) + #[arg(short, long)] + root: Option, + + /// Path to configuration file (defaults to .skry.toml) + #[arg(short, long)] + config: Option, + + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand)] +enum Commands { + /// Generate context in one-shot mode + Generate { + /// File path for symbol resolution + #[arg(short, long)] + file: Option, + + /// Symbol name to resolve + #[arg(short, long)] + symbol: Option, + + /// Natural language search query + #[arg(long)] + search: Option, + + /// Maximum traversal depth + #[arg(short, long)] + depth: Option, + + /// User intent/instruction + #[arg(short, long)] + intent: Option, + }, + /// Start the standby daemon + Standby, + /// Query the running standby daemon + Query { + /// Symbol to query + #[arg(short, long)] + symbol: Option, + + /// Search query + #[arg(long)] + search: Option, + }, +} + +#[tokio::main] +async fn main() -> Result<()> { + // Initialize tracing + tracing_subscriber::fmt() + .with_env_filter( + tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")), + ) + .init(); + + let cli = Cli::parse(); + + // Load configuration + let config = if let Some(config_path) = cli.config { + Config::load(config_path)? + } else { + Config::load_or_default() + }; + + // Determine project root + let root = cli + .root + .unwrap_or_else(|| std::env::current_dir().expect("Failed to get current directory")); + + info!("Using project root: {:?}", root); + + // Execute command + match cli.command { + Commands::Generate { + file, + symbol, + search, + depth, + intent, + } => { + generate(config, root, file, symbol, search, depth, intent).await?; + } + Commands::Standby => { + run_standby().await?; + } + Commands::Query { symbol, search } => { + query_daemon(symbol, search).await?; + } + } + + Ok(()) +} + +/// Execute one-shot generation mode +async fn generate( + config: Config, + _root: PathBuf, + file: Option, + symbol: Option, + search: Option, + depth: Option, + intent: Option, +) -> Result<()> { + info!("Running in one-shot mode"); + + // Parse intent + let parsed_intent = pipeline::Pipeline::parse_intent(file, symbol, search, depth)?; + + // Create pipeline + let max_depth = match &parsed_intent { + pipeline::Intent::Search { depth, .. } => *depth, + _ => depth.unwrap_or(3), + }; + let pipeline = pipeline::Pipeline::new(max_depth); + + // Resolve entry point + let entry = pipeline.resolve_entry_point(&parsed_intent).await?; + info!("Entry point: {:?}", entry); + + // Traverse dependencies + let graph = pipeline.traverse_dependencies(&entry).await?; + info!("Traversed {} dependencies", graph.nodes.len()); + + // Assemble context + let assembler = + assembly::Assembler::new(config.general.token_budget, config.general.pruning_strategy); + + let intent_str = intent.unwrap_or_else(|| "Context retrieval".to_string()); + let output = assembler.assemble(&graph, &intent_str)?; + + // Print to stdout + println!("{}", output); + + Ok(()) +} + +/// Run the standby daemon +async fn run_standby() -> Result<()> { + info!("Starting standby daemon"); + + let daemon = standby::StandbyDaemon::new(); + daemon.start().await?; + + // Keep the daemon running + // In a full implementation, this would listen for signals or commands + info!("Press Ctrl+C to stop the daemon"); + tokio::signal::ctrl_c().await?; + + info!("Received shutdown signal"); + daemon.stop().await?; + + Ok(()) +} + +/// Query the running daemon +async fn query_daemon(symbol: Option, search: Option) -> Result<()> { + // TODO: Implement IPC to connect to the actual running daemon process + // For now, this creates a new instance which won't have access to the running daemon + let daemon = standby::StandbyDaemon::new(); + + let query = if let Some(sym) = symbol { + format!("symbol:{}", sym) + } else if let Some(s) = search { + format!("search:{}", s) + } else { + anyhow::bail!("Must provide either --symbol or --search") + }; + + let result = daemon.query(&query).await?; + println!("{}", result); + + Ok(()) +} diff --git a/src/pipeline.rs b/src/pipeline.rs new file mode 100644 index 0000000..5ec3330 --- /dev/null +++ b/src/pipeline.rs @@ -0,0 +1,165 @@ +//! Pipeline for processing LSP queries +//! +//! Implements the core pipeline: Intent Parsing → Entry Point → LSP Query → Graph Traversal + +use anyhow::Result; +use lsp_types::{Position, Uri}; +use std::path::PathBuf; +use tracing::{debug, info}; + +/// Represents the user's intent for the query +#[derive(Debug, Clone)] +pub enum Intent { + /// Direct symbol resolution + Symbol { file: PathBuf, symbol: String }, + /// Natural language search + Search { query: String, depth: usize }, +} + +/// Entry point for the LSP traversal +#[derive(Debug, Clone)] +pub struct EntryPoint { + pub file: PathBuf, + pub position: Position, +} + +/// Graph traversal result containing dependency information +#[derive(Debug, Clone)] +pub struct DependencyGraph { + pub nodes: Vec, + pub depth: usize, +} + +/// A node in the dependency graph +#[derive(Debug, Clone)] +pub struct DependencyNode { + pub symbol: String, + pub location: Uri, + pub content: String, + pub references: Vec, +} + +/// Pipeline orchestrator +pub struct Pipeline { + max_depth: usize, +} + +impl Pipeline { + /// Create a new pipeline with a maximum traversal depth + pub fn new(max_depth: usize) -> Self { + Self { max_depth } + } + + /// Parse the user's intent from command-line arguments + pub fn parse_intent( + file: Option, + symbol: Option, + search: Option, + depth: Option, + ) -> Result { + if let (Some(file), Some(symbol)) = (file, symbol) { + info!("Intent: Symbol resolution"); + Ok(Intent::Symbol { file, symbol }) + } else if let Some(query) = search { + info!("Intent: Natural language search"); + Ok(Intent::Search { + query, + depth: depth.unwrap_or(2), + }) + } else { + anyhow::bail!("Must provide either --file and --symbol, or --search") + } + } + + /// Resolve the entry point from the intent + pub async fn resolve_entry_point(&self, intent: &Intent) -> Result { + match intent { + Intent::Symbol { file, symbol } => { + debug!("Resolving entry point for symbol: {}", symbol); + + // In a full implementation, this would: + // 1. Open the file + // 2. Search for the symbol + // 3. Return the position + + Ok(EntryPoint { + file: file.clone(), + position: Position::new(0, 0), + }) + } + Intent::Search { query, .. } => { + debug!("Searching for entry point: {}", query); + + // In a full implementation, this would: + // 1. Use grep/fuzzy finding to locate the query + // 2. Return the best match + + Ok(EntryPoint { + file: PathBuf::from("src/main.rs"), + position: Position::new(0, 0), + }) + } + } + } + + /// Traverse the dependency graph starting from an entry point + pub async fn traverse_dependencies(&self, _entry: &EntryPoint) -> Result { + debug!( + "Starting dependency traversal (max depth: {})", + self.max_depth + ); + + // Placeholder for actual LSP-based traversal + // Would recursively call textDocument/definition and textDocument/references + + Ok(DependencyGraph { + nodes: Vec::new(), + depth: 0, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_symbol_intent() { + let intent = Pipeline::parse_intent( + Some(PathBuf::from("src/main.rs")), + Some("ServerConfig".to_string()), + None, + None, + ) + .unwrap(); + + match intent { + Intent::Symbol { file, symbol } => { + assert_eq!(file, PathBuf::from("src/main.rs")); + assert_eq!(symbol, "ServerConfig"); + } + _ => panic!("Expected Symbol intent"), + } + } + + #[test] + fn test_parse_search_intent() { + let intent = + Pipeline::parse_intent(None, None, Some("auth_middleware".to_string()), Some(3)) + .unwrap(); + + match intent { + Intent::Search { query, depth } => { + assert_eq!(query, "auth_middleware"); + assert_eq!(depth, 3); + } + _ => panic!("Expected Search intent"), + } + } + + #[test] + fn test_pipeline_creation() { + let pipeline = Pipeline::new(5); + assert_eq!(pipeline.max_depth, 5); + } +} diff --git a/src/standby.rs b/src/standby.rs new file mode 100644 index 0000000..3428474 --- /dev/null +++ b/src/standby.rs @@ -0,0 +1,122 @@ +//! Standby (daemon) mode implementation +//! +//! Keeps the LSP server running in the background for multiple queries. + +use anyhow::Result; +use std::sync::Arc; +use tokio::sync::Mutex; +use tracing::{info, warn}; + +/// Standby daemon state +pub struct StandbyDaemon { + running: Arc>, +} + +impl StandbyDaemon { + /// Create a new standby daemon + pub fn new() -> Self { + Self { + running: Arc::new(Mutex::new(false)), + } + } + + /// Start the daemon + pub async fn start(&self) -> Result<()> { + let mut running = self.running.lock().await; + + if *running { + warn!("Daemon is already running"); + return Ok(()); + } + + info!("Starting standby daemon"); + *running = true; + + // In a full implementation, this would: + // 1. Start LSP servers for configured languages + // 2. Listen for incoming query requests + // 3. Handle requests without restarting servers + + info!("Standby daemon started successfully"); + info!("LSP servers are now running in the background"); + info!("Use 'skry query' to send queries to the daemon"); + + Ok(()) + } + + /// Stop the daemon + pub async fn stop(&self) -> Result<()> { + let mut running = self.running.lock().await; + + if !*running { + warn!("Daemon is not running"); + return Ok(()); + } + + info!("Stopping standby daemon"); + *running = false; + + // Clean up LSP servers + + info!("Standby daemon stopped"); + Ok(()) + } + + /// Check if the daemon is running + pub async fn is_running(&self) -> bool { + *self.running.lock().await + } + + /// Send a query to the running daemon + pub async fn query(&self, _query: &str) -> Result { + // TODO: This should connect to the daemon via IPC (Unix socket/named pipe) + // Currently only checks local instance state, not the actual daemon process + if !self.is_running().await { + anyhow::bail!("Daemon is not running. Start it with 'skry standby'"); + } + + info!("Processing query via standby daemon"); + + // In a full implementation, this would: + // 1. Parse the query + // 2. Use the running LSP servers + // 3. Return results + + Ok("Query processed (placeholder)".to_string()) + } +} + +impl Default for StandbyDaemon { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_daemon_creation() { + let daemon = StandbyDaemon::new(); + assert!(!daemon.is_running().await); + } + + #[tokio::test] + async fn test_daemon_start_stop() { + let daemon = StandbyDaemon::new(); + + daemon.start().await.unwrap(); + assert!(daemon.is_running().await); + + daemon.stop().await.unwrap(); + assert!(!daemon.is_running().await); + } + + #[tokio::test] + async fn test_query_without_daemon() { + let daemon = StandbyDaemon::new(); + let result = daemon.query("test").await; + assert!(result.is_err()); + } +}