mirror of
https://github.com/qodo-ai/pr-agent.git
synced 2025-07-04 12:50:38 +08:00
Compare commits
946 Commits
v0.23
...
mrT23-patc
Author | SHA1 | Date | |
---|---|---|---|
0cabf57247 | |||
4bfd845877 | |||
e8a80264ca | |||
75f4c7f681 | |||
2277a31661 | |||
805d9cac69 | |||
beffa8dfe4 | |||
a42c5f4f93 | |||
63c98d30d9 | |||
50c52e32c9 | |||
f48c95d113 | |||
2ead9fe4df | |||
4d52715d25 | |||
83baac975d | |||
3f6fa5cccb | |||
a7fbd694cc | |||
78b11fca4c | |||
640c1d8dda | |||
dfa98cdfa3 | |||
5518f1ca26 | |||
c2bad8ab29 | |||
e9c1c91a4b | |||
eba116fedd | |||
94616a3429 | |||
c1fa22d068 | |||
4f1e4073e4 | |||
b320767c97 | |||
cd5bdf9e59 | |||
e8b8909ab7 | |||
ee8cdcf2b2 | |||
1cb21c6615 | |||
b5f52c5fea | |||
4e481c5088 | |||
7367c00c3d | |||
fb3aa8c96c | |||
983ce131f2 | |||
e571841b5c | |||
c537e9ff66 | |||
fc5454630a | |||
c2ca79da0d | |||
63f1d449ce | |||
0adaddccdf | |||
8a1f07dd6b | |||
8e5d2a0f4d | |||
4e1cae3869 | |||
7be26b665e | |||
664f1938c9 | |||
e0b6db9bec | |||
2f2cdd787d | |||
2b25053de8 | |||
054957fcfe | |||
0075084a22 | |||
e62db20c1e | |||
67d4c96166 | |||
b335cacffd | |||
87a5a7e156 | |||
24aa15f074 | |||
ad4a96caf1 | |||
e7f874a4b2 | |||
4ab9392042 | |||
536d97b9da | |||
cea1b58714 | |||
5ed260d885 | |||
e58a535594 | |||
d949f440a8 | |||
4923c8d810 | |||
fe6540275c | |||
1997da3aac | |||
d0c442b74b | |||
1f86cbc942 | |||
633a1c7bd0 | |||
2d9141adad | |||
95d0d4d71d | |||
5daf8e043c | |||
bd611bc1c2 | |||
4bb04e37c2 | |||
b3f116bb35 | |||
d15d08bb3b | |||
445a7fc015 | |||
c1c5c353e5 | |||
a74fca7b7d | |||
7479ae3224 | |||
6bb5ce58f2 | |||
2783f9b1bb | |||
f4b895d870 | |||
07d40e2c05 | |||
6e1178b168 | |||
c889e566e8 | |||
d9540d3b43 | |||
793f76fec5 | |||
5adc246040 | |||
d4d58babd5 | |||
2df4bc8b53 | |||
e431979b8b | |||
bde594c9e8 | |||
b570c758aa | |||
f6b80174b5 | |||
833ae67820 | |||
ee913336ad | |||
79d589e145 | |||
2d4cd3e270 | |||
7f950a3aa9 | |||
379fa957ea | |||
a1a7c8e44c | |||
6d0ed06fc4 | |||
e695af6917 | |||
5318047202 | |||
5bf5f63c78 | |||
5971a06d73 | |||
71e477a993 | |||
efc621a58b | |||
951fa3385a | |||
56e15c0348 | |||
4c82b8a43e | |||
2d5daf4364 | |||
aa95d5fb68 | |||
f6b470bf5e | |||
36df75ce14 | |||
e2be1f1cee | |||
d1caa0f15f | |||
2c2af93eed | |||
04197a9271 | |||
9faf1521b4 | |||
8819293770 | |||
0ec4491542 | |||
4fd121d34a | |||
6e80f5fcce | |||
4a1b042152 | |||
6fbd95e1a9 | |||
fd6e81978f | |||
467136a48a | |||
3b14d3a3c2 | |||
8218fa6e13 | |||
8463c4f549 | |||
014b1f20c5 | |||
2f73ab6eab | |||
16dc29a23a | |||
bd9522057f | |||
b3d4af6cbf | |||
5df9698bae | |||
e89b65ed38 | |||
6a145af159 | |||
39a375b3e4 | |||
dbd76ecde5 | |||
e95920c58c | |||
59899f0c62 | |||
5e46955d52 | |||
95d0fafa75 | |||
71c558d306 | |||
7b2c41e0d2 | |||
4aad67b563 | |||
0f973681ea | |||
19f6078ed0 | |||
3994a2ba60 | |||
796b36f0d1 | |||
12d603fdb4 | |||
6540e2c674 | |||
83e68f168a | |||
5e1b04980e | |||
495c1ebe5f | |||
ad71de82a9 | |||
11676943b6 | |||
b88507aa23 | |||
a5c5e6f4ae | |||
47cd361663 | |||
c84b3d04b9 | |||
7d9288bb1a | |||
93e64367d2 | |||
6c131b8406 | |||
dd89e1f2dc | |||
e8e4fb0afa | |||
3360a28b3e | |||
20c506d2e0 | |||
a1921d931c | |||
31aa460f5f | |||
23678c1d4d | |||
8d7825233a | |||
4688b20284 | |||
c9f02e63e1 | |||
c2f1f2dba0 | |||
3ab2cac089 | |||
989670b159 | |||
7e8361b5fd | |||
eaaaf6a6a2 | |||
07f3933f6d | |||
84786495ed | |||
d09aa1b13e | |||
e9615c6994 | |||
f3ee4a75b5 | |||
452abe2e18 | |||
a768969d37 | |||
9ef9198468 | |||
d0ea901bca | |||
57089c931b | |||
03d2bea50b | |||
721d38d4ed | |||
bbec5d9cc9 | |||
0be2750dfa | |||
5e5c251cd0 | |||
048ae8ee9e | |||
1a77e9afaf | |||
184a52d325 | |||
3d38060dff | |||
c4dc263f2c | |||
f67cc0dd18 | |||
872b27bfd8 | |||
cb88489dbe | |||
da786b8020 | |||
6a51b8501d | |||
d34edb83ff | |||
c6eb253ed1 | |||
a61f1889d1 | |||
75a120952c | |||
f9a7b18073 | |||
6352e6e3bf | |||
f49217e058 | |||
e11cec7d9e | |||
c45cde93ef | |||
9e25667d97 | |||
7dc9e73423 | |||
e3d779c30d | |||
88a93bdcd7 | |||
3c31048afc | |||
fc5dda0957 | |||
936894e4d1 | |||
dec2859fc4 | |||
a4d9a65fc6 | |||
683108d3a5 | |||
e68e100117 | |||
158892047b | |||
7d5e59cd40 | |||
e8fc351ce9 | |||
43e91b0df7 | |||
39a461b3b2 | |||
19ade4acf0 | |||
10f8b522db | |||
d26ca4f71c | |||
9160f756db | |||
84c3a7b969 | |||
d9f9cc65b3 | |||
7d99c0db8e | |||
fe20a8c5e7 | |||
43c95106d4 | |||
1dd5f0b848 | |||
8610aa27a4 | |||
91bf3c0749 | |||
159155785e | |||
eabc296246 | |||
b44030114e | |||
1d6f87be3b | |||
a7c6fa7bd2 | |||
a825aec5f3 | |||
4df097c228 | |||
6871e1b27a | |||
4afe05761d | |||
7d1b6c2f0a | |||
3547cf2057 | |||
f2043d639c | |||
6240de3898 | |||
f08b20c667 | |||
e64b468556 | |||
d48d14dac7 | |||
eb0c959ca9 | |||
741a70ad9d | |||
22ee03981e | |||
b1336e7d08 | |||
751caca141 | |||
612004727c | |||
577ee0241d | |||
a141ca133c | |||
a14b6a580d | |||
cc5005c490 | |||
3a5d0f54ce | |||
cd8ba4f59f | |||
fe27f96bf1 | |||
2c3aa7b2dc | |||
c934523f2d | |||
2f4545dc15 | |||
cbd490b3d7 | |||
b07f96d26a | |||
065777040f | |||
9c82047dc3 | |||
e0c15409bb | |||
d956c72cb6 | |||
dfb3d801cf | |||
5c5a3e267c | |||
f9380c2440 | |||
e6a1f14c0e | |||
6339845eb4 | |||
732cc18fd6 | |||
84d0f80c81 | |||
ee26bf35c1 | |||
7a5e9102fd | |||
a8c97bfa73 | |||
af653a048f | |||
d2663f959a | |||
e650fe9ce9 | |||
daeca42ae8 | |||
04496f9b0e | |||
0eacb3e35e | |||
c5ed2f040a | |||
c394fc2767 | |||
157251493a | |||
4a982a849d | |||
6e3544f523 | |||
bf3ebbb95f | |||
eb44ecb1be | |||
45bae48701 | |||
b2181e4c79 | |||
5939d3b17b | |||
c1f4964a55 | |||
022e407d84 | |||
93ba2d239a | |||
fa49dd5167 | |||
16029e66ad | |||
7bd6713335 | |||
ef3241285d | |||
d9ef26dc1c | |||
02949b2b96 | |||
d301c76b65 | |||
dacb45dd8a | |||
443d06df06 | |||
15e8c988a4 | |||
60fab1b301 | |||
84c1c1b1ca | |||
7419a6d51a | |||
ee58a92fb3 | |||
6b64924355 | |||
2f5e8472b9 | |||
852bb371af | |||
7c90e44656 | |||
81dea65856 | |||
a3d572fb69 | |||
7186bf4bb3 | |||
115fca58a3 | |||
cbf60ca636 | |||
64ac45d03b | |||
db062e3e35 | |||
e85472f367 | |||
597f1c6f83 | |||
66d4f56777 | |||
fbfb9e0881 | |||
223b5408d7 | |||
509135a8d4 | |||
8db7151bf0 | |||
b8cfcdbc12 | |||
a3cd433184 | |||
0f284711e6 | |||
67b46e7f30 | |||
68f2cec077 | |||
8e94c8b2f5 | |||
a221f8edd0 | |||
3b47c75c32 | |||
2e34d7a05a | |||
204a0a7912 | |||
9786499fa6 | |||
4f14742233 | |||
c077c71fdb | |||
7b5a3d45bd | |||
c6c6a9b4f0 | |||
a5e7c37fcc | |||
12a9e13509 | |||
0b4b6b1589 | |||
bf049381bd | |||
65c917b84b | |||
b4700bd7c0 | |||
a957554262 | |||
d491a942cc | |||
6c55a2720a | |||
f1d0401f82 | |||
c5bd09e2c9 | |||
c70acdc7cd | |||
1c6f7f9c06 | |||
0b32b253ca | |||
3efd2213f2 | |||
0705bd03c4 | |||
927d005e99 | |||
0dccfdbbf0 | |||
dcb7b66fd7 | |||
b7437147af | |||
e82afdd2cb | |||
0946da3810 | |||
d1f4069d3f | |||
d45a892fd2 | |||
4a91b8ed8d | |||
fb85cb721a | |||
3a52122677 | |||
13c6ed9098 | |||
9dd1995464 | |||
eb804d0b34 | |||
e0ee878e84 | |||
27abe48a34 | |||
8fe504a7ec | |||
f6ba49819a | |||
22bf7af9ba | |||
840e8c4d6b | |||
49f8d86c77 | |||
05827d125b | |||
74ee9a333e | |||
e9769fa602 | |||
3adff8cf4c | |||
d249b47ce9 | |||
892d1ad15c | |||
76d95bb6d7 | |||
7db9a03805 | |||
4eef3e9190 | |||
014ea884d2 | |||
c1c5ee7cfb | |||
3ac1ddc5d7 | |||
e6c56c7355 | |||
727b08fde3 | |||
5d9d48dc82 | |||
8e8062fefc | |||
23a3e208a5 | |||
bb84063ef2 | |||
a476e85fa7 | |||
4b05a3e858 | |||
cd158f24f6 | |||
ada0a3d10f | |||
ddf1afb23f | |||
e2b5489495 | |||
6459535e39 | |||
5a719c1904 | |||
1a2ea2c87d | |||
ca79bafab3 | |||
618224beef | |||
481c2a5985 | |||
e21d9dc9e3 | |||
6872a7076b | |||
c2ae429805 | |||
af5a50ac6a | |||
bccc2844b9 | |||
d80103751e | |||
8ff8b1d48e | |||
da0bd84746 | |||
b42ded61f8 | |||
dfa4f22be2 | |||
7d55fc174b | |||
968fb71577 | |||
454365913f | |||
bbaba2dbda | |||
e4c6792866 | |||
183dd5d2fc | |||
2e79392a5f | |||
da4148f336 | |||
070ed21103 | |||
640bc1e28a | |||
6c4aa468a9 | |||
da20efd050 | |||
c25aaad176 | |||
b4c20d683c | |||
7bc20d6f16 | |||
f894e8831b | |||
25b7e1e777 | |||
6ba2fb212d | |||
4a60046f7c | |||
35b1f5e747 | |||
d77a819d92 | |||
f3fd439d47 | |||
6188afff48 | |||
57cfdcf274 | |||
1333ac47bc | |||
267e01409b | |||
8bdebcb99f | |||
a13400b9b8 | |||
89f9cf5adc | |||
6b653dbe48 | |||
109b965407 | |||
511c5a31db | |||
3dd8050004 | |||
4b7d01972c | |||
05ec944a8b | |||
4713ae74b7 | |||
c828cdde62 | |||
6f14f9c8e1 | |||
9f8cc75bd3 | |||
0668ccbb9e | |||
8a287f8ed6 | |||
d5625db3c8 | |||
d6b779eef8 | |||
804cb9ec1d | |||
47d32283ca | |||
397963257d | |||
a3fd15bb92 | |||
ded7d96649 | |||
bbf06e25ef | |||
7e5ddf7e37 | |||
0198c61cf7 | |||
20d8e76a7f | |||
be8052251a | |||
ba08b13446 | |||
835684b92a | |||
90295b6429 | |||
08d6bbc94c | |||
8229d98842 | |||
9e28aca919 | |||
3e780783cc | |||
5c7b65810c | |||
f2f82e8805 | |||
1e51acff22 | |||
81e847e477 | |||
a70fe27d94 | |||
1a5835a947 | |||
4b74506107 | |||
08319f8492 | |||
b447080777 | |||
da398ce56f | |||
16763d81b4 | |||
80fe297bc9 | |||
5d68b0c492 | |||
8d5f015e5c | |||
be03f83318 | |||
cbfd250c0c | |||
7ce46e65a1 | |||
600f230ba7 | |||
4f4f13b8b2 | |||
146b8823a9 | |||
fdb1ff8057 | |||
ce8e637800 | |||
306af02d22 | |||
a23541912b | |||
0851767774 | |||
585a7f1c69 | |||
8d82cb2e04 | |||
7586514abf | |||
480a025877 | |||
8f943a0d44 | |||
2102c51422 | |||
29028d43cf | |||
95d1b0d0c5 | |||
cc0e432247 | |||
0fb158fd47 | |||
867a430a38 | |||
a94496285f | |||
567c144176 | |||
c08b59a74d | |||
0ba81e1ac7 | |||
2cb0dd2496 | |||
a8367d1a22 | |||
1a3345c6e6 | |||
564845adff | |||
3ea691e70a | |||
5047d076f8 | |||
7de6bb0150 | |||
a1582b5338 | |||
dd8d78e7d8 | |||
5af6cc7538 | |||
6cc562d6a2 | |||
19b051b992 | |||
be68ee89f3 | |||
db6c75a130 | |||
74688846e0 | |||
09b0a04a47 | |||
cc1b65f886 | |||
1451d82d6b | |||
01ba6fe63d | |||
74f9da1135 | |||
f80c2ae2c8 | |||
e444da8378 | |||
25ad8a09ce | |||
897e791b1a | |||
7f94dda54e | |||
538a592882 | |||
a3cb7277a7 | |||
b5cd560402 | |||
fd38c33fcb | |||
f767a3dfde | |||
9f8b619858 | |||
8de16939ba | |||
6ed5537065 | |||
1a9638cf87 | |||
49521aafff | |||
c8e8ed89d2 | |||
ebc5cafb2b | |||
52e8d7bc6a | |||
f7344fd787 | |||
86103c65e8 | |||
a4658b9960 | |||
5fd831c448 | |||
332d3a0c5e | |||
edef712b6a | |||
1831f2cec4 | |||
8706f643ef | |||
35a75095ea | |||
0aa296d03e | |||
24f7e8622f | |||
d01cfe443c | |||
6150256040 | |||
147a8e0ef3 | |||
9199d84796 | |||
39913ef12a | |||
d2a744e70c | |||
be93c52380 | |||
7ccefca35e | |||
14b4723734 | |||
c8f1c03061 | |||
b02fa22948 | |||
85754d2d79 | |||
f0d780c7ec | |||
19048ee705 | |||
b8d2b263b9 | |||
6f17c08f72 | |||
65c0bc414f | |||
015719134f | |||
1ed6b7a54a | |||
14067a02db | |||
be75bb6a16 | |||
883d945687 | |||
8090115f30 | |||
6fa226dee7 | |||
13c1cdbf90 | |||
d4d9a7f8b4 | |||
c14c49727f | |||
292a5015d6 | |||
2f7f60a469 | |||
adce35765b | |||
6776f7c296 | |||
7287a94e88 | |||
e2cf1d0068 | |||
8ada3111ec | |||
9c9611e81a | |||
4fb93e3b62 | |||
5a27e1dd7e | |||
6e6151d201 | |||
e468efb53e | |||
95e1ebada1 | |||
d74c867eca | |||
2448281a45 | |||
9e063bf48a | |||
5432469ef6 | |||
2c496b9d4e | |||
5ac41dddd6 | |||
9df554ed1c | |||
23af1afa03 | |||
fdcbdfce98 | |||
cf14e45674 | |||
1c51b5b762 | |||
e5715e12cb | |||
578d7c69f8 | |||
29c50758bc | |||
97b48da03b | |||
4203ee4ca8 | |||
84dc976ebb | |||
d9571ee7cb | |||
7373ed36e6 | |||
cdf13925b0 | |||
c2f52539aa | |||
0442cdcd3d | |||
93773f3c08 | |||
53a974c282 | |||
c9ed271eaf | |||
6a5ff2fa3b | |||
25d661c152 | |||
d20c9c6c94 | |||
d1d861e163 | |||
033db1015e | |||
abf2f68c61 | |||
441e098e2a | |||
2bbf4b366e | |||
b9d096187a | |||
ce156751e8 | |||
dae87d7da8 | |||
a99ebf8953 | |||
2a9e3ee1ef | |||
2beefab89a | |||
415f44d763 | |||
8fb9b8ed3e | |||
4f1dccf67b | |||
3778cc2745 | |||
8793f8d9b0 | |||
61837c69a3 | |||
ffaf5d5271 | |||
cd526a233c | |||
745e955d1f | |||
771d0b8c60 | |||
91a7c08546 | |||
4d9d6f7477 | |||
2591a5d6c1 | |||
772499fce1 | |||
d467f5a7fd | |||
2d5b060168 | |||
b7eb6be5a0 | |||
df57367426 | |||
660a60924e | |||
8aa76a0ac5 | |||
b034d16c23 | |||
9bec97c66c | |||
8fd8d298e7 | |||
2e186ebae8 | |||
fc40ca9196 | |||
91a8938a37 | |||
d97e1862da | |||
f042c061de | |||
c47afd9c0d | |||
c6d16ced07 | |||
e9535ea164 | |||
dc8a4be2d4 | |||
f9de8f283b | |||
bd5c19ee05 | |||
7cbe797108 | |||
435d9d41c8 | |||
a510d93e6e | |||
48cc2f6833 | |||
229d7b34c7 | |||
03b194c337 | |||
a6f772c6d5 | |||
ba1ba98dec | |||
5954c7cec2 | |||
dc1a8e8314 | |||
aa87bc60f6 | |||
c76aabc71e | |||
81081186d9 | |||
4a71ec90c6 | |||
3456c8e039 | |||
402a388be0 | |||
4e26c02b01 | |||
ea4f88edd3 | |||
217f615dfb | |||
a6fb351789 | |||
bfab660414 | |||
2e63653bb0 | |||
b9df034c97 | |||
bae8d36698 | |||
67a04e1cb2 | |||
4fea780b9b | |||
01c18d7d98 | |||
f4b06640d2 | |||
f1981092d3 | |||
8414e109c5 | |||
8adfca5b3c | |||
672cdc03ab | |||
86a9cfedc8 | |||
7ac9f27b70 | |||
c97c39d57d | |||
a3b3d6c77a | |||
2e41701d07 | |||
578f56148a | |||
b3da84b4aa | |||
f89bdcf3c3 | |||
e7e3970874 | |||
1f7a8eada0 | |||
38638bd1c4 | |||
26f3bd8900 | |||
a2fb415c53 | |||
8038eaf876 | |||
d8572f8d13 | |||
78b11c80c7 | |||
cb65b05e85 | |||
1aa6dd9b5d | |||
11d69e05aa | |||
0722af4702 | |||
99e99345b2 | |||
5252e1826d | |||
a18a0bf2e3 | |||
396d11aa45 | |||
4a38861d06 | |||
5feb66597e | |||
8589941ffe | |||
7f0e6aeb37 | |||
8a768aa7fd | |||
f399f9ebe4 | |||
cc73d4599b | |||
4228f92e7e | |||
1f4ab43fa6 | |||
b59111e4a6 | |||
70da871876 | |||
9c1ab06491 | |||
5c4bc0a008 | |||
ef37271ce9 | |||
8dd4c15d4b | |||
f9afada1ed | |||
4c1c313031 | |||
1f126069b1 | |||
12742ef499 | |||
63e921a2c5 | |||
8f04387331 | |||
a06670bc27 | |||
2525392814 | |||
23aa2a9388 | |||
e85b75fe64 | |||
df04a7e046 | |||
9c3f080112 | |||
ed65493718 | |||
983233c193 | |||
7438190ed1 | |||
2b2b851cb9 | |||
5701816b2e | |||
40a25a1082 | |||
e238a88824 | |||
61bdfd3b99 | |||
c00d1e9858 | |||
1a8b143f58 | |||
dfbe7432b8 | |||
ab69f1769b | |||
089210d9fa | |||
0f9d89c67a | |||
84b80f792d | |||
219d962cbe | |||
e531245f4a | |||
89e9413d75 | |||
b370cb6ae7 | |||
4201779ce2 | |||
71f7c09ed7 | |||
edad244a86 | |||
9752987966 | |||
200da44e5a | |||
4c0fd37ac2 | |||
c996c7117f | |||
943ba95924 | |||
8a75d3101d | |||
944f54b431 | |||
9be5cc6dec | |||
884286ebf1 | |||
620dbbeb1a | |||
c07059e139 | |||
3b88d6afdb | |||
e717e8ae81 | |||
8ec1fb5937 | |||
cb10ceadd7 | |||
96d3f3cc0b | |||
a98d972041 | |||
09a1d74a00 | |||
31f6f8f8ea | |||
e7c99f0e6f | |||
ac53e6728d | |||
b100e7098a | |||
2b77d07725 | |||
ee1676cf7e | |||
3420e6f30d | |||
85cc0ad08c | |||
3756b547da | |||
e34bcace29 | |||
2a675b80ca | |||
1cefd23739 | |||
aef9a04b32 | |||
fe4e642a47 | |||
039d85b836 | |||
0fa342ddd2 | |||
c95a8cde72 | |||
23ec25c949 | |||
9560bc1b44 | |||
346ea8fbae | |||
d671c78233 | |||
240e0374e7 | |||
288e9bb8ca | |||
d8545a2b28 | |||
95f23de7ec | |||
0390a85f5a | |||
172d0c0358 | |||
41588efe9a | |||
f50832e19b | |||
927f124dca | |||
232b540f60 | |||
452eda25cd | |||
110e593d03 | |||
af84409c1d | |||
c2c69f2950 | |||
e946a0ea9f | |||
866476080c | |||
27d6560de8 | |||
6ba7b3eea2 | |||
86d9612882 | |||
49f608c968 | |||
11f85cad62 | |||
5f5257d195 | |||
495e2ccb7d | |||
a176adb23e | |||
68ef11a2fc | |||
38c38ec280 | |||
3904eebf85 | |||
778d7ce1ed | |||
3067afbcb3 | |||
70f7a90226 | |||
7eadb45c09 | |||
ac247dbc2c | |||
3a77652660 | |||
0bd4c9b78a | |||
81d07a55d7 | |||
652ced5406 | |||
aaf037570b | |||
cfa565b5d7 | |||
c8819472cf | |||
53744af32f | |||
41c6502190 | |||
32604d8103 | |||
581c95c4ab | |||
789c48a216 | |||
6b9de6b253 | |||
003846a90d | |||
d088f9c19a | |||
a272c761a9 | |||
9449f2aebe | |||
28ea4a685a | |||
b798291bc8 | |||
62df50cf86 | |||
917e1607de | |||
8f11a19c32 | |||
0f5cccd18f | |||
2be459e576 | |||
cbdb451c95 | |||
6871193381 | |||
8a7f3501ea | |||
80bbe23ad5 | |||
05f3fa5ebc | |||
1b2a2075ae | |||
3d3b49e3ee | |||
174b4b76eb | |||
2b28153749 | |||
6151bfac25 | |||
5d6e1de157 | |||
ce35d2c313 | |||
b51abe9af7 | |||
20206af1bf | |||
34ae1f1ab6 | |||
887283632b | |||
7f84b5738e | |||
dc917587ef | |||
b2710ec029 | |||
41c48ca5b5 | |||
e0012702c6 | |||
dfb339ab44 | |||
54947573bf | |||
228ceff3a6 | |||
8766140554 | |||
034ec8f53a | |||
eccd00b86f | |||
4b351cfe38 | |||
734a027702 | |||
d0948329d3 | |||
6135bf1f53 | |||
ea9deccb91 | |||
daa68f3b2f | |||
e82430891c | |||
19ca7f887a | |||
888306c160 | |||
3ef4daafd5 | |||
f76f750757 | |||
055bc4ceec | |||
487efa4bf4 | |||
050ffcdd06 | |||
8f9879cf01 | |||
c3fac86067 | |||
9a57d00951 | |||
745d0c537c | |||
5b594dadee | |||
4246792261 |
4
.github/workflows/build-and-test.yaml
vendored
4
.github/workflows/build-and-test.yaml
vendored
@ -36,6 +36,4 @@ jobs:
|
||||
- id: test
|
||||
name: Test dev docker
|
||||
run: |
|
||||
docker run --rm codiumai/pr-agent:test pytest -v
|
||||
|
||||
|
||||
docker run --rm codiumai/pr-agent:test pytest -v tests/unittest
|
||||
|
54
.github/workflows/code_coverage.yaml
vendored
Normal file
54
.github/workflows/code_coverage.yaml
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
name: Code-coverage
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# push:
|
||||
# branches:
|
||||
# - main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- id: checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- id: dockerx
|
||||
name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- id: build
|
||||
name: Build dev docker
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/Dockerfile
|
||||
push: false
|
||||
load: true
|
||||
tags: codiumai/pr-agent:test
|
||||
cache-from: type=gha,scope=dev
|
||||
cache-to: type=gha,mode=max,scope=dev
|
||||
target: test
|
||||
|
||||
- id: code_cov
|
||||
name: Test dev docker
|
||||
run: |
|
||||
docker run --name test_container codiumai/pr-agent:test pytest tests/unittest --cov=pr_agent --cov-report term --cov-report xml:coverage.xml
|
||||
docker cp test_container:/app/coverage.xml coverage.xml
|
||||
docker rm test_container
|
||||
|
||||
|
||||
- name: Validate coverage report
|
||||
run: |
|
||||
if [ ! -f coverage.xml ]; then
|
||||
echo "Coverage report not found"
|
||||
exit 1
|
||||
fi
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v4.0.1
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
46
.github/workflows/e2e_tests.yaml
vendored
Normal file
46
.github/workflows/e2e_tests.yaml
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
name: PR-Agent E2E tests
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# schedule:
|
||||
# - cron: '0 0 * * *' # This cron expression runs the workflow every night at midnight UTC
|
||||
|
||||
jobs:
|
||||
pr_agent_job:
|
||||
runs-on: ubuntu-latest
|
||||
name: PR-Agent E2E GitHub App Test
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- id: build
|
||||
name: Build dev docker
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/Dockerfile
|
||||
push: false
|
||||
load: true
|
||||
tags: codiumai/pr-agent:test
|
||||
cache-from: type=gha,scope=dev
|
||||
cache-to: type=gha,mode=max,scope=dev
|
||||
target: test
|
||||
|
||||
- id: test1
|
||||
name: E2E test github app
|
||||
run: |
|
||||
docker run -e GITHUB.USER_TOKEN=${{ secrets.TOKEN_GITHUB }} --rm codiumai/pr-agent:test pytest -v tests/e2e_tests/test_github_app.py
|
||||
|
||||
- id: test2
|
||||
name: E2E gitlab webhook
|
||||
run: |
|
||||
docker run -e gitlab.PERSONAL_ACCESS_TOKEN=${{ secrets.TOKEN_GITLAB }} --rm codiumai/pr-agent:test pytest -v tests/e2e_tests/test_gitlab_webhook.py
|
||||
|
||||
|
||||
- id: test3
|
||||
name: E2E bitbucket app
|
||||
run: |
|
||||
docker run -e BITBUCKET.USERNAME=${{ secrets.BITBUCKET_USERNAME }} -e BITBUCKET.PASSWORD=${{ secrets.BITBUCKET_PASSWORD }} --rm codiumai/pr-agent:test pytest -v tests/e2e_tests/test_bitbucket_app.py
|
3
.github/workflows/pr-agent-review.yaml
vendored
3
.github/workflows/pr-agent-review.yaml
vendored
@ -30,6 +30,3 @@ jobs:
|
||||
GITHUB_ACTION_CONFIG.AUTO_DESCRIBE: true
|
||||
GITHUB_ACTION_CONFIG.AUTO_REVIEW: true
|
||||
GITHUB_ACTION_CONFIG.AUTO_IMPROVE: true
|
||||
|
||||
|
||||
|
||||
|
17
.github/workflows/pre-commit.yml
vendored
Normal file
17
.github/workflows/pre-commit.yml
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
# disabled. We might run it manually if needed.
|
||||
name: pre-commit
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# pull_request:
|
||||
# push:
|
||||
# branches: [main]
|
||||
|
||||
jobs:
|
||||
pre-commit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v5
|
||||
# SEE https://github.com/pre-commit/action
|
||||
- uses: pre-commit/action@v3.0.1
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,6 +1,7 @@
|
||||
.idea/
|
||||
.lsp/
|
||||
.vscode/
|
||||
.env
|
||||
venv/
|
||||
pr_agent/settings/.secrets.toml
|
||||
__pycache__
|
||||
@ -9,3 +10,4 @@ dist/
|
||||
build/
|
||||
.DS_Store
|
||||
docs/.cache/
|
||||
.qodo
|
||||
|
46
.pre-commit-config.yaml
Normal file
46
.pre-commit-config.yaml
Normal file
@ -0,0 +1,46 @@
|
||||
# See https://pre-commit.com for more information
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
|
||||
default_language_version:
|
||||
python: python3
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v5.0.0
|
||||
hooks:
|
||||
- id: check-added-large-files
|
||||
- id: check-toml
|
||||
- id: check-yaml
|
||||
- id: end-of-file-fixer
|
||||
- id: trailing-whitespace
|
||||
# - repo: https://github.com/rhysd/actionlint
|
||||
# rev: v1.7.3
|
||||
# hooks:
|
||||
# - id: actionlint
|
||||
- repo: https://github.com/pycqa/isort
|
||||
# rev must match what's in dev-requirements.txt
|
||||
rev: 5.13.2
|
||||
hooks:
|
||||
- id: isort
|
||||
# - repo: https://github.com/PyCQA/bandit
|
||||
# rev: 1.7.10
|
||||
# hooks:
|
||||
# - id: bandit
|
||||
# args: [
|
||||
# "-c", "pyproject.toml",
|
||||
# ]
|
||||
# - repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# rev: v0.7.1
|
||||
# hooks:
|
||||
# - id: ruff
|
||||
# args:
|
||||
# - --fix
|
||||
# - id: ruff-format
|
||||
# - repo: https://github.com/PyCQA/autoflake
|
||||
# rev: v2.3.1
|
||||
# hooks:
|
||||
# - id: autoflake
|
||||
# args:
|
||||
# - --in-place
|
||||
# - --remove-all-unused-imports
|
||||
# - --remove-unused-variables
|
@ -1,10 +1,11 @@
|
||||
FROM python:3.10 as base
|
||||
FROM python:3.12 as base
|
||||
|
||||
WORKDIR /app
|
||||
ADD pyproject.toml .
|
||||
ADD requirements.txt .
|
||||
RUN pip install . && rm pyproject.toml requirements.txt
|
||||
ENV PYTHONPATH=/app
|
||||
ADD docs docs
|
||||
ADD pr_agent pr_agent
|
||||
ADD github_action/entrypoint.sh /
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
306
README.md
306
README.md
@ -10,58 +10,99 @@
|
||||
|
||||
</picture>
|
||||
<br/>
|
||||
CodiumAI PR-Agent aims to help efficiently review and handle pull requests, by providing AI feedbacks and suggestions
|
||||
PR-Agent aims to help efficiently review and handle pull requests, by providing AI feedback and suggestions
|
||||
</div>
|
||||
|
||||
[](https://github.com/Codium-ai/pr-agent/blob/main/LICENSE)
|
||||
[](https://chromewebstore.google.com/detail/pr-agent-chrome-extension/ephlnjeghhogofkifjloamocljapahnl)
|
||||
[](https://pr-agent-docs.codium.ai/finetuning_benchmark/)
|
||||
[](https://chromewebstore.google.com/detail/qodo-merge-ai-powered-cod/ephlnjeghhogofkifjloamocljapahnl)
|
||||
[](https://github.com/apps/qodo-merge-pro/)
|
||||
[](https://github.com/apps/qodo-merge-pro-for-open-source/)
|
||||
[](https://discord.com/channels/1057273017547378788/1126104260430528613)
|
||||
[](https://twitter.com/codiumai)
|
||||
<a href="https://github.com/Codium-ai/pr-agent/commits/main">
|
||||
<img alt="GitHub" src="https://img.shields.io/github/last-commit/Codium-ai/pr-agent/main?style=for-the-badge" height="20">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
### [Documentation](https://pr-agent-docs.codium.ai/)
|
||||
- See the [Installation Guide](https://pr-agent-docs.codium.ai/installation/) for instructions on installing PR-Agent on different platforms.
|
||||
### [Documentation](https://qodo-merge-docs.qodo.ai/)
|
||||
|
||||
- See the [Usage Guide](https://pr-agent-docs.codium.ai/usage-guide/) for instructions on running PR-Agent tools via different interfaces, such as CLI, PR Comments, or by automatically triggering them when a new PR is opened.
|
||||
- See the [Installation Guide](https://qodo-merge-docs.qodo.ai/installation/) for instructions on installing PR-Agent on different platforms.
|
||||
|
||||
- See the [Tools Guide](https://pr-agent-docs.codium.ai/tools/) for a detailed description of the different tools, and the available configurations for each tool.
|
||||
- See the [Usage Guide](https://qodo-merge-docs.qodo.ai/usage-guide/) for instructions on running PR-Agent tools via different interfaces, such as CLI, PR Comments, or by automatically triggering them when a new PR is opened.
|
||||
|
||||
- See the [Tools Guide](https://qodo-merge-docs.qodo.ai/tools/) for a detailed description of the different tools, and the available configurations for each tool.
|
||||
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [News and Updates](#news-and-updates)
|
||||
- [Overview](#overview)
|
||||
- [Example results](#example-results)
|
||||
- [Try it now](#try-it-now)
|
||||
- [PR-Agent Pro 💎](#pr-agent-pro-)
|
||||
- [Qodo Merge 💎](https://qodo-merge-docs.qodo.ai/overview/pr_agent_pro/)
|
||||
- [How it works](#how-it-works)
|
||||
- [Why use PR-Agent?](#why-use-pr-agent)
|
||||
|
||||
## News and Updates
|
||||
|
||||
### July 4, 2024
|
||||
### Jan 25, 2025
|
||||
|
||||
Added improved support for claude-sonnet-3.5 model (anthropic, vertex, bedrock), including dedicated prompts.
|
||||
The open-source GitHub organization was updated:
|
||||
`https://github.com/codium-ai/pr-agent` →
|
||||
`https://github.com/qodo-ai/pr-agent`
|
||||
|
||||
### June 17, 2024
|
||||
The docker should be redirected automatically to the new location.
|
||||
However, if you have any issues, please update the GitHub action docker image from
|
||||
`uses: Codium-ai/pr-agent@main`
|
||||
to
|
||||
`uses: qodo-ai/pr-agent@main`
|
||||
|
||||
New option for a self-review checkbox is now available for the `/improve` tool, along with the ability(💎) to enable auto-approve, or demand self-review in addition to human reviewer. See more [here](https://pr-agent-docs.codium.ai/tools/improve/#self-review).
|
||||
|
||||
<kbd><img src="https://www.codium.ai/images/pr_agent/self_review_1.png" width="512"></kbd>
|
||||
### Jan 2, 2025
|
||||
|
||||
### June 6, 2024
|
||||
New tool [/Implement](https://qodo-merge-docs.qodo.ai/tools/implement/) (💎), which converts human code review discussions and feedback into ready-to-commit code changes.
|
||||
|
||||
New option now available (💎) - **apply suggestions**:
|
||||
<kbd><img src="https://www.qodo.ai/images/pr_agent/implement1.png" width="512"></kbd>
|
||||
|
||||
<kbd><img src="https://www.codium.ai/images/pr_agent/apply_suggestion_1.png" width="512"></kbd>
|
||||
|
||||
→
|
||||
### Jan 1, 2025
|
||||
|
||||
<kbd><img src="https://www.codium.ai/images/pr_agent/apply_suggestion_2.png" width="512"></kbd>
|
||||
Update logic and [documentation](https://qodo-merge-docs.qodo.ai/usage-guide/changing_a_model/#ollama) for running local models via Ollama.
|
||||
|
||||
### December 30, 2024
|
||||
|
||||
Following [feedback](https://research.kudelskisecurity.com/2024/08/29/careful-where-you-code-multiple-vulnerabilities-in-ai-powered-pr-agent/) from the community, we have addressed two vulnerabilities identified in the open-source PR-Agent project. The fixes are now included in the newly released version (v0.26), available as of today.
|
||||
|
||||
### December 25, 2024
|
||||
|
||||
The `review` tool previously included a legacy feature for providing code suggestions (controlled by '--pr_reviewer.num_code_suggestion'). This functionality has been deprecated. Use instead the [`improve`](https://qodo-merge-docs.qodo.ai/tools/improve/) tool, which offers higher quality and more actionable code suggestions.
|
||||
|
||||
### December 2, 2024
|
||||
|
||||
Open-source repositories can now freely use Qodo Merge, and enjoy easy one-click installation using a marketplace [app](https://github.com/apps/qodo-merge-pro-for-open-source).
|
||||
|
||||
<kbd><img src="https://github.com/user-attachments/assets/b0838724-87b9-43b0-ab62-73739a3a855c" width="512"></kbd>
|
||||
|
||||
See [here](https://qodo-merge-docs.qodo.ai/installation/pr_agent_pro/) for more details about installing Qodo Merge for private repositories.
|
||||
|
||||
|
||||
### November 18, 2024
|
||||
|
||||
A new mode was enabled by default for code suggestions - `--pr_code_suggestions.focus_only_on_problems=true`:
|
||||
|
||||
- This option reduces the number of code suggestions received
|
||||
- The suggestions will focus more on identifying and fixing code problems, rather than style considerations like best practices, maintainability, or readability.
|
||||
- The suggestions will be categorized into just two groups: "Possible Issues" and "General".
|
||||
|
||||
Still, if you prefer the previous mode, you can set `--pr_code_suggestions.focus_only_on_problems=false` in the [configuration file](https://qodo-merge-docs.qodo.ai/usage-guide/configuration_options/).
|
||||
|
||||
**Example results:**
|
||||
|
||||
Original mode
|
||||
|
||||
<kbd><img src="https://qodo.ai/images/pr_agent/code_suggestions_original_mode.png" width="512"></kbd>
|
||||
|
||||
Focused mode
|
||||
|
||||
<kbd><img src="https://qodo.ai/images/pr_agent/code_suggestions_focused_mode.png" width="512"></kbd>
|
||||
|
||||
|
||||
## Overview
|
||||
@ -69,70 +110,76 @@ New option now available (💎) - **apply suggestions**:
|
||||
|
||||
Supported commands per platform:
|
||||
|
||||
| | | GitHub | Gitlab | Bitbucket | Azure DevOps |
|
||||
|-------|---------------------------------------------------------------------------------------------------------|:--------------------:|:--------------------:|:--------------------:|:--------------------:|
|
||||
| TOOLS | Review | ✅ | ✅ | ✅ | ✅ |
|
||||
| | ⮑ Incremental | ✅ | | | |
|
||||
| | ⮑ [SOC2 Compliance](https://pr-agent-docs.codium.ai/tools/review/#soc2-ticket-compliance) 💎 | ✅ | ✅ | ✅ | ✅ |
|
||||
| | Describe | ✅ | ✅ | ✅ | ✅ |
|
||||
| | ⮑ [Inline File Summary](https://pr-agent-docs.codium.ai/tools/describe#inline-file-summary) 💎 | ✅ | | | |
|
||||
| | Improve | ✅ | ✅ | ✅ | ✅ |
|
||||
| | ⮑ Extended | ✅ | ✅ | ✅ | ✅ |
|
||||
| | Ask | ✅ | ✅ | ✅ | ✅ |
|
||||
| | ⮑ [Ask on code lines](https://pr-agent-docs.codium.ai/tools/ask#ask-lines) | ✅ | ✅ | | |
|
||||
| | [Custom Prompt](https://pr-agent-docs.codium.ai/tools/custom_prompt/) 💎 | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [Test](https://pr-agent-docs.codium.ai/tools/test/) 💎 | ✅ | ✅ | | ✅ |
|
||||
| | Reflect and Review | ✅ | ✅ | ✅ | ✅ |
|
||||
| | Update CHANGELOG.md | ✅ | ✅ | ✅ | ✅ |
|
||||
| | Find Similar Issue | ✅ | | | |
|
||||
| | [Add PR Documentation](https://pr-agent-docs.codium.ai/tools/documentation/) 💎 | ✅ | ✅ | | ✅ |
|
||||
| | [Custom Labels](https://pr-agent-docs.codium.ai/tools/custom_labels/) 💎 | ✅ | ✅ | | ✅ |
|
||||
| | [Analyze](https://pr-agent-docs.codium.ai/tools/analyze/) 💎 | ✅ | ✅ | | ✅ |
|
||||
| | [CI Feedback](https://pr-agent-docs.codium.ai/tools/ci_feedback/) 💎 | ✅ | | | |
|
||||
| | [Similar Code](https://pr-agent-docs.codium.ai/tools/similar_code/) 💎 | ✅ | | | |
|
||||
| | | GitHub | GitLab | Bitbucket | Azure DevOps |
|
||||
|-------|---------------------------------------------------------------------------------------------------------|:--------------------:|:--------------------:|:--------------------:|:------------:|
|
||||
| TOOLS | [Review](https://qodo-merge-docs.qodo.ai/tools/review/) | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [Describe](https://qodo-merge-docs.qodo.ai/tools/describe/) | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [Improve](https://qodo-merge-docs.qodo.ai/tools/improve/) | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [Ask](https://qodo-merge-docs.qodo.ai/tools/ask/) | ✅ | ✅ | ✅ | ✅ |
|
||||
| | ⮑ [Ask on code lines](https://qodo-merge-docs.qodo.ai/tools/ask/#ask-lines) | ✅ | ✅ | | |
|
||||
| | [Update CHANGELOG](https://qodo-merge-docs.qodo.ai/tools/update_changelog/) | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [Ticket Context](https://qodo-merge-docs.qodo.ai/core-abilities/fetching_ticket_context/) 💎 | ✅ | ✅ | ✅ | |
|
||||
| | [Utilizing Best Practices](https://qodo-merge-docs.qodo.ai/tools/improve/#best-practices) 💎 | ✅ | ✅ | ✅ | |
|
||||
| | [PR Chat](https://qodo-merge-docs.qodo.ai/chrome-extension/features/#pr-chat) 💎 | ✅ | | | |
|
||||
| | [Suggestion Tracking](https://qodo-merge-docs.qodo.ai/tools/improve/#suggestion-tracking) 💎 | ✅ | ✅ | | |
|
||||
| | [CI Feedback](https://qodo-merge-docs.qodo.ai/tools/ci_feedback/) 💎 | ✅ | | | |
|
||||
| | [PR Documentation](https://qodo-merge-docs.qodo.ai/tools/documentation/) 💎 | ✅ | ✅ | | |
|
||||
| | [Custom Labels](https://qodo-merge-docs.qodo.ai/tools/custom_labels/) 💎 | ✅ | ✅ | | |
|
||||
| | [Analyze](https://qodo-merge-docs.qodo.ai/tools/analyze/) 💎 | ✅ | ✅ | | |
|
||||
| | [Similar Code](https://qodo-merge-docs.qodo.ai/tools/similar_code/) 💎 | ✅ | | | |
|
||||
| | [Custom Prompt](https://qodo-merge-docs.qodo.ai/tools/custom_prompt/) 💎 | ✅ | ✅ | ✅ | |
|
||||
| | [Test](https://qodo-merge-docs.qodo.ai/tools/test/) 💎 | ✅ | ✅ | | |
|
||||
| | [Implement](https://qodo-merge-docs.qodo.ai/tools/implement/) 💎 | ✅ | ✅ | ✅ | |
|
||||
| | | | | | |
|
||||
| USAGE | CLI | ✅ | ✅ | ✅ | ✅ |
|
||||
| | App / webhook | ✅ | ✅ | ✅ | ✅ |
|
||||
| | Tagging bot | ✅ | | | |
|
||||
| | Actions | ✅ | | ✅ | |
|
||||
| USAGE | [CLI](https://qodo-merge-docs.qodo.ai/usage-guide/automations_and_usage/#local-repo-cli) | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [App / webhook](https://qodo-merge-docs.qodo.ai/usage-guide/automations_and_usage/#github-app) | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [Tagging bot](https://github.com/Codium-ai/pr-agent#try-it-now) | ✅ | | | |
|
||||
| | [Actions](https://qodo-merge-docs.qodo.ai/installation/github/#run-as-a-github-action) | ✅ |✅| ✅ |✅|
|
||||
| | | | | | |
|
||||
| CORE | PR compression | ✅ | ✅ | ✅ | ✅ |
|
||||
| | Repo language prioritization | ✅ | ✅ | ✅ | ✅ |
|
||||
| CORE | [PR compression](https://qodo-merge-docs.qodo.ai/core-abilities/compression_strategy/) | ✅ | ✅ | ✅ | ✅ |
|
||||
| | Adaptive and token-aware file patch fitting | ✅ | ✅ | ✅ | ✅ |
|
||||
| | Multiple models support | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [Static code analysis](https://pr-agent-docs.codium.ai/core-abilities/#static-code-analysis) 💎 | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [Global and wiki configurations](https://pr-agent-docs.codium.ai/usage-guide/configuration_options/) 💎 | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [PR interactive actions](https://www.codium.ai/images/pr_agent/pr-actions.mp4) 💎 | ✅ | | | |
|
||||
- 💎 means this feature is available only in [PR-Agent Pro](https://www.codium.ai/pricing/)
|
||||
| | [Multiple models support](https://qodo-merge-docs.qodo.ai/usage-guide/changing_a_model/) | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [Local and global metadata](https://qodo-merge-docs.qodo.ai/core-abilities/metadata/) | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [Dynamic context](https://qodo-merge-docs.qodo.ai/core-abilities/dynamic_context/) | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [Self reflection](https://qodo-merge-docs.qodo.ai/core-abilities/self_reflection/) | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [Static code analysis](https://qodo-merge-docs.qodo.ai/core-abilities/static_code_analysis/) 💎 | ✅ | ✅ | ✅ | |
|
||||
| | [Global and wiki configurations](https://qodo-merge-docs.qodo.ai/usage-guide/configuration_options/) 💎 | ✅ | ✅ | ✅ | |
|
||||
| | [PR interactive actions](https://www.qodo.ai/images/pr_agent/pr-actions.mp4) 💎 | ✅ | ✅ | | |
|
||||
| | [Impact Evaluation](https://qodo-merge-docs.qodo.ai/core-abilities/impact_evaluation/) 💎 | ✅ | ✅ | | |
|
||||
- 💎 means this feature is available only in [Qodo-Merge](https://www.qodo.ai/pricing/)
|
||||
|
||||
[//]: # (- Support for additional git providers is described in [here](./docs/Full_environments.md))
|
||||
___
|
||||
|
||||
‣ **Auto Description ([`/describe`](https://pr-agent-docs.codium.ai/tools/describe/))**: Automatically generating PR description - title, type, summary, code walkthrough and labels.
|
||||
‣ **Auto Description ([`/describe`](https://qodo-merge-docs.qodo.ai/tools/describe/))**: Automatically generating PR description - title, type, summary, code walkthrough and labels.
|
||||
\
|
||||
‣ **Auto Review ([`/review`](https://pr-agent-docs.codium.ai/tools/review/))**: Adjustable feedback about the PR, possible issues, security concerns, review effort and more.
|
||||
‣ **Auto Review ([`/review`](https://qodo-merge-docs.qodo.ai/tools/review/))**: Adjustable feedback about the PR, possible issues, security concerns, review effort and more.
|
||||
\
|
||||
‣ **Code Suggestions ([`/improve`](https://pr-agent-docs.codium.ai/tools/improve/))**: Code suggestions for improving the PR.
|
||||
‣ **Code Suggestions ([`/improve`](https://qodo-merge-docs.qodo.ai/tools/improve/))**: Code suggestions for improving the PR.
|
||||
\
|
||||
‣ **Question Answering ([`/ask ...`](https://pr-agent-docs.codium.ai/tools/ask/))**: Answering free-text questions about the PR.
|
||||
‣ **Question Answering ([`/ask ...`](https://qodo-merge-docs.qodo.ai/tools/ask/))**: Answering free-text questions about the PR.
|
||||
\
|
||||
‣ **Update Changelog ([`/update_changelog`](https://pr-agent-docs.codium.ai/tools/update_changelog/))**: Automatically updating the CHANGELOG.md file with the PR changes.
|
||||
‣ **Update Changelog ([`/update_changelog`](https://qodo-merge-docs.qodo.ai/tools/update_changelog/))**: Automatically updating the CHANGELOG.md file with the PR changes.
|
||||
\
|
||||
‣ **Find Similar Issue ([`/similar_issue`](https://pr-agent-docs.codium.ai/tools/similar_issues/))**: Automatically retrieves and presents similar issues.
|
||||
‣ **Find Similar Issue ([`/similar_issue`](https://qodo-merge-docs.qodo.ai/tools/similar_issues/))**: Automatically retrieves and presents similar issues.
|
||||
\
|
||||
‣ **Add Documentation 💎 ([`/add_docs`](https://pr-agent-docs.codium.ai/tools/documentation/))**: Generates documentation to methods/functions/classes that changed in the PR.
|
||||
‣ **Add Documentation 💎 ([`/add_docs`](https://qodo-merge-docs.qodo.ai/tools/documentation/))**: Generates documentation to methods/functions/classes that changed in the PR.
|
||||
\
|
||||
‣ **Generate Custom Labels 💎 ([`/generate_labels`](https://pr-agent-docs.codium.ai/tools/custom_labels/))**: Generates custom labels for the PR, based on specific guidelines defined by the user.
|
||||
‣ **Generate Custom Labels 💎 ([`/generate_labels`](https://qodo-merge-docs.qodo.ai/tools/custom_labels/))**: Generates custom labels for the PR, based on specific guidelines defined by the user.
|
||||
\
|
||||
‣ **Analyze 💎 ([`/analyze`](https://pr-agent-docs.codium.ai/tools/analyze/))**: Identify code components that changed in the PR, and enables to interactively generate tests, docs, and code suggestions for each component.
|
||||
‣ **Analyze 💎 ([`/analyze`](https://qodo-merge-docs.qodo.ai/tools/analyze/))**: Identify code components that changed in the PR, and enables to interactively generate tests, docs, and code suggestions for each component.
|
||||
\
|
||||
‣ **Custom Prompt 💎 ([`/custom_prompt`](https://pr-agent-docs.codium.ai/tools/custom_prompt/))**: Automatically generates custom suggestions for improving the PR code, based on specific guidelines defined by the user.
|
||||
‣ **Test 💎 ([`/test`](https://qodo-merge-docs.qodo.ai/tools/test/))**: Generate tests for a selected component, based on the PR code changes.
|
||||
\
|
||||
‣ **Generate Tests 💎 ([`/test component_name`](https://pr-agent-docs.codium.ai/tools/test/))**: Generates unit tests for a selected component, based on the PR code changes.
|
||||
‣ **Custom Prompt 💎 ([`/custom_prompt`](https://qodo-merge-docs.qodo.ai/tools/custom_prompt/))**: Automatically generates custom suggestions for improving the PR code, based on specific guidelines defined by the user.
|
||||
\
|
||||
‣ **CI Feedback 💎 ([`/checks ci_job`](https://pr-agent-docs.codium.ai/tools/ci_feedback/))**: Automatically generates feedback and analysis for a failed CI job.
|
||||
‣ **Generate Tests 💎 ([`/test component_name`](https://qodo-merge-docs.qodo.ai/tools/test/))**: Generates unit tests for a selected component, based on the PR code changes.
|
||||
\
|
||||
‣ **Similar Code 💎 ([`/find_similar_component`](https://pr-agent-docs.codium.ai/tools/similar_code/))**: Retrieves the most similar code components from inside the organization's codebase, or from open-source code.
|
||||
‣ **CI Feedback 💎 ([`/checks ci_job`](https://qodo-merge-docs.qodo.ai/tools/ci_feedback/))**: Automatically generates feedback and analysis for a failed CI job.
|
||||
\
|
||||
‣ **Similar Code 💎 ([`/find_similar_component`](https://qodo-merge-docs.qodo.ai/tools/similar_code/))**: Retrieves the most similar code components from inside the organization's codebase, or from open-source code.
|
||||
\
|
||||
‣ **Implement 💎 ([`/implement`](https://qodo-merge-docs.qodo.ai/tools/implement/))**: Generates implementation code from review suggestions.
|
||||
___
|
||||
|
||||
## Example results
|
||||
@ -163,50 +210,8 @@ ___
|
||||
</kbd>
|
||||
</p>
|
||||
</div>
|
||||
<hr>
|
||||
|
||||
<h4><a href="https://github.com/Codium-ai/pr-agent/pull/530">/generate_labels</a></h4>
|
||||
<div align="center">
|
||||
<p float="center">
|
||||
<kbd><img src="https://www.codium.ai/images/pr_agent/geneare_custom_labels_main_short.png" width="300"></kbd>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
[//]: # (<h4><a href="https://github.com/Codium-ai/pr-agent/pull/78#issuecomment-1639739496">/reflect_and_review:</a></h4>)
|
||||
|
||||
[//]: # (<div align="center">)
|
||||
|
||||
[//]: # (<p float="center">)
|
||||
|
||||
[//]: # (<img src="https://www.codium.ai/images/reflect_and_review.gif" width="800">)
|
||||
|
||||
[//]: # (</p>)
|
||||
|
||||
[//]: # (</div>)
|
||||
|
||||
[//]: # (<h4><a href="https://github.com/Codium-ai/pr-agent/pull/229#issuecomment-1695020538">/ask:</a></h4>)
|
||||
|
||||
[//]: # (<div align="center">)
|
||||
|
||||
[//]: # (<p float="center">)
|
||||
|
||||
[//]: # (<img src="https://www.codium.ai/images/ask-2.gif" width="800">)
|
||||
|
||||
[//]: # (</p>)
|
||||
|
||||
[//]: # (</div>)
|
||||
|
||||
[//]: # (<h4><a href="https://github.com/Codium-ai/pr-agent/pull/229#issuecomment-1695024952">/improve:</a></h4>)
|
||||
|
||||
[//]: # (<div align="center">)
|
||||
|
||||
[//]: # (<p float="center">)
|
||||
|
||||
[//]: # (<img src="https://www.codium.ai/images/improve-2.gif" width="800">)
|
||||
|
||||
[//]: # (</p>)
|
||||
|
||||
[//]: # (</div>)
|
||||
<div align="left">
|
||||
|
||||
|
||||
@ -221,61 +226,28 @@ For example, add a comment to any pull request with the following text:
|
||||
```
|
||||
@CodiumAI-Agent /review
|
||||
```
|
||||
and the agent will respond with a review of your PR
|
||||
and the agent will respond with a review of your PR.
|
||||
|
||||
Note that this is a promotional bot, suitable only for initial experimentation.
|
||||
It does not have 'edit' access to your repo, for example, so it cannot update the PR description or add labels (`@CodiumAI-Agent /describe` will publish PR description as a comment). In addition, the bot cannot be used on private repositories, as it does not have access to the files there.
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
To set up your own PR-Agent, see the [Installation](https://pr-agent-docs.codium.ai/installation/) section below.
|
||||
Note that when you set your own PR-Agent or use CodiumAI hosted PR-Agent, there is no need to mention `@CodiumAI-Agent ...`. Instead, directly start with the command, e.g., `/ask ...`.
|
||||
To set up your own PR-Agent, see the [Installation](https://qodo-merge-docs.qodo.ai/installation/) section below.
|
||||
Note that when you set your own PR-Agent or use Qodo hosted PR-Agent, there is no need to mention `@CodiumAI-Agent ...`. Instead, directly start with the command, e.g., `/ask ...`.
|
||||
|
||||
---
|
||||
|
||||
[//]: # (## Installation)
|
||||
|
||||
[//]: # (To use your own version of PR-Agent, you first need to acquire two tokens:)
|
||||
|
||||
[//]: # ()
|
||||
[//]: # (1. An OpenAI key from [here](https://platform.openai.com/), with access to GPT-4.)
|
||||
|
||||
[//]: # (2. A GitHub personal access token (classic) with the repo scope.)
|
||||
|
||||
[//]: # ()
|
||||
[//]: # (There are several ways to use PR-Agent:)
|
||||
|
||||
[//]: # ()
|
||||
[//]: # (**Locally**)
|
||||
|
||||
[//]: # (- [Using pip package](https://pr-agent-docs.codium.ai/installation/locally/#using-pip-package))
|
||||
|
||||
[//]: # (- [Using Docker image](https://pr-agent-docs.codium.ai/installation/locally/#using-docker-image))
|
||||
|
||||
[//]: # (- [Run from source](https://pr-agent-docs.codium.ai/installation/locally/#run-from-source))
|
||||
|
||||
[//]: # ()
|
||||
[//]: # (**GitHub specific methods**)
|
||||
|
||||
[//]: # (- [Run as a GitHub Action](https://pr-agent-docs.codium.ai/installation/github/#run-as-a-github-action))
|
||||
|
||||
[//]: # (- [Run as a GitHub App](https://pr-agent-docs.codium.ai/installation/github/#run-as-a-github-app))
|
||||
|
||||
[//]: # ()
|
||||
[//]: # (**GitLab specific methods**)
|
||||
|
||||
[//]: # (- [Run a GitLab webhook server](https://pr-agent-docs.codium.ai/installation/gitlab/))
|
||||
|
||||
[//]: # ()
|
||||
[//]: # (**BitBucket specific methods**)
|
||||
|
||||
[//]: # (- [Run as a Bitbucket Pipeline](https://pr-agent-docs.codium.ai/installation/bitbucket/))
|
||||
|
||||
## PR-Agent Pro 💎
|
||||
[PR-Agent Pro](https://www.codium.ai/pricing/) is a hosted version of PR-Agent, provided by CodiumAI. It is available for a monthly fee, and provides the following benefits:
|
||||
1. **Fully managed** - We take care of everything for you - hosting, models, regular updates, and more. Installation is as simple as signing up and adding the PR-Agent app to your GitHub\GitLab\BitBucket repo.
|
||||
2. **Improved privacy** - No data will be stored or used to train models. PR-Agent Pro will employ zero data retention, and will use an OpenAI account with zero data retention.
|
||||
3. **Improved support** - PR-Agent Pro users will receive priority support, and will be able to request new features and capabilities.
|
||||
4. **Extra features** -In addition to the benefits listed above, PR-Agent Pro will emphasize more customization, and the usage of static code analysis, in addition to LLM logic, to improve results.
|
||||
See [here](https://pr-agent-docs.codium.ai/#pr-agent-pro) for a list of features available in PR-Agent Pro.
|
||||
## Qodo Merge 💎
|
||||
[Qodo Merge](https://www.qodo.ai/pricing/) is a hosted version of PR-Agent, provided by Qodo. It is available for a monthly fee, and provides the following benefits:
|
||||
1. **Fully managed** - We take care of everything for you - hosting, models, regular updates, and more. Installation is as simple as signing up and adding the Qodo Merge app to your GitHub\GitLab\BitBucket repo.
|
||||
2. **Improved privacy** - No data will be stored or used to train models. Qodo Merge will employ zero data retention, and will use an OpenAI account with zero data retention.
|
||||
3. **Improved support** - Qodo Merge users will receive priority support, and will be able to request new features and capabilities.
|
||||
4. **Extra features** -In addition to the benefits listed above, Qodo Merge will emphasize more customization, and the usage of static code analysis, in addition to LLM logic, to improve results.
|
||||
See [here](https://qodo-merge-docs.qodo.ai/overview/pr_agent_pro/) for a list of features available in Qodo Merge.
|
||||
|
||||
|
||||
|
||||
@ -283,9 +255,9 @@ See [here](https://pr-agent-docs.codium.ai/#pr-agent-pro) for a list of features
|
||||
|
||||
The following diagram illustrates PR-Agent tools and their flow:
|
||||
|
||||

|
||||

|
||||
|
||||
Check out the [PR Compression strategy](https://pr-agent-docs.codium.ai/core-abilities/#pr-compression-strategy) page for more details on how we convert a code diff to a manageable LLM prompt
|
||||
Check out the [PR Compression strategy](https://qodo-merge-docs.qodo.ai/core-abilities/#pr-compression-strategy) page for more details on how we convert a code diff to a manageable LLM prompt
|
||||
|
||||
## Why use PR-Agent?
|
||||
|
||||
@ -294,7 +266,7 @@ A reasonable question that can be asked is: `"Why use PR-Agent? What makes it st
|
||||
Here are some advantages of PR-Agent:
|
||||
|
||||
- We emphasize **real-life practical usage**. Each tool (review, improve, ask, ...) has a single GPT-4 call, no more. We feel that this is critical for realistic team usage - obtaining an answer quickly (~30 seconds) and affordably.
|
||||
- Our [PR Compression strategy](https://pr-agent-docs.codium.ai/core-abilities/#pr-compression-strategy) is a core ability that enables to effectively tackle both short and long PRs.
|
||||
- Our [PR Compression strategy](https://qodo-merge-docs.qodo.ai/core-abilities/#pr-compression-strategy) is a core ability that enables to effectively tackle both short and long PRs.
|
||||
- Our JSON prompting strategy enables to have **modular, customizable tools**. For example, the '/review' tool categories can be controlled via the [configuration](pr_agent/settings/configuration.toml) file. Adding additional categories is easy and accessible.
|
||||
- We support **multiple git providers** (GitHub, Gitlab, Bitbucket), **multiple ways** to use the tool (CLI, GitHub Action, GitHub App, Docker, ...), and **multiple models** (GPT-4, GPT-3.5, Anthropic, Cohere, Llama2).
|
||||
|
||||
@ -306,24 +278,24 @@ Here are some advantages of PR-Agent:
|
||||
- If you host PR-Agent with your OpenAI API key, it is between you and OpenAI. You can read their API data privacy policy here:
|
||||
https://openai.com/enterprise-privacy
|
||||
|
||||
### CodiumAI-hosted PR-Agent Pro 💎
|
||||
### Qodo-hosted Qodo Merge 💎
|
||||
|
||||
- When using PR-Agent Pro 💎, hosted by CodiumAI, we will not store any of your data, nor will we use it for training. You will also benefit from an OpenAI account with zero data retention.
|
||||
- When using Qodo Merge 💎, hosted by Qodo, we will not store any of your data, nor will we use it for training. You will also benefit from an OpenAI account with zero data retention.
|
||||
|
||||
- For certain clients, CodiumAI-hosted PR-Agent Pro will use CodiumAI’s proprietary models — if this is the case, you will be notified.
|
||||
- For certain clients, Qodo-hosted Qodo Merge will use Qodo’s proprietary models — if this is the case, you will be notified.
|
||||
|
||||
- No passive collection of Code and Pull Requests’ data — PR-Agent will be active only when you invoke it, and it will then extract and analyze only data relevant to the executed command and queried pull request.
|
||||
- No passive collection of Code and Pull Requests’ data — Qodo Merge will be active only when you invoke it, and it will then extract and analyze only data relevant to the executed command and queried pull request.
|
||||
|
||||
### PR-Agent Chrome extension
|
||||
### Qodo Merge Chrome extension
|
||||
|
||||
- The [PR-Agent Chrome extension](https://chromewebstore.google.com/detail/pr-agent-chrome-extension/ephlnjeghhogofkifjloamocljapahnl) serves solely to modify the visual appearance of a GitHub PR screen. It does not transmit any user's repo or pull request code. Code is only sent for processing when a user submits a GitHub comment that activates a PR-Agent tool, in accordance with the standard privacy policy of PR-Agent.
|
||||
- The [Qodo Merge Chrome extension](https://chromewebstore.google.com/detail/qodo-merge-ai-powered-cod/ephlnjeghhogofkifjloamocljapahnl) serves solely to modify the visual appearance of a GitHub PR screen. It does not transmit any user's repo or pull request code. Code is only sent for processing when a user submits a GitHub comment that activates a PR-Agent tool, in accordance with the standard privacy policy of Qodo-Merge.
|
||||
|
||||
## Links
|
||||
|
||||
[](https://discord.gg/kG35uSHDBc)
|
||||
|
||||
- Discord community: https://discord.gg/kG35uSHDBc
|
||||
- CodiumAI site: https://codium.ai
|
||||
- Blog: https://www.codium.ai/blog/
|
||||
- Troubleshooting: https://www.codium.ai/blog/technical-faq-and-troubleshooting/
|
||||
- Support: support@codium.ai
|
||||
- Qodo site: https://www.qodo.ai/
|
||||
- Blog: https://www.qodo.ai/blog/
|
||||
- Troubleshooting: https://www.qodo.ai/blog/technical-faq-and-troubleshooting/
|
||||
- Support: support@qodo.ai
|
||||
|
5
codecov.yml
Normal file
5
codecov.yml
Normal file
@ -0,0 +1,5 @@
|
||||
comment: false
|
||||
coverage:
|
||||
status:
|
||||
patch: false
|
||||
project: false
|
@ -1,41 +1,42 @@
|
||||
FROM python:3.10 as base
|
||||
FROM python:3.12.3 AS base
|
||||
|
||||
WORKDIR /app
|
||||
ADD pyproject.toml .
|
||||
ADD requirements.txt .
|
||||
ADD docs docs
|
||||
RUN pip install . && rm pyproject.toml requirements.txt
|
||||
ENV PYTHONPATH=/app
|
||||
|
||||
FROM base as github_app
|
||||
FROM base AS github_app
|
||||
ADD pr_agent pr_agent
|
||||
CMD ["python", "-m", "gunicorn", "-k", "uvicorn.workers.UvicornWorker", "-c", "pr_agent/servers/gunicorn_config.py", "--forwarded-allow-ips", "*", "pr_agent.servers.github_app:app"]
|
||||
|
||||
FROM base as bitbucket_app
|
||||
FROM base AS bitbucket_app
|
||||
ADD pr_agent pr_agent
|
||||
CMD ["python", "pr_agent/servers/bitbucket_app.py"]
|
||||
|
||||
FROM base as bitbucket_server_webhook
|
||||
FROM base AS bitbucket_server_webhook
|
||||
ADD pr_agent pr_agent
|
||||
CMD ["python", "pr_agent/servers/bitbucket_server_webhook.py"]
|
||||
|
||||
FROM base as github_polling
|
||||
FROM base AS github_polling
|
||||
ADD pr_agent pr_agent
|
||||
CMD ["python", "pr_agent/servers/github_polling.py"]
|
||||
|
||||
FROM base as gitlab_webhook
|
||||
FROM base AS gitlab_webhook
|
||||
ADD pr_agent pr_agent
|
||||
CMD ["python", "pr_agent/servers/gitlab_webhook.py"]
|
||||
|
||||
FROM base as azure_devops_webhook
|
||||
FROM base AS azure_devops_webhook
|
||||
ADD pr_agent pr_agent
|
||||
CMD ["python", "pr_agent/servers/azuredevops_server_webhook.py"]
|
||||
|
||||
FROM base as test
|
||||
FROM base AS test
|
||||
ADD requirements-dev.txt .
|
||||
RUN pip install -r requirements-dev.txt && rm requirements-dev.txt
|
||||
ADD pr_agent pr_agent
|
||||
ADD tests tests
|
||||
|
||||
FROM base as cli
|
||||
FROM base AS cli
|
||||
ADD pr_agent pr_agent
|
||||
ENTRYPOINT ["python", "pr_agent/cli.py"]
|
||||
|
@ -1 +1 @@
|
||||
# [Visit Our Docs Portal](https://pr-agent-docs.codium.ai/)
|
||||
# [Visit Our Docs Portal](https://qodo-merge-docs.qodo.ai/)
|
||||
|
@ -1 +1 @@
|
||||
pr-agent-docs.codium.ai
|
||||
qodo-merge-docs.qodo.ai
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 4.2 KiB |
@ -1,140 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 28.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="64px" height="64px" viewBox="0 0 64 64" enable-background="new 0 0 64 64" xml:space="preserve">
|
||||
<g>
|
||||
<defs>
|
||||
<rect id="SVGID_1_" x="0.4" y="0.1" width="63.4" height="63.4"/>
|
||||
</defs>
|
||||
<clipPath id="SVGID_00000008836131916906499950000015813697852011234749_">
|
||||
<use xlink:href="#SVGID_1_" overflow="visible"/>
|
||||
</clipPath>
|
||||
<g clip-path="url(#SVGID_00000008836131916906499950000015813697852011234749_)">
|
||||
<path fill="#05E5AD" d="M21.4,9.8c3,0,5.9,0.7,8.5,1.9c-5.7,3.4-9.8,11.1-9.8,20.1c0,9,4,16.7,9.8,20.1c-2.6,1.2-5.5,1.9-8.5,1.9
|
||||
c-11.6,0-21-9.8-21-22S9.8,9.8,21.4,9.8z"/>
|
||||
|
||||
<radialGradient id="SVGID_00000150822754378345238340000008985053211526864828_" cx="-140.0905" cy="350.1757" r="4.8781" gradientTransform="matrix(-4.7708 -6.961580e-02 -0.1061 7.2704 -601.3099 -2523.8489)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" style="stop-color:#6447FF"/>
|
||||
<stop offset="6.666670e-02" style="stop-color:#6348FE"/>
|
||||
<stop offset="0.1333" style="stop-color:#614DFC"/>
|
||||
<stop offset="0.2" style="stop-color:#5C54F8"/>
|
||||
<stop offset="0.2667" style="stop-color:#565EF3"/>
|
||||
<stop offset="0.3333" style="stop-color:#4E6CEC"/>
|
||||
<stop offset="0.4" style="stop-color:#447BE4"/>
|
||||
<stop offset="0.4667" style="stop-color:#3A8DDB"/>
|
||||
<stop offset="0.5333" style="stop-color:#2F9FD1"/>
|
||||
<stop offset="0.6" style="stop-color:#25B1C8"/>
|
||||
<stop offset="0.6667" style="stop-color:#1BC0C0"/>
|
||||
<stop offset="0.7333" style="stop-color:#13CEB9"/>
|
||||
<stop offset="0.8" style="stop-color:#0DD8B4"/>
|
||||
<stop offset="0.8667" style="stop-color:#08DFB0"/>
|
||||
<stop offset="0.9333" style="stop-color:#06E4AE"/>
|
||||
<stop offset="1" style="stop-color:#05E5AD"/>
|
||||
</radialGradient>
|
||||
<path fill="url(#SVGID_00000150822754378345238340000008985053211526864828_)" d="M21.4,9.8c3,0,5.9,0.7,8.5,1.9
|
||||
c-5.7,3.4-9.8,11.1-9.8,20.1c0,9,4,16.7,9.8,20.1c-2.6,1.2-5.5,1.9-8.5,1.9c-11.6,0-21-9.8-21-22S9.8,9.8,21.4,9.8z"/>
|
||||
|
||||
<radialGradient id="SVGID_00000022560571240417802950000012439139323268113305_" cx="-191.7649" cy="385.7387" r="4.8781" gradientTransform="matrix(-2.5514 -0.7616 -0.8125 2.7217 -130.733 -1180.2209)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" style="stop-color:#6447FF"/>
|
||||
<stop offset="6.666670e-02" style="stop-color:#6348FE"/>
|
||||
<stop offset="0.1333" style="stop-color:#614DFC"/>
|
||||
<stop offset="0.2" style="stop-color:#5C54F8"/>
|
||||
<stop offset="0.2667" style="stop-color:#565EF3"/>
|
||||
<stop offset="0.3333" style="stop-color:#4E6CEC"/>
|
||||
<stop offset="0.4" style="stop-color:#447BE4"/>
|
||||
<stop offset="0.4667" style="stop-color:#3A8DDB"/>
|
||||
<stop offset="0.5333" style="stop-color:#2F9FD1"/>
|
||||
<stop offset="0.6" style="stop-color:#25B1C8"/>
|
||||
<stop offset="0.6667" style="stop-color:#1BC0C0"/>
|
||||
<stop offset="0.7333" style="stop-color:#13CEB9"/>
|
||||
<stop offset="0.8" style="stop-color:#0DD8B4"/>
|
||||
<stop offset="0.8667" style="stop-color:#08DFB0"/>
|
||||
<stop offset="0.9333" style="stop-color:#06E4AE"/>
|
||||
<stop offset="1" style="stop-color:#05E5AD"/>
|
||||
</radialGradient>
|
||||
<path fill="url(#SVGID_00000022560571240417802950000012439139323268113305_)" d="M38,18.3c-2.1-2.8-4.9-5.1-8.1-6.6
|
||||
c2-1.2,4.2-1.9,6.6-1.9c2.2,0,4.3,0.6,6.2,1.7C40.8,12.9,39.2,15.3,38,18.3L38,18.3z"/>
|
||||
|
||||
<radialGradient id="SVGID_00000143611122169386473660000017673587931016751800_" cx="-194.7918" cy="395.2442" r="4.8781" gradientTransform="matrix(-2.5514 -0.7616 -0.8125 2.7217 -130.733 -1172.9556)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" style="stop-color:#6447FF"/>
|
||||
<stop offset="6.666670e-02" style="stop-color:#6348FE"/>
|
||||
<stop offset="0.1333" style="stop-color:#614DFC"/>
|
||||
<stop offset="0.2" style="stop-color:#5C54F8"/>
|
||||
<stop offset="0.2667" style="stop-color:#565EF3"/>
|
||||
<stop offset="0.3333" style="stop-color:#4E6CEC"/>
|
||||
<stop offset="0.4" style="stop-color:#447BE4"/>
|
||||
<stop offset="0.4667" style="stop-color:#3A8DDB"/>
|
||||
<stop offset="0.5333" style="stop-color:#2F9FD1"/>
|
||||
<stop offset="0.6" style="stop-color:#25B1C8"/>
|
||||
<stop offset="0.6667" style="stop-color:#1BC0C0"/>
|
||||
<stop offset="0.7333" style="stop-color:#13CEB9"/>
|
||||
<stop offset="0.8" style="stop-color:#0DD8B4"/>
|
||||
<stop offset="0.8667" style="stop-color:#08DFB0"/>
|
||||
<stop offset="0.9333" style="stop-color:#06E4AE"/>
|
||||
<stop offset="1" style="stop-color:#05E5AD"/>
|
||||
</radialGradient>
|
||||
<path fill="url(#SVGID_00000143611122169386473660000017673587931016751800_)" d="M38,45.2c1.2,3,2.9,5.3,4.7,6.8
|
||||
c-1.9,1.1-4,1.7-6.2,1.7c-2.3,0-4.6-0.7-6.6-1.9C33.1,50.4,35.8,48.1,38,45.2L38,45.2z"/>
|
||||
<path fill="#684BFE" d="M20.1,31.8c0-9,4-16.7,9.8-20.1c3.2,1.5,6,3.8,8.1,6.6c-1.5,3.7-2.5,8.4-2.5,13.5s0.9,9.8,2.5,13.5
|
||||
c-2.1,2.8-4.9,5.1-8.1,6.6C24.1,48.4,20.1,40.7,20.1,31.8z"/>
|
||||
|
||||
<radialGradient id="SVGID_00000147942998054305738810000004710078864578628519_" cx="-212.7358" cy="363.2475" r="4.8781" gradientTransform="matrix(-2.3342 -1.063 -1.623 3.5638 149.3813 -1470.1027)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" style="stop-color:#6447FF"/>
|
||||
<stop offset="6.666670e-02" style="stop-color:#6348FE"/>
|
||||
<stop offset="0.1333" style="stop-color:#614DFC"/>
|
||||
<stop offset="0.2" style="stop-color:#5C54F8"/>
|
||||
<stop offset="0.2667" style="stop-color:#565EF3"/>
|
||||
<stop offset="0.3333" style="stop-color:#4E6CEC"/>
|
||||
<stop offset="0.4" style="stop-color:#447BE4"/>
|
||||
<stop offset="0.4667" style="stop-color:#3A8DDB"/>
|
||||
<stop offset="0.5333" style="stop-color:#2F9FD1"/>
|
||||
<stop offset="0.6" style="stop-color:#25B1C8"/>
|
||||
<stop offset="0.6667" style="stop-color:#1BC0C0"/>
|
||||
<stop offset="0.7333" style="stop-color:#13CEB9"/>
|
||||
<stop offset="0.8" style="stop-color:#0DD8B4"/>
|
||||
<stop offset="0.8667" style="stop-color:#08DFB0"/>
|
||||
<stop offset="0.9333" style="stop-color:#06E4AE"/>
|
||||
<stop offset="1" style="stop-color:#05E5AD"/>
|
||||
</radialGradient>
|
||||
<path fill="url(#SVGID_00000147942998054305738810000004710078864578628519_)" d="M50.7,42.5c0.6,3.3,1.5,6.1,2.5,8
|
||||
c-1.8,2-3.8,3.1-6,3.1c-1.6,0-3.1-0.6-4.5-1.7C46.1,50.2,48.9,46.8,50.7,42.5L50.7,42.5z"/>
|
||||
|
||||
<radialGradient id="SVGID_00000083770737908230256670000016126156495859285174_" cx="-208.5327" cy="357.2025" r="4.8781" gradientTransform="matrix(-2.3342 -1.063 -1.623 3.5638 149.3813 -1476.8097)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" style="stop-color:#6447FF"/>
|
||||
<stop offset="6.666670e-02" style="stop-color:#6348FE"/>
|
||||
<stop offset="0.1333" style="stop-color:#614DFC"/>
|
||||
<stop offset="0.2" style="stop-color:#5C54F8"/>
|
||||
<stop offset="0.2667" style="stop-color:#565EF3"/>
|
||||
<stop offset="0.3333" style="stop-color:#4E6CEC"/>
|
||||
<stop offset="0.4" style="stop-color:#447BE4"/>
|
||||
<stop offset="0.4667" style="stop-color:#3A8DDB"/>
|
||||
<stop offset="0.5333" style="stop-color:#2F9FD1"/>
|
||||
<stop offset="0.6" style="stop-color:#25B1C8"/>
|
||||
<stop offset="0.6667" style="stop-color:#1BC0C0"/>
|
||||
<stop offset="0.7333" style="stop-color:#13CEB9"/>
|
||||
<stop offset="0.8" style="stop-color:#0DD8B4"/>
|
||||
<stop offset="0.8667" style="stop-color:#08DFB0"/>
|
||||
<stop offset="0.9333" style="stop-color:#06E4AE"/>
|
||||
<stop offset="1" style="stop-color:#05E5AD"/>
|
||||
</radialGradient>
|
||||
<path fill="url(#SVGID_00000083770737908230256670000016126156495859285174_)" d="M42.7,11.5c1.4-1.1,2.9-1.7,4.5-1.7
|
||||
c2.2,0,4.3,1.1,6,3.1c-1,2-1.9,4.7-2.5,8C48.9,16.7,46.1,13.4,42.7,11.5L42.7,11.5z"/>
|
||||
<path fill="#684BFE" d="M38,45.2c2.8-3.7,4.4-8.4,4.4-13.5c0-5.1-1.7-9.8-4.4-13.5c1.2-3,2.9-5.3,4.7-6.8c3.4,1.9,6.2,5.3,8,9.5
|
||||
c-0.6,3.2-0.9,6.9-0.9,10.8s0.3,7.6,0.9,10.8c-1.8,4.3-4.6,7.6-8,9.5C40.8,50.6,39.2,48.2,38,45.2L38,45.2z"/>
|
||||
<path fill="#321BB2" d="M38,45.2c-1.5-3.7-2.5-8.4-2.5-13.5S36.4,22,38,18.3c2.8,3.7,4.4,8.4,4.4,13.5S40.8,41.5,38,45.2z"/>
|
||||
<path fill="#05E6AD" d="M53.2,12.9c1.1-2,2.3-3.1,3.6-3.1c3.9,0,7,9.8,7,22s-3.1,22-7,22c-1.3,0-2.6-1.1-3.6-3.1
|
||||
c3.4-3.8,5.7-10.8,5.7-18.8C58.8,23.8,56.6,16.8,53.2,12.9z"/>
|
||||
|
||||
<radialGradient id="SVGID_00000009565123575973598080000009335550354766300606_" cx="-7.8671" cy="278.2442" r="4.8781" gradientTransform="matrix(1.5187 0 0 -7.8271 69.237 2209.3281)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" style="stop-color:#05E5AD"/>
|
||||
<stop offset="0.32" style="stop-color:#05E5AD;stop-opacity:0"/>
|
||||
<stop offset="0.9028" style="stop-color:#6447FF"/>
|
||||
</radialGradient>
|
||||
<path fill="url(#SVGID_00000009565123575973598080000009335550354766300606_)" d="M53.2,12.9c1.1-2,2.3-3.1,3.6-3.1
|
||||
c3.9,0,7,9.8,7,22s-3.1,22-7,22c-1.3,0-2.6-1.1-3.6-3.1c3.4-3.8,5.7-10.8,5.7-18.8C58.8,23.8,56.6,16.8,53.2,12.9z"/>
|
||||
<path fill="#684BFE" d="M52.8,31.8c0-3.9-0.8-7.6-2.1-10.8c0.6-3.3,1.5-6.1,2.5-8c3.4,3.8,5.7,10.8,5.7,18.8c0,8-2.3,15-5.7,18.8
|
||||
c-1-2-1.9-4.7-2.5-8C52,39.3,52.8,35.7,52.8,31.8z"/>
|
||||
<path fill="#321BB2" d="M50.7,42.5c-0.6-3.2-0.9-6.9-0.9-10.8s0.3-7.6,0.9-10.8c1.3,3.2,2.1,6.9,2.1,10.8S52,39.3,50.7,42.5z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 109.77 81.94"><defs><style>.cls-1{fill:#7968fa;}.cls-1,.cls-2{stroke-width:0px;}.cls-2{fill:#5ae3ae;}</style></defs><path class="cls-2" d="m109.77,40.98c0,22.62-7.11,40.96-15.89,40.96-3.6,0-6.89-3.09-9.58-8.31,6.82-7.46,11.22-19.3,11.22-32.64s-4.4-25.21-11.22-32.67C86.99,3.09,90.29,0,93.89,0c8.78,0,15.89,18.33,15.89,40.97"/><path class="cls-1" d="m95.53,40.99c0,13.35-4.4,25.19-11.23,32.64-3.81-7.46-6.28-19.3-6.28-32.64s2.47-25.21,6.28-32.67c6.83,7.46,11.23,19.32,11.23,32.67"/><path class="cls-2" d="m55.38,78.15c-4.99,2.42-10.52,3.79-16.38,3.79C17.46,81.93,0,63.6,0,40.98S17.46,0,39,0C44.86,0,50.39,1.37,55.38,3.79c-9.69,6.47-16.43,20.69-16.43,37.19s6.73,30.7,16.43,37.17"/><path class="cls-1" d="m78.02,40.99c0,16.48-9.27,30.7-22.65,37.17-9.69-6.47-16.43-20.69-16.43-37.17S45.68,10.28,55.38,3.81c13.37,6.49,22.65,20.69,22.65,37.19"/><path class="cls-2" d="m84.31,73.63c-4.73,5.22-10.64,8.31-17.06,8.31-4.24,0-8.27-1.35-11.87-3.79,13.37-6.48,22.65-20.7,22.65-37.17,0,13.35,2.47,25.19,6.28,32.64"/><path class="cls-2" d="m84.31,8.31c-3.81,7.46-6.28,19.32-6.28,32.67,0-16.5-9.27-30.7-22.65-37.19,3.6-2.45,7.63-3.8,11.87-3.8,6.43,0,12.33,3.09,17.06,8.31"/></svg>
|
||||
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 1.2 KiB |
BIN
docs/docs/assets/logo_.png
Normal file
BIN
docs/docs/assets/logo_.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.7 KiB |
4
docs/docs/chrome-extension/data_privacy.md
Normal file
4
docs/docs/chrome-extension/data_privacy.md
Normal file
@ -0,0 +1,4 @@
|
||||
We take your code's security and privacy seriously:
|
||||
|
||||
- The Chrome extension will not send your code to any external servers.
|
||||
- For private repositories, we will first validate the user's identity and permissions. After authentication, we generate responses using the existing Qodo Merge integration.
|
51
docs/docs/chrome-extension/features.md
Normal file
51
docs/docs/chrome-extension/features.md
Normal file
@ -0,0 +1,51 @@
|
||||
|
||||
### PR chat
|
||||
|
||||
The PR-Chat feature allows to freely chat with your PR code, within your GitHub environment.
|
||||
It will seamlessly use the PR as context to your chat session, and provide AI-powered feedback.
|
||||
|
||||
To enable private chat, simply install the Qodo Merge Chrome extension. After installation, each PR's file-changed tab will include a chat box, where you may ask questions about your code.
|
||||
This chat session is **private**, and won't be visible to other users.
|
||||
|
||||
All open-source repositories are supported.
|
||||
For private repositories, you will also need to install Qodo Merge. After installation, make sure to open at least one new PR to fully register your organization. Once done, you can chat with both new and existing PRs across all installed repositories.
|
||||
|
||||
#### Context-aware PR chat
|
||||
|
||||
Qodo Merge constructs a comprehensive context for each pull request, incorporating the PR description, commit messages, and code changes with extended dynamic context. This contextual information, along with additional PR-related data, forms the foundation for an AI-powered chat session. The agent then leverages this rich context to provide intelligent, tailored responses to user inquiries about the pull request.
|
||||
|
||||
<img src="https://codium.ai/images/pr_agent/pr_chat_1.png" width="768">
|
||||
<img src="https://codium.ai/images/pr_agent/pr_chat_2.png" width="768">
|
||||
|
||||
|
||||
### Toolbar extension
|
||||
With Qodo Merge Chrome extension, it's [easier than ever](https://www.youtube.com/watch?v=gT5tli7X4H4) to interactively configure and experiment with the different tools and configuration options.
|
||||
|
||||
For private repositories, after you found the setup that works for you, you can also easily export it as a persistent configuration file, and use it for automatic commands.
|
||||
|
||||
<img src="https://codium.ai/images/pr_agent/toolbar1.png" width="512">
|
||||
|
||||
<img src="https://codium.ai/images/pr_agent/toolbar2.png" width="512">
|
||||
|
||||
### Qodo Merge filters
|
||||
|
||||
Qodo Merge filters is a sidepanel option. that allows you to filter different message in the conversation tab.
|
||||
|
||||
For example, you can choose to present only message from Qodo Merge, or filter those messages, focusing only on user's comments.
|
||||
|
||||
<img src="https://codium.ai/images/pr_agent/pr_agent_filters1.png" width="256">
|
||||
|
||||
<img src="https://codium.ai/images/pr_agent/pr_agent_filters2.png" width="256">
|
||||
|
||||
|
||||
### Enhanced code suggestions
|
||||
|
||||
Qodo Merge Chrome extension adds the following capabilities to code suggestions tool's comments:
|
||||
|
||||
- Auto-expand the table when you are viewing a code block, to avoid clipping.
|
||||
- Adding a "quote-and-reply" button, that enables to address and comment on a specific suggestion (for example, asking the author to fix the issue)
|
||||
|
||||
|
||||
<img src="https://codium.ai/images/pr_agent/chrome_extension_code_suggestion1.png" width="512">
|
||||
|
||||
<img src="https://codium.ai/images/pr_agent/chrome_extension_code_suggestion2.png" width="512">
|
@ -1,49 +1,14 @@
|
||||
## PR-Agent chrome extension
|
||||
PR-Agent Chrome extension is a collection of tools that integrates seamlessly with your GitHub environment, aiming to enhance your PR-Agent usage experience, and providing additional features.
|
||||
[Qodo Merge Chrome extension](https://chromewebstore.google.com/detail/pr-agent-chrome-extension/ephlnjeghhogofkifjloamocljapahnl){:target="_blank"} is a collection of tools that integrates seamlessly with your GitHub environment, aiming to enhance your Git usage experience, and providing AI-powered capabilities to your PRs.
|
||||
|
||||
## Features
|
||||
With a single-click installation you will gain access to a context-aware chat on your pull requests code, a toolbar extension with multiple AI feedbacks, Qodo Merge filters, and additional abilities.
|
||||
|
||||
### Toolbar extension
|
||||
With PR-Agent Chrome extension, it's [easier than ever](https://www.youtube.com/watch?v=gT5tli7X4H4) to interactively configure and experiment with the different tools and configuration options.
|
||||
The extension is powered by top code models like Claude 3.5 Sonnet and GPT4. All the extension's features are free to use on public repositories.
|
||||
|
||||
After you found the setup that works for you, you can also easily export it as a persistent configuration file, and use it for automatic commands.
|
||||
For private repositories, you will need to install [Qodo Merge](https://github.com/apps/qodo-merge-pro){:target="_blank"} in addition to the extension (Quick GitHub app setup with a 14-day free trial. No credit card needed).
|
||||
For a demonstration of how to install Qodo Merge and use it with the Chrome extension, please refer to the tutorial video at the provided [link](https://codium.ai/images/pr_agent/private_repos.mp4){:target="_blank"}.
|
||||
|
||||
<img src="https://codium.ai/images/pr_agent/toolbar1.png" width="512">
|
||||
<img src="https://codium.ai/images/pr_agent/PR-AgentChat.gif" width="768">
|
||||
|
||||
<img src="https://codium.ai/images/pr_agent/toolbar2.png" width="512">
|
||||
### Supported browsers
|
||||
|
||||
### PR-Agent filters
|
||||
|
||||
PR-Agent filters is a sidepanel option. that allows you to filter different message in the conversation tab.
|
||||
|
||||
For example, you can choose to present only message from PR-Agent, or filter those messages, focusing only on user's comments.
|
||||
|
||||
<img src="https://codium.ai/images/pr_agent/pr_agent_filters1.png" width="256">
|
||||
|
||||
<img src="https://codium.ai/images/pr_agent/pr_agent_filters2.png" width="256">
|
||||
|
||||
|
||||
### Enhanced code suggestions
|
||||
|
||||
PR-Agent Chrome extension adds the following capabilities to code suggestions tool's comments:
|
||||
|
||||
- Auto-expand the table when you are viewing a code block, to avoid clipping.
|
||||
- Adding a "quote-and-reply" button, that enables to address and comment on a specific suggestion (for example, asking the author to fix the issue)
|
||||
|
||||
|
||||
<img src="https://codium.ai/images/pr_agent/chrome_extension_code_suggestion1.png" width="512">
|
||||
|
||||
<img src="https://codium.ai/images/pr_agent/chrome_extension_code_suggestion2.png" width="512">
|
||||
|
||||
## Installation
|
||||
|
||||
Go to the marketplace and install the extension:
|
||||
[PR-Agent Chrome Extension](https://chromewebstore.google.com/detail/pr-agent-chrome-extension/ephlnjeghhogofkifjloamocljapahnl)
|
||||
|
||||
## Pre-requisites
|
||||
|
||||
The PR-Agent Chrome extension will work on any repo where you have previously [installed PR-Agent](https://pr-agent-docs.codium.ai/installation/).
|
||||
|
||||
## Data privacy and security
|
||||
|
||||
The PR-Agent Chrome extension only modifies the visual appearance of a GitHub PR screen. It does not transmit any user's repo or pull request code. Code is only sent for processing when a user submits a GitHub comment that activates a PR-Agent tool, in accordance with the standard privacy policy of PR-Agent.
|
||||
The extension is supported on all Chromium-based browsers, including Google Chrome, Arc, Opera, Brave, and Microsoft Edge.
|
||||
|
69
docs/docs/core-abilities/auto_best_practices.md
Normal file
69
docs/docs/core-abilities/auto_best_practices.md
Normal file
@ -0,0 +1,69 @@
|
||||
# Auto Best Practices 💎
|
||||
`Supported Git Platforms: GitHub`
|
||||
|
||||
## Overview
|
||||
|
||||
{width=684}
|
||||
|
||||
> Note - enabling a [Wiki](https://qodo-merge-docs.qodo.ai/usage-guide/enabling_a_wiki/) is required for this feature.
|
||||
|
||||
### Finding Code Problems - Exploration Phase
|
||||
|
||||
The `improve` tool identifies potential issues, problems and bugs in Pull Request (PR) code changes.
|
||||
Rather than focusing on minor issues like code style or formatting, the tool intelligently analyzes code to detect meaningful problems.
|
||||
|
||||
The analysis intentionally takes a flexible, _exploratory_ approach to identify meaningful potential issues, allowing the tool to surface relevant code suggestions without being constrained by predefined categories.
|
||||
|
||||
### Tracking Implemented Suggestions
|
||||
|
||||
Qodo Merge features a novel [tracking system](https://qodo-merge-docs.qodo.ai/tools/improve/#suggestion-tracking) that automatically detects when PR authors implement AI-generated code suggestions.
|
||||
All accepted suggestions are aggregated in a repository-specific wiki page called [`.pr_agent_accepted_suggestions`](https://github.com/qodo-ai/pr-agent/wiki/.pr_agent_accepted_suggestions)
|
||||
|
||||
|
||||
### Learning and Applying Auto Best Practices
|
||||
|
||||
Monthly, Qodo Merge analyzes the collection of accepted suggestions to generate repository-specific best practices, stored in [`.pr_agent_auto_best_practices`](https://github.com/qodo-ai/pr-agent/wiki/.pr_agent_auto_best_practices) wiki file.
|
||||
These best practices reflect recurring patterns in accepted code improvements.
|
||||
|
||||
The `improve` tool will incorporate these best practices as an additional analysis layer, checking PR code changes against known patterns of previously accepted improvements.
|
||||
This creates a two-phase analysis:
|
||||
|
||||
1. Open exploration for general code issues
|
||||
2. Targeted checking against established best practices - exploiting the knowledge gained from past suggestions
|
||||
|
||||
By keeping these phases decoupled, the tool remains free to discover new or unseen issues and problems, while also learning from past experiences.
|
||||
|
||||
|
||||
When presenting the suggestions generated by the `improve` tool, Qodo Merge will add a dedicated label for each suggestion generated from the auto best practices - 'Learned best practice':
|
||||
|
||||
{width=684}
|
||||
|
||||
|
||||
## Auto Best Practices vs Custom Best Practices
|
||||
|
||||
Teams and companies can also manually define their own [custom best practices](https://qodo-merge-docs.qodo.ai/tools/improve/#best-practices) in Qodo Merge.
|
||||
|
||||
When custom best practices exist, Qodo Merge will still generate an 'auto best practices' wiki file, though it won't be used by the `improve` tool.
|
||||
However, this auto-generated file can still serve two valuable purposes:
|
||||
|
||||
1. It can help enhance your custom best practices with additional insights derived from suggestions your team found valuable enough to implement
|
||||
2. It demonstrates effective patterns for writing AI-friendly best practices
|
||||
|
||||
Even when using custom best practices, we recommend regularly reviewing the auto best practices file to refine your custom rules.
|
||||
|
||||
## Relevant configurations
|
||||
|
||||
```toml
|
||||
[auto_best_practices]
|
||||
# Disable all auto best practices usage or generation
|
||||
enable_auto_best_practices = true
|
||||
|
||||
# Disable usage of auto best practices file in the 'improve' tool
|
||||
utilize_auto_best_practices = true
|
||||
|
||||
# Extra instructions to the auto best practices generation prompt
|
||||
extra_instructions = ""
|
||||
|
||||
# Max number of patterns to be detected
|
||||
max_patterns = 5
|
||||
```
|
2
docs/docs/core-abilities/code_oriented_yaml.md
Normal file
2
docs/docs/core-abilities/code_oriented_yaml.md
Normal file
@ -0,0 +1,2 @@
|
||||
## Overview
|
||||
TBD
|
47
docs/docs/core-abilities/compression_strategy.md
Normal file
47
docs/docs/core-abilities/compression_strategy.md
Normal file
@ -0,0 +1,47 @@
|
||||
|
||||
## Overview - PR Compression Strategy
|
||||
There are two scenarios:
|
||||
|
||||
1. The PR is small enough to fit in a single prompt (including system and user prompt)
|
||||
2. The PR is too large to fit in a single prompt (including system and user prompt)
|
||||
|
||||
For both scenarios, we first use the following strategy
|
||||
|
||||
#### Repo language prioritization strategy
|
||||
We prioritize the languages of the repo based on the following criteria:
|
||||
|
||||
1. Exclude binary files and non code files (e.g. images, pdfs, etc)
|
||||
2. Given the main languages used in the repo
|
||||
3. We sort the PR files by the most common languages in the repo (in descending order):
|
||||
* ```[[file.py, file2.py],[file3.js, file4.jsx],[readme.md]]```
|
||||
|
||||
|
||||
### Small PR
|
||||
In this case, we can fit the entire PR in a single prompt:
|
||||
1. Exclude binary files and non code files (e.g. images, pdfs, etc)
|
||||
2. We Expand the surrounding context of each patch to 3 lines above and below the patch
|
||||
|
||||
### Large PR
|
||||
|
||||
#### Motivation
|
||||
Pull Requests can be very long and contain a lot of information with varying degree of relevance to the pr-agent.
|
||||
We want to be able to pack as much information as possible in a single LMM prompt, while keeping the information relevant to the pr-agent.
|
||||
|
||||
#### Compression strategy
|
||||
We prioritize additions over deletions:
|
||||
- Combine all deleted files into a single list (`deleted files`)
|
||||
- File patches are a list of hunks, remove all hunks of type deletion-only from the hunks in the file patch
|
||||
|
||||
#### Adaptive and token-aware file patch fitting
|
||||
We use [tiktoken](https://github.com/openai/tiktoken) to tokenize the patches after the modifications described above, and we use the following strategy to fit the patches into the prompt:
|
||||
|
||||
1. Within each language we sort the files by the number of tokens in the file (in descending order):
|
||||
- ```[[file2.py, file.py],[file4.jsx, file3.js],[readme.md]]```
|
||||
2. Iterate through the patches in the order described above
|
||||
3. Add the patches to the prompt until the prompt reaches a certain buffer from the max token length
|
||||
4. If there are still patches left, add the remaining patches as a list called `other modified files` to the prompt until the prompt reaches the max token length (hard stop), skip the rest of the patches.
|
||||
5. If we haven't reached the max token length, add the `deleted files` to the prompt until the prompt reaches the max token length (hard stop), skip the rest of the patches.
|
||||
|
||||
#### Example
|
||||
|
||||
{width=768}
|
72
docs/docs/core-abilities/dynamic_context.md
Normal file
72
docs/docs/core-abilities/dynamic_context.md
Normal file
@ -0,0 +1,72 @@
|
||||
## TL;DR
|
||||
|
||||
Qodo Merge uses an **asymmetric and dynamic context strategy** to improve AI analysis of code changes in pull requests.
|
||||
It provides more context before changes than after, and dynamically adjusts the context based on code structure (e.g., enclosing functions or classes).
|
||||
This approach balances providing sufficient context for accurate analysis, while avoiding needle-in-the-haystack information overload that could degrade AI performance or exceed token limits.
|
||||
|
||||
## Introduction
|
||||
|
||||
Pull request code changes are retrieved in a unified diff format, showing three lines of context before and after each modified section, with additions marked by '+' and deletions by '-'.
|
||||
```
|
||||
@@ -12,5 +12,5 @@ def func1():
|
||||
code line that already existed in the file...
|
||||
code line that already existed in the file...
|
||||
code line that already existed in the file....
|
||||
-code line that was removed in the PR
|
||||
+new code line added in the PR
|
||||
code line that already existed in the file...
|
||||
code line that already existed in the file...
|
||||
code line that already existed in the file...
|
||||
|
||||
@@ -26,2 +26,4 @@ def func2():
|
||||
...
|
||||
```
|
||||
|
||||
This unified diff format can be challenging for AI models to interpret accurately, as it provides limited context for understanding the full scope of code changes.
|
||||
The presentation of code using '+', '-', and ' ' symbols to indicate additions, deletions, and unchanged lines respectively also differs from the standard code formatting typically used to train AI models.
|
||||
|
||||
|
||||
## Challenges of expanding the context window
|
||||
|
||||
While expanding the context window is technically feasible, it presents a more fundamental trade-off:
|
||||
|
||||
Pros:
|
||||
|
||||
- Enhanced context allows the model to better comprehend and localize the code changes, results (potentially) in more precise analysis and suggestions. Without enough context, the model may struggle to understand the code changes and provide relevant feedback.
|
||||
|
||||
Cons:
|
||||
|
||||
- Excessive context may overwhelm the model with extraneous information, creating a "needle in a haystack" scenario where focusing on the relevant details (the code that actually changed) becomes challenging.
|
||||
LLM quality is known to degrade when the context gets larger.
|
||||
Pull requests often encompass multiple changes across many files, potentially spanning hundreds of lines of modified code. This complexity presents a genuine risk of overwhelming the model with excessive context.
|
||||
|
||||
- Increased context expands the token count, increasing processing time and cost, and may prevent the model from processing the entire pull request in a single pass.
|
||||
|
||||
## Asymmetric and dynamic context
|
||||
To address these challenges, Qodo Merge employs an **asymmetric** and **dynamic** context strategy, providing the model with more focused and relevant context information for each code change.
|
||||
|
||||
**Asymmetric:**
|
||||
|
||||
We start by recognizing that the context preceding a code change is typically more crucial for understanding the modification than the context following it.
|
||||
Consequently, Qodo Merge implements an asymmetric context policy, decoupling the context window into two distinct segments: one for the code before the change and another for the code after.
|
||||
|
||||
By independently adjusting each context window, Qodo Merge can supply the model with a more tailored and pertinent context for individual code changes.
|
||||
|
||||
**Dynamic:**
|
||||
|
||||
We also employ a "dynamic" context strategy.
|
||||
We start by recognizing that the optimal context for a code change often corresponds to its enclosing code component (e.g., function, class), rather than a fixed number of lines.
|
||||
Consequently, we dynamically adjust the context window based on the code's structure, ensuring the model receives the most pertinent information for each modification.
|
||||
|
||||
To prevent overwhelming the model with excessive context, we impose a limit on the number of lines searched when identifying the enclosing component.
|
||||
This balance allows for comprehensive understanding while maintaining efficiency and limiting context token usage.
|
||||
|
||||
## Appendix - relevant configuration options
|
||||
```
|
||||
[config]
|
||||
patch_extension_skip_types =[".md",".txt"] # Skip files with these extensions when trying to extend the context
|
||||
allow_dynamic_context=true # Allow dynamic context extension
|
||||
max_extra_lines_before_dynamic_context = 8 # will try to include up to X extra lines before the hunk in the patch, until we reach an enclosing function or class
|
||||
patch_extra_lines_before = 3 # Number of extra lines (+3 default ones) to include before each hunk in the patch
|
||||
patch_extra_lines_after = 1 # Number of extra lines (+3 default ones) to include after each hunk in the patch
|
||||
```
|
170
docs/docs/core-abilities/fetching_ticket_context.md
Normal file
170
docs/docs/core-abilities/fetching_ticket_context.md
Normal file
@ -0,0 +1,170 @@
|
||||
# Fetching Ticket Context for PRs
|
||||
`Supported Git Platforms: GitHub, GitLab, Bitbucket`
|
||||
|
||||
## Overview
|
||||
Qodo Merge PR Agent streamlines code review workflows by seamlessly connecting with multiple ticket management systems.
|
||||
This integration enriches the review process by automatically surfacing relevant ticket information and context alongside code changes.
|
||||
|
||||
## Ticket systems supported
|
||||
- GitHub
|
||||
- Jira (💎)
|
||||
|
||||
Ticket data fetched:
|
||||
|
||||
1. Ticket Title
|
||||
2. Ticket Description
|
||||
3. Custom Fields (Acceptance criteria)
|
||||
4. Subtasks (linked tasks)
|
||||
5. Labels
|
||||
6. Attached Images/Screenshots
|
||||
|
||||
## Affected Tools
|
||||
|
||||
Ticket Recognition Requirements:
|
||||
|
||||
- The PR description should contain a link to the ticket or if the branch name starts with the ticket id / number.
|
||||
- For Jira tickets, you should follow the instructions in [Jira Integration](https://qodo-merge-docs.qodo.ai/core-abilities/fetching_ticket_context/#jira-integration) in order to authenticate with Jira.
|
||||
|
||||
### Describe tool
|
||||
Qodo Merge PR Agent will recognize the ticket and use the ticket content (title, description, labels) to provide additional context for the code changes.
|
||||
By understanding the reasoning and intent behind modifications, the LLM can offer more insightful and relevant code analysis.
|
||||
|
||||
### Review tool
|
||||
Similarly to the `describe` tool, the `review` tool will use the ticket content to provide additional context for the code changes.
|
||||
|
||||
In addition, this feature will evaluate how well a Pull Request (PR) adheres to its original purpose/intent as defined by the associated ticket or issue mentioned in the PR description.
|
||||
Each ticket will be assigned a label (Compliance/Alignment level), Indicates the degree to which the PR fulfills its original purpose, Options: Fully compliant, Partially compliant or Not compliant.
|
||||
|
||||
|
||||
{width=768}
|
||||
|
||||
By default, the tool will automatically validate if the PR complies with the referenced ticket.
|
||||
If you want to disable this feedback, add the following line to your configuration file:
|
||||
|
||||
```toml
|
||||
[pr_reviewer]
|
||||
require_ticket_analysis_review=false
|
||||
```
|
||||
|
||||
## Providers
|
||||
|
||||
### Github Issues Integration
|
||||
|
||||
Qodo Merge PR Agent will automatically recognize Github issues mentioned in the PR description and fetch the issue content.
|
||||
Examples of valid GitHub issue references:
|
||||
|
||||
- `https://github.com/<ORG_NAME>/<REPO_NAME>/issues/<ISSUE_NUMBER>`
|
||||
- `#<ISSUE_NUMBER>`
|
||||
- `<ORG_NAME>/<REPO_NAME>#<ISSUE_NUMBER>`
|
||||
|
||||
Since Qodo Merge PR Agent is integrated with GitHub, it doesn't require any additional configuration to fetch GitHub issues.
|
||||
|
||||
### Jira Integration 💎
|
||||
|
||||
We support both Jira Cloud and Jira Server/Data Center.
|
||||
To integrate with Jira, you can link your PR to a ticket using either of these methods:
|
||||
|
||||
**Method 1: Description Reference:**
|
||||
|
||||
Include a ticket reference in your PR description using either the complete URL format https://<JIRA_ORG>.atlassian.net/browse/ISSUE-123 or the shortened ticket ID ISSUE-123.
|
||||
|
||||
**Method 2: Branch Name Detection:**
|
||||
|
||||
Name your branch with the ticket ID as a prefix (e.g., `ISSUE-123-feature-description` or `ISSUE-123/feature-description`).
|
||||
|
||||
!!! note "Jira Base URL"
|
||||
For shortened ticket IDs or branch detection (method 2), you must configure the Jira base URL in your configuration file under the [jira] section:
|
||||
|
||||
```toml
|
||||
[jira]
|
||||
jira_base_url = "https://<JIRA_ORG>.atlassian.net"
|
||||
```
|
||||
|
||||
#### Jira Cloud 💎
|
||||
There are two ways to authenticate with Jira Cloud:
|
||||
|
||||
**1) Jira App Authentication**
|
||||
|
||||
The recommended way to authenticate with Jira Cloud is to install the Qodo Merge app in your Jira Cloud instance. This will allow Qodo Merge to access Jira data on your behalf.
|
||||
|
||||
Installation steps:
|
||||
|
||||
1. Click [here](https://auth.atlassian.com/authorize?audience=api.atlassian.com&client_id=8krKmA4gMD8mM8z24aRCgPCSepZNP1xf&scope=read%3Ajira-work%20offline_access&redirect_uri=https%3A%2F%2Fregister.jira.pr-agent.codium.ai&state=qodomerge&response_type=code&prompt=consent) to install the Qodo Merge app in your Jira Cloud instance, click the `accept` button.<br>
|
||||
{width=384}
|
||||
|
||||
2. After installing the app, you will be redirected to the Qodo Merge registration page. and you will see a success message.<br>
|
||||
{width=384}
|
||||
|
||||
3. Now you can use the Jira integration in Qodo Merge PR Agent.
|
||||
|
||||
**2) Email/Token Authentication**
|
||||
|
||||
You can create an API token from your Atlassian account:
|
||||
|
||||
1. Log in to https://id.atlassian.com/manage-profile/security/api-tokens.
|
||||
|
||||
2. Click Create API token.
|
||||
|
||||
3. From the dialog that appears, enter a name for your new token and click Create.
|
||||
|
||||
4. Click Copy to clipboard.
|
||||
|
||||
{width=384}
|
||||
|
||||
5. In your [configuration file](https://qodo-merge-docs.qodo.ai/usage-guide/configuration_options/) add the following lines:
|
||||
|
||||
```toml
|
||||
[jira]
|
||||
jira_api_token = "YOUR_API_TOKEN"
|
||||
jira_api_email = "YOUR_EMAIL"
|
||||
```
|
||||
|
||||
|
||||
#### Jira Data Center/Server 💎
|
||||
|
||||
##### Local App Authentication (For Qodo Merge On-Premise Customers)
|
||||
|
||||
##### 1. Step 1: Set up an application link in Jira Data Center/Server
|
||||
* Go to Jira Administration > Applications > Application Links > Click on `Create link`
|
||||
|
||||
{width=384}
|
||||
* Choose `External application` and set the direction to `Incoming` and then click `Continue`
|
||||
|
||||
{width=256}
|
||||
* In the following screen, enter the following details:
|
||||
* Name: `Qodo Merge`
|
||||
* Redirect URL: Enter your Qodo Merge URL followed `https://{QODO_MERGE_ENDPOINT}/register_ticket_provider`
|
||||
* Permission: Select `Read`
|
||||
* Click `Save`
|
||||
|
||||
{width=384}
|
||||
* Copy the `Client ID` and `Client secret` and set them in your `.secrets` file:
|
||||
|
||||
{width=256}
|
||||
```toml
|
||||
[jira]
|
||||
jira_app_secret = "..."
|
||||
jira_client_id = "..."
|
||||
```
|
||||
|
||||
##### 2. Step 2: Authenticate with Jira Data Center/Server
|
||||
* Open this URL in your browser: `https://{QODO_MERGE_ENDPOINT}/jira_auth`
|
||||
* Click on link
|
||||
|
||||
{width=384}
|
||||
|
||||
* You will be redirected to Jira Data Center/Server, click `Allow`
|
||||
* You will be redirected back to Qodo Merge PR Agent and you will see a success message.
|
||||
|
||||
|
||||
##### Personal Access Token (PAT) Authentication
|
||||
We also support Personal Access Token (PAT) Authentication method.
|
||||
|
||||
1. Create a [Personal Access Token (PAT)](https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html) in your Jira account
|
||||
2. In your Configuration file/Environment variables/Secrets file, add the following lines:
|
||||
|
||||
```toml
|
||||
[jira]
|
||||
jira_base_url = "YOUR_JIRA_BASE_URL" # e.g. https://jira.example.com
|
||||
jira_api_token = "YOUR_API_TOKEN"
|
||||
```
|
44
docs/docs/core-abilities/impact_evaluation.md
Normal file
44
docs/docs/core-abilities/impact_evaluation.md
Normal file
@ -0,0 +1,44 @@
|
||||
# Overview - Impact Evaluation 💎
|
||||
|
||||
Demonstrating the return on investment (ROI) of AI-powered initiatives is crucial for modern organizations.
|
||||
To address this need, Qodo Merge has developed an AI impact measurement tools and metrics, providing advanced analytics to help businesses quantify the tangible benefits of AI adoption in their PR review process.
|
||||
|
||||
|
||||
## Auto Impact Validator - Real-Time Tracking of Implemented Qodo Merge Suggestions
|
||||
|
||||
### How It Works
|
||||
When a user pushes a new commit to the pull request, Qodo Merge automatically compares the updated code against the previous suggestions, marking them as implemented if the changes address these recommendations, whether directly or indirectly:
|
||||
|
||||
1. **Direct Implementation:** The user directly addresses the suggestion as-is in the PR, either by clicking on the "apply code suggestion" checkbox or by making the changes manually.
|
||||
2. **Indirect Implementation:** Qodo Merge recognizes when a suggestion's intent is fulfilled, even if the exact code changes differ from the original recommendation. It marks these suggestions as implemented, acknowledging that users may achieve the same goal through alternative solutions.
|
||||
|
||||
### Real-Time Visual Feedback
|
||||
Upon confirming that a suggestion was implemented, Qodo Merge automatically adds a ✅ (check mark) to the relevant suggestion, enabling transparent tracking of Qodo Merge's impact analysis.
|
||||
Qodo Merge will also add, inside the relevant suggestions, an explanation of how the new code was impacted by each suggestion.
|
||||
|
||||
{width=512}
|
||||
|
||||
### Dashboard Metrics
|
||||
The dashboard provides macro-level insights into the overall impact of Qodo Merge on the pull-request process with key productivity metrics.
|
||||
|
||||
By offering clear, data-driven evidence of Qodo Merge's impact, it empowers leadership teams to make informed decisions about the tool's effectiveness and ROI.
|
||||
|
||||
Here are key metrics that the dashboard tracks:
|
||||
|
||||
#### Qodo Merge Impacts per 1K Lines
|
||||
{width=512}
|
||||
> Explanation: for every 1K lines of code (additions/edits), Qodo Merge had on average ~X suggestions implemented.
|
||||
|
||||
**Why This Metric Matters:**
|
||||
|
||||
1. **Standardized and Comparable Measurement:** By measuring impacts per 1K lines of code additions, you create a standardized metric that can be compared across different projects, teams, customers, and time periods. This standardization is crucial for meaningful analysis, benchmarking, and identifying where Qodo Merge is most effective.
|
||||
2. **Accounts for PR Variability and Incentivizes Quality:** This metric addresses the fact that "Not all PRs are created equal." By normalizing against lines of code rather than PR count, you account for the variability in PR sizes and focus on the quality and impact of suggestions rather than just the number of PRs affected.
|
||||
3. **Quantifies Value and ROI:** The metric directly correlates with the value Qodo Merge is providing, showing how frequently it offers improvements relative to the amount of new code being written. This provides a clear, quantifiable way to demonstrate Qodo Merge's return on investment to stakeholders.
|
||||
|
||||
#### Suggestion Effectiveness Across Categories
|
||||
{width=512}
|
||||
> Explanation: This chart illustrates the distribution of implemented suggestions across different categories, enabling teams to better understand Qodo Merge's impact on various aspects of code quality and development practices.
|
||||
|
||||
#### Suggestion Score Distribution
|
||||
{width=512}
|
||||
> Explanation: The distribution of the suggestion score for the implemented suggestions, ensuring that higher-scored suggestions truly represent more significant improvements.
|
@ -1,52 +1,30 @@
|
||||
## PR Compression Strategy
|
||||
There are two scenarios:
|
||||
# Core Abilities
|
||||
Qodo Merge utilizes a variety of core abilities to provide a comprehensive and efficient code review experience. These abilities include:
|
||||
|
||||
1. The PR is small enough to fit in a single prompt (including system and user prompt)
|
||||
2. The PR is too large to fit in a single prompt (including system and user prompt)
|
||||
- [Fetching ticket context](https://qodo-merge-docs.qodo.ai/core-abilities/fetching_ticket_context/)
|
||||
- [Auto best practices](https://qodo-merge-docs.qodo.ai/core-abilities/auto_best_practices/)
|
||||
- [Local and global metadata](https://qodo-merge-docs.qodo.ai/core-abilities/metadata/)
|
||||
- [Dynamic context](https://qodo-merge-docs.qodo.ai/core-abilities/dynamic_context/)
|
||||
- [Self-reflection](https://qodo-merge-docs.qodo.ai/core-abilities/self_reflection/)
|
||||
- [Impact evaluation](https://qodo-merge-docs.qodo.ai/core-abilities/impact_evaluation/)
|
||||
- [Interactivity](https://qodo-merge-docs.qodo.ai/core-abilities/interactivity/)
|
||||
- [Compression strategy](https://qodo-merge-docs.qodo.ai/core-abilities/compression_strategy/)
|
||||
- [Code-oriented YAML](https://qodo-merge-docs.qodo.ai/core-abilities/code_oriented_yaml/)
|
||||
- [Static code analysis](https://qodo-merge-docs.qodo.ai/core-abilities/static_code_analysis/)
|
||||
- [Code fine-tuning benchmark](https://qodo-merge-docs.qodo.ai/finetuning_benchmark/)
|
||||
|
||||
For both scenarios, we first use the following strategy
|
||||
## Blogs
|
||||
|
||||
#### Repo language prioritization strategy
|
||||
We prioritize the languages of the repo based on the following criteria:
|
||||
Here are some additional technical blogs from Qodo, that delve deeper into the core capabilities and features of Large Language Models (LLMs) when applied to coding tasks.
|
||||
These resources provide more comprehensive insights into leveraging LLMs for software development.
|
||||
|
||||
1. Exclude binary files and non code files (e.g. images, pdfs, etc)
|
||||
2. Given the main languages used in the repo
|
||||
3. We sort the PR files by the most common languages in the repo (in descending order):
|
||||
* ```[[file.py, file2.py],[file3.js, file4.jsx],[readme.md]]```
|
||||
### Code Generation and LLMs
|
||||
- [State-of-the-art Code Generation with AlphaCodium – From Prompt Engineering to Flow Engineering](https://www.qodo.ai/blog/qodoflow-state-of-the-art-code-generation-for-code-contests/)
|
||||
- [RAG for a Codebase with 10k Repos](https://www.qodo.ai/blog/rag-for-large-scale-code-repos/)
|
||||
|
||||
### Development Processes
|
||||
- [Understanding the Challenges and Pain Points of the Pull Request Cycle](https://www.qodo.ai/blog/understanding-the-challenges-and-pain-points-of-the-pull-request-cycle/)
|
||||
- [Introduction to Code Coverage Testing](https://www.qodo.ai/blog/introduction-to-code-coverage-testing/)
|
||||
|
||||
### Small PR
|
||||
In this case, we can fit the entire PR in a single prompt:
|
||||
1. Exclude binary files and non code files (e.g. images, pdfs, etc)
|
||||
2. We Expand the surrounding context of each patch to 3 lines above and below the patch
|
||||
|
||||
### Large PR
|
||||
|
||||
#### Motivation
|
||||
Pull Requests can be very long and contain a lot of information with varying degree of relevance to the pr-agent.
|
||||
We want to be able to pack as much information as possible in a single LMM prompt, while keeping the information relevant to the pr-agent.
|
||||
|
||||
#### Compression strategy
|
||||
We prioritize additions over deletions:
|
||||
- Combine all deleted files into a single list (`deleted files`)
|
||||
- File patches are a list of hunks, remove all hunks of type deletion-only from the hunks in the file patch
|
||||
|
||||
#### Adaptive and token-aware file patch fitting
|
||||
We use [tiktoken](https://github.com/openai/tiktoken) to tokenize the patches after the modifications described above, and we use the following strategy to fit the patches into the prompt:
|
||||
|
||||
1. Within each language we sort the files by the number of tokens in the file (in descending order):
|
||||
- ```[[file2.py, file.py],[file4.jsx, file3.js],[readme.md]]```
|
||||
2. Iterate through the patches in the order described above
|
||||
3. Add the patches to the prompt until the prompt reaches a certain buffer from the max token length
|
||||
4. If there are still patches left, add the remaining patches as a list called `other modified files` to the prompt until the prompt reaches the max token length (hard stop), skip the rest of the patches.
|
||||
5. If we haven't reached the max token length, add the `deleted files` to the prompt until the prompt reaches the max token length (hard stop), skip the rest of the patches.
|
||||
|
||||
#### Example
|
||||
|
||||
{width=768}
|
||||
|
||||
## YAML Prompting
|
||||
TBD
|
||||
|
||||
## Static Code Analysis 💎
|
||||
TBD
|
||||
### Cost Optimization
|
||||
- [Reduce Your Costs by 30% When Using GPT for Python Code](https://www.qodo.ai/blog/reduce-your-costs-by-30-when-using-gpt-3-for-python-code/)
|
||||
|
2
docs/docs/core-abilities/interactivity.md
Normal file
2
docs/docs/core-abilities/interactivity.md
Normal file
@ -0,0 +1,2 @@
|
||||
## Interactive invocation 💎
|
||||
TBD
|
56
docs/docs/core-abilities/metadata.md
Normal file
56
docs/docs/core-abilities/metadata.md
Normal file
@ -0,0 +1,56 @@
|
||||
## Local and global metadata injection with multi-stage analysis
|
||||
(1)
|
||||
Qodo Merge initially retrieves for each PR the following data:
|
||||
|
||||
- PR title and branch name
|
||||
- PR original description
|
||||
- Commit messages history
|
||||
- PR diff patches, in [hunk diff](https://loicpefferkorn.net/2014/02/diff-files-what-are-hunks-and-how-to-extract-them/) format
|
||||
- The entire content of the files that were modified in the PR
|
||||
|
||||
!!! tip "Tip: Organization-level metadata"
|
||||
In addition to the inputs above, Qodo Merge can incorporate supplementary preferences provided by the user, like [`extra_instructions` and `organization best practices`](https://qodo-merge-docs.qodo.ai/tools/improve/#extra-instructions-and-best-practices). This information can be used to enhance the PR analysis.
|
||||
|
||||
(2)
|
||||
By default, the first command that Qodo Merge executes is [`describe`](https://qodo-merge-docs.qodo.ai/tools/describe/), which generates three types of outputs:
|
||||
|
||||
- PR Type (e.g. bug fix, feature, refactor, etc)
|
||||
- PR Description - a bullet point summary of the PR
|
||||
- Changes walkthrough - for each modified file, provide a one-line summary followed by a detailed bullet point list of the changes.
|
||||
|
||||
These AI-generated outputs are now considered as part of the PR metadata, and can be used in subsequent commands like `review` and `improve`.
|
||||
This effectively enables multi-stage chain-of-thought analysis, without doing any additional API calls which will cost time and money.
|
||||
|
||||
For example, when generating code suggestions for different files, Qodo Merge can inject the AI-generated ["Changes walkthrough"](https://github.com/Codium-ai/pr-agent/pull/1202#issue-2511546839) file summary in the prompt:
|
||||
|
||||
```
|
||||
## File: 'src/file1.py'
|
||||
### AI-generated file summary:
|
||||
- edited function `func1` that does X
|
||||
- Removed function `func2` that was not used
|
||||
- ....
|
||||
|
||||
@@ ... @@ def func1():
|
||||
__new hunk__
|
||||
11 unchanged code line0
|
||||
12 unchanged code line1
|
||||
13 +new code line2 added
|
||||
14 unchanged code line3
|
||||
__old hunk__
|
||||
unchanged code line0
|
||||
unchanged code line1
|
||||
-old code line2 removed
|
||||
unchanged code line3
|
||||
|
||||
@@ ... @@ def func2():
|
||||
__new hunk__
|
||||
...
|
||||
__old hunk__
|
||||
...
|
||||
```
|
||||
|
||||
(3) The entire PR files that were retrieved are also used to expand and enhance the PR context (see [Dynamic Context](https://qodo-merge-docs.qodo.ai/core-abilities/dynamic_context/)).
|
||||
|
||||
|
||||
(4) All the metadata described above represents several level of cumulative analysis - ranging from hunk level, to file level, to PR level, to organization level.
|
||||
This comprehensive approach enables Qodo Merge AI models to generate more precise and contextually relevant suggestions and feedback.
|
50
docs/docs/core-abilities/self_reflection.md
Normal file
50
docs/docs/core-abilities/self_reflection.md
Normal file
@ -0,0 +1,50 @@
|
||||
## TL;DR
|
||||
|
||||
Qodo Merge implements a **self-reflection** process where the AI model reflects, scores, and re-ranks its own suggestions, eliminating irrelevant or incorrect ones.
|
||||
This approach improves the quality and relevance of suggestions, saving users time and enhancing their experience.
|
||||
Configuration options allow users to set a score threshold for further filtering out suggestions.
|
||||
|
||||
## Introduction - Efficient Review with Hierarchical Presentation
|
||||
|
||||
|
||||
Given that not all generated code suggestions will be relevant, it is crucial to enable users to review them in a fast and efficient way, allowing quick identification and filtering of non-applicable ones.
|
||||
|
||||
To achieve this goal, Qodo Merge offers a dedicated hierarchical structure when presenting suggestions to users:
|
||||
|
||||
- A "category" section groups suggestions by their category, allowing users to quickly dismiss irrelevant suggestions.
|
||||
- Each suggestion is first described by a one-line summary, which can be expanded to a full description by clicking on a collapsible.
|
||||
- Upon expanding a suggestion, the user receives a more comprehensive description, and a code snippet demonstrating the recommendation.
|
||||
|
||||
!!! note "Fast Review"
|
||||
This hierarchical structure is designed to facilitate rapid review of each suggestion, with users spending an average of ~5-10 seconds per item.
|
||||
|
||||
## Self-reflection and Re-ranking
|
||||
|
||||
The AI model is initially tasked with generating suggestions, and outputting them in order of importance.
|
||||
However, in practice we observe that models often struggle to simultaneously generate high-quality code suggestions and rank them well in a single pass.
|
||||
Furthermore, the initial set of generated suggestions sometimes contains easily identifiable errors.
|
||||
|
||||
To address these issues, we implemented a "self-reflection" process that refines suggestion ranking and eliminates irrelevant or incorrect proposals.
|
||||
This process consists of the following steps:
|
||||
|
||||
1. Presenting the generated suggestions to the model in a follow-up call.
|
||||
2. Instructing the model to score each suggestion on a scale of 0-10 and provide a rationale for the assigned score.
|
||||
3. Utilizing these scores to re-rank the suggestions and filter out incorrect ones (with a score of 0).
|
||||
4. Optionally, filtering out all suggestions below a user-defined score threshold.
|
||||
|
||||
Note that presenting all generated suggestions simultaneously provides the model with a comprehensive context, enabling it to make more informed decisions compared to evaluating each suggestion individually.
|
||||
|
||||
To conclude, the self-reflection process enables Qodo Merge to prioritize suggestions based on their importance, eliminate inaccurate or irrelevant proposals, and optionally exclude suggestions that fall below a specified threshold of significance.
|
||||
This results in a more refined and valuable set of suggestions for the user, saving time and improving the overall experience.
|
||||
|
||||
## Example Results
|
||||
|
||||
{width=768}
|
||||
{width=768}
|
||||
|
||||
|
||||
## Appendix - Relevant Configuration Options
|
||||
```
|
||||
[pr_code_suggestions]
|
||||
suggestions_score_threshold = 0 # Filter out suggestions with a score below this threshold (0-10)
|
||||
```
|
70
docs/docs/core-abilities/static_code_analysis.md
Normal file
70
docs/docs/core-abilities/static_code_analysis.md
Normal file
@ -0,0 +1,70 @@
|
||||
## Overview - Static Code Analysis 💎
|
||||
|
||||
By combining static code analysis with LLM capabilities, Qodo Merge can provide a comprehensive analysis of the PR code changes on a component level.
|
||||
|
||||
It scans the PR code changes, finds all the code components (methods, functions, classes) that changed, and enables to interactively generate tests, docs, code suggestions and similar code search for each component.
|
||||
|
||||
!!! note "Language that are currently supported:"
|
||||
Python, Java, C++, JavaScript, TypeScript, C#.
|
||||
|
||||
|
||||
## Capabilities
|
||||
|
||||
### Analyze PR
|
||||
|
||||
|
||||
The [`analyze`](https://qodo-merge-docs.qodo.ai/tools/analyze/) tool enables to interactively generate tests, docs, code suggestions and similar code search for each component that changed in the PR.
|
||||
It can be invoked manually by commenting on any PR:
|
||||
```
|
||||
/analyze
|
||||
```
|
||||
|
||||
An example result:
|
||||
|
||||
{width=768}
|
||||
|
||||
Clicking on each checkbox will trigger the relevant tool for the selected component.
|
||||
|
||||
### Generate Tests
|
||||
|
||||
The [`test`](https://qodo-merge-docs.qodo.ai/tools/test/) tool generate tests for a selected component, based on the PR code changes.
|
||||
It can be invoked manually by commenting on any PR:
|
||||
```
|
||||
/test component_name
|
||||
```
|
||||
where 'component_name' is the name of a specific component in the PR, Or be triggered interactively by using the `analyze` tool.
|
||||
|
||||
{width=768}
|
||||
|
||||
### Generate Docs for a Component
|
||||
|
||||
The [`add_docs`](https://qodo-merge-docs.qodo.ai/tools/documentation/) tool scans the PR code changes, and automatically generate docstrings for any code components that changed in the PR.
|
||||
It can be invoked manually by commenting on any PR:
|
||||
```
|
||||
/add_docs component_name
|
||||
```
|
||||
|
||||
Or be triggered interactively by using the `analyze` tool.
|
||||
|
||||
{width=768}
|
||||
|
||||
### Generate Code Suggestions for a Component
|
||||
The [`improve_component`](https://qodo-merge-docs.qodo.ai/tools/improve_component/) tool generates code suggestions for a specific code component that changed in the PR.
|
||||
It can be invoked manually by commenting on any PR:
|
||||
```
|
||||
/improve_component component_name
|
||||
```
|
||||
|
||||
Or be triggered interactively by using the `analyze` tool.
|
||||
|
||||
{width=768}
|
||||
|
||||
### Find Similar Code
|
||||
|
||||
The [`similar code`](https://qodo-merge-docs.qodo.ai/tools/similar_code/) tool retrieves the most similar code components from inside the organization's codebase or from open-source code, including details about the license associated with each repository.
|
||||
|
||||
For example:
|
||||
|
||||
`Global Search` for a method called `chat_completion`:
|
||||
|
||||
{width=768}
|
@ -11,6 +11,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
.md-nav--primary {
|
||||
position: relative; /* Ensure the element is positioned */
|
||||
}
|
||||
|
||||
.md-nav--primary::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 10px; /* Move the border 10 pixels to the right */
|
||||
width: 2px;
|
||||
height: 100%;
|
||||
background-color: #f5f5f5; /* Match the border color */
|
||||
}
|
||||
/*.md-nav__title, .md-nav__link {*/
|
||||
/* font-size: 18px;*/
|
||||
/* margin-top: 14px; !* Adjust the space as needed *!*/
|
||||
|
90
docs/docs/faq/index.md
Normal file
90
docs/docs/faq/index.md
Normal file
@ -0,0 +1,90 @@
|
||||
# FAQ
|
||||
|
||||
??? note "Q: Can Qodo Merge serve as a substitute for a human reviewer?"
|
||||
#### Answer:<span style="display:none;">1</span>
|
||||
|
||||
Qodo Merge is designed to assist, not replace, human reviewers.
|
||||
|
||||
Reviewing PRs is a tedious and time-consuming task often seen as a "chore". In addition, the longer the PR – the shorter the relative feedback, since long PRs can overwhelm reviewers, both in terms of technical difficulty, and the actual review time.
|
||||
Qodo Merge aims to address these pain points, and to assist and empower both the PR author and reviewer.
|
||||
|
||||
However, Qodo Merge has built-in safeguards to ensure the developer remains in the driver's seat. For example:
|
||||
|
||||
1. Preserves user's original PR header
|
||||
2. Places user's description above the AI-generated PR description
|
||||
3. Won't approve PRs; approval remains reviewer's responsibility
|
||||
4. The code suggestions are optional, and aim to:
|
||||
- Encourage self-review and self-reflection
|
||||
- Highlight potential bugs or oversights
|
||||
- Enhance code quality and promote best practices
|
||||
|
||||
Read more about this issue in our [blog](https://www.codium.ai/blog/understanding-the-challenges-and-pain-points-of-the-pull-request-cycle/)
|
||||
|
||||
___
|
||||
|
||||
??? note "Q: I received an incorrect or irrelevant suggestion. Why?"
|
||||
|
||||
#### Answer:<span style="display:none;">2</span>
|
||||
|
||||
- Modern AI models, like Claude 3.5 Sonnet and GPT-4, are improving rapidly but remain imperfect. Users should critically evaluate all suggestions rather than accepting them automatically.
|
||||
- AI errors are rare, but possible. A main value from reviewing the code suggestions lies in their high probability of catching **mistakes or bugs made by the PR author**. We believe it's worth spending 30-60 seconds reviewing suggestions, even if some aren't relevant, as this practice can enhance code quality and prevent bugs in production.
|
||||
|
||||
|
||||
- The hierarchical structure of the suggestions is designed to help the user _quickly_ understand them, and to decide which ones are relevant and which are not:
|
||||
|
||||
- Only if the `Category` header is relevant, the user should move to the summarized suggestion description.
|
||||
- Only if the summarized suggestion description is relevant, the user should click on the collapsible, to read the full suggestion description with a code preview example.
|
||||
|
||||
- In addition, we recommend to use the [`extra_instructions`](https://qodo-merge-docs.qodo.ai/tools/improve/#extra-instructions-and-best-practices) field to guide the model to suggestions that are more relevant to the specific needs of the project.
|
||||
- The interactive [PR chat](https://qodo-merge-docs.qodo.ai/chrome-extension/) also provides an easy way to get more tailored suggestions and feedback from the AI model.
|
||||
|
||||
___
|
||||
|
||||
??? note "Q: How can I get more tailored suggestions?"
|
||||
#### Answer:<span style="display:none;">3</span>
|
||||
|
||||
See [here](https://qodo-merge-docs.qodo.ai/tools/improve/#extra-instructions-and-best-practices) for more information on how to use the `extra_instructions` and `best_practices` configuration options, to guide the model to more tailored suggestions.
|
||||
|
||||
___
|
||||
|
||||
??? note "Q: Will you store my code? Are you using my code to train models?"
|
||||
#### Answer:<span style="display:none;">4</span>
|
||||
|
||||
No. Qodo Merge strict privacy policy ensures that your code is not stored or used for training purposes.
|
||||
|
||||
For a detailed overview of our data privacy policy, please refer to [this link](https://qodo-merge-docs.qodo.ai/overview/data_privacy/)
|
||||
|
||||
___
|
||||
|
||||
??? note "Q: Can I use my own LLM keys with Qodo Merge?"
|
||||
#### Answer:<span style="display:none;">5</span>
|
||||
|
||||
When you self-host the [open-source](https://github.com/Codium-ai/pr-agent) version, you use your own keys.
|
||||
|
||||
Qodo Merge with SaaS deployment is a hosted version of Qodo Merge, where Qodo manages the infrastructure and the keys.
|
||||
For enterprise customers, on-prem deployment is also available. [Contact us](https://www.codium.ai/contact/#pricing) for more information.
|
||||
___
|
||||
|
||||
??? note "Q: Can Qodo Merge review draft/offline PRs?"
|
||||
#### Answer:<span style="display:none;">5</span>
|
||||
|
||||
Yes. While Qodo Merge won't automatically review draft PRs, you can still get feedback by manually requesting it through [online commenting](https://qodo-merge-docs.qodo.ai/usage-guide/automations_and_usage/#online-usage).
|
||||
|
||||
For active PRs, you can customize the automatic feedback settings [here](https://qodo-merge-docs.qodo.ai/usage-guide/automations_and_usage/#qodo-merge-automatic-feedback) to match your team's workflow.
|
||||
___
|
||||
|
||||
??? note "Q: Can the 'Review effort' feedback be calibrated or customized?"
|
||||
#### Answer:<span style="display:none;">5</span>
|
||||
|
||||
Yes, you can customize review effort estimates using the `extra_instructions` configuration option (see [documentation](https://qodo-merge-docs.qodo.ai/tools/review/#configuration-options)).
|
||||
|
||||
Example mapping:
|
||||
|
||||
- Effort 1: < 30 minutes review time
|
||||
- Effort 2: 30-60 minutes review time
|
||||
- Effort 3: 60-90 minutes review time
|
||||
- ...
|
||||
|
||||
Note: The effort levels (1-5) are primarily meant for _comparative_ purposes, helping teams prioritize reviewing smaller PRs first. The actual review duration may vary, as the focus is on providing consistent relative effort estimates.
|
||||
|
||||
___
|
@ -1,10 +1,10 @@
|
||||
# PR-Agent Code Fine-tuning Benchmark
|
||||
# Qodo Merge Code Fine-tuning Benchmark
|
||||
|
||||
On coding tasks, the gap between open-source models and top closed-source models such as GPT4 is significant.
|
||||
<br>
|
||||
In practice, open-source models are unsuitable for most real-world code tasks, and require further fine-tuning to produce acceptable results.
|
||||
|
||||
_PR-Agent fine-tuning benchmark_ aims to benchmark open-source models on their ability to be fine-tuned for a coding task.
|
||||
_Qodo Merge fine-tuning benchmark_ aims to benchmark open-source models on their ability to be fine-tuned for a coding task.
|
||||
Specifically, we chose to fine-tune open-source models on the task of analyzing a pull request, and providing useful feedback and code suggestions.
|
||||
|
||||
Here are the results:
|
||||
@ -23,6 +23,7 @@ Here are the results:
|
||||
| QWEN-1.5-32B | 32 | 29 |
|
||||
| | | |
|
||||
| **CodeQwen1.5-7B** | **7** | **35.4** |
|
||||
| Llama-3.1-8B-Instruct | 8 | 35.2 |
|
||||
| Granite-8b-code-instruct | 8 | 34.2 |
|
||||
| CodeLlama-7b-hf | 7 | 31.8 |
|
||||
| Gemma-7B | 7 | 27.2 |
|
||||
@ -52,8 +53,8 @@ Here are the results:
|
||||
|
||||
### Training dataset
|
||||
|
||||
Our training dataset comprises 25,000 pull requests, aggregated from permissive license repos. For each pull request, we generated responses for the three main tools of PR-Agent:
|
||||
[Describe](https://pr-agent-docs.codium.ai/tools/describe/), [Review](https://pr-agent-docs.codium.ai/tools/improve/) and [Improve](https://pr-agent-docs.codium.ai/tools/improve/).
|
||||
Our training dataset comprises 25,000 pull requests, aggregated from permissive license repos. For each pull request, we generated responses for the three main tools of Qodo Merge:
|
||||
[Describe](https://qodo-merge-docs.qodo.ai/tools/describe/), [Review](https://qodo-merge-docs.qodo.ai/tools/improve/) and [Improve](https://qodo-merge-docs.qodo.ai/tools/improve/).
|
||||
|
||||
On the raw data collected, we employed various automatic and manual cleaning techniques to ensure the outputs were of the highest quality, and suitable for instruct-tuning.
|
||||
|
||||
|
@ -1,25 +1,38 @@
|
||||
# Overview
|
||||
|
||||
CodiumAI PR-Agent is an open-source tool to help efficiently review and handle pull requests.
|
||||
[PR-Agent](https://github.com/Codium-ai/pr-agent) is an open-source tool to help efficiently review and handle pull requests.
|
||||
Qodo Merge is a hosted version of PR-Agent, designed for companies and teams that require additional features and capabilities
|
||||
|
||||
- See the [Installation Guide](./installation/index.md) for instructions on installing and running the tool on different git platforms.
|
||||
|
||||
- See the [Usage Guide](./usage-guide/index.md) for instructions on running the PR-Agent commands via different interfaces, including _CLI_, _online usage_, or by _automatically triggering_ them when a new PR is opened.
|
||||
- See the [Usage Guide](./usage-guide/index.md) for instructions on running commands via different interfaces, including _CLI_, _online usage_, or by _automatically triggering_ them when a new PR is opened.
|
||||
|
||||
- See the [Tools Guide](./tools/index.md) for a detailed description of the different tools.
|
||||
|
||||
|
||||
## PR-Agent Features
|
||||
PR-Agent offers extensive pull request functionalities across various git providers.
|
||||
## Docs Smart Search
|
||||
|
||||
To search the documentation site using natural language:
|
||||
|
||||
1) Comment `/help "your question"` in either:
|
||||
|
||||
- A pull request where Qodo Merge is installed
|
||||
- A [PR Chat](https://qodo-merge-docs.qodo.ai/chrome-extension/features/#pr-chat)
|
||||
|
||||
2) The bot will respond with an [answer](https://github.com/Codium-ai/pr-agent/pull/1241#issuecomment-2365259334) that includes relevant documentation links.
|
||||
|
||||
|
||||
## Qodo Merge Features
|
||||
|
||||
Qodo Merge offers extensive pull request functionalities across various git providers:
|
||||
|
||||
| | | GitHub | Gitlab | Bitbucket | Azure DevOps |
|
||||
|-------|-----------------------------------------------------------------------------------------------------------------------|:------:|:------:|:---------:|:------------:|
|
||||
| TOOLS | Review | ✅ | ✅ | ✅ | ✅ |
|
||||
| | ⮑ Incremental | ✅ | | | |
|
||||
| | ⮑ [SOC2 Compliance](https://pr-agent-docs.codium.ai/tools/review/#soc2-ticket-compliance){:target="_blank"} 💎 | ✅ | ✅ | ✅ | ✅ |
|
||||
| | Ask | ✅ | ✅ | ✅ | ✅ |
|
||||
| | Describe | ✅ | ✅ | ✅ | ✅ |
|
||||
| | ⮑ [Inline file summary](https://pr-agent-docs.codium.ai/tools/describe/#inline-file-summary){:target="_blank"} 💎 | ✅ | ✅ | | ✅ |
|
||||
| | ⮑ [Inline file summary](https://qodo-merge-docs.qodo.ai/tools/describe/#inline-file-summary){:target="_blank"} 💎 | ✅ | ✅ | | ✅ |
|
||||
| | Improve | ✅ | ✅ | ✅ | ✅ |
|
||||
| | ⮑ Extended | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [Custom Prompt](./tools/custom_prompt.md){:target="_blank"} 💎 | ✅ | ✅ | ✅ | ✅ |
|
||||
@ -29,6 +42,8 @@ PR-Agent offers extensive pull request functionalities across various git provid
|
||||
| | [Add PR Documentation](./tools/documentation.md){:target="_blank"} 💎 | ✅ | ✅ | | ✅ |
|
||||
| | [Generate Custom Labels](./tools/describe.md#handle-custom-labels-from-the-repos-labels-page-💎){:target="_blank"} 💎 | ✅ | ✅ | | ✅ |
|
||||
| | [Analyze PR Components](./tools/analyze.md){:target="_blank"} 💎 | ✅ | ✅ | | ✅ |
|
||||
| | [Test](https://pr-agent-docs.codium.ai/tools/test/) 💎 | ✅ | ✅ | | |
|
||||
| | [Implement](https://pr-agent-docs.codium.ai/tools/implement/) 💎 | ✅ | ✅ | ✅ | |
|
||||
| | | | | | ️ |
|
||||
| USAGE | CLI | ✅ | ✅ | ✅ | ✅ |
|
||||
| | App / webhook | ✅ | ✅ | ✅ | ✅ |
|
||||
@ -42,7 +57,7 @@ PR-Agent offers extensive pull request functionalities across various git provid
|
||||
| | [Static code analysis](./tools/analyze.md/){:target="_blank"} 💎 | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [Multiple configuration options](./usage-guide/configuration_options.md){:target="_blank"} 💎 | ✅ | ✅ | ✅ | ✅ |
|
||||
|
||||
💎 marks a feature available only in [PR-Agent Pro](https://www.codium.ai/pricing/){:target="_blank"}
|
||||
💎 marks a feature available only in [Qodo Merge](https://www.codium.ai/pricing/){:target="_blank"}, and not in the open-source version.
|
||||
|
||||
|
||||
## Example Results
|
||||
@ -74,8 +89,8 @@ PR-Agent offers extensive pull request functionalities across various git provid
|
||||
|
||||
## How it Works
|
||||
|
||||
The following diagram illustrates PR-Agent tools and their flow:
|
||||
The following diagram illustrates Qodo Merge tools and their flow:
|
||||
|
||||

|
||||

|
||||
|
||||
Check out the [PR Compression strategy](core-abilities/index.md) page for more details on how we convert a code diff to a manageable LLM prompt
|
@ -1,4 +1,64 @@
|
||||
## Azure DevOps provider
|
||||
## Azure DevOps Pipeline
|
||||
You can use a pre-built Action Docker image to run PR-Agent as an Azure devops pipeline.
|
||||
add the following file to your repository under `azure-pipelines.yml`:
|
||||
```yaml
|
||||
# Opt out of CI triggers
|
||||
trigger: none
|
||||
|
||||
# Configure PR trigger
|
||||
pr:
|
||||
branches:
|
||||
include:
|
||||
- '*'
|
||||
autoCancel: true
|
||||
drafts: false
|
||||
|
||||
stages:
|
||||
- stage: pr_agent
|
||||
displayName: 'PR Agent Stage'
|
||||
jobs:
|
||||
- job: pr_agent_job
|
||||
displayName: 'PR Agent Job'
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
container:
|
||||
image: codiumai/pr-agent:latest
|
||||
options: --entrypoint ""
|
||||
variables:
|
||||
- group: pr_agent
|
||||
steps:
|
||||
- script: |
|
||||
echo "Running PR Agent action step"
|
||||
|
||||
# Construct PR_URL
|
||||
PR_URL="${SYSTEM_COLLECTIONURI}${SYSTEM_TEAMPROJECT}/_git/${BUILD_REPOSITORY_NAME}/pullrequest/${SYSTEM_PULLREQUEST_PULLREQUESTID}"
|
||||
echo "PR_URL=$PR_URL"
|
||||
|
||||
# Extract organization URL from System.CollectionUri
|
||||
ORG_URL=$(echo "$(System.CollectionUri)" | sed 's/\/$//') # Remove trailing slash if present
|
||||
echo "Organization URL: $ORG_URL"
|
||||
|
||||
export azure_devops__org="$ORG_URL"
|
||||
export config__git_provider="azure"
|
||||
|
||||
pr-agent --pr_url="$PR_URL" describe
|
||||
pr-agent --pr_url="$PR_URL" review
|
||||
pr-agent --pr_url="$PR_URL" improve
|
||||
env:
|
||||
azure_devops__pat: $(azure_devops_pat)
|
||||
openai__key: $(OPENAI_KEY)
|
||||
displayName: 'Run Qodo Merge'
|
||||
```
|
||||
This script will run Qodo Merge on every new merge request, with the `improve`, `review`, and `describe` commands.
|
||||
Note that you need to export the `azure_devops__pat` and `OPENAI_KEY` variables in the Azure DevOps pipeline settings (Pipelines -> Library -> + Variable group):
|
||||
|
||||
{width=468}
|
||||
|
||||
Make sure to give pipeline permissions to the `pr_agent` variable group.
|
||||
|
||||
> Note that Azure Pipelines lacks support for triggering workflows from PR comments. If you find a viable solution, please contribute it to our [issue tracker](https://github.com/Codium-ai/pr-agent/issues)
|
||||
|
||||
## Azure DevOps from CLI
|
||||
|
||||
To use Azure DevOps provider use the following settings in configuration.toml:
|
||||
```
|
||||
@ -20,7 +80,7 @@ org = "https://dev.azure.com/YOUR_ORGANIZATION/"
|
||||
# pat = "YOUR_PAT_TOKEN" needed only if using PAT for authentication
|
||||
```
|
||||
|
||||
### Azure DevOps Webhook
|
||||
## Azure DevOps Webhook
|
||||
|
||||
To trigger from an Azure webhook, you need to manually [add a webhook](https://learn.microsoft.com/en-us/azure/devops/service-hooks/services/webhooks?view=azure-devops).
|
||||
Use the "Pull request created" type to trigger a review, or "Pull request commented on" to trigger any supported comment with /<command> <args> comment on the relevant PR. Note that for the "Pull request commented on" trigger, only API v2.0 is supported.
|
||||
|
@ -1,9 +1,9 @@
|
||||
## Run as a Bitbucket Pipeline
|
||||
|
||||
|
||||
You can use the Bitbucket Pipeline system to run PR-Agent on every pull request open or update.
|
||||
You can use the Bitbucket Pipeline system to run Qodo Merge on every pull request open or update.
|
||||
|
||||
1. Add the following file in your repository bitbucket_pipelines.yml
|
||||
1. Add the following file in your repository bitbucket-pipelines.yml
|
||||
|
||||
```yaml
|
||||
pipelines:
|
||||
@ -27,10 +27,6 @@ You can get a Bitbucket token for your repository by following Repository Settin
|
||||
Note that comments on a PR are not supported in Bitbucket Pipeline.
|
||||
|
||||
|
||||
## Run using CodiumAI-hosted Bitbucket app
|
||||
|
||||
Please contact [support@codium.ai](mailto:support@codium.ai) or visit [CodiumAI pricing page](https://www.codium.ai/pricing/) if you're interested in a hosted BitBucket app solution that provides full functionality including PR reviews and comment handling. It's based on the [bitbucket_app.py](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/git_providers/bitbucket_provider.py) implementation.
|
||||
|
||||
|
||||
## Bitbucket Server and Data Center
|
||||
|
||||
@ -58,7 +54,7 @@ python cli.py --pr_url https://git.onpreminstanceofbitbucket.com/projects/PROJEC
|
||||
|
||||
### Run it as service
|
||||
|
||||
To run pr-agent as webhook, build the docker image:
|
||||
To run Qodo Merge as webhook, build the docker image:
|
||||
```
|
||||
docker build . -t codiumai/pr-agent:bitbucket_server_webhook --target bitbucket_server_webhook -f docker/Dockerfile
|
||||
docker push codiumai/pr-agent:bitbucket_server_webhook # Push to your Docker repository
|
||||
|
@ -21,20 +21,12 @@ jobs:
|
||||
steps:
|
||||
- name: PR Agent action step
|
||||
id: pragent
|
||||
uses: Codium-ai/pr-agent@main
|
||||
uses: qodo-ai/pr-agent@main
|
||||
env:
|
||||
OPENAI_KEY: ${{ secrets.OPENAI_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
```
|
||||
** if you want to pin your action to a specific release (v2.0 for example) for stability reasons, use:
|
||||
```yaml
|
||||
...
|
||||
steps:
|
||||
- name: PR Agent action step
|
||||
id: pragent
|
||||
uses: Codium-ai/pr-agent@v2.0
|
||||
...
|
||||
```
|
||||
|
||||
2) Add the following secret to your repository under `Settings > Secrets and variables > Actions > New repository secret > Add secret`:
|
||||
|
||||
```
|
||||
@ -47,7 +39,7 @@ The GITHUB_TOKEN secret is automatically created by GitHub.
|
||||
3) Merge this change to your main branch.
|
||||
When you open your next PR, you should see a comment from `github-actions` bot with a review of your PR, and instructions on how to use the rest of the tools.
|
||||
|
||||
4) You may configure PR-Agent by adding environment variables under the env section corresponding to any configurable property in the [configuration](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml) file. Some examples:
|
||||
4) You may configure Qodo Merge by adding environment variables under the env section corresponding to any configurable property in the [configuration](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml) file. Some examples:
|
||||
```yaml
|
||||
env:
|
||||
# ... previous environment values
|
||||
@ -55,7 +47,41 @@ When you open your next PR, you should see a comment from `github-actions` bot w
|
||||
PR_REVIEWER.REQUIRE_TESTS_REVIEW: "false" # Disable tests review
|
||||
PR_CODE_SUGGESTIONS.NUM_CODE_SUGGESTIONS: 6 # Increase number of code suggestions
|
||||
```
|
||||
See detailed usage instructions in the [USAGE GUIDE](https://pr-agent-docs.codium.ai/usage-guide/automations_and_usage/#github-action)
|
||||
See detailed usage instructions in the [USAGE GUIDE](https://qodo-merge-docs.qodo.ai/usage-guide/automations_and_usage/#github-action)
|
||||
|
||||
### Using a specific release
|
||||
!!! tip ""
|
||||
if you want to pin your action to a specific release (v0.23 for example) for stability reasons, use:
|
||||
```yaml
|
||||
...
|
||||
steps:
|
||||
- name: PR Agent action step
|
||||
id: pragent
|
||||
uses: docker://codiumai/pr-agent:0.23-github_action
|
||||
...
|
||||
```
|
||||
|
||||
For enhanced security, you can also specify the Docker image by its [digest](https://hub.docker.com/repository/docker/codiumai/pr-agent/tags):
|
||||
```yaml
|
||||
...
|
||||
steps:
|
||||
- name: PR Agent action step
|
||||
id: pragent
|
||||
uses: docker://codiumai/pr-agent@sha256:14165e525678ace7d9b51cda8652c2d74abb4e1d76b57c4a6ccaeba84663cc64
|
||||
...
|
||||
```
|
||||
|
||||
### Action for GitHub enterprise server
|
||||
!!! tip ""
|
||||
To use the action with a GitHub enterprise server, add an environment variable `GITHUB.BASE_URL` with the API URL of your GitHub server.
|
||||
|
||||
For example, if your GitHub server is at `https://github.mycompany.com`, add the following to your workflow file:
|
||||
```yaml
|
||||
env:
|
||||
# ... previous environment values
|
||||
GITHUB.BASE_URL: "https://github.mycompany.com/api/v3"
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
@ -142,7 +168,7 @@ cp pr_agent/settings/.secrets_template.toml pr_agent/settings/.secrets.toml
|
||||
|
||||
9. Install the app by navigating to the "Install App" tab and selecting your desired repositories.
|
||||
|
||||
> **Note:** When running PR-Agent from GitHub App, the default configuration file (configuration.toml) will be loaded.
|
||||
> **Note:** When running Qodo Merge from GitHub app, the default configuration file (configuration.toml) will be loaded.
|
||||
> However, you can override the default tool parameters by uploading a local configuration file `.pr_agent.toml`
|
||||
> For more information please check out the [USAGE GUIDE](../usage-guide/automations_and_usage.md#github-app)
|
||||
---
|
||||
@ -172,7 +198,7 @@ For example: `GITHUB.WEBHOOK_SECRET` --> `GITHUB__WEBHOOK_SECRET`
|
||||
|
||||
## AWS CodeCommit Setup
|
||||
|
||||
Not all features have been added to CodeCommit yet. As of right now, CodeCommit has been implemented to run the pr-agent CLI on the command line, using AWS credentials stored in environment variables. (More features will be added in the future.) The following is a set of instructions to have pr-agent do a review of your CodeCommit pull request from the command line:
|
||||
Not all features have been added to CodeCommit yet. As of right now, CodeCommit has been implemented to run the Qodo Merge CLI on the command line, using AWS credentials stored in environment variables. (More features will be added in the future.) The following is a set of instructions to have Qodo Merge do a review of your CodeCommit pull request from the command line:
|
||||
|
||||
1. Create an IAM user that you will use to read CodeCommit pull requests and post comments
|
||||
* Note: That user should have CLI access only, not Console access
|
||||
|
@ -1,20 +1,78 @@
|
||||
## Run as a GitLab Pipeline
|
||||
You can use a pre-built Action Docker image to run PR-Agent as a GitLab pipeline. This is a simple way to get started with Qodo Merge without setting up your own server.
|
||||
|
||||
(1) Add the following file to your repository under `.gitlab-ci.yml`:
|
||||
```yaml
|
||||
stages:
|
||||
- pr_agent
|
||||
|
||||
pr_agent_job:
|
||||
stage: pr_agent
|
||||
image:
|
||||
name: codiumai/pr-agent:latest
|
||||
entrypoint: [""]
|
||||
script:
|
||||
- cd /app
|
||||
- echo "Running PR Agent action step"
|
||||
- export MR_URL="$CI_MERGE_REQUEST_PROJECT_URL/merge_requests/$CI_MERGE_REQUEST_IID"
|
||||
- echo "MR_URL=$MR_URL"
|
||||
- export gitlab__url=$CI_SERVER_PROTOCOL://$CI_SERVER_FQDN
|
||||
- export gitlab__PERSONAL_ACCESS_TOKEN=$GITLAB_PERSONAL_ACCESS_TOKEN
|
||||
- export config__git_provider="gitlab"
|
||||
- export openai__key=$OPENAI_KEY
|
||||
- python -m pr_agent.cli --pr_url="$MR_URL" describe
|
||||
- python -m pr_agent.cli --pr_url="$MR_URL" review
|
||||
- python -m pr_agent.cli --pr_url="$MR_URL" improve
|
||||
rules:
|
||||
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
||||
```
|
||||
This script will run Qodo Merge on every new merge request. You can modify the `rules` section to run Qodo Merge on different events.
|
||||
You can also modify the `script` section to run different Qodo Merge commands, or with different parameters by exporting different environment variables.
|
||||
|
||||
|
||||
(2) Add the following masked variables to your GitLab repository (CI/CD -> Variables):
|
||||
|
||||
- `GITLAB_PERSONAL_ACCESS_TOKEN`: Your GitLab personal access token.
|
||||
|
||||
- `OPENAI_KEY`: Your OpenAI key.
|
||||
|
||||
Note that if your base branches are not protected, don't set the variables as `protected`, since the pipeline will not have access to them.
|
||||
|
||||
> **Note**: The `$CI_SERVER_FQDN` variable is available starting from GitLab version 16.10. If you're using an earlier version, this variable will not be available. However, you can combine `$CI_SERVER_HOST` and `$CI_SERVER_PORT` to achieve the same result. Please ensure you're using a compatible version or adjust your configuration.
|
||||
|
||||
|
||||
## Run a GitLab webhook server
|
||||
|
||||
1. From the GitLab workspace or group, create an access token. Enable the "api" scope only.
|
||||
1. From the GitLab workspace or group, create an access token with "Reporter" role ("Developer" if using Pro version of the agent) and "api" scope.
|
||||
|
||||
2. Generate a random secret for your app, and save it for later. For example, you can use:
|
||||
|
||||
```
|
||||
WEBHOOK_SECRET=$(python -c "import secrets; print(secrets.token_hex(10))")
|
||||
```
|
||||
3. Follow the instructions to build the Docker image, setup a secrets file and deploy on your own server from [here](https://pr-agent-docs.codium.ai/installation/github/#run-as-a-github-app) steps 4-7.
|
||||
|
||||
4. In the secrets file, fill in the following:
|
||||
- Your OpenAI key.
|
||||
- In the [gitlab] section, fill in personal_access_token and shared_secret. The access token can be a personal access token, or a group or project access token.
|
||||
- Set deployment_type to 'gitlab' in [configuration.toml](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml)
|
||||
3. Clone this repository:
|
||||
|
||||
5. Create a webhook in GitLab. Set the URL to the URL of your app's server. Set the secret token to the generated secret from step 2.
|
||||
In the "Trigger" section, check the ‘comments’ and ‘merge request events’ boxes.
|
||||
```
|
||||
git clone https://github.com/Codium-ai/pr-agent.git
|
||||
```
|
||||
|
||||
6. Test your installation by opening a merge request or commenting or a merge request using one of CodiumAI's commands.
|
||||
4. Prepare variables and secrets. Skip this step if you plan on settings these as environment variables when running the agent:
|
||||
1. In the configuration file/variables:
|
||||
- Set `deployment_type` to "gitlab"
|
||||
|
||||
2. In the secrets file/variables:
|
||||
- Set your AI model key in the respective section
|
||||
- In the [gitlab] section, set `personal_access_token` (with token from step 1) and `shared_secret` (with secret from step 2)
|
||||
|
||||
|
||||
5. Build a Docker image for the app and optionally push it to a Docker repository. We'll use Dockerhub as an example:
|
||||
```
|
||||
docker build . -t gitlab_pr_agent --target gitlab_webhook -f docker/Dockerfile
|
||||
docker push codiumai/pr-agent:gitlab_webhook # Push to your Docker repository
|
||||
```
|
||||
|
||||
6. Create a webhook in GitLab. Set the URL to ```http[s]://<PR_AGENT_HOSTNAME>/webhook```, the secret token to the generated secret from step 2, and enable the triggers `push`, `comments` and `merge request events`.
|
||||
|
||||
7. Test your installation by opening a merge request or commenting on a merge request using one of CodiumAI's commands.
|
||||
boxes
|
||||
|
@ -1,22 +1,17 @@
|
||||
# Installation
|
||||
|
||||
## Self-hosted PR-Agent
|
||||
If you choose to host you own PR-Agent, you first need to acquire two tokens:
|
||||
|
||||
1. An OpenAI key from [here](https://platform.openai.com/api-keys), with access to GPT-4 (or a key for [other models](../usage-guide/additional_configurations.md/#changing-a-model), if you prefer).
|
||||
2. A GitHub\GitLab\BitBucket personal access token (classic), with the repo scope. [GitHub from [here](https://github.com/settings/tokens)]
|
||||
|
||||
There are several ways to use self-hosted PR-Agent:
|
||||
|
||||
- [Locally](./locally.md)
|
||||
- [GitHub](./github.md)
|
||||
- [GitLab](./gitlab.md)
|
||||
- [BitBucket](./bitbucket.md)
|
||||
- [Azure DevOps](./azure.md)
|
||||
- [GitHub integration](./github.md)
|
||||
- [GitLab integration](./gitlab.md)
|
||||
- [BitBucket integration](./bitbucket.md)
|
||||
- [Azure DevOps integration](./azure.md)
|
||||
|
||||
## PR-Agent Pro 💎
|
||||
PR-Agent Pro, an app for GitHub\GitLab\BitBucket hosted by CodiumAI, is also available.
|
||||
## Qodo Merge 💎
|
||||
Qodo Merge, an app hosted by QodoAI for GitHub\GitLab\BitBucket, is also available.
|
||||
<br>
|
||||
With PR-Agent Pro Installation is as simple as signing up and adding the PR-Agent app to your relevant repo.
|
||||
<br>
|
||||
See [here](./pr_agent_pro.md) for more details.
|
||||
With Qodo Merge, installation is as simple as adding the Qodo Merge app to your relevant repositories.
|
||||
See [here](https://qodo-merge-docs.qodo.ai/installation/qodo_merge/) for more details.
|
||||
|
@ -1,3 +1,77 @@
|
||||
To run PR-Agent locally, you first need to acquire two keys:
|
||||
|
||||
1. An OpenAI key from [here](https://platform.openai.com/api-keys){:target="_blank"}, with access to GPT-4 (or a key for other [language models](https://qodo-merge-docs.qodo.ai/usage-guide/changing_a_model/), if you prefer).
|
||||
2. A personal access token from your Git platform (GitHub, GitLab, BitBucket) with repo scope. GitHub token, for example, can be issued from [here](https://github.com/settings/tokens){:target="_blank"}
|
||||
|
||||
|
||||
## Using Docker image
|
||||
|
||||
A list of the relevant tools can be found in the [tools guide](../tools/ask.md).
|
||||
|
||||
To invoke a tool (for example `review`), you can run PR-Agent directly from the Docker image. Here's how:
|
||||
|
||||
- For GitHub:
|
||||
```
|
||||
docker run --rm -it -e OPENAI.KEY=<your key> -e GITHUB.USER_TOKEN=<your token> codiumai/pr-agent:latest --pr_url <pr_url> review
|
||||
```
|
||||
If you are using GitHub enterprise server, you need to specify the custom url as variable.
|
||||
For example, if your GitHub server is at `https://github.mycompany.com`, add the following to the command:
|
||||
```
|
||||
-e GITHUB.BASE_URL=https://github.mycompany.com/api/v3
|
||||
```
|
||||
|
||||
- For GitLab:
|
||||
```
|
||||
docker run --rm -it -e OPENAI.KEY=<your key> -e CONFIG.GIT_PROVIDER=gitlab -e GITLAB.PERSONAL_ACCESS_TOKEN=<your token> codiumai/pr-agent:latest --pr_url <pr_url> review
|
||||
```
|
||||
|
||||
If you have a dedicated GitLab instance, you need to specify the custom url as variable:
|
||||
```
|
||||
-e GITLAB.URL=<your gitlab instance url>
|
||||
```
|
||||
|
||||
- For BitBucket:
|
||||
```
|
||||
docker run --rm -it -e CONFIG.GIT_PROVIDER=bitbucket -e OPENAI.KEY=$OPENAI_API_KEY -e BITBUCKET.BEARER_TOKEN=$BITBUCKET_BEARER_TOKEN codiumai/pr-agent:latest --pr_url=<pr_url> review
|
||||
```
|
||||
|
||||
For other git providers, update `CONFIG.GIT_PROVIDER` accordingly and check the [`pr_agent/settings/.secrets_template.toml`](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/.secrets_template.toml) file for environment variables expected names and values.
|
||||
|
||||
### Utilizing environment variables
|
||||
|
||||
It is also possible to provide or override the configuration by setting the corresponding environment variables.
|
||||
You can define the corresponding environment variables by following this convention: `<TABLE>__<KEY>=<VALUE>` or `<TABLE>.<KEY>=<VALUE>`.
|
||||
The `<TABLE>` refers to a table/section in a configuration file and `<KEY>=<VALUE>` refers to the key/value pair of a setting in the configuration file.
|
||||
|
||||
For example, suppose you want to run `pr_agent` that connects to a self-hosted GitLab instance similar to an example above.
|
||||
You can define the environment variables in a plain text file named `.env` with the following content:
|
||||
|
||||
```
|
||||
CONFIG__GIT_PROVIDER="gitlab"
|
||||
GITLAB__URL="<your url>"
|
||||
GITLAB__PERSONAL_ACCESS_TOKEN="<your token>"
|
||||
OPENAI__KEY="<your key>"
|
||||
```
|
||||
|
||||
Then, you can run `pr_agent` using Docker with the following command:
|
||||
|
||||
```shell
|
||||
docker run --rm -it --env-file .env codiumai/pr-agent:latest <tool> <tool parameter>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### I get an error when running the Docker image. What should I do?
|
||||
|
||||
If you encounter an error when running the Docker image, it is almost always due to a misconfiguration of api keys or tokens.
|
||||
|
||||
Note that litellm, which is used by pr-agent, sometimes returns non-informative error messages such as `APIError: OpenAIException - Connection error.`
|
||||
Carefully check the api keys and tokens you provided and make sure they are correct.
|
||||
Adjustments may be needed depending on your llm provider.
|
||||
|
||||
For example, for Azure OpenAI, additional keys are [needed](https://qodo-merge-docs.qodo.ai/usage-guide/changing_a_model/#azure).
|
||||
Same goes for other providers, make sure to check the [documentation](https://qodo-merge-docs.qodo.ai/usage-guide/changing_a_model/#changing-a-model)
|
||||
|
||||
## Using pip package
|
||||
|
||||
Install the package:
|
||||
@ -16,8 +90,8 @@ from pr_agent.config_loader import get_settings
|
||||
|
||||
def main():
|
||||
# Fill in the following values
|
||||
provider = "github" # GitHub provider
|
||||
user_token = "..." # GitHub user token
|
||||
provider = "github" # github/gitlab/bitbucket/azure_devops
|
||||
user_token = "..." # user token
|
||||
openai_key = "..." # OpenAI key
|
||||
pr_url = "..." # PR URL, for example 'https://github.com/Codium-ai/pr-agent/pull/809'
|
||||
command = "/review" # Command to run (e.g. '/review', '/describe', '/ask="What is the purpose of this PR?"', ...)
|
||||
@ -35,48 +109,6 @@ if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
|
||||
## Using Docker image
|
||||
|
||||
A list of the relevant tools can be found in the [tools guide](../tools/ask.md).
|
||||
|
||||
To invoke a tool (for example `review`), you can run directly from the Docker image. Here's how:
|
||||
|
||||
- For GitHub:
|
||||
```
|
||||
docker run --rm -it -e OPENAI.KEY=<your key> -e GITHUB.USER_TOKEN=<your token> codiumai/pr-agent:latest --pr_url <pr_url> review
|
||||
```
|
||||
|
||||
- For GitLab:
|
||||
```
|
||||
docker run --rm -it -e OPENAI.KEY=<your key> -e CONFIG.GIT_PROVIDER=gitlab -e GITLAB.PERSONAL_ACCESS_TOKEN=<your token> codiumai/pr-agent:latest --pr_url <pr_url> review
|
||||
```
|
||||
|
||||
Note: If you have a dedicated GitLab instance, you need to specify the custom url as variable:
|
||||
```
|
||||
docker run --rm -it -e OPENAI.KEY=<your key> -e CONFIG.GIT_PROVIDER=gitlab -e GITLAB.PERSONAL_ACCESS_TOKEN=<your token> -e GITLAB.URL=<your gitlab instance url> codiumai/pr-agent:latest --pr_url <pr_url> review
|
||||
```
|
||||
|
||||
- For BitBucket:
|
||||
```
|
||||
docker run --rm -it -e CONFIG.GIT_PROVIDER=bitbucket -e OPENAI.KEY=$OPENAI_API_KEY -e BITBUCKET.BEARER_TOKEN=$BITBUCKET_BEARER_TOKEN codiumai/pr-agent:latest --pr_url=<pr_url> review
|
||||
```
|
||||
|
||||
For other git providers, update CONFIG.GIT_PROVIDER accordingly, and check the `pr_agent/settings/.secrets_template.toml` file for the environment variables expected names and values.
|
||||
|
||||
---
|
||||
|
||||
|
||||
If you want to ensure you're running a specific version of the Docker image, consider using the image's digest:
|
||||
```bash
|
||||
docker run --rm -it -e OPENAI.KEY=<your key> -e GITHUB.USER_TOKEN=<your token> codiumai/pr-agent@sha256:71b5ee15df59c745d352d84752d01561ba64b6d51327f97d46152f0c58a5f678 --pr_url <pr_url> review
|
||||
```
|
||||
|
||||
Or you can run a [specific released versions](https://github.com/Codium-ai/pr-agent/blob/main/RELEASE_NOTES.md) of pr-agent, for example:
|
||||
```
|
||||
codiumai/pr-agent@v0.9
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Run from source
|
||||
|
||||
@ -115,7 +147,7 @@ python3 -m pr_agent.cli --issue_url <issue_url> similar_issue
|
||||
...
|
||||
```
|
||||
|
||||
[Optional] Add the pr_agent folder to your PYTHONPATH
|
||||
[Optional] Add the pr_agent folder to your PYTHONPATH
|
||||
```
|
||||
export PYTHONPATH=$PYTHONPATH:<PATH to pr_agent folder>
|
||||
```
|
49
docs/docs/installation/pr_agent.md
Normal file
49
docs/docs/installation/pr_agent.md
Normal file
@ -0,0 +1,49 @@
|
||||
# PR-Agent Installation Guide
|
||||
|
||||
PR-Agent can be deployed in various environments and platforms. Choose the installation method that best suits your needs:
|
||||
|
||||
## 🖥️ Local Installation
|
||||
|
||||
Learn how to run PR-Agent locally using:
|
||||
|
||||
- Docker image
|
||||
- pip package
|
||||
- CLI from source code
|
||||
|
||||
[View Local Installation Guide →](https://qodo-merge-docs.qodo.ai/installation/locally/)
|
||||
|
||||
## 🐙 GitHub Integration
|
||||
|
||||
Set up PR-Agent with GitHub as:
|
||||
|
||||
- GitHub Action
|
||||
- Local GitHub App
|
||||
|
||||
[View GitHub Integration Guide →](https://qodo-merge-docs.qodo.ai/installation/github/)
|
||||
|
||||
## 🦊 GitLab Integration
|
||||
|
||||
Deploy PR-Agent on GitLab as:
|
||||
|
||||
- GitLab pipeline job
|
||||
- Local GitLab webhook server
|
||||
|
||||
[View GitLab Integration Guide →](https://qodo-merge-docs.qodo.ai/installation/gitlab/)
|
||||
|
||||
## 🟦 BitBucket Integration
|
||||
|
||||
Implement PR-Agent in BitBucket as:
|
||||
|
||||
- BitBucket pipeline job
|
||||
- Local BitBucket server
|
||||
|
||||
[View BitBucket Integration Guide →](https://qodo-merge-docs.qodo.ai/installation/bitbucket/)
|
||||
|
||||
## 🔷 Azure DevOps Integration
|
||||
|
||||
Configure PR-Agent with Azure DevOps as:
|
||||
|
||||
- Azure DevOps pipeline job
|
||||
- Local Azure DevOps webhook
|
||||
|
||||
[View Azure DevOps Integration Guide →](https://qodo-merge-docs.qodo.ai/installation/azure/)
|
@ -1,64 +0,0 @@
|
||||
|
||||
## Getting Started with PR-Agent Pro
|
||||
|
||||
PR-Agent Pro is a versatile application compatible with GitHub, GitLab, and BitBucket, hosted by CodiumAI.
|
||||
See [here](https://pr-agent-docs.codium.ai/#pr-agent-pro) for more details about the benefits of using PR-Agent Pro.
|
||||
|
||||
Interested parties can subscribe to PR-Agent Pro through the following [link](https://www.codium.ai/pricing/).
|
||||
After subscribing, you are granted the ability to easily install the application across any of your repositories.
|
||||
|
||||
{width=468}
|
||||
|
||||
Each user who wants to use PR-Agent pro needs to buy a seat.
|
||||
Initially, CodiumAI offers a two-week trial period at no cost, after which continued access requires each user to secure a personal seat.
|
||||
Once a user acquires a seat, they gain the flexibility to use PR-Agent Pro across any repository where it was enabled.
|
||||
|
||||
Users without a purchased seat who interact with a repository featuring PR-Agent Pro are entitled to receive up to five complimentary feedbacks.
|
||||
Beyond this limit, PR-Agent Pro will cease to respond to their inquiries unless a seat is purchased.
|
||||
|
||||
|
||||
## Install PR-Agent Pro for GitLab (Teams & Enterprise)
|
||||
|
||||
Since GitLab platform does not support apps, installing PR-Agent Pro for GitLab is a bit more involved, and requires the following steps:
|
||||
|
||||
### Step 1
|
||||
|
||||
Acquire a personal, project or group level access token. Enable the “api” scope in order to allow PR-Agent to read pull requests, comment and respond to requests.
|
||||
|
||||
<figure markdown="1">
|
||||
{width=750}
|
||||
</figure>
|
||||
|
||||
Store the token in a safe place, you won’t be able to access it again after it was generated.
|
||||
|
||||
### Step 2
|
||||
|
||||
Generate a shared secret and link it to the access token. Browse to [https://register.gitlab.pr-agent.codium.ai](https://register.gitlab.pr-agent.codium.ai).
|
||||
Fill in your generated GitLab token and your company or personal name in the appropriate fields and click "Submit".
|
||||
|
||||
You should see "Success!" displayed above the Submit button, and a shared secret will be generated. Store it in a safe place, you won’t be able to access it again after it was generated.
|
||||
|
||||
### Step 3
|
||||
|
||||
Install a webhook for your repository or groups, by clicking “webhooks” on the settings menu. Click the “Add new webhook” button.
|
||||
|
||||
<figure markdown="1">
|
||||

|
||||
</figure>
|
||||
|
||||
In the webhook definition form, fill in the following fields:
|
||||
URL: https://pro.gitlab.pr-agent.codium.ai/webhook
|
||||
|
||||
Secret token: Your CodiumAI key
|
||||
Trigger: Check the ‘comments’ and ‘merge request events’ boxes.
|
||||
Enable SSL verification: Check the box.
|
||||
|
||||
<figure markdown="1">
|
||||
{width=750}
|
||||
</figure>
|
||||
|
||||
### Step 4
|
||||
|
||||
You’re all set!
|
||||
|
||||
Open a new merge request or add a MR comment with one of PR-Agent’s commands such as /review, /describe or /improve.
|
81
docs/docs/installation/qodo_merge.md
Normal file
81
docs/docs/installation/qodo_merge.md
Normal file
@ -0,0 +1,81 @@
|
||||
Qodo Merge is a versatile application compatible with GitHub, GitLab, and BitBucket, hosted by QodoAI.
|
||||
See [here](https://qodo-merge-docs.qodo.ai/overview/pr_agent_pro/) for more details about the benefits of using Qodo Merge.
|
||||
|
||||
A complimentary two-week trial is provided to all new users. Following the trial period, user licenses (seats) are required for continued access.
|
||||
To purchase user licenses, please visit our [pricing page](https://www.qodo.ai/pricing/).
|
||||
Once subscribed, users can seamlessly deploy the application across any of their code repositories.
|
||||
|
||||
## Install Qodo Merge for GitHub
|
||||
|
||||
### GitHub Cloud
|
||||
|
||||
Qodo Merge for GitHub cloud is available for installation through the [GitHub Marketplace](https://github.com/apps/qodo-merge-pro).
|
||||
|
||||
{width=468}
|
||||
|
||||
### GitHub Enterprise Server
|
||||
|
||||
To use Qodo Merge application on your private GitHub Enterprise Server, you will need to contact us for starting an [Enterprise](https://www.codium.ai/pricing/) trial.
|
||||
|
||||
### GitHub Open Source Projects
|
||||
|
||||
For open-source projects, Qodo Merge is available for free usage. To install Qodo Merge for your open-source repositories, use the following marketplace [link](https://github.com/apps/qodo-merge-pro-for-open-source).
|
||||
|
||||
## Install Qodo Merge for Bitbucket
|
||||
|
||||
### Bitbucket Cloud
|
||||
|
||||
Qodo Merge for Bitbucket Cloud is available for installation through the following [link](https://bitbucket.org/site/addons/authorize?addon_key=d6df813252c37258)
|
||||
|
||||
{width=468}
|
||||
|
||||
### Bitbucket Server
|
||||
|
||||
To use Qodo Merge application on your private Bitbucket Server, you will need to contact us for starting an [Enterprise](https://www.qodo.ai/pricing/) trial.
|
||||
|
||||
|
||||
## Install Qodo Merge for GitLab (Teams & Enterprise)
|
||||
|
||||
Since GitLab platform does not support apps, installing Qodo Merge for GitLab is a bit more involved, and requires the following steps:
|
||||
|
||||
#### Step 1
|
||||
|
||||
Acquire a personal, project or group level access token. Enable the “api” scope in order to allow Qodo Merge to read pull requests, comment and respond to requests.
|
||||
|
||||
<figure markdown="1">
|
||||
{width=750}
|
||||
</figure>
|
||||
|
||||
Store the token in a safe place, you won’t be able to access it again after it was generated.
|
||||
|
||||
#### Step 2
|
||||
|
||||
Generate a shared secret and link it to the access token. Browse to [https://register.gitlab.pr-agent.codium.ai](https://register.gitlab.pr-agent.codium.ai).
|
||||
Fill in your generated GitLab token and your company or personal name in the appropriate fields and click "Submit".
|
||||
|
||||
You should see "Success!" displayed above the Submit button, and a shared secret will be generated. Store it in a safe place, you won’t be able to access it again after it was generated.
|
||||
|
||||
#### Step 3
|
||||
|
||||
Install a webhook for your repository or groups, by clicking “webhooks” on the settings menu. Click the “Add new webhook” button.
|
||||
|
||||
<figure markdown="1">
|
||||

|
||||
</figure>
|
||||
|
||||
In the webhook definition form, fill in the following fields:
|
||||
URL: https://pro.gitlab.pr-agent.codium.ai/webhook
|
||||
|
||||
Secret token: Your QodoAI key
|
||||
Trigger: Check the ‘comments’ and ‘merge request events’ boxes.
|
||||
Enable SSL verification: Check the box.
|
||||
|
||||
<figure markdown="1">
|
||||
{width=750}
|
||||
</figure>
|
||||
|
||||
#### Step 4
|
||||
|
||||
You’re all set!
|
||||
|
||||
Open a new merge request or add a MR comment with one of Qodo Merge’s commands such as /review, /describe or /improve.
|
@ -1,17 +1,16 @@
|
||||
## Self-hosted PR-Agent
|
||||
|
||||
- If you host PR-Agent with your OpenAI API key, it is between you and OpenAI. You can read their API data privacy policy here:
|
||||
https://openai.com/enterprise-privacy
|
||||
- If you self-host PR-Agent with your OpenAI (or other LLM provider) API key, it is between you and the provider. We don't send your code data to Qodo servers.
|
||||
|
||||
## PR-Agent Pro 💎
|
||||
## Qodo Merge 💎
|
||||
|
||||
- When using PR-Agent Pro 💎, hosted by CodiumAI, we will not store any of your data, nor will we use it for training. You will also benefit from an OpenAI account with zero data retention.
|
||||
- When using Qodo Merge💎, hosted by Qodo, we will not store any of your data, nor will we use it for training. You will also benefit from an OpenAI account with zero data retention.
|
||||
|
||||
- For certain clients, CodiumAI-hosted PR-Agent Pro will use CodiumAI’s proprietary models. If this is the case, you will be notified.
|
||||
- For certain clients, Qodo Merge will use Qodo’s proprietary models. If this is the case, you will be notified.
|
||||
|
||||
- No passive collection of Code and Pull Requests’ data — PR-Agent will be active only when you invoke it, and it will then extract and analyze only data relevant to the executed command and queried pull request.
|
||||
- No passive collection of Code and Pull Requests’ data — Qodo Merge will be active only when you invoke it, and it will then extract and analyze only data relevant to the executed command and queried pull request.
|
||||
|
||||
|
||||
## PR-Agent Chrome extension
|
||||
## Qodo Merge Chrome extension
|
||||
|
||||
- The [PR-Agent Chrome extension](https://chromewebstore.google.com/detail/pr-agent-chrome-extension/ephlnjeghhogofkifjloamocljapahnl) serves solely to modify the visual appearance of a GitHub PR screen. It does not transmit any user's repo or pull request code. Code is only sent for processing when a user submits a GitHub comment that activates a PR-Agent tool, in accordance with the standard privacy policy of PR-Agent.
|
||||
- The [Qodo Merge Chrome extension](https://chromewebstore.google.com/detail/pr-agent-chrome-extension/ephlnjeghhogofkifjloamocljapahnl) will not send your code to any external servers.
|
||||
|
@ -1,81 +0,0 @@
|
||||
# Overview
|
||||
|
||||
CodiumAI PR-Agent is an open-source tool to help efficiently review and handle pull requests.
|
||||
|
||||
- See the [Installation Guide](./installation/index.md) for instructions on installing and running the tool on different git platforms.
|
||||
|
||||
- See the [Usage Guide](./usage-guide/index.md) for instructions on running the PR-Agent commands via different interfaces, including _CLI_, _online usage_, or by _automatically triggering_ them when a new PR is opened.
|
||||
|
||||
- See the [Tools Guide](./tools/index.md) for a detailed description of the different tools.
|
||||
|
||||
|
||||
## PR-Agent Features
|
||||
PR-Agent offers extensive pull request functionalities across various git providers.
|
||||
|
||||
| | | GitHub | Gitlab | Bitbucket | Azure DevOps |
|
||||
|-------|-----------------------------------------------------------------------------------------------------------------------|:------:|:------:|:---------:|:------------:|
|
||||
| TOOLS | Review | ✅ | ✅ | ✅ | ✅ |
|
||||
| | ⮑ Incremental | ✅ | | | |
|
||||
| | ⮑ [SOC2 Compliance](https://pr-agent-docs.codium.ai/tools/review/#soc2-ticket-compliance){:target="_blank"} 💎 | ✅ | ✅ | ✅ | ✅ |
|
||||
| | Ask | ✅ | ✅ | ✅ | ✅ |
|
||||
| | Describe | ✅ | ✅ | ✅ | ✅ |
|
||||
| | ⮑ [Inline file summary](https://pr-agent-docs.codium.ai/tools/describe/#inline-file-summary){:target="_blank"} 💎 | ✅ | ✅ | | ✅ |
|
||||
| | Improve | ✅ | ✅ | ✅ | ✅ |
|
||||
| | ⮑ Extended | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [Custom Prompt](./tools/custom_prompt.md){:target="_blank"} 💎 | ✅ | ✅ | ✅ | ✅ |
|
||||
| | Reflect and Review | ✅ | ✅ | ✅ | ✅ |
|
||||
| | Update CHANGELOG.md | ✅ | ✅ | ✅ | ️ |
|
||||
| | Find Similar Issue | ✅ | | | ️ |
|
||||
| | [Add PR Documentation](./tools/documentation.md){:target="_blank"} 💎 | ✅ | ✅ | | ✅ |
|
||||
| | [Generate Custom Labels](./tools/describe.md#handle-custom-labels-from-the-repos-labels-page-💎){:target="_blank"} 💎 | ✅ | ✅ | | ✅ |
|
||||
| | [Analyze PR Components](./tools/analyze.md){:target="_blank"} 💎 | ✅ | ✅ | | ✅ |
|
||||
| | | | | | ️ |
|
||||
| USAGE | CLI | ✅ | ✅ | ✅ | ✅ |
|
||||
| | App / webhook | ✅ | ✅ | ✅ | ✅ |
|
||||
| | Actions | ✅ | | | ️ |
|
||||
| | | | | |
|
||||
| CORE | PR compression | ✅ | ✅ | ✅ | ✅ |
|
||||
| | Repo language prioritization | ✅ | ✅ | ✅ | ✅ |
|
||||
| | Adaptive and token-aware file patch fitting | ✅ | ✅ | ✅ | ✅ |
|
||||
| | Multiple models support | ✅ | ✅ | ✅ | ✅ |
|
||||
| | Incremental PR review | ✅ | | | |
|
||||
| | [Static code analysis](./tools/analyze.md/){:target="_blank"} 💎 | ✅ | ✅ | ✅ | ✅ |
|
||||
| | [Multiple configuration options](./usage-guide/configuration_options.md){:target="_blank"} 💎 | ✅ | ✅ | ✅ | ✅ |
|
||||
|
||||
💎 marks a feature available only in [PR-Agent Pro](https://www.codium.ai/pricing/){:target="_blank"}
|
||||
|
||||
|
||||
## Example Results
|
||||
<hr>
|
||||
|
||||
#### [/describe](https://github.com/Codium-ai/pr-agent/pull/530)
|
||||
<figure markdown="1">
|
||||
{width=512}
|
||||
</figure>
|
||||
<hr>
|
||||
|
||||
#### [/review](https://github.com/Codium-ai/pr-agent/pull/732#issuecomment-1975099151)
|
||||
<figure markdown="1">
|
||||
{width=512}
|
||||
</figure>
|
||||
<hr>
|
||||
|
||||
#### [/improve](https://github.com/Codium-ai/pr-agent/pull/732#issuecomment-1975099159)
|
||||
<figure markdown="1">
|
||||
{width=512}
|
||||
</figure>
|
||||
<hr>
|
||||
|
||||
#### [/generate_labels](https://github.com/Codium-ai/pr-agent/pull/530)
|
||||
<figure markdown="1">
|
||||
{width=300}
|
||||
</figure>
|
||||
<hr>
|
||||
|
||||
## How it Works
|
||||
|
||||
The following diagram illustrates PR-Agent tools and their flow:
|
||||
|
||||

|
||||
|
||||
Check out the [PR Compression strategy](core-abilities/index.md) page for more details on how we convert a code diff to a manageable LLM prompt
|
@ -1,18 +1,52 @@
|
||||
[PR-Agent Pro](https://www.codium.ai/pricing/) is a hosted version of PR-Agent, provided by CodiumAI. It is available for a monthly fee, and provides the following benefits:
|
||||
### Overview
|
||||
|
||||
1. **Fully managed** - We take care of everything for you - hosting, models, regular updates, and more. Installation is as simple as signing up and adding the PR-Agent app to your GitHub\GitLab\BitBucket repo.
|
||||
2. **Improved privacy** - No data will be stored or used to train models. PR-Agent Pro will employ zero data retention, and will use an OpenAI account with zero data retention.
|
||||
3. **Improved support** - PR-Agent Pro users will receive priority support, and will be able to request new features and capabilities.
|
||||
4. **Extra features** -In addition to the benefits listed above, PR-Agent Pro will emphasize more customization, and the usage of static code analysis, in addition to LLM logic, to improve results. It has the following additional tools and features:
|
||||
- (Tool): [**Analyze PR components**](./tools/analyze.md/)
|
||||
- (Tool): [**Custom Prompt Suggestions**](./tools/custom_prompt.md/)
|
||||
- (Tool): [**Tests**](./tools/test.md/)
|
||||
- (Tool): [**PR documentation**](./tools/documentation.md/)
|
||||
- (Tool): [**Improve Component**](https://pr-agent-docs.codium.ai/tools/improve_component/)
|
||||
- (Tool): [**Similar code search**](https://pr-agent-docs.codium.ai/tools/similar_code/)
|
||||
- (Tool): [**CI feedback**](./tools/ci_feedback.md/)
|
||||
- (Feature): [**Interactive triggering**](./usage-guide/automations_and_usage.md/#interactive-triggering)
|
||||
- (Feature): [**SOC2 compliance check**](./tools/review.md/#soc2-ticket-compliance)
|
||||
- (Feature): [**Custom labels**](./tools/describe.md/#handle-custom-labels-from-the-repos-labels-page)
|
||||
- (Feature): [**Global and wiki configuration**](./usage-guide/configuration_options.md/#wiki-configuration-file)
|
||||
- (Feature): [**Inline file summary**](https://pr-agent-docs.codium.ai/tools/describe/#inline-file-summary)
|
||||
[Qodo Merge](https://www.codium.ai/pricing/){:target="_blank"} is a hosted version of open-source [PR-Agent](https://github.com/Codium-ai/pr-agent){:target="_blank"}. A complimentary two-week trial is offered, followed by a monthly subscription fee.
|
||||
Qodo Merge is designed for companies and teams that require additional features and capabilities. It provides the following benefits:
|
||||
|
||||
1. **Fully managed** - We take care of everything for you - hosting, models, regular updates, and more. Installation is as simple as signing up and adding the Qodo Merge app to your GitHub\GitLab\BitBucket repo.
|
||||
|
||||
2. **Improved privacy** - No data will be stored or used to train models. Qodo Merge will employ zero data retention, and will use an OpenAI and Claude accounts with zero data retention.
|
||||
|
||||
3. **Improved support** - Qodo Merge users will receive priority support, and will be able to request new features and capabilities.
|
||||
|
||||
4. **Supporting self-hosted git servers** - Qodo Merge can be installed on GitHub Enterprise Server, GitLab, and BitBucket. For more information, see the [installation guide](https://qodo-merge-docs.qodo.ai/installation/pr_agent_pro/).
|
||||
|
||||
5. **PR Chat** - Qodo Merge allows you to engage in [private chat](https://qodo-merge-docs.qodo.ai/chrome-extension/features/#pr-chat) about your pull requests on private repositories.
|
||||
|
||||
### Additional features
|
||||
|
||||
Here are some of the additional features and capabilities that Qodo Merge offers:
|
||||
|
||||
| Feature | Description |
|
||||
|----------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| [**Model selection**](https://qodo-merge-docs.qodo.ai/usage-guide/PR_agent_pro_models/) | Choose the model that best fits your needs, among top models like `GPT4` and `Claude-Sonnet-3.5`
|
||||
| [**Global and wiki configuration**](https://qodo-merge-docs.qodo.ai/usage-guide/configuration_options/) | Control configurations for many repositories from a single location; <br>Edit configuration of a single repo without committing code |
|
||||
| [**Apply suggestions**](https://qodo-merge-docs.qodo.ai/tools/improve/#overview) | Generate committable code from the relevant suggestions interactively by clicking on a checkbox |
|
||||
| [**Suggestions impact**](https://qodo-merge-docs.qodo.ai/tools/improve/#assessing-impact) | Automatically mark suggestions that were implemented by the user (either directly in GitHub, or indirectly in the IDE) to enable tracking of the impact of the suggestions |
|
||||
| [**CI feedback**](https://qodo-merge-docs.qodo.ai/tools/ci_feedback/) | Automatically analyze failed CI checks on GitHub and provide actionable feedback in the PR conversation, helping to resolve issues quickly |
|
||||
| [**Advanced usage statistics**](https://www.codium.ai/contact/#/) | Qodo Merge offers detailed statistics at user, repository, and company levels, including metrics about Qodo Merge usage, and also general statistics and insights |
|
||||
| [**Incorporating companies' best practices**](https://qodo-merge-docs.qodo.ai/tools/improve/#best-practices) | Use the companies' best practices as reference to increase the effectiveness and the relevance of the code suggestions |
|
||||
| [**Interactive triggering**](https://qodo-merge-docs.qodo.ai/tools/analyze/#example-usage) | Interactively apply different tools via the `analyze` command |
|
||||
| [**Custom labels**](https://qodo-merge-docs.qodo.ai/tools/describe/#handle-custom-labels-from-the-repos-labels-page) | Define custom labels for Qodo Merge to assign to the PR |
|
||||
|
||||
### Additional tools
|
||||
|
||||
Here are additional tools that are available only for Qodo Merge users:
|
||||
|
||||
| Feature | Description |
|
||||
|---------------------------------------------------------------------------------------|-------------|
|
||||
| [**Custom Prompt Suggestions**](https://qodo-merge-docs.qodo.ai/tools/custom_prompt/) | Generate code suggestions based on custom prompts from the user |
|
||||
| [**Analyze PR components**](https://qodo-merge-docs.qodo.ai/tools/analyze/) | Identify the components that changed in the PR, and enable to interactively apply different tools to them |
|
||||
| [**Tests**](https://qodo-merge-docs.qodo.ai/tools/test/) | Generate tests for code components that changed in the PR |
|
||||
| [**PR documentation**](https://qodo-merge-docs.qodo.ai/tools/documentation/) | Generate docstring for code components that changed in the PR |
|
||||
| [**Improve Component**](https://qodo-merge-docs.qodo.ai/tools/improve_component/) | Generate code suggestions for code components that changed in the PR |
|
||||
| [**Similar code search**](https://qodo-merge-docs.qodo.ai/tools/similar_code/) | Search for similar code in the repository, organization, or entire GitHub |
|
||||
| [**Code implementation**](https://qodo-merge-docs.qodo.ai/tools/implement/) | Generates implementation code from review suggestions |
|
||||
|
||||
|
||||
### Supported languages
|
||||
|
||||
Qodo Merge leverages the world's leading code models - Claude 3.5 Sonnet and GPT-4.
|
||||
As a result, its primary tools such as `describe`, `review`, and `improve`, as well as the PR-chat feature, support virtually all programming languages.
|
||||
|
||||
For specialized commands that require static code analysis, Qodo Merge offers support for specific languages. For more details about features that require static code analysis, please refer to the [documentation](https://qodo-merge-docs.qodo.ai/tools/analyze/#overview).
|
||||
|
@ -1,7 +1,7 @@
|
||||
## Overview
|
||||
The `analyze` tool combines advanced static code analysis with LLM capabilities to provide a comprehensive analysis of the PR code changes.
|
||||
|
||||
The tool scans the PR code changes, find the code components (methods, functions, classes) that changed, and enables to interactively generate tests, docs, code suggestions and similar code search for each component.
|
||||
The tool scans the PR code changes, finds the code components (methods, functions, classes) that changed, and enables to interactively generate tests, docs, code suggestions and similar code search for each component.
|
||||
|
||||
It can be invoked manually by commenting on any PR:
|
||||
```
|
||||
|
@ -25,10 +25,10 @@ There are 3 ways to enable custom labels:
|
||||
When working from CLI, you need to apply the [configuration changes](#configuration-options) to the [custom_labels file](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/custom_labels.toml):
|
||||
|
||||
#### 2. Repo configuration file
|
||||
To enable custom labels, you need to apply the [configuration changes](#configuration-options) to the local `.pr_agent.toml` file in you repository.
|
||||
To enable custom labels, you need to apply the [configuration changes](#configuration-options) to the local `.pr_agent.toml` file in your repository.
|
||||
|
||||
#### 3. Handle custom labels from the Repo's labels page 💎
|
||||
> This feature is available only in PR-Agent Pro
|
||||
> This feature is available only in Qodo Merge
|
||||
|
||||
* GitHub : `https://github.com/{owner}/{repo}/labels`, or click on the "Labels" tab in the issues or PRs page.
|
||||
* GitLab : `https://gitlab.com/{owner}/{repo}/-/labels`, or click on "Manage" -> "Labels" on the left menu.
|
||||
|
@ -25,7 +25,7 @@ If you want to edit [configurations](#configuration-options), add the relevant o
|
||||
|
||||
### Automatic triggering
|
||||
|
||||
To run the `describe` automatically when a PR is opened, define in a [configuration file](https://pr-agent-docs.codium.ai/usage-guide/configuration_options/#wiki-configuration-file):
|
||||
To run the `describe` automatically when a PR is opened, define in a [configuration file](https://qodo-merge-docs.qodo.ai/usage-guide/configuration_options/#wiki-configuration-file):
|
||||
```
|
||||
[github_app]
|
||||
pr_commands = [
|
||||
@ -34,7 +34,7 @@ pr_commands = [
|
||||
]
|
||||
|
||||
[pr_description]
|
||||
publish_labels = ...
|
||||
publish_labels = true
|
||||
...
|
||||
```
|
||||
|
||||
@ -49,7 +49,7 @@ publish_labels = ...
|
||||
<table>
|
||||
<tr>
|
||||
<td><b>publish_labels</b></td>
|
||||
<td>If set to true, the tool will publish the labels to the PR. Default is true.</td>
|
||||
<td>If set to true, the tool will publish labels to the PR. Default is false.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>publish_description_as_comment</b></td>
|
||||
@ -157,7 +157,7 @@ The marker `pr_agent:type` will be replaced with the PR type, `pr_agent:summary`
|
||||
The default labels of the describe tool are quite generic, since they are meant to be used in any repo: [`Bug fix`, `Tests`, `Enhancement`, `Documentation`, `Other`].
|
||||
|
||||
You can define custom labels that are relevant for your repo and use cases.
|
||||
Custom labels can be defined in a [configuration file](https://pr-agent-docs.codium.ai/tools/custom_labels/#configuration-options), or directly in the repo's [labels page](#handle-custom-labels-from-the-repos-labels-page).
|
||||
Custom labels can be defined in a [configuration file](https://qodo-merge-docs.qodo.ai/tools/custom_labels/#configuration-options), or directly in the repo's [labels page](#handle-custom-labels-from-the-repos-labels-page).
|
||||
|
||||
Make sure to provide proper title, and a detailed and well-phrased description for each label, so the tool will know when to suggest it.
|
||||
Each label description should be a **conditional statement**, that indicates if to add the label to the PR or not, according to the PR content.
|
||||
@ -205,7 +205,7 @@ The description should be comprehensive and detailed, indicating when to add the
|
||||
## Usage Tips
|
||||
|
||||
!!! tip "Automation"
|
||||
- When you first install PR-Agent app, the [default mode](../usage-guide/automations_and_usage.md#github-app) for the describe tool is:
|
||||
- When you first install Qodo Merge app, the [default mode](../usage-guide/automations_and_usage.md#github-app) for the describe tool is:
|
||||
```
|
||||
pr_commands = ["/describe", ...]
|
||||
```
|
||||
|
@ -1,6 +1,6 @@
|
||||
## Overview
|
||||
The `help` tool provides a list of all the available tools and their descriptions.
|
||||
For PR-Agent Pro users, it also enables to trigger each tool by checking the relevant box.
|
||||
For Qodo Merge users, it also enables to trigger each tool by checking the relevant box.
|
||||
|
||||
It can be invoked manually by commenting on any PR:
|
||||
```
|
||||
|
50
docs/docs/tools/implement.md
Normal file
50
docs/docs/tools/implement.md
Normal file
@ -0,0 +1,50 @@
|
||||
## Overview
|
||||
|
||||
The `implement` tool converts human code review discussions and feedback into ready-to-commit code changes.
|
||||
It leverages LLM technology to transform PR comments and review suggestions into concrete implementation code, helping developers quickly turn feedback into working solutions.
|
||||
|
||||
## Usage Scenarios
|
||||
|
||||
|
||||
### For Reviewers
|
||||
|
||||
Reviewers can request code changes by: <br>
|
||||
1. Selecting the code block to be modified. <br>
|
||||
2. Adding a comment with the syntax:
|
||||
```
|
||||
/implement <code-change-description>
|
||||
```
|
||||
|
||||
{width=640}
|
||||
|
||||
|
||||
### For PR Authors
|
||||
|
||||
PR authors can implement suggested changes by replying to a review comment using either: <br>
|
||||
1. Add specific implementation details as described above
|
||||
```
|
||||
/implement <code-change-description>
|
||||
```
|
||||
2. Use the original review comment as instructions
|
||||
```
|
||||
/implement
|
||||
```
|
||||
|
||||
{width=640}
|
||||
|
||||
### For Referencing Comments
|
||||
|
||||
You can reference and implement changes from any comment by:
|
||||
```
|
||||
/implement <link-to-review-comment>
|
||||
```
|
||||
|
||||
{width=640}
|
||||
|
||||
Note that the implementation will occur within the review discussion thread.
|
||||
|
||||
|
||||
**Configuration options** <br>
|
||||
- Use `/implement` to implement code change within and based on the review discussion. <br>
|
||||
- Use `/implement <code-change-description>` inside a review discussion to implement specific instructions. <br>
|
||||
- Use `/implement <link-to-review-comment>` to indirectly call the tool from any comment. <br>
|
@ -1,38 +1,43 @@
|
||||
## Overview
|
||||
The `improve` tool scans the PR code changes, and automatically generates suggestions for improving the PR code.
|
||||
The `improve` tool scans the PR code changes, and automatically generates [meaningful](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/pr_code_suggestions_prompts.toml#L41) suggestions for improving the PR code.
|
||||
The tool can be triggered automatically every time a new PR is [opened](../usage-guide/automations_and_usage.md#github-app-automatic-tools-when-a-new-pr-is-opened), or it can be invoked manually by commenting on any PR:
|
||||
```
|
||||
```toml
|
||||
/improve
|
||||
```
|
||||
|
||||
{width=512}
|
||||
|
||||
{width=512}
|
||||
|
||||
Note that the `Apply this suggestion` checkbox, which interactively converts a suggestion into a commitable code comment, is available only for Qodo Merge💎 users.
|
||||
|
||||
|
||||
## Example usage
|
||||
|
||||
### Manual triggering
|
||||
|
||||
Invoke the tool manually by commenting `/improve` on any PR. The code suggestions by default are presented as a single comment:
|
||||
|
||||
{width=512}
|
||||
|
||||
To edit [configurations](#configuration-options) related to the improve tool, use the following template:
|
||||
```
|
||||
```toml
|
||||
/improve --pr_code_suggestions.some_config1=... --pr_code_suggestions.some_config2=...
|
||||
```
|
||||
|
||||
For example, you can choose to present the suggestions as commitable code comments, by running the following command:
|
||||
```
|
||||
For example, you can choose to present all the suggestions as commitable code comments, by running the following command:
|
||||
```toml
|
||||
/improve --pr_code_suggestions.commitable_code_suggestions=true
|
||||
```
|
||||
|
||||
{width=512}
|
||||
|
||||
|
||||
Note that a single comment has a significantly smaller PR footprint. We recommend this mode for most cases.
|
||||
Also note that collapsible are not supported in _Bitbucket_. Hence, the suggestions are presented there as code comments.
|
||||
As can be seen, a single table comment has a significantly smaller PR footprint. We recommend this mode for most cases.
|
||||
Also note that collapsible are not supported in _Bitbucket_. Hence, the suggestions can only be presented in Bitbucket as code comments.
|
||||
|
||||
### Automatic triggering
|
||||
|
||||
To run the `improve` automatically when a PR is opened, define in a [configuration file](https://pr-agent-docs.codium.ai/usage-guide/configuration_options/#wiki-configuration-file):
|
||||
```
|
||||
To run the `improve` automatically when a PR is opened, define in a [configuration file](https://qodo-merge-docs.qodo.ai/usage-guide/configuration_options/#wiki-configuration-file):
|
||||
```toml
|
||||
[github_app]
|
||||
pr_commands = [
|
||||
"/improve",
|
||||
@ -47,72 +52,311 @@ num_code_suggestions_per_chunk = ...
|
||||
- The `pr_commands` lists commands that will be executed automatically when a PR is opened.
|
||||
- The `[pr_code_suggestions]` section contains the configurations for the `improve` tool you want to edit (if any)
|
||||
|
||||
### Extended mode
|
||||
### Assessing Impact 💎
|
||||
|
||||
An extended mode, which does not involve PR Compression and provides more comprehensive suggestions, can be invoked by commenting on any PR by setting:
|
||||
```
|
||||
Note that Qodo Merge tracks two types of implementations:
|
||||
|
||||
- Direct implementation - when the user directly applies the suggestion by clicking the `Apply` checkbox.
|
||||
- Indirect implementation - when the user implements the suggestion in their IDE environment. In this case, Qodo Merge will utilize, after each commit, a dedicated logic to identify if a suggestion was implemented, and will mark it as implemented.
|
||||
|
||||
{width=512}
|
||||
|
||||
In post-process, Qodo Merge counts the number of suggestions that were implemented, and provides general statistics and insights about the suggestions' impact on the PR process.
|
||||
|
||||
{width=512}
|
||||
|
||||
{width=512}
|
||||
|
||||
## Suggestion tracking 💎
|
||||
`Platforms supported: GitHub, GitLab`
|
||||
|
||||
Qodo Merge employs a novel detection system to automatically [identify](https://qodo-merge-docs.qodo.ai/core-abilities/impact_evaluation/) AI code suggestions that PR authors have accepted and implemented.
|
||||
|
||||
Accepted suggestions are also automatically documented in a dedicated wiki page called `.pr_agent_accepted_suggestions`, allowing users to track historical changes, assess the tool's effectiveness, and learn from previously implemented recommendations in the repository.
|
||||
An example [result](https://github.com/Codium-ai/pr-agent/wiki/.pr_agent_accepted_suggestions):
|
||||
|
||||
[{width=768}](https://github.com/Codium-ai/pr-agent/wiki/.pr_agent_accepted_suggestions)
|
||||
|
||||
This dedicated wiki page will also serve as a foundation for future AI model improvements, allowing it to learn from historically implemented suggestions and generate more targeted, contextually relevant recommendations.
|
||||
|
||||
This feature is controlled by a boolean configuration parameter: `pr_code_suggestions.wiki_page_accepted_suggestions` (default is true).
|
||||
|
||||
!!! note "Wiki must be enabled"
|
||||
While the aggregation process is automatic, GitHub repositories require a one-time manual wiki setup.
|
||||
|
||||
To initialize the wiki: navigate to `Wiki`, select `Create the first page`, then click `Save page`.
|
||||
|
||||
{width=768}
|
||||
|
||||
Once a wiki repo is created, the tool will automatically use this wiki for tracking suggestions.
|
||||
|
||||
!!! note "Why a wiki page?"
|
||||
Your code belongs to you, and we respect your privacy. Hence, we won't store any code suggestions in an external database.
|
||||
|
||||
Instead, we leverage a dedicated private page, within your repository wiki, to track suggestions. This approach offers convenient secure suggestion tracking while avoiding pull requests or any noise to the main repository.
|
||||
|
||||
## `Extra instructions` and `best practices`
|
||||
|
||||
The `improve` tool can be further customized by providing additional instructions and best practices to the AI model.
|
||||
|
||||
### Extra instructions
|
||||
|
||||
>`Platforms supported: GitHub, GitLab, Bitbucket, Azure DevOps`
|
||||
|
||||
You can use the `extra_instructions` configuration option to give the AI model additional instructions for the `improve` tool.
|
||||
Be specific, clear, and concise in the instructions. With extra instructions, you are the prompter.
|
||||
|
||||
Examples for possible instructions:
|
||||
```toml
|
||||
[pr_code_suggestions]
|
||||
auto_extended_mode=true
|
||||
extra_instructions="""\
|
||||
(1) Answer in japanese
|
||||
(2) Don't suggest to add try-except block
|
||||
(3) Ignore changes in toml files
|
||||
...
|
||||
"""
|
||||
```
|
||||
(This mode is true by default).
|
||||
Use triple quotes to write multi-line instructions. Use bullet points or numbers to make the instructions more readable.
|
||||
|
||||
Note that the extended mode divides the PR code changes into chunks, up to the token limits, where each chunk is handled separately (might use multiple calls to GPT-4 for large PRs).
|
||||
Hence, the total number of suggestions is proportional to the number of chunks, i.e., the size of the PR.
|
||||
### Best practices 💎
|
||||
|
||||
>`Platforms supported: GitHub, GitLab, Bitbucket`
|
||||
|
||||
Another option to give additional guidance to the AI model is by creating a `best_practices.md` file, either in your repository's root directory or as a [**wiki page**](https://github.com/Codium-ai/pr-agent/wiki) (we recommend the wiki page, as editing and maintaining it over time is easier).
|
||||
This page can contain a list of best practices, coding standards, and guidelines that are specific to your repo/organization.
|
||||
|
||||
The AI model will use this wiki page as a reference, and in case the PR code violates any of the guidelines, it will create additional suggestions, with a dedicated label: `Organization
|
||||
best practice`.
|
||||
|
||||
Example for a python `best_practices.md` content:
|
||||
```markdown
|
||||
## Project best practices
|
||||
- Make sure that I/O operations are encapsulated in a try-except block
|
||||
- Use the `logging` module for logging instead of `print` statements
|
||||
- Use `is` and `is not` to compare with `None`
|
||||
- Use `if __name__ == '__main__':` to run the code only when the script is executed
|
||||
- Use `with` statement to open files
|
||||
...
|
||||
```
|
||||
|
||||
Tips for writing an effective `best_practices.md` file:
|
||||
|
||||
- Write clearly and concisely
|
||||
- Include brief code examples when helpful
|
||||
- Focus on project-specific guidelines, that will result in relevant suggestions you actually want to get
|
||||
- Keep the file relatively short, under 800 lines, since:
|
||||
- AI models may not process effectively very long documents
|
||||
- Long files tend to contain generic guidelines already known to AI
|
||||
|
||||
#### Local and global best practices
|
||||
By default, Qodo Merge will look for a local `best_practices.md` wiki file in the root of the relevant local repo.
|
||||
|
||||
If you want to enable also a global `best_practices.md` wiki file, set first in the global configuration file:
|
||||
|
||||
```toml
|
||||
[best_practices]
|
||||
enable_global_best_practices = true
|
||||
```
|
||||
|
||||
Then, create a `best_practices.md` wiki file in the root of [global](https://qodo-merge-docs.qodo.ai/usage-guide/configuration_options/#global-configuration-file) configuration repository, `pr-agent-settings`.
|
||||
|
||||
#### Best practices for multiple languages
|
||||
For a git organization working with multiple programming languages, you can maintain a centralized global `best_practices.md` file containing language-specific guidelines.
|
||||
When reviewing pull requests, Qodo Merge automatically identifies the programming language and applies the relevant best practices from this file.
|
||||
|
||||
To do this, structure your `best_practices.md` file using the following format:
|
||||
|
||||
```
|
||||
# [Python]
|
||||
...
|
||||
# [Java]
|
||||
...
|
||||
# [JavaScript]
|
||||
...
|
||||
```
|
||||
|
||||
#### Dedicated label for best practices suggestions
|
||||
Best practice suggestions are labeled as `Organization best practice` by default.
|
||||
To customize this label, modify it in your configuration file:
|
||||
|
||||
```toml
|
||||
[best_practices]
|
||||
organization_name = "..."
|
||||
```
|
||||
|
||||
And the label will be: `{organization_name} best practice`.
|
||||
|
||||
|
||||
#### Example results
|
||||
|
||||
{width=512}
|
||||
|
||||
### Auto best practices 💎
|
||||
|
||||
>`Platforms supported: GitHub`
|
||||
|
||||
'Auto best practices' is a novel Qodo Merge capability that:
|
||||
|
||||
1. Identifies recurring patterns from accepted suggestions
|
||||
2. **Automatically** generates [best practices page](https://github.com/qodo-ai/pr-agent/wiki/.pr_agent_auto_best_practices) based on what your team consistently values
|
||||
3. Applies these learned patterns to future code reviews
|
||||
|
||||
This creates an automatic feedback loop where the system continuously learns from your team's choices to provide increasingly relevant suggestions.
|
||||
The system maintains two analysis phases:
|
||||
|
||||
- Open exploration for new issues
|
||||
- Targeted checking against established best practices
|
||||
|
||||
Note that when a [custom best practices](https://qodo-merge-docs.qodo.ai/tools/improve/#best-practices) exist, Qodo Merge will still generate an 'auto best practices' wiki file, though it won't use it in the `improve` tool.
|
||||
Learn more about utilizing 'auto best practices' in our [detailed guide](https://qodo-merge-docs.qodo.ai/core-abilities/auto_best_practices/).
|
||||
|
||||
#### Relevant configurations
|
||||
|
||||
```toml
|
||||
[auto_best_practices]
|
||||
# Disable all auto best practices usage or generation
|
||||
enable_auto_best_practices = true
|
||||
|
||||
# Disable usage of auto best practices file in the 'improve' tool
|
||||
utilize_auto_best_practices = true
|
||||
|
||||
# Extra instructions to the auto best practices generation prompt
|
||||
extra_instructions = ""
|
||||
|
||||
# Max number of patterns to be detected
|
||||
max_patterns = 5
|
||||
```
|
||||
|
||||
|
||||
### Combining `extra instructions` and `best practices` 💎
|
||||
|
||||
The `extra instructions` configuration is more related to the `improve` tool prompt. It can be used, for example, to avoid specific suggestions ("Don't suggest to add try-except block", "Ignore changes in toml files", ...) or to emphasize specific aspects or formats ("Answer in Japanese", "Give only short suggestions", ...)
|
||||
|
||||
In contrast, the `best_practices.md` file is a general guideline for the way code should be written in the repo.
|
||||
|
||||
Using a combination of both can help the AI model to provide relevant and tailored suggestions.
|
||||
|
||||
|
||||
## Usage Tips
|
||||
|
||||
### Implementing the proposed code suggestions
|
||||
Each generated suggestion consists of three key elements:
|
||||
|
||||
1. A single-line summary of the proposed change
|
||||
2. An expandable section containing a comprehensive description of the suggestion
|
||||
3. A diff snippet showing the recommended code modification (before and after)
|
||||
|
||||
We advise users to apply critical analysis and judgment when implementing the proposed suggestions.
|
||||
In addition to mistakes (which may happen, but are rare), sometimes the presented code modification may serve more as an _illustrative example_ than a direct applicable solution.
|
||||
In such cases, we recommend prioritizing the suggestion's detailed description, using the diff snippet primarily as a supporting reference.
|
||||
|
||||
### Dual publishing mode
|
||||
Our recommended approach for presenting code suggestions is through a [table](https://qodo-merge-docs.qodo.ai/tools/improve/#overview) (`--pr_code_suggestions.commitable_code_suggestions=false`).
|
||||
This method significantly reduces the PR footprint and allows for quick and easy digestion of multiple suggestions.
|
||||
|
||||
We also offer a complementary **dual publishing mode**. When enabled, suggestions exceeding a certain score threshold are not only displayed in the table, but also presented as commitable PR comments.
|
||||
This mode helps highlight suggestions deemed more critical.
|
||||
|
||||
To activate dual publishing mode, use the following setting:
|
||||
|
||||
```toml
|
||||
[pr_code_suggestions]
|
||||
dual_publishing_score_threshold = x
|
||||
```
|
||||
|
||||
Where x represents the minimum score threshold (>=) for suggestions to be presented as commitable PR comments in addition to the table. Default is -1 (disabled).
|
||||
|
||||
### Self-review
|
||||
If you set in a configuration file:
|
||||
```
|
||||
```toml
|
||||
[pr_code_suggestions]
|
||||
demand_code_suggestions_self_review = true
|
||||
```
|
||||
|
||||
The `improve` tool will add a checkbox below the suggestions, prompting user to acknowledge that they have reviewed the suggestions.
|
||||
You can set the content of the checkbox text via:
|
||||
```
|
||||
```toml
|
||||
[pr_code_suggestions]
|
||||
code_suggestions_self_review_text = "... (your text here) ..."
|
||||
```
|
||||
|
||||
{width=512}
|
||||
|
||||
💎 In addition, by setting:
|
||||
```
|
||||
|
||||
!!! tip "Tip - Reducing visual footprint after self-review 💎"
|
||||
|
||||
The configuration parameter `pr_code_suggestions.fold_suggestions_on_self_review` (default is True)
|
||||
can be used to automatically fold the suggestions after the user clicks the self-review checkbox.
|
||||
|
||||
This reduces the visual footprint of the suggestions, and also indicates to the PR reviewer that the suggestions have been reviewed by the PR author, and don't require further attention.
|
||||
|
||||
|
||||
|
||||
!!! tip "Tip - Demanding self-review from the PR author 💎"
|
||||
|
||||
By setting:
|
||||
```toml
|
||||
[pr_code_suggestions]
|
||||
approve_pr_on_self_review = true
|
||||
```
|
||||
the tool can automatically approve the PR when the user checks the self-review checkbox.
|
||||
the tool can automatically add an approval when the PR author clicks the self-review checkbox.
|
||||
|
||||
|
||||
- If you set the number of required reviewers for a PR to 2, this effectively means that the PR author must click the self-review checkbox before the PR can be merged (in addition to a human reviewer).
|
||||
|
||||
!!! tip "Demanding self-review from the PR author"
|
||||
If you set the number of required reviewers for a PR to 2, this effectively means that the PR author must click the self-review checkbox before the PR can be merged (in addition to a human reviewer).
|
||||
{width=512}
|
||||
|
||||
- If you keep the number of required reviewers for a PR to 1 and enable this configuration, this effectively means that the PR author can approve the PR by actively clicking the self-review checkbox.
|
||||
|
||||
To prevent unauthorized approvals, this configuration defaults to false, and cannot be altered through online comments; enabling requires a direct update to the configuration file and a commit to the repository. This ensures that utilizing the feature demands a deliberate documented decision by the repository owner.
|
||||
|
||||
|
||||
### How many code suggestions are generated?
|
||||
Qodo Merge uses a dynamic strategy to generate code suggestions based on the size of the pull request (PR). Here's how it works:
|
||||
|
||||
1) Chunking large PRs:
|
||||
|
||||
- Qodo Merge divides large PRs into 'chunks'.
|
||||
- Each chunk contains up to `pr_code_suggestions.max_context_tokens` tokens (default: 14,000).
|
||||
|
||||
|
||||
2) Generating suggestions:
|
||||
|
||||
- For each chunk, Qodo Merge generates up to `pr_code_suggestions.num_code_suggestions_per_chunk` suggestions (default: 4).
|
||||
|
||||
|
||||
This approach has two main benefits:
|
||||
|
||||
- Scalability: The number of suggestions scales with the PR size, rather than being fixed.
|
||||
- Quality: By processing smaller chunks, the AI can maintain higher quality suggestions, as larger contexts tend to decrease AI performance.
|
||||
|
||||
Note: Chunking is primarily relevant for large PRs. For most PRs (up to 500 lines of code), Qodo Merge will be able to process the entire code in a single call.
|
||||
|
||||
|
||||
## Configuration options
|
||||
|
||||
!!! example "General options"
|
||||
??? example "General options"
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><b>num_code_suggestions</b></td>
|
||||
<td>Number of code suggestions provided by the 'improve' tool. Default is 4 for CLI, 0 for auto tools.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>extra_instructions</b></td>
|
||||
<td>Optional extra instructions to the tool. For example: "focus on the changes in the file X. Ignore change in ...".</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>rank_suggestions</b></td>
|
||||
<td>If set to true, the tool will rank the suggestions, based on importance. Default is false.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>commitable_code_suggestions</b></td>
|
||||
<td>If set to true, the tool will display the suggestions as commitable code comments. Default is false.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>persistent_comment</b></td>
|
||||
<td>If set to true, the improve comment will be persistent, meaning that every new improve request will edit the previous one. Default is false.</td>
|
||||
<td><b>dual_publishing_score_threshold</b></td>
|
||||
<td>Minimum score threshold for suggestions to be presented as commitable PR comments in addition to the table. Default is -1 (disabled).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>self_reflect_on_suggestions</b></td>
|
||||
<td>If set to true, the improve tool will calculate an importance score for each suggestion [1-10], and sort the suggestion labels group based on this score. Default is true.</td>
|
||||
<td><b>focus_only_on_problems</b></td>
|
||||
<td>If set to true, suggestions will focus primarily on identifying and fixing code problems, and less on style considerations like best practices, maintainability, or readability. Default is true.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>persistent_comment</b></td>
|
||||
<td>If set to true, the improve comment will be persistent, meaning that every new improve request will edit the previous one. Default is false.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>suggestions_score_threshold</b></td>
|
||||
@ -126,70 +370,45 @@ the tool can automatically approve the PR when the user checks the self-review c
|
||||
<td><b>enable_help_text</b></td>
|
||||
<td>If set to true, the tool will display a help text in the comment. Default is true.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>enable_chat_text</b></td>
|
||||
<td>If set to true, the tool will display a reference to the PR chat in the comment. Default is true.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>wiki_page_accepted_suggestions</b></td>
|
||||
<td>If set to true, the tool will automatically track accepted suggestions in a dedicated wiki page called `.pr_agent_accepted_suggestions`. Default is true.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>allow_thumbs_up_down</b></td>
|
||||
<td>If set to true, all code suggestions will have thumbs up and thumbs down buttons, to encourage users to provide feedback on the suggestions. Default is false.</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
!!! example "params for 'extended' mode"
|
||||
??? example "Params for number of suggestions and AI calls"
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><b>auto_extended_mode</b></td>
|
||||
<td>Enable extended mode automatically (no need for the --extended option). Default is true.</td>
|
||||
<td>Enable chunking the PR code and running the tool on each chunk. Default is true.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>num_code_suggestions_per_chunk</b></td>
|
||||
<td>Number of code suggestions provided by the 'improve' tool, per chunk. Default is 5.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>rank_extended_suggestions</b></td>
|
||||
<td>If set to true, the tool will rank the suggestions, based on importance. Default is true.</td>
|
||||
<td>Number of code suggestions provided by the 'improve' tool, per chunk. Default is 4.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>max_number_of_calls</b></td>
|
||||
<td>Maximum number of chunks. Default is 5.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>final_clip_factor</b></td>
|
||||
<td>Factor to remove suggestions with low confidence. Default is 0.9.</td>
|
||||
<td>Maximum number of chunks. Default is 3.</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Usage Tips
|
||||
|
||||
!!! tip "Extra instructions"
|
||||
|
||||
Extra instructions are very important for the `improve` tool, since they enable you to guide the model to suggestions that are more relevant to the specific needs of the project.
|
||||
|
||||
Be specific, clear, and concise in the instructions. With extra instructions, you are the prompter. Specify relevant aspects that you want the model to focus on.
|
||||
|
||||
Examples for extra instructions:
|
||||
```
|
||||
[pr_code_suggestions] # /improve #
|
||||
extra_instructions="""\
|
||||
Emphasize the following aspects:
|
||||
- Does the code logic cover relevant edge cases?
|
||||
- Is the code logic clear and easy to understand?
|
||||
- Is the code logic efficient?
|
||||
...
|
||||
"""
|
||||
```
|
||||
Use triple quotes to write multi-line instructions. Use bullet points to make the instructions more readable.
|
||||
|
||||
!!! tip "Review vs. Improve tools comparison"
|
||||
|
||||
- The [review](https://pr-agent-docs.codium.ai/tools/review/) tool includes a section called 'Possible issues', that also provide feedback on the PR Code.
|
||||
In this section, the model is instructed to focus **only** on [major bugs and issues](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/pr_reviewer_prompts.toml#L71).
|
||||
- The `improve` tool, on the other hand, has a broader mandate, and in addition to bugs and issues, it can also give suggestions for improving code quality and making the code more efficient, readable, and maintainable (see [here](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/pr_code_suggestions_prompts.toml#L34)).
|
||||
- Hence, if you are interested only in feedback about clear bugs, the `review` tool might suffice. If you want a more detailed feedback, including broader suggestions for improving the PR code, also enable the `improve` tool to run on each PR.
|
||||
|
||||
## A note on code suggestions quality
|
||||
|
||||
- While the current AI for code is getting better and better (GPT-4), it's not flawless. Not all the suggestions will be perfect, and a user should not accept all of them automatically. Critical reading and judgment are required.
|
||||
- While mistakes of the AI are rare but can happen, a real benefit from the suggestions of the `improve` (and [`review`](https://pr-agent-docs.codium.ai/tools/review/)) tool is to catch, with high probability, **mistakes or bugs done by the PR author**, when they happen. So, it's a good practice to spend the needed ~30-60 seconds to review the suggestions, even if not all of them are always relevant.
|
||||
- AI models for code are getting better and better (Sonnet-3.5 and GPT-4), but they are not flawless. Not all the suggestions will be perfect, and a user should not accept all of them automatically. Critical reading and judgment are required.
|
||||
- While mistakes of the AI are rare but can happen, a real benefit from the suggestions of the `improve` (and [`review`](https://qodo-merge-docs.qodo.ai/tools/review/)) tool is to catch, with high probability, **mistakes or bugs done by the PR author**, when they happen. So, it's a good practice to spend the needed ~30-60 seconds to review the suggestions, even if not all of them are always relevant.
|
||||
- The hierarchical structure of the suggestions is designed to help the user to _quickly_ understand them, and to decide which ones are relevant and which are not:
|
||||
|
||||
- Only if the `Category` header is relevant, the user should move to the summarized suggestion description
|
||||
- Only if the summarized suggestion description is relevant, the user should click on the collapsible, to read the full suggestion description with a code preview example.
|
||||
|
||||
In addition, we recommend to use the `extra_instructions` field to guide the model to suggestions that are more relevant to the specific needs of the project.
|
||||
<br>
|
||||
Consider also trying the [Custom Prompt Tool](./custom_prompt.md) 💎, that will **only** propose code suggestions that follow specific guidelines defined by user.
|
||||
- In addition, we recommend to use the [`extra_instructions`](https://qodo-merge-docs.qodo.ai/tools/improve/#extra-instructions-and-best-practices) field to guide the model to suggestions that are more relevant to the specific needs of the project.
|
||||
- The interactive [PR chat](https://qodo-merge-docs.qodo.ai/chrome-extension/) also provides an easy way to get more tailored suggestions and feedback from the AI model.
|
||||
|
@ -1,9 +1,9 @@
|
||||
# Tools
|
||||
|
||||
Here is a list of PR-Agent tools, each with a dedicated page that explains how to use it:
|
||||
Here is a list of Qodo Merge tools, each with a dedicated page that explains how to use it:
|
||||
|
||||
| Tool | Description |
|
||||
|------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
|------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **[PR Description (`/describe`](./describe.md))** | Automatically generating PR description - title, type, summary, code walkthrough and labels |
|
||||
| **[PR Review (`/review`](./review.md))** | Adjustable feedback about the PR, possible issues, security concerns, review effort and more |
|
||||
| **[Code Suggestions (`/improve`](./improve.md))** | Code suggestions for improving the PR |
|
||||
@ -14,9 +14,10 @@ Here is a list of PR-Agent tools, each with a dedicated page that explains how t
|
||||
| **💎 [Add Documentation (`/add_docs`](./documentation.md))** | Generates documentation to methods/functions/classes that changed in the PR |
|
||||
| **💎 [Generate Custom Labels (`/generate_labels`](./custom_labels.md))** | Generates custom labels for the PR, based on specific guidelines defined by the user |
|
||||
| **💎 [Analyze (`/analyze`](./analyze.md))** | Identify code components that changed in the PR, and enables to interactively generate tests, docs, and code suggestions for each component|
|
||||
| **💎 [Test (`/test`](./test.md))** | generate tests for a selected component, based on the PR code changes |
|
||||
| **💎 [Custom Prompt (`/custom_prompt`](./custom_prompt.md))** | Automatically generates custom suggestions for improving the PR code, based on specific guidelines defined by the user |
|
||||
| **💎 [Generate Tests (`/test component_name`](./test.md))** | Automatically generates unit tests for a selected component, based on the PR code changes |
|
||||
| **💎 [Improve Component (`/improve_component component_name`](./improve_component.md))** | Generates code suggestions for a specific code component that changed in the PR |
|
||||
| **💎 [CI Feedback (`/checks ci_job`](./ci_feedback.md))** | Automatically generates feedback and analysis for a failed CI job |
|
||||
|
||||
Note that the tools marked with 💎 are available only for PR-Agent Pro users.
|
||||
| **💎 [Implement (`/implement`](./implement.md))** | Generates implementation code from review suggestions |
|
||||
Note that the tools marked with 💎 are available only for Qodo Merge users.
|
||||
|
@ -8,6 +8,9 @@ The tool can be triggered automatically every time a new PR is [opened](../usage
|
||||
|
||||
Note that the main purpose of the `review` tool is to provide the **PR reviewer** with useful feedbacks and insights. The PR author, in contrast, may prefer to save time and focus on the output of the [improve](./improve.md) tool, which provides actionable code suggestions.
|
||||
|
||||
(Read more about the different personas in the PR process and how Qodo Merge aims to assist them in our [blog](https://www.codium.ai/blog/understanding-the-challenges-and-pain-points-of-the-pull-request-cycle/))
|
||||
|
||||
|
||||
## Example usage
|
||||
|
||||
### Manual triggering
|
||||
@ -27,7 +30,7 @@ If you want to edit [configurations](#configuration-options), add the relevant o
|
||||
|
||||
### Automatic triggering
|
||||
|
||||
To run the `review` automatically when a PR is opened, define in a [configuration file](https://pr-agent-docs.codium.ai/usage-guide/configuration_options/#wiki-configuration-file):
|
||||
To run the `review` automatically when a PR is opened, define in a [configuration file](https://qodo-merge-docs.qodo.ai/usage-guide/configuration_options/#wiki-configuration-file):
|
||||
```
|
||||
[github_app]
|
||||
pr_commands = [
|
||||
@ -36,64 +39,27 @@ pr_commands = [
|
||||
]
|
||||
|
||||
[pr_reviewer]
|
||||
num_code_suggestions = ...
|
||||
extra_instructions = "..."
|
||||
...
|
||||
```
|
||||
|
||||
- The `pr_commands` lists commands that will be executed automatically when a PR is opened.
|
||||
- The `[pr_reviewer]` section contains the configurations for the `review` tool you want to edit (if any).
|
||||
|
||||
### Incremental Mode
|
||||
Incremental review only considers changes since the last PR-Agent review. This can be useful when working on the PR in an iterative manner, and you want to focus on the changes since the last review instead of reviewing the entire PR again.
|
||||
For invoking the incremental mode, the following command can be used:
|
||||
```
|
||||
/review -i
|
||||
```
|
||||
Note that the incremental mode is only available for GitHub.
|
||||
|
||||
{width=512}
|
||||
|
||||
[//]: # (### PR Reflection)
|
||||
|
||||
[//]: # ()
|
||||
[//]: # (By invoking:)
|
||||
|
||||
[//]: # (```)
|
||||
|
||||
[//]: # (/reflect_and_review)
|
||||
|
||||
[//]: # (```)
|
||||
|
||||
[//]: # (The tool will first ask the author questions about the PR, and will guide the review based on their answers.)
|
||||
|
||||
[//]: # ()
|
||||
[//]: # ({width=512})
|
||||
|
||||
[//]: # ()
|
||||
[//]: # ({width=512})
|
||||
|
||||
[//]: # ()
|
||||
[//]: # ({width=512})
|
||||
|
||||
|
||||
|
||||
## Configuration options
|
||||
|
||||
!!! example "General options"
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><b>num_code_suggestions</b></td>
|
||||
<td>Number of code suggestions provided by the 'review' tool. For manual comments, default is 4. For PR-Agent app auto tools, default is 0, meaning no code suggestions will be provided by the review tool, unless you manually edit pr_commands.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>inline_code_comments</b></td>
|
||||
<td>If set to true, the tool will publish the code suggestions as comments on the code diff. Default is false.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>persistent_comment</b></td>
|
||||
<td>If set to true, the review comment will be persistent, meaning that every new review request will edit the previous one. Default is true.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>final_update_message</b></td>
|
||||
<td>When set to true, updating a persistent review comment during online commenting will automatically add a short comment with a link to the updated review in the pull request .Default is true.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>extra_instructions</b></td>
|
||||
<td>Optional extra instructions to the tool. For example: "focus on the changes in the file X. Ignore change in ...".</td>
|
||||
@ -127,20 +93,9 @@ Note that the incremental mode is only available for GitHub.
|
||||
<td><b>require_security_review</b></td>
|
||||
<td>If set to true, the tool will add a section that checks if the PR contains a possible security or vulnerability issue. Default is true.</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
!!! example "SOC2 ticket compliance 💎"
|
||||
|
||||
This sub-tool checks if the PR description properly contains a ticket to a project management system (e.g., Jira, Asana, Trello, etc.), as required by SOC2 compliance. If not, it will add a label to the PR: "Missing SOC2 ticket".
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><b>require_soc2_ticket</b></td>
|
||||
<td>If set to true, the SOC2 ticket checker sub-tool will be enabled. Default is false.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>soc2_ticket_prompt</b></td>
|
||||
<td>The prompt for the SOC2 ticket review. Default is: `Does the PR description include a link to ticket in a project management system (e.g., Jira, Asana, Trello, etc.) ?`. Edit this field if your compliance requirements are different.</td>
|
||||
<td><b>require_ticket_analysis_review</b></td>
|
||||
<td>If set to true, and the PR contains a GitHub or Jira ticket link, the tool will add a section that checks if the PR in fact fulfilled the ticket requirements. Default is true.</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@ -166,11 +121,7 @@ If enabled, the `review` tool can approve a PR when a specific comment, `/review
|
||||
<table>
|
||||
<tr>
|
||||
<td><b>enable_auto_approval</b></td>
|
||||
<td>If set to true, the tool will approve the PR when invoked with the 'auto_approve' command. Default is false. This flag can be changed only from configuration file.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>maximal_review_effort</b></td>
|
||||
<td>Maximal effort level for auto-approval. If the PR's estimated review effort is above this threshold, the auto-approval will not run. Default is 5.</td>
|
||||
<td>If set to true, the tool will approve the PR when invoked with the 'auto_approve' command. Default is false. This flag can be changed only from a configuration file.</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@ -182,17 +133,17 @@ If enabled, the `review` tool can approve a PR when a specific comment, `/review
|
||||
It is recommended to review the [Configuration options](#configuration-options) section, and choose the relevant options for your use case.
|
||||
|
||||
Some of the features that are disabled by default are quite useful, and should be considered for enabling. For example:
|
||||
`require_score_review`, `require_soc2_ticket`, and more.
|
||||
`require_score_review`, and more.
|
||||
|
||||
On the other hand, if you find one of the enabled features to be irrelevant for your use case, disable it. No default configuration can fit all use cases.
|
||||
|
||||
!!! tip "Automation"
|
||||
When you first install PR-Agent app, the [default mode](../usage-guide/automations_and_usage.md#github-app-automatic-tools-when-a-new-pr-is-opened) for the `review` tool is:
|
||||
When you first install Qodo Merge app, the [default mode](../usage-guide/automations_and_usage.md#github-app-automatic-tools-when-a-new-pr-is-opened) for the `review` tool is:
|
||||
```
|
||||
pr_commands = ["/review --pr_reviewer.num_code_suggestions=0", ...]
|
||||
pr_commands = ["/review", ...]
|
||||
```
|
||||
Meaning the `review` tool will run automatically on every PR, without providing code suggestions.
|
||||
Edit this field to enable/disable the tool, or to change the used configurations.
|
||||
Meaning the `review` tool will run automatically on every PR, without any additional configurations.
|
||||
Edit this field to enable/disable the tool, or to change the configurations used.
|
||||
|
||||
!!! tip "Possible labels from the review tool"
|
||||
|
||||
@ -210,7 +161,7 @@ If enabled, the `review` tool can approve a PR when a specific comment, `/review
|
||||
|
||||
Be specific, clear, and concise in the instructions. With extra instructions, you are the prompter. Specify the relevant sub-tool, and the relevant aspects of the PR that you want to emphasize.
|
||||
|
||||
Examples for extra instructions:
|
||||
Examples of extra instructions:
|
||||
```
|
||||
[pr_reviewer]
|
||||
extra_instructions="""\
|
||||
@ -226,7 +177,7 @@ If enabled, the `review` tool can approve a PR when a specific comment, `/review
|
||||
|
||||
!!! tip "Auto-approval"
|
||||
|
||||
PR-Agent can approve a PR when a specific comment is invoked.
|
||||
Qodo Merge can approve a PR when a specific comment is invoked.
|
||||
|
||||
To ensure safety, the auto-approval feature is disabled by default. To enable auto-approval, you need to actively set in a pre-defined configuration file the following:
|
||||
```
|
||||
@ -240,22 +191,11 @@ If enabled, the `review` tool can approve a PR when a specific comment, `/review
|
||||
```
|
||||
/review auto_approve
|
||||
```
|
||||
PR-Agent will automatically approve the PR, and add a comment with the approval.
|
||||
Qodo Merge will automatically approve the PR, and add a comment with the approval.
|
||||
|
||||
|
||||
You can also enable auto-approval only if the PR meets certain requirements, such as that the `estimated_review_effort` label is equal or below a certain threshold, by adjusting the flag:
|
||||
```
|
||||
[pr_reviewer]
|
||||
maximal_review_effort = 5
|
||||
```
|
||||
!!! tip "Code suggestions"
|
||||
|
||||
[//]: # (!!! tip "Code suggestions")
|
||||
The `review` tool previously included a legacy feature for providing code suggestions (controlled by `--pr_reviewer.num_code_suggestion`). This functionality has been deprecated and replaced by the [`improve`](./improve.md) tool, which offers higher quality and more actionable code suggestions.
|
||||
|
||||
[//]: # ()
|
||||
[//]: # ( If you set `num_code_suggestions`>0 , the `review` tool will also provide code suggestions.)
|
||||
|
||||
[//]: # ( )
|
||||
[//]: # ( Notice If you are interested **only** in the code suggestions, it is recommended to use the [`improve`](./improve.md) feature instead, since it is a dedicated only to code suggestions, and usually gives better results.)
|
||||
|
||||
[//]: # ( Use the `review` tool if you want to get more comprehensive feedback, which includes code suggestions as well.)
|
||||
|
||||
|
@ -8,9 +8,9 @@ For example:
|
||||
{width=768}
|
||||
|
||||
|
||||
PR-Agent will examine the code component and will extract the most relevant keywords to search for similar code:
|
||||
Qodo Merge will examine the code component and will extract the most relevant keywords to search for similar code:
|
||||
|
||||
- `extracted keywords`: the keywords that were extracted from the code by PR-Agent. the link will open a search page with the extracted keywords, to allow the user to modify the search if needed.
|
||||
- `extracted keywords`: the keywords that were extracted from the code by Qodo Merge. the link will open a search page with the extracted keywords, to allow the user to modify the search if needed.
|
||||
- `search context`: the context in which the search will be performed, organization's codebase or open-source code (Global).
|
||||
- `similar code`: the most similar code components found. the link will open the code component in the relevant file.
|
||||
- `relevant repositories`: the open-source repositories in which that are relevant to the searched code component and it's keywords.
|
||||
@ -49,9 +49,10 @@ It can be invoked automatically from the analyze table, can be accessed by:
|
||||
/analyze
|
||||
```
|
||||
Choose the components you want to find similar code for, and click on the `similar` checkbox.
|
||||
|
||||
{width=768}
|
||||
|
||||
If you are looking to search for similar code in the organization's codebase, you can click on the `Organization` checkbox, and it will invoke a new search command just for the organization's codebase.
|
||||
You can search for similar code either within the organization's codebase or globally, which includes open-source repositories. Each result will include the relevant code components along with their associated license details.
|
||||
|
||||
{width=768}
|
||||
|
||||
|
@ -10,19 +10,15 @@ To get a list of the components that changed in the PR and choose the relevant c
|
||||
## Example usage
|
||||
|
||||
Invoke the tool manually by commenting `/test` on any PR:
|
||||
|
||||
{width=704}
|
||||
|
||||
The tool will generate tests for the selected component (if no component is stated, it will generate tests for largest component):
|
||||
|
||||
{width=768}
|
||||
{width=768}
|
||||
|
||||
{width=768}
|
||||
|
||||
(Example taken from [here](https://github.com/Codium-ai/pr-agent/pull/598#issuecomment-1913679429)):
|
||||
|
||||
**Notes**
|
||||
- Language that are currently supported by the tool: Python, Java, C++, JavaScript, TypeScript, C#.
|
||||
**Notes** <br>
|
||||
- The following languages are currently supported: Python, Java, C++, JavaScript, TypeScript, C#. <br>
|
||||
- This tool can also be triggered interactively by using the [`analyze`](./analyze.md) tool.
|
||||
|
||||
|
||||
|
@ -17,3 +17,4 @@ Under the section `pr_update_changelog`, the [configuration file](https://github
|
||||
|
||||
- `push_changelog_changes`: whether to push the changes to CHANGELOG.md, or just print them. Default is false (print only).
|
||||
- `extra_instructions`: Optional extra instructions to the tool. For example: "focus on the changes in the file X. Ignore change in ...
|
||||
- `add_pr_link`: whether the model should try to add a link to the PR in the changelog. Default is true.
|
189
docs/docs/usage-guide/EXAMPLE_BEST_PRACTICE.md
Normal file
189
docs/docs/usage-guide/EXAMPLE_BEST_PRACTICE.md
Normal file
@ -0,0 +1,189 @@
|
||||
## Recommend Python Best Practices
|
||||
This document outlines a series of recommended best practices for Python development. These guidelines aim to improve code quality, maintainability, and readability.
|
||||
|
||||
### Imports
|
||||
|
||||
Use `import` statements for packages and modules only, not for individual types, classes, or functions.
|
||||
|
||||
#### Definition
|
||||
|
||||
Reusability mechanism for sharing code from one module to another.
|
||||
|
||||
#### Decision
|
||||
|
||||
- Use `import x` for importing packages and modules.
|
||||
- Use `from x import y` where `x` is the package prefix and `y` is the module name with no prefix.
|
||||
- Use `from x import y as z` in any of the following circumstances:
|
||||
- Two modules named `y` are to be imported.
|
||||
- `y` conflicts with a top-level name defined in the current module.
|
||||
- `y` conflicts with a common parameter name that is part of the public API (e.g., `features`).
|
||||
- `y` is an inconveniently long name, or too generic in the context of your code
|
||||
- Use `import y as z` only when `z` is a standard abbreviation (e.g., `import numpy as np`).
|
||||
|
||||
For example the module `sound.effects.echo` may be imported as follows:
|
||||
|
||||
```
|
||||
from sound.effects import echo
|
||||
...
|
||||
echo.EchoFilter(input, output, delay=0.7, atten=4)
|
||||
|
||||
```
|
||||
|
||||
Do not use relative names in imports. Even if the module is in the same package, use the full package name. This helps prevent unintentionally importing a package twice.
|
||||
|
||||
##### Exemptions
|
||||
|
||||
Exemptions from this rule:
|
||||
|
||||
- Symbols from the following modules are used to support static analysis and type checking:
|
||||
- [`typing` module](https://google.github.io/styleguide/pyguide.html#typing-imports)
|
||||
- [`collections.abc` module](https://google.github.io/styleguide/pyguide.html#typing-imports)
|
||||
- [`typing_extensions` module](https://github.com/python/typing_extensions/blob/main/README.md)
|
||||
- Redirects from the [six.moves module](https://six.readthedocs.io/#module-six.moves).
|
||||
|
||||
### Packages
|
||||
|
||||
Import each module using the full pathname location of the module.
|
||||
|
||||
#### Decision
|
||||
|
||||
All new code should import each module by its full package name.
|
||||
|
||||
Imports should be as follows:
|
||||
|
||||
```
|
||||
Yes:
|
||||
# Reference absl.flags in code with the complete name (verbose).
|
||||
import absl.flags
|
||||
from doctor.who import jodie
|
||||
|
||||
_FOO = absl.flags.DEFINE_string(...)
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
Yes:
|
||||
# Reference flags in code with just the module name (common).
|
||||
from absl import flags
|
||||
from doctor.who import jodie
|
||||
|
||||
_FOO = flags.DEFINE_string(...)
|
||||
|
||||
```
|
||||
|
||||
_(assume this file lives in `doctor/who/` where `jodie.py` also exists)_
|
||||
|
||||
```
|
||||
No:
|
||||
# Unclear what module the author wanted and what will be imported. The actual
|
||||
# import behavior depends on external factors controlling sys.path.
|
||||
# Which possible jodie module did the author intend to import?
|
||||
import jodie
|
||||
|
||||
```
|
||||
|
||||
The directory the main binary is located in should not be assumed to be in `sys.path` despite that happening in some environments. This being the case, code should assume that `import jodie` refers to a third-party or top-level package named `jodie`, not a local `jodie.py`.
|
||||
|
||||
### Default Iterators and Operators
|
||||
Use default iterators and operators for types that support them, like lists, dictionaries, and files.
|
||||
|
||||
#### Definition
|
||||
|
||||
Container types, like dictionaries and lists, define default iterators and membership test operators (“in” and “not in”).
|
||||
|
||||
#### Decision
|
||||
|
||||
Use default iterators and operators for types that support them, like lists, dictionaries, and files. The built-in types define iterator methods, too. Prefer these methods to methods that return lists, except that you should not mutate a container while iterating over it.
|
||||
|
||||
```
|
||||
Yes: for key in adict: ...
|
||||
if obj in alist: ...
|
||||
for line in afile: ...
|
||||
for k, v in adict.items(): ...
|
||||
```
|
||||
|
||||
```
|
||||
No: for key in adict.keys(): ...
|
||||
for line in afile.readlines(): ...
|
||||
```
|
||||
|
||||
### Lambda Functions
|
||||
|
||||
Okay for one-liners. Prefer generator expressions over `map()` or `filter()` with a `lambda`.
|
||||
|
||||
#### Decision
|
||||
|
||||
Lambdas are allowed. If the code inside the lambda function spans multiple lines or is longer than 60-80 chars, it might be better to define it as a regular [nested function](https://google.github.io/styleguide/pyguide.html#lexical-scoping).
|
||||
|
||||
For common operations like multiplication, use the functions from the `operator` module instead of lambda functions. For example, prefer `operator.mul` to `lambda x, y: x * y`.
|
||||
|
||||
### Default Argument Values
|
||||
|
||||
Okay in most cases.
|
||||
|
||||
#### Definition
|
||||
|
||||
You can specify values for variables at the end of a function’s parameter list, e.g., `def foo(a, b=0):`. If `foo` is called with only one argument, `b` is set to 0. If it is called with two arguments, `b` has the value of the second argument.
|
||||
|
||||
#### Decision
|
||||
|
||||
Okay to use with the following caveat:
|
||||
|
||||
Do not use mutable objects as default values in the function or method definition.
|
||||
|
||||
```
|
||||
Yes: def foo(a, b=None):
|
||||
if b is None:
|
||||
b = []
|
||||
Yes: def foo(a, b: Sequence | None = None):
|
||||
if b is None:
|
||||
b = []
|
||||
Yes: def foo(a, b: Sequence = ()): # Empty tuple OK since tuples are immutable.
|
||||
...
|
||||
```
|
||||
|
||||
```
|
||||
from absl import flags
|
||||
_FOO = flags.DEFINE_string(...)
|
||||
|
||||
No: def foo(a, b=[]):
|
||||
...
|
||||
No: def foo(a, b=time.time()): # Is `b` supposed to represent when this module was loaded?
|
||||
...
|
||||
No: def foo(a, b=_FOO.value): # sys.argv has not yet been parsed...
|
||||
...
|
||||
No: def foo(a, b: Mapping = {}): # Could still get passed to unchecked code.
|
||||
...
|
||||
```
|
||||
|
||||
### True/False Evaluations
|
||||
|
||||
|
||||
Use the “implicit” false if possible, e.g., `if foo:` rather than `if foo != []:`
|
||||
|
||||
### Lexical Scoping
|
||||
|
||||
Okay to use.
|
||||
|
||||
An example of the use of this feature is:
|
||||
|
||||
```
|
||||
def get_adder(summand1: float) -> Callable[[float], float]:
|
||||
"""Returns a function that adds numbers to a given number."""
|
||||
def adder(summand2: float) -> float:
|
||||
return summand1 + summand2
|
||||
|
||||
return adder
|
||||
```
|
||||
#### Decision
|
||||
|
||||
Okay to use.
|
||||
|
||||
|
||||
### Threading
|
||||
|
||||
Do not rely on the atomicity of built-in types.
|
||||
|
||||
While Python’s built-in data types such as dictionaries appear to have atomic operations, there are corner cases where they aren’t atomic (e.g. if `__hash__` or `__eq__` are implemented as Python methods) and their atomicity should not be relied upon. Neither should you rely on atomic variable assignment (since this in turn depends on dictionaries).
|
||||
|
||||
Use the `queue` module’s `Queue` data type as the preferred way to communicate data between threads. Otherwise, use the `threading` module and its locking primitives. Prefer condition variables and `threading.Condition` instead of using lower-level locks.
|
18
docs/docs/usage-guide/PR_agent_pro_models.md
Normal file
18
docs/docs/usage-guide/PR_agent_pro_models.md
Normal file
@ -0,0 +1,18 @@
|
||||
## Qodo Merge Models
|
||||
|
||||
The default models used by Qodo Merge are a combination of Claude-3.5-sonnet and OpenAI's GPT-4 models.
|
||||
|
||||
Users can configure Qodo Merge to use solely a specific model by editing the [configuration](https://qodo-merge-docs.qodo.ai/usage-guide/configuration_options/) file.
|
||||
|
||||
For example, to restrict Qodo Merge to using only `Claude-3.5-sonnet`, add this setting:
|
||||
|
||||
```
|
||||
[config]
|
||||
model="claude-3-5-sonnet"
|
||||
```
|
||||
|
||||
Or to restrict Qodo Merge to using only `GPT-4o`, add this setting:
|
||||
```
|
||||
[config]
|
||||
model="gpt-4o"
|
||||
```
|
@ -1,6 +1,28 @@
|
||||
## Show possible configurations
|
||||
The possible configurations of Qodo Merge are stored in [here](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml){:target="_blank"}.
|
||||
In the [tools](https://qodo-merge-docs.qodo.ai/tools/) page you can find explanations on how to use these configurations for each tool.
|
||||
|
||||
To print all the available configurations as a comment on your PR, you can use the following command:
|
||||
```
|
||||
/config
|
||||
```
|
||||
|
||||
{width=512}
|
||||
|
||||
|
||||
To view the **actual** configurations used for a specific tool, after all the user settings are applied, you can add for each tool a `--config.output_relevant_configurations=true` suffix.
|
||||
For example:
|
||||
```
|
||||
/improve --config.output_relevant_configurations=true
|
||||
```
|
||||
Will output an additional field showing the actual configurations used for the `improve` tool.
|
||||
|
||||
{width=512}
|
||||
|
||||
|
||||
## Ignoring files from analysis
|
||||
|
||||
In some cases, you may want to exclude specific files or directories from the analysis performed by CodiumAI PR-Agent. This can be useful, for example, when you have files that are generated automatically or files that shouldn't be reviewed, like vendored code.
|
||||
In some cases, you may want to exclude specific files or directories from the analysis performed by Qodo Merge. This can be useful, for example, when you have files that are generated automatically or files that shouldn't be reviewed, like vendor code.
|
||||
|
||||
You can ignore files or folders using the following methods:
|
||||
- `IGNORE.GLOB`
|
||||
@ -30,7 +52,7 @@ regex = ['.*\.py$']
|
||||
|
||||
## Extra instructions
|
||||
|
||||
All PR-Agent tools have a parameter called `extra_instructions`, that enables to add free-text extra instructions. Example usage:
|
||||
All Qodo Merge tools have a parameter called `extra_instructions`, that enables to add free-text extra instructions. Example usage:
|
||||
```
|
||||
/update_changelog --pr_update_changelog.extra_instructions="Make sure to update also the version ..."
|
||||
```
|
||||
@ -42,173 +64,11 @@ This mode provides a very good speed-quality-cost tradeoff, and can handle most
|
||||
When the PR is above the token limit, it employs a [PR Compression strategy](../core-abilities/index.md).
|
||||
|
||||
However, for very large PRs, or in case you want to emphasize quality over speed and cost, there are two possible solutions:
|
||||
1) [Use a model](https://codium-ai.github.io/Docs-PR-Agent/usage-guide/#changing-a-model) with larger context, like GPT-32K, or claude-100K. This solution will be applicable for all the tools.
|
||||
2) For the `/improve` tool, there is an ['extended' mode](https://codium-ai.github.io/Docs-PR-Agent/tools/#improve) (`/improve --extended`),
|
||||
which divides the PR to chunks, and processes each chunk separately. With this mode, regardless of the model, no compression will be done (but for large PRs, multiple model calls may occur)
|
||||
1) [Use a model](https://qodo-merge-docs.qodo.ai/usage-guide/changing_a_model/) with larger context, like GPT-32K, or claude-100K. This solution will be applicable for all the tools.
|
||||
2) For the `/improve` tool, there is an ['extended' mode](https://qodo-merge-docs.qodo.ai/tools/improve/) (`/improve --extended`),
|
||||
which divides the PR into chunks, and processes each chunk separately. With this mode, regardless of the model, no compression will be done (but for large PRs, multiple model calls may occur)
|
||||
|
||||
|
||||
## Changing a model
|
||||
|
||||
See [here](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/algo/__init__.py) for the list of available models.
|
||||
To use a different model than the default (GPT-4), you need to edit [configuration file](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml#L2).
|
||||
For models and environments not from OPENAI, you might need to provide additional keys and other parameters. See below for instructions.
|
||||
|
||||
### Azure
|
||||
|
||||
To use Azure, set in your `.secrets.toml` (working from CLI), or in the GitHub `Settings > Secrets and variables` (working from GitHub App or GitHub Action):
|
||||
```
|
||||
[openai]
|
||||
key = "" # your azure api key
|
||||
api_type = "azure"
|
||||
api_version = '2023-05-15' # Check Azure documentation for the current API version
|
||||
api_base = "" # The base URL for your Azure OpenAI resource. e.g. "https://<your resource name>.openai.azure.com"
|
||||
deployment_id = "" # The deployment name you chose when you deployed the engine
|
||||
```
|
||||
|
||||
and set in your configuration file:
|
||||
```
|
||||
[config]
|
||||
model="" # the OpenAI model you've deployed on Azure (e.g. gpt-3.5-turbo)
|
||||
```
|
||||
|
||||
### Hugging Face
|
||||
|
||||
**Local**
|
||||
You can run Hugging Face models locally through either [VLLM](https://docs.litellm.ai/docs/providers/vllm) or [Ollama](https://docs.litellm.ai/docs/providers/ollama)
|
||||
|
||||
E.g. to use a new Hugging Face model locally via Ollama, set:
|
||||
```
|
||||
[__init__.py]
|
||||
MAX_TOKENS = {
|
||||
"model-name-on-ollama": <max_tokens>
|
||||
}
|
||||
e.g.
|
||||
MAX_TOKENS={
|
||||
...,
|
||||
"ollama/llama2": 4096
|
||||
}
|
||||
|
||||
|
||||
[config] # in configuration.toml
|
||||
model = "ollama/llama2"
|
||||
model_turbo = "ollama/llama2"
|
||||
|
||||
[ollama] # in .secrets.toml
|
||||
api_base = ... # the base url for your Hugging Face inference endpoint
|
||||
# e.g. if running Ollama locally, you may use:
|
||||
api_base = "http://localhost:11434/"
|
||||
```
|
||||
|
||||
### Inference Endpoints
|
||||
|
||||
To use a new model with Hugging Face Inference Endpoints, for example, set:
|
||||
```
|
||||
[__init__.py]
|
||||
MAX_TOKENS = {
|
||||
"model-name-on-huggingface": <max_tokens>
|
||||
}
|
||||
e.g.
|
||||
MAX_TOKENS={
|
||||
...,
|
||||
"meta-llama/Llama-2-7b-chat-hf": 4096
|
||||
}
|
||||
[config] # in configuration.toml
|
||||
model = "huggingface/meta-llama/Llama-2-7b-chat-hf"
|
||||
model_turbo = "huggingface/meta-llama/Llama-2-7b-chat-hf"
|
||||
|
||||
[huggingface] # in .secrets.toml
|
||||
key = ... # your Hugging Face api key
|
||||
api_base = ... # the base url for your Hugging Face inference endpoint
|
||||
```
|
||||
(you can obtain a Llama2 key from [here](https://replicate.com/replicate/llama-2-70b-chat/api))
|
||||
|
||||
### Replicate
|
||||
|
||||
To use Llama2 model with Replicate, for example, set:
|
||||
```
|
||||
[config] # in configuration.toml
|
||||
model = "replicate/llama-2-70b-chat:2c1608e18606fad2812020dc541930f2d0495ce32eee50074220b87300bc16e1"
|
||||
model_turbo = "replicate/llama-2-70b-chat:2c1608e18606fad2812020dc541930f2d0495ce32eee50074220b87300bc16e1"
|
||||
[replicate] # in .secrets.toml
|
||||
key = ...
|
||||
```
|
||||
(you can obtain a Llama2 key from [here](https://replicate.com/replicate/llama-2-70b-chat/api))
|
||||
|
||||
|
||||
Also, review the [AiHandler](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/algo/ai_handler.py) file for instructions on how to set keys for other models.
|
||||
|
||||
### Groq
|
||||
|
||||
To use Llama3 model with Groq, for example, set:
|
||||
```
|
||||
[config] # in configuration.toml
|
||||
model = "llama3-70b-8192"
|
||||
model_turbo = "llama3-70b-8192"
|
||||
fallback_models = ["groq/llama3-70b-8192"]
|
||||
[groq] # in .secrets.toml
|
||||
key = ... # your Groq api key
|
||||
```
|
||||
(you can obtain a Groq key from [here](https://console.groq.com/keys))
|
||||
|
||||
### Vertex AI
|
||||
|
||||
To use Google's Vertex AI platform and its associated models (chat-bison/codechat-bison) set:
|
||||
|
||||
```
|
||||
[config] # in configuration.toml
|
||||
model = "vertex_ai/codechat-bison"
|
||||
model_turbo = "vertex_ai/codechat-bison"
|
||||
fallback_models="vertex_ai/codechat-bison"
|
||||
|
||||
[vertexai] # in .secrets.toml
|
||||
vertex_project = "my-google-cloud-project"
|
||||
vertex_location = ""
|
||||
```
|
||||
|
||||
Your [application default credentials](https://cloud.google.com/docs/authentication/application-default-credentials) will be used for authentication so there is no need to set explicit credentials in most environments.
|
||||
|
||||
If you do want to set explicit credentials then you can use the `GOOGLE_APPLICATION_CREDENTIALS` environment variable set to a path to a json credentials file.
|
||||
|
||||
### Anthropic
|
||||
|
||||
To use Anthropic models, set the relevant models in the configuration section of the configuration file:
|
||||
```
|
||||
[config]
|
||||
model="anthropic/claude-3-opus-20240229"
|
||||
model_turbo="anthropic/claude-3-opus-20240229"
|
||||
fallback_models=["anthropic/claude-3-opus-20240229"]
|
||||
```
|
||||
|
||||
And also set the api key in the .secrets.toml file:
|
||||
```
|
||||
[anthropic]
|
||||
KEY = "..."
|
||||
```
|
||||
|
||||
### Amazon Bedrock
|
||||
|
||||
To use Amazon Bedrock and its foundational models, add the below configuration:
|
||||
|
||||
```
|
||||
[config] # in configuration.toml
|
||||
model="bedrock/anthropic.claude-3-sonnet-20240229-v1:0"
|
||||
model_turbo="bedrock/anthropic.claude-3-sonnet-20240229-v1:0"
|
||||
fallback_models=["bedrock/anthropic.claude-v2:1"]
|
||||
|
||||
[aws] # in .secrets.toml
|
||||
bedrock_region = "us-east-1"
|
||||
```
|
||||
|
||||
Note that you have to add access to foundational models before using them. Please refer to [this document](https://docs.aws.amazon.com/bedrock/latest/userguide/setting-up.html) for more details.
|
||||
|
||||
If you are using the claude-3 model, please configure the following settings as there are parameters incompatible with claude-3.
|
||||
```
|
||||
[litellm]
|
||||
drop_params = true
|
||||
```
|
||||
|
||||
AWS session is automatically authenticated from your environment, but you can also explicitly set `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` environment variables.
|
||||
|
||||
|
||||
## Patch Extra Lines
|
||||
|
||||
@ -225,19 +85,21 @@ By default, around any change in your PR, git patch provides three lines of cont
|
||||
code line that already existed in the file...
|
||||
```
|
||||
|
||||
For the `review`, `describe`, `ask` and `add_docs` tools, if the token budget allows, PR-Agent tries to increase the number of lines of context, via the parameter:
|
||||
Qodo Merge will try to increase the number of lines of context, via the parameter:
|
||||
```
|
||||
[config]
|
||||
patch_extra_lines=3
|
||||
patch_extra_lines_before=3
|
||||
patch_extra_lines_after=1
|
||||
```
|
||||
|
||||
Increasing this number provides more context to the model, but will also increase the token budget.
|
||||
If the PR is too large (see [PR Compression strategy](https://github.com/Codium-ai/pr-agent/blob/main/PR_COMPRESSION.md)), PR-Agent automatically sets this number to 0, using the original git patch.
|
||||
Increasing this number provides more context to the model, but will also increase the token budget, and may overwhelm the model with too much information, unrelated to the actual PR code changes.
|
||||
|
||||
If the PR is too large (see [PR Compression strategy](https://github.com/Codium-ai/pr-agent/blob/main/PR_COMPRESSION.md)), Qodo Merge may automatically set this number to 0, and will use the original git patch.
|
||||
|
||||
|
||||
## Editing the prompts
|
||||
|
||||
The prompts for the various PR-Agent tools are defined in the `pr_agent/settings` folder.
|
||||
The prompts for the various Qodo Merge tools are defined in the `pr_agent/settings` folder.
|
||||
In practice, the prompts are loaded and stored as a standard setting object.
|
||||
Hence, editing them is similar to editing any other configuration value - just place the relevant key in `.pr_agent.toml`file, and override the default value.
|
||||
|
||||
@ -252,3 +114,103 @@ user="""
|
||||
"""
|
||||
```
|
||||
Note that the new prompt will need to generate an output compatible with the relevant [post-process function](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/tools/pr_description.py#L137).
|
||||
|
||||
## Integrating with Logging Observability Platforms
|
||||
|
||||
Various logging observability tools can be used out-of-the box when using the default LiteLLM AI Handler. Simply configure the LiteLLM callback settings in `configuration.toml` and set environment variables according to the LiteLLM [documentation](https://docs.litellm.ai/docs/).
|
||||
|
||||
For example, to use [LangSmith](https://www.langchain.com/langsmith) you can add the following to your `configuration.toml` file:
|
||||
```
|
||||
[litellm]
|
||||
enable_callbacks = true
|
||||
success_callback = ["langsmith"]
|
||||
failure_callback = ["langsmith"]
|
||||
service_callback = []
|
||||
```
|
||||
|
||||
Then set the following environment variables:
|
||||
|
||||
```
|
||||
LANGSMITH_API_KEY=<api_key>
|
||||
LANGSMITH_PROJECT=<project>
|
||||
LANGSMITH_BASE_URL=<url>
|
||||
```
|
||||
|
||||
## Ignoring automatic commands in PRs
|
||||
|
||||
Qodo Merge allows you to automatically ignore certain PRs based on various criteria:
|
||||
|
||||
- PRs with specific titles (using regex matching)
|
||||
- PRs between specific branches (using regex matching)
|
||||
- PRs that don't include changes from specific folders (using regex matching)
|
||||
- PRs containing specific labels
|
||||
- PRs opened by specific users
|
||||
|
||||
### Example usage
|
||||
|
||||
#### Ignoring PRs with specific titles
|
||||
|
||||
To ignore PRs with a specific title such as "[Bump]: ...", you can add the following to your `configuration.toml` file:
|
||||
|
||||
```
|
||||
[config]
|
||||
ignore_pr_title = ["\\[Bump\\]"]
|
||||
```
|
||||
|
||||
Where the `ignore_pr_title` is a list of regex patterns to match the PR title you want to ignore. Default is `ignore_pr_title = ["^\\[Auto\\]", "^Auto"]`.
|
||||
|
||||
#### Ignoring PRs between specific branches
|
||||
|
||||
To ignore PRs from specific source or target branches, you can add the following to your `configuration.toml` file:
|
||||
|
||||
```
|
||||
[config]
|
||||
ignore_pr_source_branches = ['develop', 'main', 'master', 'stage']
|
||||
ignore_pr_target_branches = ["qa"]
|
||||
```
|
||||
|
||||
Where the `ignore_pr_source_branches` and `ignore_pr_target_branches` are lists of regex patterns to match the source and target branches you want to ignore.
|
||||
They are not mutually exclusive, you can use them together or separately.
|
||||
|
||||
#### Ignoring PRs that don't include changes from specific folders
|
||||
|
||||
To allow only specific folders (often needed in large monorepos), set:
|
||||
|
||||
```
|
||||
[config]
|
||||
allow_only_specific_folders=['folder1','folder2']
|
||||
```
|
||||
|
||||
For the configuration above, automatic feedback will only be triggered when the PR changes include files from 'folder1' or 'folder2'
|
||||
|
||||
#### Ignoring PRs containg specific labels
|
||||
|
||||
To ignore PRs containg specific labels, you can add the following to your `configuration.toml` file:
|
||||
|
||||
```
|
||||
[config]
|
||||
ignore_pr_labels = ["do-not-merge"]
|
||||
```
|
||||
|
||||
Where the `ignore_pr_labels` is a list of labels that when present in the PR, the PR will be ignored.
|
||||
|
||||
#### Ignoring PRs from specific users
|
||||
|
||||
Qodo Merge automatically identifies and ignores pull requests created by bots using:
|
||||
|
||||
- GitHub's native bot detection system
|
||||
- Name-based pattern matching
|
||||
|
||||
While this detection is robust, it may not catch all cases, particularly when:
|
||||
|
||||
- Bots are registered as regular user accounts
|
||||
- Bot names don't match common patterns
|
||||
|
||||
To supplement the automatic bot detection, you can manually specify users to ignore. Add the following to your `configuration.toml` file to ignore PRs from specific users:
|
||||
```
|
||||
[config]
|
||||
ignore_pr_authors = ["my-special-bot-user", ...]
|
||||
```
|
||||
|
||||
Where the `ignore_pr_authors` is a list of usernames that you want to ignore.
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
## Local repo (CLI)
|
||||
When running from your locally cloned PR-Agent repo (CLI), your local configuration file will be used.
|
||||
|
||||
When running from your locally cloned Qodo Merge repo (CLI), your local configuration file will be used.
|
||||
Examples of invoking the different tools via the CLI:
|
||||
|
||||
- **Review**: `python -m pr_agent.cli --pr_url=<pr_url> review`
|
||||
@ -26,15 +27,45 @@ verbosity_level=2
|
||||
```
|
||||
This is useful for debugging or experimenting with different tools.
|
||||
|
||||
(3)
|
||||
|
||||
### Online usage
|
||||
**git provider**: The [git_provider](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml#L5) field in a configuration file determines the GIT provider that will be used by Qodo Merge. Currently, the following providers are supported:
|
||||
`
|
||||
"github", "gitlab", "bitbucket", "azure", "codecommit", "local", "gerrit"
|
||||
`
|
||||
|
||||
Online usage means invoking PR-Agent tools by [comments](https://github.com/Codium-ai/pr-agent/pull/229#issuecomment-1695021901) on a PR.
|
||||
Default is "github".
|
||||
|
||||
### CLI Health Check
|
||||
To verify that Qodo Merge has been configured correctly, you can run this health check command from the repository root:
|
||||
|
||||
```bash
|
||||
python -m tests.health_test.main
|
||||
```
|
||||
|
||||
If the health check passes, you will see the following output:
|
||||
|
||||
```
|
||||
========
|
||||
Health test passed successfully
|
||||
========
|
||||
```
|
||||
|
||||
At the end of the run.
|
||||
|
||||
Before running the health check, ensure you have:
|
||||
|
||||
- Configured your [LLM provider](https://qodo-merge-docs.qodo.ai/usage-guide/changing_a_model/)
|
||||
- Added a valid GitHub token to your configuration file
|
||||
|
||||
## Online usage
|
||||
|
||||
Online usage means invoking Qodo Merge tools by [comments](https://github.com/Codium-ai/pr-agent/pull/229#issuecomment-1695021901) on a PR.
|
||||
Commands for invoking the different tools via comments:
|
||||
|
||||
- **Review**: `/review`
|
||||
- **Describe**: `/describe`
|
||||
- **Improve**: `/improve`
|
||||
- **Improve**: `/improve` (or `/improve_code` for bitbucket, since `/improve` is sometimes reserved)
|
||||
- **Ask**: `/ask "..."`
|
||||
- **Reflect**: `/reflect`
|
||||
- **Update Changelog**: `/update_changelog`
|
||||
@ -48,67 +79,81 @@ For example, if you want to edit the `review` tool configurations, you can run:
|
||||
Any configuration value in [configuration file](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml) file can be similarly edited. Comment `/config` to see the list of available configurations.
|
||||
|
||||
|
||||
## GitHub App
|
||||
## Qodo Merge Automatic Feedback
|
||||
|
||||
!!! note "Configurations for PR-Agent Pro"
|
||||
PR-Agent Pro for GitHub is an App, hosted by CodiumAI. So all the instructions below are relevant also for PR-Agent Pro users.
|
||||
|
||||
### Disabling all automatic feedback
|
||||
|
||||
To easily disable all automatic feedback from Qodo Merge (GitHub App, GitLab Webhook, BitBucket App, Azure DevOps Webhook), set in a configuration file:
|
||||
|
||||
```toml
|
||||
[config]
|
||||
disable_auto_feedback = true
|
||||
```
|
||||
|
||||
When this parameter is set to `true`, Qodo Merge will not run any automatic tools (like `describe`, `review`, `improve`) when a new PR is opened, or when new code is pushed to an open PR.
|
||||
|
||||
### GitHub App
|
||||
|
||||
!!! note "Configurations for Qodo Merge"
|
||||
Qodo Merge for GitHub is an App, hosted by Qodo. So all the instructions below are relevant also for Qodo Merge users.
|
||||
Same goes for [GitLab webhook](#gitlab-webhook) and [BitBucket App](#bitbucket-app) sections.
|
||||
|
||||
### GitHub app automatic tools when a new PR is opened
|
||||
#### GitHub app automatic tools when a new PR is opened
|
||||
|
||||
The [github_app](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml#L108) section defines GitHub app specific configurations.
|
||||
The [github_app](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml#L220) section defines GitHub app specific configurations.
|
||||
|
||||
The configuration parameter `pr_commands` defines the list of tools that will be **run automatically** when a new PR is opened.
|
||||
```
|
||||
The configuration parameter `pr_commands` defines the list of tools that will be **run automatically** when a new PR is opened:
|
||||
```toml
|
||||
[github_app]
|
||||
pr_commands = [
|
||||
"/describe --pr_description.final_update_message=false",
|
||||
"/review --pr_reviewer.num_code_suggestions=0",
|
||||
"/describe",
|
||||
"/review",
|
||||
"/improve",
|
||||
]
|
||||
```
|
||||
This means that when a new PR is opened/reopened or marked as ready for review, PR-Agent will run the `describe`, `review` and `improve` tools.
|
||||
For the `review` tool, for example, the `num_code_suggestions` parameter will be set to 0.
|
||||
|
||||
You can override the default tool parameters by using one the three options for a [configuration file](https://codium-ai.github.io/Docs-PR-Agent/usage-guide/#configuration-options): **wiki**, **local**, or **global**.
|
||||
For example, if your local `.pr_agent.toml` file contains:
|
||||
```
|
||||
This means that when a new PR is opened/reopened or marked as ready for review, Qodo Merge will run the `describe`, `review` and `improve` tools.
|
||||
|
||||
You can override the default tool parameters by using one the three options for a [configuration file](https://qodo-merge-docs.qodo.ai/usage-guide/configuration_options/): **wiki**, **local**, or **global**.
|
||||
For example, if your configuration file contains:
|
||||
|
||||
```toml
|
||||
[pr_description]
|
||||
generate_ai_title = true
|
||||
```
|
||||
Every time you run the `describe` tool, including automatic runs, the PR title will be generated by the AI.
|
||||
|
||||
To cancel the automatic run of all the tools, set:
|
||||
```
|
||||
Every time you run the `describe` tool (including automatic runs) the PR title will be generated by the AI.
|
||||
|
||||
You can customize configurations specifically for automated runs by using the `--config_path=<value>` parameter.
|
||||
For instance, to modify the `review` tool settings only for newly opened PRs, use:
|
||||
```toml
|
||||
[github_app]
|
||||
pr_commands = []
|
||||
pr_commands = [
|
||||
"/describe",
|
||||
"/review --pr_reviewer.extra_instructions='focus on the file: ...'",
|
||||
"/improve",
|
||||
]
|
||||
```
|
||||
|
||||
You can also disable automatic runs for PRs with specific titles, by setting the `ignore_pr_titles` parameter with the relevant regex. For example:
|
||||
```
|
||||
[github_app]
|
||||
ignore_pr_title = ["^[Auto]", ".*ignore.*"]
|
||||
```
|
||||
will ignore PRs with titles that start with "Auto" or contain the word "ignore".
|
||||
|
||||
### GitHub app automatic tools for push actions (commits to an open PR)
|
||||
#### GitHub app automatic tools for push actions (commits to an open PR)
|
||||
|
||||
In addition to running automatic tools when a PR is opened, the GitHub app can also respond to new code that is pushed to an open PR.
|
||||
|
||||
The configuration toggle `handle_push_trigger` can be used to enable this feature.
|
||||
The configuration parameter `push_commands` defines the list of tools that will be **run automatically** when new code is pushed to the PR.
|
||||
```
|
||||
```toml
|
||||
[github_app]
|
||||
handle_push_trigger = true
|
||||
push_commands = [
|
||||
"/describe",
|
||||
"/review --pr_reviewer.num_code_suggestions=0 --pr_reviewer.final_update_message=false",
|
||||
"/review",
|
||||
]
|
||||
```
|
||||
This means that when new code is pushed to the PR, the PR-Agent will run the `describe` and `review` tools, with the specified parameters.
|
||||
This means that when new code is pushed to the PR, the Qodo Merge will run the `describe` and `review` tools, with the specified parameters.
|
||||
|
||||
## GitHub Action
|
||||
`GitHub Action` is a different way to trigger PR-Agent tools, and uses a different configuration mechanism than `GitHub App`.<br>
|
||||
### GitHub Action
|
||||
`GitHub Action` is a different way to trigger Qodo Merge tools, and uses a different configuration mechanism than `GitHub App`.<br>
|
||||
You can configure settings for `GitHub Action` by adding environment variables under the env section in `.github/workflows/pr_agent.yml` file.
|
||||
Specifically, start by setting the following environment variables:
|
||||
```yaml
|
||||
@ -118,30 +163,37 @@ Specifically, start by setting the following environment variables:
|
||||
github_action_config.auto_review: "true" # enable\disable auto review
|
||||
github_action_config.auto_describe: "true" # enable\disable auto describe
|
||||
github_action_config.auto_improve: "true" # enable\disable auto improve
|
||||
github_action_config.pr_actions: '["opened", "reopened", "ready_for_review", "review_requested"]'
|
||||
```
|
||||
`github_action_config.auto_review`, `github_action_config.auto_describe` and `github_action_config.auto_improve` are used to enable/disable automatic tools that run when a new PR is opened.
|
||||
If not set, the default configuration is for all three tools to run automatically when a new PR is opened.
|
||||
|
||||
`github_action_config.pr_actions` is used to configure which `pull_requests` events will trigger the enabled auto flags
|
||||
If not set, the default configuration is `["opened", "reopened", "ready_for_review", "review_requested"]`
|
||||
|
||||
`github_action_config.enable_output` are used to enable/disable github actions [output parameter](https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#outputs-for-docker-container-and-javascript-actions) (default is `true`).
|
||||
Review result is output as JSON to `steps.{step-id}.outputs.review` property.
|
||||
The JSON structure is equivalent to the yaml data structure defined in [pr_reviewer_prompts.toml](https://github.com/idubnori/pr-agent/blob/main/pr_agent/settings/pr_reviewer_prompts.toml).
|
||||
|
||||
Note that you can give additional config parameters by adding environment variables to `.github/workflows/pr_agent.yml`, or by using a `.pr_agent.toml` [configuration file](https://pr-agent-docs.codium.ai/usage-guide/configuration_options/#global-configuration-file) in the root of your repo
|
||||
Note that you can give additional config parameters by adding environment variables to `.github/workflows/pr_agent.yml`, or by using a `.pr_agent.toml` [configuration file](https://qodo-merge-docs.qodo.ai/usage-guide/configuration_options/#global-configuration-file) in the root of your repo
|
||||
|
||||
For example, you can set an environment variable: `pr_description.publish_labels=false`, or add a `.pr_agent.toml` file with the following content:
|
||||
```
|
||||
|
||||
```toml
|
||||
[pr_description]
|
||||
publish_labels = false
|
||||
```
|
||||
to prevent PR-Agent from publishing labels when running the `describe` tool.
|
||||
|
||||
## GitLab Webhook
|
||||
to prevent Qodo Merge from publishing labels when running the `describe` tool.
|
||||
|
||||
### GitLab Webhook
|
||||
After setting up a GitLab webhook, to control which commands will run automatically when a new MR is opened, you can set the `pr_commands` parameter in the configuration file, similar to the GitHub App:
|
||||
```
|
||||
|
||||
```toml
|
||||
[gitlab]
|
||||
pr_commands = [
|
||||
"/describe",
|
||||
"/review --pr_reviewer.num_code_suggestions=0",
|
||||
"/review",
|
||||
"/improve",
|
||||
]
|
||||
```
|
||||
@ -149,49 +201,65 @@ pr_commands = [
|
||||
the GitLab webhook can also respond to new code that is pushed to an open MR.
|
||||
The configuration toggle `handle_push_trigger` can be used to enable this feature.
|
||||
The configuration parameter `push_commands` defines the list of tools that will be **run automatically** when new code is pushed to the MR.
|
||||
```
|
||||
```toml
|
||||
[gitlab]
|
||||
handle_push_trigger = true
|
||||
push_commands = [
|
||||
"/describe",
|
||||
"/review --pr_reviewer.num_code_suggestions=0 --pr_reviewer.final_update_message=false",
|
||||
"/review",
|
||||
]
|
||||
```
|
||||
|
||||
Note that to use the 'handle_push_trigger' feature, you need to give the gitlab webhook also the "Push events" scope.
|
||||
|
||||
## BitBucket App
|
||||
Similar to GitHub app, when running PR-Agent from BitBucket App, the default [configuration file](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml) from a pre-built docker will be initially loaded.
|
||||
### BitBucket App
|
||||
Similar to GitHub app, when running Qodo Merge from BitBucket App, the default [configuration file](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml) from a pre-built docker will be initially loaded.
|
||||
|
||||
By uploading a local `.pr_agent.toml` file to the root of the repo's main branch, you can edit and customize any configuration parameter. Note that you need to upload `.pr_agent.toml` prior to creating a PR, in order for the configuration to take effect.
|
||||
|
||||
For example, if your local `.pr_agent.toml` file contains:
|
||||
```
|
||||
```toml
|
||||
[pr_reviewer]
|
||||
inline_code_comments = true
|
||||
extra_instructions = "Answer in japanese"
|
||||
```
|
||||
|
||||
Each time you invoke a `/review` tool, it will use inline code comments.
|
||||
Each time you invoke a `/review` tool, it will use the extra instructions you set in the local configuration file.
|
||||
|
||||
### BitBucket Self-Hosted App automatic tools
|
||||
|
||||
Note that among other limitations, BitBucket provides relatively low rate-limits for applications (up to 1000 requests per hour), and does not provide an API to track the actual rate-limit usage.
|
||||
If you experience a lack of responses from Qodo Merge, you might want to set: `bitbucket_app.avoid_full_files=true` in your configuration file.
|
||||
This will prevent Qodo Merge from acquiring the full file content, and will only use the diff content. This will reduce the number of requests made to BitBucket, at the cost of small decrease in accuracy, as dynamic context will not be applicable.
|
||||
|
||||
|
||||
#### BitBucket Self-Hosted App automatic tools
|
||||
|
||||
To control which commands will run automatically when a new PR is opened, you can set the `pr_commands` parameter in the configuration file:
|
||||
Specifically, set the following values:
|
||||
|
||||
```
|
||||
```toml
|
||||
[bitbucket_app]
|
||||
pr_commands = [
|
||||
"/review --pr_reviewer.num_code_suggestions=0",
|
||||
"/review",
|
||||
"/improve --pr_code_suggestions.commitable_code_suggestions=true --pr_code_suggestions.suggestions_score_threshold=7",
|
||||
]
|
||||
```
|
||||
Note that we set specifically for bitbucket, we recommend using: `--pr_code_suggestions.suggestions_score_threshold=7` and that is the default value we set for bitbucket.
|
||||
Since this platform only supports inline code suggestions, we want to limit the number of suggestions, and only present a limited number.
|
||||
|
||||
## Azure DevOps provider
|
||||
To enable BitBucket app to respond to each **push** to the PR, set (for example):
|
||||
```toml
|
||||
[bitbucket_app]
|
||||
handle_push_trigger = true
|
||||
push_commands = [
|
||||
"/describe",
|
||||
"/review",
|
||||
]
|
||||
```
|
||||
|
||||
### Azure DevOps provider
|
||||
|
||||
To use Azure DevOps provider use the following settings in configuration.toml:
|
||||
```
|
||||
```toml
|
||||
[config]
|
||||
git_provider="azure"
|
||||
```
|
||||
@ -210,14 +278,14 @@ org = "https://dev.azure.com/YOUR_ORGANIZATION/"
|
||||
# pat = "YOUR_PAT_TOKEN" needed only if using PAT for authentication
|
||||
```
|
||||
|
||||
### Azure DevOps Webhook
|
||||
#### Azure DevOps Webhook
|
||||
|
||||
To control which commands will run automatically when a new PR is opened, you can set the `pr_commands` parameter in the configuration file, similar to the GitHub App:
|
||||
```
|
||||
```toml
|
||||
[azure_devops_server]
|
||||
pr_commands = [
|
||||
"/describe",
|
||||
"/review --pr_reviewer.num_code_suggestions=0",
|
||||
"/review",
|
||||
"/improve",
|
||||
]
|
||||
```
|
||||
|
203
docs/docs/usage-guide/changing_a_model.md
Normal file
203
docs/docs/usage-guide/changing_a_model.md
Normal file
@ -0,0 +1,203 @@
|
||||
## Changing a model in PR-Agent
|
||||
|
||||
See [here](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/algo/__init__.py) for a list of available models.
|
||||
To use a different model than the default (GPT-4), you need to edit in the [configuration file](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml#L2) the fields:
|
||||
```
|
||||
[config]
|
||||
model = "..."
|
||||
fallback_models = ["..."]
|
||||
```
|
||||
|
||||
For models and environments not from OpenAI, you might need to provide additional keys and other parameters.
|
||||
You can give parameters via a configuration file (see below for instructions), or from environment variables. See [litellm documentation](https://litellm.vercel.app/docs/proxy/quick_start#supported-llms) for the environment variables relevant per model.
|
||||
|
||||
### Azure
|
||||
|
||||
To use Azure, set in your `.secrets.toml` (working from CLI), or in the GitHub `Settings > Secrets and variables` (working from GitHub App or GitHub Action):
|
||||
```
|
||||
[openai]
|
||||
key = "" # your azure api key
|
||||
api_type = "azure"
|
||||
api_version = '2023-05-15' # Check Azure documentation for the current API version
|
||||
api_base = "" # The base URL for your Azure OpenAI resource. e.g. "https://<your resource name>.openai.azure.com"
|
||||
deployment_id = "" # The deployment name you chose when you deployed the engine
|
||||
```
|
||||
|
||||
and set in your configuration file:
|
||||
```
|
||||
[config]
|
||||
model="" # the OpenAI model you've deployed on Azure (e.g. gpt-4o)
|
||||
fallback_models=["..."]
|
||||
```
|
||||
|
||||
### Ollama
|
||||
|
||||
You can run models locally through either [VLLM](https://docs.litellm.ai/docs/providers/vllm) or [Ollama](https://docs.litellm.ai/docs/providers/ollama)
|
||||
|
||||
E.g. to use a new model locally via Ollama, set in `.secrets.toml` or in a configuration file:
|
||||
```
|
||||
[config]
|
||||
model = "ollama/qwen2.5-coder:32b"
|
||||
fallback_models=["ollama/qwen2.5-coder:32b"]
|
||||
custom_model_max_tokens=128000 # set the maximal input tokens for the model
|
||||
duplicate_examples=true # will duplicate the examples in the prompt, to help the model to generate structured output
|
||||
|
||||
[ollama]
|
||||
api_base = "http://localhost:11434" # or whatever port you're running Ollama on
|
||||
```
|
||||
|
||||
!!! note "Local models vs commercial models"
|
||||
Qodo Merge is compatible with almost any AI model, but analyzing complex code repositories and pull requests requires a model specifically optimized for code analysis.
|
||||
|
||||
Commercial models such as GPT-4, Claude Sonnet, and Gemini have demonstrated robust capabilities in generating structured output for code analysis tasks with large input. In contrast, most open-source models currently available (as of January 2025) face challenges with these complex tasks.
|
||||
|
||||
Based on our testing, local open-source models are suitable for experimentation and learning purposes, but they are not suitable for production-level code analysis tasks.
|
||||
|
||||
Hence, for production workflows and real-world usage, we recommend using commercial models.
|
||||
|
||||
### Hugging Face
|
||||
|
||||
To use a new model with Hugging Face Inference Endpoints, for example, set:
|
||||
```
|
||||
[config] # in configuration.toml
|
||||
model = "huggingface/meta-llama/Llama-2-7b-chat-hf"
|
||||
fallback_models=["huggingface/meta-llama/Llama-2-7b-chat-hf"]
|
||||
custom_model_max_tokens=... # set the maximal input tokens for the model
|
||||
|
||||
[huggingface] # in .secrets.toml
|
||||
key = ... # your Hugging Face api key
|
||||
api_base = ... # the base url for your Hugging Face inference endpoint
|
||||
```
|
||||
(you can obtain a Llama2 key from [here](https://replicate.com/replicate/llama-2-70b-chat/api))
|
||||
|
||||
### Replicate
|
||||
|
||||
To use Llama2 model with Replicate, for example, set:
|
||||
```
|
||||
[config] # in configuration.toml
|
||||
model = "replicate/llama-2-70b-chat:2c1608e18606fad2812020dc541930f2d0495ce32eee50074220b87300bc16e1"
|
||||
fallback_models=["replicate/llama-2-70b-chat:2c1608e18606fad2812020dc541930f2d0495ce32eee50074220b87300bc16e1"]
|
||||
[replicate] # in .secrets.toml
|
||||
key = ...
|
||||
```
|
||||
(you can obtain a Llama2 key from [here](https://replicate.com/replicate/llama-2-70b-chat/api))
|
||||
|
||||
|
||||
Also, review the [AiHandler](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/algo/ai_handler.py) file for instructions on how to set keys for other models.
|
||||
|
||||
### Groq
|
||||
|
||||
To use Llama3 model with Groq, for example, set:
|
||||
```
|
||||
[config] # in configuration.toml
|
||||
model = "llama3-70b-8192"
|
||||
fallback_models = ["groq/llama3-70b-8192"]
|
||||
[groq] # in .secrets.toml
|
||||
key = ... # your Groq api key
|
||||
```
|
||||
(you can obtain a Groq key from [here](https://console.groq.com/keys))
|
||||
|
||||
### Vertex AI
|
||||
|
||||
To use Google's Vertex AI platform and its associated models (chat-bison/codechat-bison) set:
|
||||
|
||||
```
|
||||
[config] # in configuration.toml
|
||||
model = "vertex_ai/codechat-bison"
|
||||
fallback_models="vertex_ai/codechat-bison"
|
||||
|
||||
[vertexai] # in .secrets.toml
|
||||
vertex_project = "my-google-cloud-project"
|
||||
vertex_location = ""
|
||||
```
|
||||
|
||||
Your [application default credentials](https://cloud.google.com/docs/authentication/application-default-credentials) will be used for authentication so there is no need to set explicit credentials in most environments.
|
||||
|
||||
If you do want to set explicit credentials, then you can use the `GOOGLE_APPLICATION_CREDENTIALS` environment variable set to a path to a json credentials file.
|
||||
|
||||
### Google AI Studio
|
||||
|
||||
To use [Google AI Studio](https://aistudio.google.com/) models, set the relevant models in the configuration section of the configuration file:
|
||||
|
||||
```toml
|
||||
[config] # in configuration.toml
|
||||
model="google_ai_studio/gemini-1.5-flash"
|
||||
fallback_models=["google_ai_studio/gemini-1.5-flash"]
|
||||
|
||||
[google_ai_studio] # in .secrets.toml
|
||||
gemini_api_key = "..."
|
||||
```
|
||||
|
||||
If you don't want to set the API key in the .secrets.toml file, you can set the `GOOGLE_AI_STUDIO.GEMINI_API_KEY` environment variable.
|
||||
|
||||
### Anthropic
|
||||
|
||||
To use Anthropic models, set the relevant models in the configuration section of the configuration file:
|
||||
|
||||
```
|
||||
[config]
|
||||
model="anthropic/claude-3-opus-20240229"
|
||||
fallback_models=["anthropic/claude-3-opus-20240229"]
|
||||
```
|
||||
|
||||
And also set the api key in the .secrets.toml file:
|
||||
```
|
||||
[anthropic]
|
||||
KEY = "..."
|
||||
```
|
||||
|
||||
### Amazon Bedrock
|
||||
|
||||
To use Amazon Bedrock and its foundational models, add the below configuration:
|
||||
|
||||
```
|
||||
[config] # in configuration.toml
|
||||
model="bedrock/anthropic.claude-3-sonnet-20240229-v1:0"
|
||||
fallback_models=["bedrock/anthropic.claude-v2:1"]
|
||||
```
|
||||
|
||||
Note that you have to add access to foundational models before using them. Please refer to [this document](https://docs.aws.amazon.com/bedrock/latest/userguide/setting-up.html) for more details.
|
||||
|
||||
If you are using the claude-3 model, please configure the following settings as there are parameters incompatible with claude-3.
|
||||
```
|
||||
[litellm]
|
||||
drop_params = true
|
||||
```
|
||||
|
||||
AWS session is automatically authenticated from your environment, but you can also explicitly set `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and `AWS_REGION_NAME` environment variables. Please refer to [this document](https://litellm.vercel.app/docs/providers/bedrock) for more details.
|
||||
|
||||
### DeepSeek
|
||||
|
||||
To use deepseek-chat model with DeepSeek, for example, set:
|
||||
|
||||
```toml
|
||||
[config] # in configuration.toml
|
||||
model = "deepseek/deepseek-chat"
|
||||
fallback_models=["deepseek/deepseek-chat"]
|
||||
```
|
||||
|
||||
and fill up your key
|
||||
|
||||
```toml
|
||||
[deepseek] # in .secrets.toml
|
||||
key = ...
|
||||
```
|
||||
|
||||
(you can obtain a deepseek-chat key from [here](https://platform.deepseek.com))
|
||||
|
||||
### Custom models
|
||||
|
||||
If the relevant model doesn't appear [here](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/algo/__init__.py), you can still use it as a custom model:
|
||||
|
||||
(1) Set the model name in the configuration file:
|
||||
```
|
||||
[config]
|
||||
model="custom_model_name"
|
||||
fallback_models=["custom_model_name"]
|
||||
```
|
||||
(2) Set the maximal tokens for the model:
|
||||
```
|
||||
[config]
|
||||
custom_model_max_tokens= ...
|
||||
```
|
||||
(3) Go to [litellm documentation](https://litellm.vercel.app/docs/proxy/quick_start#supported-llms), find the model you want to use, and set the relevant environment variables.
|
@ -1,7 +1,7 @@
|
||||
The different tools and sub-tools used by CodiumAI PR-Agent are adjustable via the **[configuration file](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml)**.
|
||||
The different tools and sub-tools used by Qodo Merge are adjustable via the **[configuration file](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml)**.
|
||||
|
||||
In addition to general configuration options, each tool has its own configurations. For example, the `review` tool will use parameters from the [pr_reviewer](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml#L16) section in the configuration file.
|
||||
See the [Tools Guide](https://codium-ai.github.io/Docs-PR-Agent/tools/) for a detailed description of the different tools and their configurations.
|
||||
See the [Tools Guide](https://qodo-merge-docs.qodo.ai/tools/) for a detailed description of the different tools and their configurations.
|
||||
|
||||
There are three ways to set persistent configurations:
|
||||
|
||||
@ -18,23 +18,29 @@ In terms of precedence, wiki configurations will override local configurations,
|
||||
|
||||
## Wiki configuration file 💎
|
||||
|
||||
For GitHub and GitLab, with PR-Agent-Pro you can set configurations by creating a page called `.pr_agent.toml` in the [wiki](https://github.com/Codium-ai/pr-agent/wiki/pr_agent.toml) of the repo.
|
||||
`Platforms supported: GitHub, GitLab, Bitbucket`
|
||||
|
||||
With Qodo Merge, you can set configurations by creating a page called `.pr_agent.toml` in the [wiki](https://github.com/Codium-ai/pr-agent/wiki/pr_agent.toml) of the repo.
|
||||
The advantage of this method is that it allows to set configurations without needing to commit new content to the repo - just edit the wiki page and **save**.
|
||||
|
||||
|
||||
{width=512}
|
||||
|
||||
Click [here](https://codium.ai/images/pr_agent/wiki_configuration_pr_agent.mp4) to see a short instructional video. We recommend surrounding the configuration content with triple-quotes, to allow better presentation when displayed in the wiki as markdown.
|
||||
Click [here](https://codium.ai/images/pr_agent/wiki_configuration_pr_agent.mp4) to see a short instructional video. We recommend surrounding the configuration content with triple-quotes (or \`\`\`toml), to allow better presentation when displayed in the wiki as markdown.
|
||||
An example content:
|
||||
|
||||
```
|
||||
```toml
|
||||
[pr_description]
|
||||
generate_ai_title=true
|
||||
```
|
||||
|
||||
PR-Agent will know to remove the triple-quotes when reading the configuration content.
|
||||
Qodo Merge will know to remove the surrounding quotes when reading the configuration content.
|
||||
|
||||
## Local configuration file
|
||||
|
||||
`Platforms supported: GitHub, GitLab, Bitbucket, Azure DevOps`
|
||||
|
||||
|
||||
By uploading a local `.pr_agent.toml` file to the root of the repo's main branch, you can edit and customize any configuration parameter. Note that you need to upload `.pr_agent.toml` prior to creating a PR, in order for the configuration to take effect.
|
||||
|
||||
For example, if you set in `.pr_agent.toml`:
|
||||
@ -53,9 +59,13 @@ Then you can give a list of extra instructions to the `review` tool.
|
||||
|
||||
## Global configuration file 💎
|
||||
|
||||
`Platforms supported: GitHub, GitLab, Bitbucket`
|
||||
|
||||
If you create a repo called `pr-agent-settings` in your **organization**, it's configuration file `.pr_agent.toml` will be used as a global configuration file for any other repo that belongs to the same organization.
|
||||
Parameters from a local `.pr_agent.toml` file, in a specific repo, will override the global configuration parameters.
|
||||
|
||||
For example, in the GitHub organization `Codium-ai`:
|
||||
- The repo [`https://github.com/Codium-ai/pr-agent-settings`](https://github.com/Codium-ai/pr-agent-settings/blob/main/.pr_agent.toml) contains a `.pr_agent.toml` file that serves as a global configuration file for all the repos in the GitHub organization `Codium-ai`.
|
||||
|
||||
- The file [`https://github.com/Codium-ai/pr-agent-settings/.pr_agent.toml`](https://github.com/Codium-ai/pr-agent-settings/blob/main/.pr_agent.toml) serves as a global configuration file for all the repos in the GitHub organization `Codium-ai`.
|
||||
|
||||
- The repo [`https://github.com/Codium-ai/pr-agent`](https://github.com/Codium-ai/pr-agent/blob/main/.pr_agent.toml) inherits the global configuration file from `pr-agent-settings`.
|
||||
|
33
docs/docs/usage-guide/enabling_a_wiki.md
Normal file
33
docs/docs/usage-guide/enabling_a_wiki.md
Normal file
@ -0,0 +1,33 @@
|
||||
`Supported Git Platforms: GitHub, GitLab, Bitbucket`
|
||||
|
||||
|
||||
For optimal functionality of Qodo Merge, we recommend enabling a wiki for each repository where Qodo Merge is installed. The wiki serves several important purposes:
|
||||
|
||||
**Key Wiki Features: 💎**
|
||||
|
||||
- Storing a [configuration file](https://qodo-merge-docs.qodo.ai/usage-guide/configuration_options/#wiki-configuration-file)
|
||||
- Defining a [`best_practices.md`](https://qodo-merge-docs.qodo.ai/tools/improve/#best-practices) file
|
||||
- Track [accepted suggestions](https://qodo-merge-docs.qodo.ai/tools/improve/#suggestion-tracking)
|
||||
- Facilitates learning over time by creating an [auto_best_practices.md](https://qodo-merge-docs.qodo.ai/core-abilities/auto_best_practices) file
|
||||
|
||||
|
||||
**Setup Instructions (GitHub):**
|
||||
|
||||
To enable a wiki for your repository:
|
||||
|
||||
1. Navigate to your repository's main page on GitHub
|
||||
2. Select "Settings" from the top navigation bar
|
||||
3. Locate the "Features" section
|
||||
4. Enable the "Wikis" option by checking the corresponding box
|
||||
5. Return to your repository's main page
|
||||
6. Look for the newly added "Wiki" tab in the top navigation
|
||||
7. Initialize your wiki by clicking "Create the first page" and saving (this step is important - without creating an initial page, the wiki will not be fully functional)
|
||||
|
||||
### Why Wiki?
|
||||
|
||||
- Your code (and its derivatives, including accepted code suggestions) is yours. Qodo Merge will never store it on external servers.
|
||||
- Repository changes typically require pull requests, which create overhead and are time-consuming. This process is too cumbersome for auto data aggregation, and is not very convenient even for managing frequently updated content like configuration files and best practices.
|
||||
- A repository wiki page provides an ideal balance:
|
||||
- It lives within your repository, making it suitable for code-related documentation
|
||||
- It enables quick updates without the overhead of pull requests
|
||||
- It maintains full Git version control, allowing you to track changes over time.
|
@ -1,10 +1,12 @@
|
||||
# Usage guide
|
||||
|
||||
This page provides a detailed guide on how to use PR-Agent. It includes information on how to adjust PR-Agent configurations, define which tools will run automatically, manage mail notifications, and other advanced configurations.
|
||||
This page provides a detailed guide on how to use Qodo Merge.
|
||||
It includes information on how to adjust Qodo Merge configurations, define which tools will run automatically, and other advanced configurations.
|
||||
|
||||
|
||||
- [Introduction](./introduction.md)
|
||||
- [Configuration Options](./configuration_options.md)
|
||||
- [Enabling a Wiki](./enabling_a_wiki)
|
||||
- [Configuration File](./configuration_options.md)
|
||||
- [Usage and Automation](./automations_and_usage.md)
|
||||
- [Local Repo (CLI)](./automations_and_usage.md#local-repo-cli)
|
||||
- [Online Usage](./automations_and_usage.md#online-usage)
|
||||
@ -14,6 +16,7 @@ This page provides a detailed guide on how to use PR-Agent. It includes informat
|
||||
- [BitBucket App](./automations_and_usage.md#bitbucket-app)
|
||||
- [Azure DevOps Provider](./automations_and_usage.md#azure-devops-provider)
|
||||
- [Managing Mail Notifications](./mail_notifications.md)
|
||||
- [Changing a Model](./changing_a_model.md)
|
||||
- [Additional Configurations Walkthrough](./additional_configurations.md)
|
||||
- [Ignoring files from analysis](./additional_configurations.md#ignoring-files-from-analysis)
|
||||
- [Extra instructions](./additional_configurations.md#extra-instructions)
|
||||
@ -21,3 +24,4 @@ This page provides a detailed guide on how to use PR-Agent. It includes informat
|
||||
- [Changing a model](./additional_configurations.md#changing-a-model)
|
||||
- [Patch Extra Lines](./additional_configurations.md#patch-extra-lines)
|
||||
- [Editing the prompts](./additional_configurations.md#editing-the-prompts)
|
||||
- [Qodo Merge Models](./PR_agent_pro_models.md)
|
||||
|
@ -1,18 +1,12 @@
|
||||
|
||||
After [installation](https://pr-agent-docs.codium.ai/installation/), there are three basic ways to invoke CodiumAI PR-Agent:
|
||||
After [installation](https://qodo-merge-docs.qodo.ai/installation/), there are three basic ways to invoke Qodo Merge:
|
||||
|
||||
1. Locally running a CLI command
|
||||
2. Online usage - by [commenting](https://github.com/Codium-ai/pr-agent/pull/229#issuecomment-1695021901) on a PR
|
||||
3. Enabling PR-Agent tools to run automatically when a new PR is opened
|
||||
2. Online usage - by [commenting](https://github.com/Codium-ai/pr-agent/pull/229#issuecomment-1695021901){:target="_blank"} on a PR
|
||||
3. Enabling Qodo Merge tools to run automatically when a new PR is opened
|
||||
|
||||
|
||||
Specifically, CLI commands can be issued by invoking a pre-built [docker image](https://pr-agent-docs.codium.ai/installation/locally/#using-docker-image), or by invoking a [locally cloned repo](https://pr-agent-docs.codium.ai/installation/locally/#run-from-source).
|
||||
For online usage, you will need to setup either a [GitHub App](https://pr-agent-docs.codium.ai/installation/github/#run-as-a-github-app), or a [GitHub Action](https://pr-agent-docs.codium.ai/installation/github/#run-as-a-github-action).
|
||||
GitHub App and GitHub Action also enable to run PR-Agent specific tool automatically when a new PR is opened.
|
||||
|
||||
|
||||
**git provider**: The [git_provider](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml#L5) field in the configuration file determines the GIT provider that will be used by PR-Agent. Currently, the following providers are supported:
|
||||
`
|
||||
"github", "gitlab", "bitbucket", "azure", "codecommit", "local", "gerrit"
|
||||
`
|
||||
Specifically, CLI commands can be issued by invoking a pre-built [docker image](https://qodo-merge-docs.qodo.ai/installation/locally/#using-docker-image), or by invoking a [locally cloned repo](https://qodo-merge-docs.qodo.ai/installation/locally/#run-from-source).
|
||||
|
||||
For online usage, you will need to setup either a [GitHub App](https://qodo-merge-docs.qodo.ai/installation/github/#run-as-a-github-app) or a [GitHub Action](https://qodo-merge-docs.qodo.ai/installation/github/#run-as-a-github-action) (GitHub), a [GitLab webhook](https://qodo-merge-docs.qodo.ai/installation/gitlab/#run-a-gitlab-webhook-server) (GitLab), or a [BitBucket App](https://qodo-merge-docs.qodo.ai/installation/bitbucket/#run-using-codiumai-hosted-bitbucket-app) (BitBucket).
|
||||
These platforms also enable to run Qodo Merge specific tools automatically when a new PR is opened, or on each push to a branch.
|
||||
|
@ -1,15 +1,15 @@
|
||||
|
||||
Unfortunately, it is not possible in GitHub to disable mail notifications from a specific user.
|
||||
If you are subscribed to notifications for a repo with PR-Agent, we recommend turning off notifications for PR comments, to avoid lengthy emails:
|
||||
If you are subscribed to notifications for a repo with Qodo Merge, we recommend turning off notifications for PR comments, to avoid lengthy emails:
|
||||
|
||||
{width=512}
|
||||
|
||||
As an alternative, you can filter in your mail provider the notifications specifically from the PR-Agent bot, [see how](https://www.quora.com/How-can-you-filter-emails-for-specific-people-in-Gmail#:~:text=On%20the%20Filters%20and%20Blocked,the%20body%20of%20the%20email).
|
||||
As an alternative, you can filter in your mail provider the notifications specifically from the Qodo Merge bot, [see how](https://www.quora.com/How-can-you-filter-emails-for-specific-people-in-Gmail#:~:text=On%20the%20Filters%20and%20Blocked,the%20body%20of%20the%20email).
|
||||
|
||||
{width=512}
|
||||
|
||||
|
||||
Another option to reduce the mail overload, yet still receive notifications on PR-Agent tools, is to disable the help collapsible section in PR-Agent bot comments.
|
||||
Another option to reduce the mail overload, yet still receive notifications on Qodo Merge tools, is to disable the help collapsible section in Qodo Merge bot comments.
|
||||
This can done by setting `enable_help_text=false` for the relevant tool in the configuration file.
|
||||
For example, to disable the help text for the `pr_reviewer` tool, set:
|
||||
```
|
||||
|
@ -1,27 +1,26 @@
|
||||
site_name: PR-Agent Documentation
|
||||
site_name: Qodo Merge (and open-source PR-Agent)
|
||||
repo_url: https://github.com/Codium-ai/pr-agent
|
||||
repo_name: Codium-ai/pr-agent
|
||||
|
||||
nav:
|
||||
- Overview:
|
||||
- 'index.md'
|
||||
- 💎 PR-Agent Pro: 'overview/pr_agent_pro.md'
|
||||
- 💎 Qodo Merge: 'overview/pr_agent_pro.md'
|
||||
- Data Privacy: 'overview/data_privacy.md'
|
||||
- Installation:
|
||||
- 'installation/index.md'
|
||||
- Locally: 'installation/locally.md'
|
||||
- GitHub: 'installation/github.md'
|
||||
- GitLab: 'installation/gitlab.md'
|
||||
- BitBucket: 'installation/bitbucket.md'
|
||||
- Azure DevOps: 'installation/azure.md'
|
||||
- 💎 PR-Agent Pro: 'installation/pr_agent_pro.md'
|
||||
- PR-Agent: 'installation/pr_agent.md'
|
||||
- 💎 Qodo Merge: 'installation/qodo_merge.md'
|
||||
- Usage Guide:
|
||||
- 'usage-guide/index.md'
|
||||
- Introduction: 'usage-guide/introduction.md'
|
||||
- Configuration Options: 'usage-guide/configuration_options.md'
|
||||
- Managing Mail Notifications: 'usage-guide/mail_notifications.md'
|
||||
- Enabling a Wiki: 'usage-guide/enabling_a_wiki.md'
|
||||
- Configuration File: 'usage-guide/configuration_options.md'
|
||||
- Usage and Automation: 'usage-guide/automations_and_usage.md'
|
||||
- Managing Mail Notifications: 'usage-guide/mail_notifications.md'
|
||||
- Changing a Model: 'usage-guide/changing_a_model.md'
|
||||
- Additional Configurations: 'usage-guide/additional_configurations.md'
|
||||
- 💎 Qodo Merge Models: 'usage-guide/PR_agent_pro_models'
|
||||
- Tools:
|
||||
- 'tools/index.md'
|
||||
- Describe: 'tools/describe.md'
|
||||
@ -39,9 +38,27 @@ nav:
|
||||
- 💎 Custom Prompt: 'tools/custom_prompt.md'
|
||||
- 💎 CI Feedback: 'tools/ci_feedback.md'
|
||||
- 💎 Similar Code: 'tools/similar_code.md'
|
||||
- Core Abilities: 'core-abilities/index.md'
|
||||
- Chrome Extension: 'chrome-extension/index.md'
|
||||
- 💎 Implement: 'tools/implement.md'
|
||||
- Core Abilities:
|
||||
- 'core-abilities/index.md'
|
||||
- Fetching ticket context: 'core-abilities/fetching_ticket_context.md'
|
||||
- Auto best practices: 'core-abilities/auto_best_practices.md'
|
||||
- Local and global metadata: 'core-abilities/metadata.md'
|
||||
- Dynamic context: 'core-abilities/dynamic_context.md'
|
||||
- Self-reflection: 'core-abilities/self_reflection.md'
|
||||
- Impact evaluation: 'core-abilities/impact_evaluation.md'
|
||||
- Interactivity: 'core-abilities/interactivity.md'
|
||||
- Compression strategy: 'core-abilities/compression_strategy.md'
|
||||
- Code-oriented YAML: 'core-abilities/code_oriented_yaml.md'
|
||||
- Static code analysis: 'core-abilities/static_code_analysis.md'
|
||||
- Code Fine-tuning Benchmark: 'finetuning_benchmark/index.md'
|
||||
- Chrome Extension:
|
||||
- Qodo Merge Chrome Extension: 'chrome-extension/index.md'
|
||||
- Features: 'chrome-extension/features.md'
|
||||
- Data Privacy: 'chrome-extension/data_privacy.md'
|
||||
- FAQ:
|
||||
- FAQ: 'faq/index.md'
|
||||
# - Code Fine-tuning Benchmark: 'finetuning_benchmark/index.md'
|
||||
|
||||
theme:
|
||||
logo: assets/logo.svg
|
||||
@ -131,7 +148,7 @@ markdown_extensions:
|
||||
emoji_generator: !!python/name:material.extensions.emoji.to_svg
|
||||
- toc:
|
||||
title: On this page
|
||||
toc_depth: 2
|
||||
toc_depth: 3
|
||||
permalink: true
|
||||
|
||||
|
||||
|
@ -82,11 +82,11 @@
|
||||
|
||||
<footer class="wrapper">
|
||||
<div class="container">
|
||||
<p class="footer-text">© 2024 <a href="https://www.codium.ai/" target="_blank" rel="noopener">CodiumAI</a></p>
|
||||
<p class="footer-text">© 2024 <a href="https://www.qodo.ai/" target="_blank" rel="noopener">Qodo</a></p>
|
||||
<div class="footer-links">
|
||||
<a href="https://codiumate-docs.codium.ai/">Codiumate</a>
|
||||
<a href="https://qodo-gen-docs.qodo.ai/">Qodo Gen</a>
|
||||
<p>|</p>
|
||||
<a href="https://alpha-codium-docs.codium.ai/">AlphaCodium</a>
|
||||
<a href="https://qodo-flow-docs.qodo.ai/">AlphaCodium</a>
|
||||
</div>
|
||||
<div class="social-icons">
|
||||
<a href="https://github.com/Codium-ai" target="_blank" rel="noopener" title="github.com" class="social-link">
|
||||
@ -95,16 +95,16 @@
|
||||
<a href="https://discord.com/invite/SgSxuQ65GF" target="_blank" rel="noopener" title="discord.com" class="social-link">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--! Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc.--><path d="M524.531 69.836a1.5 1.5 0 0 0-.764-.7A485.065 485.065 0 0 0 404.081 32.03a1.816 1.816 0 0 0-1.923.91 337.461 337.461 0 0 0-14.9 30.6 447.848 447.848 0 0 0-134.426 0 309.541 309.541 0 0 0-15.135-30.6 1.89 1.89 0 0 0-1.924-.91 483.689 483.689 0 0 0-119.688 37.107 1.712 1.712 0 0 0-.788.676C39.068 183.651 18.186 294.69 28.43 404.354a2.016 2.016 0 0 0 .765 1.375 487.666 487.666 0 0 0 146.825 74.189 1.9 1.9 0 0 0 2.063-.676A348.2 348.2 0 0 0 208.12 430.4a1.86 1.86 0 0 0-1.019-2.588 321.173 321.173 0 0 1-45.868-21.853 1.885 1.885 0 0 1-.185-3.126 251.047 251.047 0 0 0 9.109-7.137 1.819 1.819 0 0 1 1.9-.256c96.229 43.917 200.41 43.917 295.5 0a1.812 1.812 0 0 1 1.924.233 234.533 234.533 0 0 0 9.132 7.16 1.884 1.884 0 0 1-.162 3.126 301.407 301.407 0 0 1-45.89 21.83 1.875 1.875 0 0 0-1 2.611 391.055 391.055 0 0 0 30.014 48.815 1.864 1.864 0 0 0 2.063.7A486.048 486.048 0 0 0 610.7 405.729a1.882 1.882 0 0 0 .765-1.352c12.264-126.783-20.532-236.912-86.934-334.541ZM222.491 337.58c-28.972 0-52.844-26.587-52.844-59.239s23.409-59.241 52.844-59.241c29.665 0 53.306 26.82 52.843 59.239 0 32.654-23.41 59.241-52.843 59.241Zm195.38 0c-28.971 0-52.843-26.587-52.843-59.239s23.409-59.241 52.843-59.241c29.667 0 53.307 26.82 52.844 59.239 0 32.654-23.177 59.241-52.844 59.241Z"></path></svg>
|
||||
</a>
|
||||
<a href="https://www.youtube.com/@Codium-AI" target="_blank" rel="noopener" title="www.youtube.com" class="social-link">
|
||||
<a href="https://www.youtube.com/@QodoAI" target="_blank" rel="noopener" title="www.youtube.com" class="social-link">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--! Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc.--><path d="M549.655 124.083c-6.281-23.65-24.787-42.276-48.284-48.597C458.781 64 288 64 288 64S117.22 64 74.629 75.486c-23.497 6.322-42.003 24.947-48.284 48.597-11.412 42.867-11.412 132.305-11.412 132.305s0 89.438 11.412 132.305c6.281 23.65 24.787 41.5 48.284 47.821C117.22 448 288 448 288 448s170.78 0 213.371-11.486c23.497-6.321 42.003-24.171 48.284-47.821 11.412-42.867 11.412-132.305 11.412-132.305s0-89.438-11.412-132.305zm-317.51 213.508V175.185l142.739 81.205-142.739 81.201z"></path></svg>
|
||||
</a>
|
||||
<a href="https://www.linkedin.com/company/codiumai" target="_blank" rel="noopener" title="www.linkedin.com" class="social-link">
|
||||
<a href="https://www.linkedin.com/company/qodoai" target="_blank" rel="noopener" title="www.linkedin.com" class="social-link">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc.--><path d="M416 32H31.9C14.3 32 0 46.5 0 64.3v383.4C0 465.5 14.3 480 31.9 480H416c17.6 0 32-14.5 32-32.3V64.3c0-17.8-14.4-32.3-32-32.3zM135.4 416H69V202.2h66.5V416zm-33.2-243c-21.3 0-38.5-17.3-38.5-38.5S80.9 96 102.2 96c21.2 0 38.5 17.3 38.5 38.5 0 21.3-17.2 38.5-38.5 38.5zm282.1 243h-66.4V312c0-24.8-.5-56.7-34.5-56.7-34.6 0-39.9 27-39.9 54.9V416h-66.4V202.2h63.7v29.2h.9c8.9-16.8 30.6-34.5 62.9-34.5 67.2 0 79.7 44.3 79.7 101.9V416z"></path></svg>
|
||||
</a>
|
||||
<a href="https://twitter.com/CodiumAI" target="_blank" rel="noopener" title="twitter.com" class="social-link">
|
||||
<a href="https://twitter.com/QodoAI" target="_blank" rel="noopener" title="twitter.com" class="social-link">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc.--><path d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"></path></svg>
|
||||
</a>
|
||||
<a href="https://www.instagram.com/codiumai/" target="_blank" rel="noopener" title="www.instagram.com" class="social-link">
|
||||
<a href="https://www.instagram.com/qodo_ai" target="_blank" rel="noopener" title="www.instagram.com" class="social-link">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc.--><path d="M224.1 141c-63.6 0-114.9 51.3-114.9 114.9s51.3 114.9 114.9 114.9S339 319.5 339 255.9 287.7 141 224.1 141zm0 189.6c-41.1 0-74.7-33.5-74.7-74.7s33.5-74.7 74.7-74.7 74.7 33.5 74.7 74.7-33.6 74.7-74.7 74.7zm146.4-194.3c0 14.9-12 26.8-26.8 26.8-14.9 0-26.8-12-26.8-26.8s12-26.8 26.8-26.8 26.8 12 26.8 26.8zm76.1 27.2c-1.7-35.9-9.9-67.7-36.2-93.9-26.2-26.2-58-34.4-93.9-36.2-37-2.1-147.9-2.1-184.9 0-35.8 1.7-67.6 9.9-93.9 36.1s-34.4 58-36.2 93.9c-2.1 37-2.1 147.9 0 184.9 1.7 35.9 9.9 67.7 36.2 93.9s58 34.4 93.9 36.2c37 2.1 147.9 2.1 184.9 0 35.9-1.7 67.7-9.9 93.9-36.2 26.2-26.2 34.4-58 36.2-93.9 2.1-37 2.1-147.8 0-184.8zM398.8 388c-7.8 19.6-22.9 34.7-42.6 42.6-29.5 11.7-99.5 9-132.1 9s-102.7 2.6-132.1-9c-19.6-7.8-34.7-22.9-42.6-42.6-11.7-29.5-9-99.5-9-132.1s-2.6-102.7 9-132.1c7.8-19.6 22.9-34.7 42.6-42.6 29.5-11.7 99.5-9 132.1-9s102.7-2.6 132.1 9c19.6 7.8 34.7 22.9 42.6 42.6 11.7 29.5 9 99.5 9 132.1s2.7 102.7-9 132.1z"></path></svg>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -3,5 +3,5 @@
|
||||
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
|
||||
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
||||
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
|
||||
})(window,document,'script','dataLayer','GTM-5C9KZBM3');</script>
|
||||
})(window,document,'script','dataLayer','GTM-M6PJSFV');</script>
|
||||
<!-- End Google Tag Manager -->
|
@ -1 +0,0 @@
|
||||
|
||||
|
@ -3,7 +3,6 @@ from functools import partial
|
||||
|
||||
from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler
|
||||
from pr_agent.algo.ai_handlers.litellm_ai_handler import LiteLLMAIHandler
|
||||
|
||||
from pr_agent.algo.utils import update_settings_from_args
|
||||
from pr_agent.config_loader import get_settings
|
||||
from pr_agent.git_providers.utils import apply_repo_settings
|
||||
@ -14,7 +13,6 @@ from pr_agent.tools.pr_config import PRConfig
|
||||
from pr_agent.tools.pr_description import PRDescription
|
||||
from pr_agent.tools.pr_generate_labels import PRGenerateLabels
|
||||
from pr_agent.tools.pr_help_message import PRHelpMessage
|
||||
from pr_agent.tools.pr_information_from_user import PRInformationFromUser
|
||||
from pr_agent.tools.pr_line_questions import PR_LineQuestions
|
||||
from pr_agent.tools.pr_questions import PRQuestions
|
||||
from pr_agent.tools.pr_reviewer import PRReviewer
|
||||
@ -26,8 +24,6 @@ command2class = {
|
||||
"answer": PRReviewer,
|
||||
"review": PRReviewer,
|
||||
"review_pr": PRReviewer,
|
||||
"reflect": PRInformationFromUser,
|
||||
"reflect_and_review": PRInformationFromUser,
|
||||
"describe": PRDescription,
|
||||
"describe_pr": PRDescription,
|
||||
"improve": PRCodeSuggestions,
|
||||
@ -50,7 +46,6 @@ commands = list(command2class.keys())
|
||||
class PRAgent:
|
||||
def __init__(self, ai_handler: partial[BaseAiHandler,] = LiteLLMAIHandler):
|
||||
self.ai_handler = ai_handler # will be initialized in run_action
|
||||
self.forbidden_cli_args = ['enable_auto_approval']
|
||||
|
||||
async def handle_request(self, pr_url, request, notify=None) -> bool:
|
||||
# First, apply repo specific settings if exists
|
||||
@ -65,10 +60,21 @@ class PRAgent:
|
||||
else:
|
||||
action, *args = request
|
||||
|
||||
forbidden_cli_args = ['enable_auto_approval', 'approve_pr_on_self_review', 'base_url', 'url', 'app_name', 'secret_provider',
|
||||
'git_provider', 'skip_keys', 'openai.key', 'ANALYTICS_FOLDER', 'uri', 'app_id', 'webhook_secret',
|
||||
'bearer_token', 'PERSONAL_ACCESS_TOKEN', 'override_deployment_type', 'private_key',
|
||||
'local_cache_path', 'enable_local_cache', 'jira_base_url', 'api_base', 'api_type', 'api_version',
|
||||
'skip_keys']
|
||||
if args:
|
||||
for forbidden_arg in self.forbidden_cli_args:
|
||||
for arg in args:
|
||||
if forbidden_arg in arg:
|
||||
if arg.startswith('--'):
|
||||
arg_word = arg.lower()
|
||||
arg_word = arg_word.replace('__', '.') # replace double underscore with dot, e.g. --openai__key -> --openai.key
|
||||
for forbidden_arg in forbidden_cli_args:
|
||||
forbidden_arg_word = forbidden_arg.lower()
|
||||
if '.' not in forbidden_arg_word:
|
||||
forbidden_arg_word = '.' + forbidden_arg_word
|
||||
if forbidden_arg_word in arg_word:
|
||||
get_logger().error(
|
||||
f"CLI argument for param '{forbidden_arg}' is forbidden. Use instead a configuration file."
|
||||
)
|
||||
@ -77,12 +83,10 @@ class PRAgent:
|
||||
|
||||
action = action.lstrip("/").lower()
|
||||
if action not in command2class:
|
||||
get_logger().debug(f"Unknown command: {action}")
|
||||
get_logger().error(f"Unknown command: {action}")
|
||||
return False
|
||||
with get_logger().contextualize(command=action):
|
||||
with get_logger().contextualize(command=action, pr_url=pr_url):
|
||||
get_logger().info("PR-Agent request handler started", analytics=True)
|
||||
if action == "reflect_and_review":
|
||||
get_settings().pr_reviewer.ask_and_reflect = True
|
||||
if action == "answer":
|
||||
if notify:
|
||||
notify()
|
||||
|
@ -16,18 +16,38 @@ MAX_TOKENS = {
|
||||
'gpt-4-turbo-preview': 128000, # 128K, but may be limited by config.max_model_tokens
|
||||
'gpt-4-turbo-2024-04-09': 128000, # 128K, but may be limited by config.max_model_tokens
|
||||
'gpt-4-turbo': 128000, # 128K, but may be limited by config.max_model_tokens
|
||||
'gpt-4o-mini': 128000, # 128K, but may be limited by config.max_model_tokens
|
||||
'gpt-4o-mini-2024-07-18': 128000, # 128K, but may be limited by config.max_model_tokens
|
||||
'gpt-4o-2024-08-06': 128000, # 128K, but may be limited by config.max_model_tokens
|
||||
'gpt-4o-2024-11-20': 128000, # 128K, but may be limited by config.max_model_tokens
|
||||
'o1-mini': 128000, # 128K, but may be limited by config.max_model_tokens
|
||||
'o1-mini-2024-09-12': 128000, # 128K, but may be limited by config.max_model_tokens
|
||||
'o1-preview': 128000, # 128K, but may be limited by config.max_model_tokens
|
||||
'o1-preview-2024-09-12': 128000, # 128K, but may be limited by config.max_model_tokens
|
||||
'o1-2024-12-17': 204800, # 200K, but may be limited by config.max_model_tokens
|
||||
'o1': 204800, # 200K, but may be limited by config.max_model_tokens
|
||||
'claude-instant-1': 100000,
|
||||
'claude-2': 100000,
|
||||
'command-nightly': 4096,
|
||||
'deepseek/deepseek-chat': 128000, # 128K, but may be limited by config.max_model_tokens
|
||||
'deepseek/deepseek-reasoner': 64000, # 64K, but may be limited by config.max_model_tokens
|
||||
'replicate/llama-2-70b-chat:2c1608e18606fad2812020dc541930f2d0495ce32eee50074220b87300bc16e1': 4096,
|
||||
'meta-llama/Llama-2-7b-chat-hf': 4096,
|
||||
'vertex_ai/codechat-bison': 6144,
|
||||
'vertex_ai/codechat-bison-32k': 32000,
|
||||
'vertex_ai/claude-3-haiku@20240307': 100000,
|
||||
'vertex_ai/claude-3-5-haiku@20241022': 100000,
|
||||
'vertex_ai/claude-3-sonnet@20240229': 100000,
|
||||
'vertex_ai/claude-3-opus@20240229': 100000,
|
||||
'vertex_ai/claude-3-5-sonnet@20240620': 100000,
|
||||
'vertex_ai/claude-3-5-sonnet-v2@20241022': 100000,
|
||||
'vertex_ai/gemini-1.5-pro': 1048576,
|
||||
'vertex_ai/gemini-1.5-flash': 1048576,
|
||||
'vertex_ai/gemini-2.0-flash-exp': 1048576,
|
||||
'vertex_ai/gemma2': 8200,
|
||||
'gemini/gemini-1.5-pro': 1048576,
|
||||
'gemini/gemini-1.5-flash': 1048576,
|
||||
'gemini/gemini-2.0-flash-exp': 1048576,
|
||||
'codechat-bison': 6144,
|
||||
'codechat-bison-32k': 32000,
|
||||
'anthropic.claude-instant-v1': 100000,
|
||||
@ -35,13 +55,29 @@ MAX_TOKENS = {
|
||||
'anthropic.claude-v2': 100000,
|
||||
'anthropic/claude-3-opus-20240229': 100000,
|
||||
'anthropic/claude-3-5-sonnet-20240620': 100000,
|
||||
'anthropic/claude-3-5-sonnet-20241022': 100000,
|
||||
'anthropic/claude-3-5-haiku-20241022': 100000,
|
||||
'bedrock/anthropic.claude-instant-v1': 100000,
|
||||
'bedrock/anthropic.claude-v2': 100000,
|
||||
'bedrock/anthropic.claude-v2:1': 100000,
|
||||
'bedrock/anthropic.claude-3-sonnet-20240229-v1:0': 100000,
|
||||
'bedrock/anthropic.claude-3-haiku-20240307-v1:0': 100000,
|
||||
'bedrock/anthropic.claude-3-5-haiku-20241022-v1:0': 100000,
|
||||
'bedrock/anthropic.claude-3-5-sonnet-20240620-v1:0': 100000,
|
||||
'bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0': 100000,
|
||||
"bedrock/us.anthropic.claude-3-5-sonnet-20241022-v2:0": 100000,
|
||||
'claude-3-5-sonnet': 100000,
|
||||
'groq/llama3-8b-8192': 8192,
|
||||
'groq/llama3-70b-8192': 8192,
|
||||
'groq/llama-3.1-8b-instant': 8192,
|
||||
'groq/llama-3.3-70b-versatile': 128000,
|
||||
'groq/mixtral-8x7b-32768': 32768,
|
||||
'groq/gemma2-9b-it': 8192,
|
||||
'ollama/llama3': 4096,
|
||||
'watsonx/meta-llama/llama-3-8b-instruct': 4096,
|
||||
"watsonx/meta-llama/llama-3-70b-instruct": 4096,
|
||||
"watsonx/meta-llama/llama-3-405b-instruct": 16384,
|
||||
"watsonx/ibm/granite-13b-chat-v2": 8191,
|
||||
"watsonx/ibm/granite-34b-code-instruct": 8191,
|
||||
"watsonx/mistralai/mistral-large": 32768,
|
||||
}
|
||||
|
@ -1,17 +1,18 @@
|
||||
try:
|
||||
from langchain_openai import ChatOpenAI, AzureChatOpenAI
|
||||
from langchain_core.messages import SystemMessage, HumanMessage
|
||||
from langchain_core.messages import HumanMessage, SystemMessage
|
||||
from langchain_openai import AzureChatOpenAI, ChatOpenAI
|
||||
except: # we don't enforce langchain as a dependency, so if it's not installed, just move on
|
||||
pass
|
||||
|
||||
import functools
|
||||
|
||||
from openai import APIError, RateLimitError, Timeout
|
||||
from retry import retry
|
||||
|
||||
from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler
|
||||
from pr_agent.config_loader import get_settings
|
||||
from pr_agent.log import get_logger
|
||||
|
||||
from openai import APIError, RateLimitError, Timeout
|
||||
from retry import retry
|
||||
import functools
|
||||
|
||||
OPENAI_RETRIES = 5
|
||||
|
||||
|
||||
@ -20,35 +21,13 @@ class LangChainOpenAIHandler(BaseAiHandler):
|
||||
# Initialize OpenAIHandler specific attributes here
|
||||
super().__init__()
|
||||
self.azure = get_settings().get("OPENAI.API_TYPE", "").lower() == "azure"
|
||||
try:
|
||||
if self.azure:
|
||||
# using a partial function so we can set the deployment_id later to support fallback_deployments
|
||||
# but still need to access the other settings now so we can raise a proper exception if they're missing
|
||||
self._chat = functools.partial(
|
||||
lambda **kwargs: AzureChatOpenAI(**kwargs),
|
||||
openai_api_key=get_settings().openai.key,
|
||||
openai_api_base=get_settings().openai.api_base,
|
||||
openai_api_version=get_settings().openai.api_version,
|
||||
)
|
||||
else:
|
||||
# for llms that compatible with openai, should use custom api base
|
||||
openai_api_base = get_settings().get("OPENAI.API_BASE", None)
|
||||
if openai_api_base is None or len(openai_api_base) == 0:
|
||||
self._chat = ChatOpenAI(openai_api_key=get_settings().openai.key)
|
||||
else:
|
||||
self._chat = ChatOpenAI(openai_api_key=get_settings().openai.key, openai_api_base=openai_api_base)
|
||||
except AttributeError as e:
|
||||
if getattr(e, "name"):
|
||||
raise ValueError(f"OpenAI {e.name} is required") from e
|
||||
else:
|
||||
raise e
|
||||
|
||||
# Create a default unused chat object to trigger early validation
|
||||
self._create_chat(self.deployment_id)
|
||||
|
||||
def chat(self, messages: list, model: str, temperature: float):
|
||||
if self.azure:
|
||||
# we must set the deployment_id only here (instead of the __init__ method) to support fallback_deployments
|
||||
return self._chat.invoke(input = messages, model=model, temperature=temperature, deployment_name=self.deployment_id)
|
||||
else:
|
||||
return self._chat.invoke(input = messages, model=model, temperature=temperature)
|
||||
chat = self._create_chat(self.deployment_id)
|
||||
return chat.invoke(input=messages, model=model, temperature=temperature)
|
||||
|
||||
@property
|
||||
def deployment_id(self):
|
||||
@ -71,3 +50,27 @@ class LangChainOpenAIHandler(BaseAiHandler):
|
||||
except (Exception) as e:
|
||||
get_logger().error("Unknown error during OpenAI inference: ", e)
|
||||
raise e
|
||||
|
||||
def _create_chat(self, deployment_id=None):
|
||||
try:
|
||||
if self.azure:
|
||||
# using a partial function so we can set the deployment_id later to support fallback_deployments
|
||||
# but still need to access the other settings now so we can raise a proper exception if they're missing
|
||||
return AzureChatOpenAI(
|
||||
openai_api_key=get_settings().openai.key,
|
||||
openai_api_version=get_settings().openai.api_version,
|
||||
azure_deployment=deployment_id,
|
||||
azure_endpoint=get_settings().openai.api_base,
|
||||
)
|
||||
else:
|
||||
# for llms that compatible with openai, should use custom api base
|
||||
openai_api_base = get_settings().get("OPENAI.API_BASE", None)
|
||||
if openai_api_base is None or len(openai_api_base) == 0:
|
||||
return ChatOpenAI(openai_api_key=get_settings().openai.key)
|
||||
else:
|
||||
return ChatOpenAI(openai_api_key=get_settings().openai.key, openai_api_base=openai_api_base)
|
||||
except AttributeError as e:
|
||||
if getattr(e, "name"):
|
||||
raise ValueError(f"OpenAI {e.name} is required") from e
|
||||
else:
|
||||
raise e
|
||||
|
@ -1,11 +1,13 @@
|
||||
import os
|
||||
import requests
|
||||
import boto3
|
||||
|
||||
import litellm
|
||||
import openai
|
||||
import requests
|
||||
from litellm import acompletion
|
||||
from tenacity import retry, retry_if_exception_type, stop_after_attempt
|
||||
|
||||
from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler
|
||||
from pr_agent.algo.utils import get_version
|
||||
from pr_agent.config_loader import get_settings
|
||||
from pr_agent.log import get_logger
|
||||
|
||||
@ -44,6 +46,12 @@ class LiteLLMAIHandler(BaseAiHandler):
|
||||
litellm.use_client = True
|
||||
if get_settings().get("LITELLM.DROP_PARAMS", None):
|
||||
litellm.drop_params = get_settings().litellm.drop_params
|
||||
if get_settings().get("LITELLM.SUCCESS_CALLBACK", None):
|
||||
litellm.success_callback = get_settings().litellm.success_callback
|
||||
if get_settings().get("LITELLM.FAILURE_CALLBACK", None):
|
||||
litellm.failure_callback = get_settings().litellm.failure_callback
|
||||
if get_settings().get("LITELLM.SERVICE_CALLBACK", None):
|
||||
litellm.service_callback = get_settings().litellm.service_callback
|
||||
if get_settings().get("OPENAI.ORG", None):
|
||||
litellm.organization = get_settings().openai.org
|
||||
if get_settings().get("OPENAI.API_TYPE", None):
|
||||
@ -77,6 +85,15 @@ class LiteLLMAIHandler(BaseAiHandler):
|
||||
litellm.vertex_location = get_settings().get(
|
||||
"VERTEXAI.VERTEX_LOCATION", None
|
||||
)
|
||||
# Google AI Studio
|
||||
# SEE https://docs.litellm.ai/docs/providers/gemini
|
||||
if get_settings().get("GOOGLE_AI_STUDIO.GEMINI_API_KEY", None):
|
||||
os.environ["GEMINI_API_KEY"] = get_settings().google_ai_studio.gemini_api_key
|
||||
|
||||
# Support deepseek models
|
||||
if get_settings().get("DEEPSEEK.KEY", None):
|
||||
os.environ['DEEPSEEK_API_KEY'] = get_settings().get("DEEPSEEK.KEY")
|
||||
|
||||
def prepare_logs(self, response, system, user, resp, finish_reason):
|
||||
response_log = response.dict().copy()
|
||||
response_log['system'] = system
|
||||
@ -89,6 +106,60 @@ class LiteLLMAIHandler(BaseAiHandler):
|
||||
response_log['main_pr_language'] = 'unknown'
|
||||
return response_log
|
||||
|
||||
def add_litellm_callbacks(selfs, kwargs) -> dict:
|
||||
captured_extra = []
|
||||
|
||||
def capture_logs(message):
|
||||
# Parsing the log message and context
|
||||
record = message.record
|
||||
log_entry = {}
|
||||
if record.get('extra', None).get('command', None) is not None:
|
||||
log_entry.update({"command": record['extra']["command"]})
|
||||
if record.get('extra', {}).get('pr_url', None) is not None:
|
||||
log_entry.update({"pr_url": record['extra']["pr_url"]})
|
||||
|
||||
# Append the log entry to the captured_logs list
|
||||
captured_extra.append(log_entry)
|
||||
|
||||
# Adding the custom sink to Loguru
|
||||
handler_id = get_logger().add(capture_logs)
|
||||
get_logger().debug("Capturing logs for litellm callbacks")
|
||||
get_logger().remove(handler_id)
|
||||
|
||||
context = captured_extra[0] if len(captured_extra) > 0 else None
|
||||
|
||||
command = context.get("command", "unknown")
|
||||
pr_url = context.get("pr_url", "unknown")
|
||||
git_provider = get_settings().config.git_provider
|
||||
|
||||
metadata = dict()
|
||||
callbacks = litellm.success_callback + litellm.failure_callback + litellm.service_callback
|
||||
if "langfuse" in callbacks:
|
||||
metadata.update({
|
||||
"trace_name": command,
|
||||
"tags": [git_provider, command, f'version:{get_version()}'],
|
||||
"trace_metadata": {
|
||||
"command": command,
|
||||
"pr_url": pr_url,
|
||||
},
|
||||
})
|
||||
if "langsmith" in callbacks:
|
||||
metadata.update({
|
||||
"run_name": command,
|
||||
"tags": [git_provider, command, f'version:{get_version()}'],
|
||||
"extra": {
|
||||
"metadata": {
|
||||
"command": command,
|
||||
"pr_url": pr_url,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
# Adding the captured logs to the kwargs
|
||||
kwargs["metadata"] = metadata
|
||||
|
||||
return kwargs
|
||||
|
||||
@property
|
||||
def deployment_id(self):
|
||||
"""
|
||||
@ -106,7 +177,12 @@ class LiteLLMAIHandler(BaseAiHandler):
|
||||
deployment_id = self.deployment_id
|
||||
if self.azure:
|
||||
model = 'azure/' + model
|
||||
if 'claude' in model and not system:
|
||||
system = "No system prompt provided"
|
||||
get_logger().warning(
|
||||
"Empty system prompt for claude model. Adding a newline character to prevent OpenAI API error.")
|
||||
messages = [{"role": "system", "content": system}, {"role": "user", "content": user}]
|
||||
|
||||
if img_path:
|
||||
try:
|
||||
# check if the image link is alive
|
||||
@ -121,14 +197,41 @@ class LiteLLMAIHandler(BaseAiHandler):
|
||||
messages[1]["content"] = [{"type": "text", "text": messages[1]["content"]},
|
||||
{"type": "image_url", "image_url": {"url": img_path}}]
|
||||
|
||||
# Currently, model OpenAI o1 series does not support a separate system and user prompts
|
||||
O1_MODEL_PREFIX = 'o1'
|
||||
model_type = model.split('/')[-1] if '/' in model else model
|
||||
if (model_type.startswith(O1_MODEL_PREFIX)) or ("deepseek-reasoner" in model):
|
||||
user = f"{system}\n\n\n{user}"
|
||||
system = ""
|
||||
get_logger().info(f"Using model {model}, combining system and user prompts")
|
||||
messages = [{"role": "user", "content": user}]
|
||||
kwargs = {
|
||||
"model": model,
|
||||
"deployment_id": deployment_id,
|
||||
"messages": messages,
|
||||
"timeout": get_settings().config.ai_timeout,
|
||||
"api_base": self.api_base,
|
||||
}
|
||||
else:
|
||||
kwargs = {
|
||||
"model": model,
|
||||
"deployment_id": deployment_id,
|
||||
"messages": messages,
|
||||
"temperature": temperature,
|
||||
"force_timeout": get_settings().config.ai_timeout,
|
||||
"timeout": get_settings().config.ai_timeout,
|
||||
"api_base": self.api_base,
|
||||
}
|
||||
|
||||
if get_settings().litellm.get("enable_callbacks", False):
|
||||
kwargs = self.add_litellm_callbacks(kwargs)
|
||||
|
||||
seed = get_settings().config.get("seed", -1)
|
||||
if temperature > 0 and seed >= 0:
|
||||
raise ValueError(f"Seed ({seed}) is not supported with temperature ({temperature}) > 0")
|
||||
elif seed >= 0:
|
||||
get_logger().info(f"Using fixed seed of {seed}")
|
||||
kwargs["seed"] = seed
|
||||
|
||||
if self.repetition_penalty:
|
||||
kwargs["repetition_penalty"] = self.repetition_penalty
|
||||
|
||||
@ -140,13 +243,13 @@ class LiteLLMAIHandler(BaseAiHandler):
|
||||
|
||||
response = await acompletion(**kwargs)
|
||||
except (openai.APIError, openai.APITimeoutError) as e:
|
||||
get_logger().error("Error during OpenAI inference: ", e)
|
||||
get_logger().warning(f"Error during LLM inference: {e}")
|
||||
raise
|
||||
except (openai.RateLimitError) as e:
|
||||
get_logger().error("Rate limit error during OpenAI inference: ", e)
|
||||
get_logger().error(f"Rate limit error during LLM inference: {e}")
|
||||
raise
|
||||
except (Exception) as e:
|
||||
get_logger().error("Unknown error during OpenAI inference: ", e)
|
||||
get_logger().warning(f"Unknown error during LLM inference: {e}")
|
||||
raise openai.APIError from e
|
||||
if response is None or len(response["choices"]) == 0:
|
||||
raise openai.APIError
|
||||
|
@ -1,8 +1,10 @@
|
||||
from os import environ
|
||||
from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler
|
||||
import openai
|
||||
from openai.error import APIError, RateLimitError, Timeout, TryAgain
|
||||
from openai import APIError, AsyncOpenAI, RateLimitError, Timeout
|
||||
from retry import retry
|
||||
|
||||
from pr_agent.algo.ai_handlers.base_ai_handler import BaseAiHandler
|
||||
from pr_agent.config_loader import get_settings
|
||||
from pr_agent.log import get_logger
|
||||
|
||||
@ -14,7 +16,7 @@ class OpenAIHandler(BaseAiHandler):
|
||||
# Initialize OpenAIHandler specific attributes here
|
||||
try:
|
||||
super().__init__()
|
||||
openai.api_key = get_settings().openai.key
|
||||
environ["OPENAI_API_KEY"] = get_settings().openai.key
|
||||
if get_settings().get("OPENAI.ORG", None):
|
||||
openai.organization = get_settings().openai.org
|
||||
if get_settings().get("OPENAI.API_TYPE", None):
|
||||
@ -24,7 +26,7 @@ class OpenAIHandler(BaseAiHandler):
|
||||
if get_settings().get("OPENAI.API_VERSION", None):
|
||||
openai.api_version = get_settings().openai.api_version
|
||||
if get_settings().get("OPENAI.API_BASE", None):
|
||||
openai.api_base = get_settings().openai.api_base
|
||||
environ["OPENAI_BASE_URL"] = get_settings().openai.api_base
|
||||
|
||||
except AttributeError as e:
|
||||
raise ValueError("OpenAI key is required") from e
|
||||
@ -36,28 +38,26 @@ class OpenAIHandler(BaseAiHandler):
|
||||
"""
|
||||
return get_settings().get("OPENAI.DEPLOYMENT_ID", None)
|
||||
|
||||
@retry(exceptions=(APIError, Timeout, TryAgain, AttributeError, RateLimitError),
|
||||
@retry(exceptions=(APIError, Timeout, AttributeError, RateLimitError),
|
||||
tries=OPENAI_RETRIES, delay=2, backoff=2, jitter=(1, 3))
|
||||
async def chat_completion(self, model: str, system: str, user: str, temperature: float = 0.2):
|
||||
try:
|
||||
deployment_id = self.deployment_id
|
||||
get_logger().info("System: ", system)
|
||||
get_logger().info("User: ", user)
|
||||
messages = [{"role": "system", "content": system}, {"role": "user", "content": user}]
|
||||
|
||||
chat_completion = await openai.ChatCompletion.acreate(
|
||||
client = AsyncOpenAI()
|
||||
chat_completion = await client.chat.completions.create(
|
||||
model=model,
|
||||
deployment_id=deployment_id,
|
||||
messages=messages,
|
||||
temperature=temperature,
|
||||
)
|
||||
resp = chat_completion["choices"][0]['message']['content']
|
||||
finish_reason = chat_completion["choices"][0]["finish_reason"]
|
||||
usage = chat_completion.get("usage")
|
||||
resp = chat_completion.choices[0].message.content
|
||||
finish_reason = chat_completion.choices[0].finish_reason
|
||||
usage = chat_completion.usage
|
||||
get_logger().info("AI response", response=resp, messages=messages, finish_reason=finish_reason,
|
||||
model=model, usage=usage)
|
||||
return resp, finish_reason
|
||||
except (APIError, Timeout, TryAgain) as e:
|
||||
except (APIError, Timeout) as e:
|
||||
get_logger().error("Error during OpenAI inference: ", e)
|
||||
raise
|
||||
except (RateLimitError) as e:
|
||||
@ -65,4 +65,4 @@ class OpenAIHandler(BaseAiHandler):
|
||||
raise
|
||||
except (Exception) as e:
|
||||
get_logger().error("Unknown error during OpenAI inference: ", e)
|
||||
raise TryAgain from e
|
||||
raise
|
||||
|
@ -33,9 +33,29 @@ def filter_ignored(files, platform = 'github'):
|
||||
if platform == 'github':
|
||||
files = [f for f in files if (f.filename and not r.match(f.filename))]
|
||||
elif platform == 'bitbucket':
|
||||
files = [f for f in files if (f.new.path and not r.match(f.new.path))]
|
||||
# files = [f for f in files if (f.new.path and not r.match(f.new.path))]
|
||||
files_o = []
|
||||
for f in files:
|
||||
if hasattr(f, 'new'):
|
||||
if f.new and f.new.path and not r.match(f.new.path):
|
||||
files_o.append(f)
|
||||
continue
|
||||
if hasattr(f, 'old'):
|
||||
if f.old and f.old.path and not r.match(f.old.path):
|
||||
files_o.append(f)
|
||||
continue
|
||||
files = files_o
|
||||
elif platform == 'gitlab':
|
||||
files = [f for f in files if (f['new_path'] and not r.match(f['new_path']))]
|
||||
# files = [f for f in files if (f['new_path'] and not r.match(f['new_path']))]
|
||||
files_o = []
|
||||
for f in files:
|
||||
if 'new_path' in f and f['new_path'] and not r.match(f['new_path']):
|
||||
files_o.append(f)
|
||||
continue
|
||||
if 'old_path' in f and f['old_path'] and not r.match(f['old_path']):
|
||||
files_o.append(f)
|
||||
continue
|
||||
files = files_o
|
||||
elif platform == 'azure':
|
||||
files = [f for f in files if not r.match(f)]
|
||||
|
||||
|
@ -1,50 +1,174 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import traceback
|
||||
|
||||
from pr_agent.config_loader import get_settings
|
||||
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
|
||||
from pr_agent.config_loader import get_settings
|
||||
from pr_agent.log import get_logger
|
||||
|
||||
|
||||
def extend_patch(original_file_str, patch_str, num_lines) -> str:
|
||||
"""
|
||||
Extends the given patch to include a specified number of surrounding lines.
|
||||
|
||||
Args:
|
||||
original_file_str (str): The original file to which the patch will be applied.
|
||||
patch_str (str): The patch to be applied to the original file.
|
||||
num_lines (int): The number of surrounding lines to include in the extended patch.
|
||||
|
||||
Returns:
|
||||
str: The extended patch string.
|
||||
"""
|
||||
if not patch_str or num_lines == 0:
|
||||
def extend_patch(original_file_str, patch_str, patch_extra_lines_before=0,
|
||||
patch_extra_lines_after=0, filename: str = "") -> str:
|
||||
if not patch_str or (patch_extra_lines_before == 0 and patch_extra_lines_after == 0) or not original_file_str:
|
||||
return patch_str
|
||||
|
||||
original_file_str = decode_if_bytes(original_file_str)
|
||||
if not original_file_str:
|
||||
return patch_str
|
||||
|
||||
if should_skip_patch(filename):
|
||||
return patch_str
|
||||
|
||||
if type(original_file_str) == bytes:
|
||||
try:
|
||||
original_file_str = original_file_str.decode('utf-8')
|
||||
extended_patch_str = process_patch_lines(patch_str, original_file_str,
|
||||
patch_extra_lines_before, patch_extra_lines_after)
|
||||
except Exception as e:
|
||||
get_logger().warning(f"Failed to extend patch: {e}", artifact={"traceback": traceback.format_exc()})
|
||||
return patch_str
|
||||
|
||||
return extended_patch_str
|
||||
|
||||
|
||||
def decode_if_bytes(original_file_str):
|
||||
if isinstance(original_file_str, (bytes, bytearray)):
|
||||
try:
|
||||
return original_file_str.decode('utf-8')
|
||||
except UnicodeDecodeError:
|
||||
encodings_to_try = ['iso-8859-1', 'latin-1', 'ascii', 'utf-16']
|
||||
for encoding in encodings_to_try:
|
||||
try:
|
||||
return original_file_str.decode(encoding)
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
return ""
|
||||
return original_file_str
|
||||
|
||||
|
||||
def should_skip_patch(filename):
|
||||
patch_extension_skip_types = get_settings().config.patch_extension_skip_types
|
||||
if patch_extension_skip_types and filename:
|
||||
return any(filename.endswith(skip_type) for skip_type in patch_extension_skip_types)
|
||||
return False
|
||||
|
||||
|
||||
def process_patch_lines(patch_str, original_file_str, patch_extra_lines_before, patch_extra_lines_after):
|
||||
allow_dynamic_context = get_settings().config.allow_dynamic_context
|
||||
patch_extra_lines_before_dynamic = get_settings().config.max_extra_lines_before_dynamic_context
|
||||
|
||||
original_lines = original_file_str.splitlines()
|
||||
len_original_lines = len(original_lines)
|
||||
patch_lines = patch_str.splitlines()
|
||||
extended_patch_lines = []
|
||||
|
||||
is_valid_hunk = True
|
||||
start1, size1, start2, size2 = -1, -1, -1, -1
|
||||
RE_HUNK_HEADER = re.compile(
|
||||
r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@[ ]?(.*)")
|
||||
try:
|
||||
for line in patch_lines:
|
||||
for i,line in enumerate(patch_lines):
|
||||
if line.startswith('@@'):
|
||||
match = RE_HUNK_HEADER.match(line)
|
||||
# identify hunk header
|
||||
if match:
|
||||
# finish previous hunk
|
||||
if start1 != -1:
|
||||
extended_patch_lines.extend(
|
||||
original_lines[start1 + size1 - 1:start1 + size1 - 1 + num_lines])
|
||||
# finish processing previous hunk
|
||||
if is_valid_hunk and (start1 != -1 and patch_extra_lines_after > 0):
|
||||
delta_lines = [f' {line}' for line in original_lines[start1 + size1 - 1:start1 + size1 - 1 + patch_extra_lines_after]]
|
||||
extended_patch_lines.extend(delta_lines)
|
||||
|
||||
section_header, size1, size2, start1, start2 = extract_hunk_headers(match)
|
||||
|
||||
is_valid_hunk = check_if_hunk_lines_matches_to_file(i, original_lines, patch_lines, start1)
|
||||
|
||||
if is_valid_hunk and (patch_extra_lines_before > 0 or patch_extra_lines_after > 0):
|
||||
def _calc_context_limits(patch_lines_before):
|
||||
extended_start1 = max(1, start1 - patch_lines_before)
|
||||
extended_size1 = size1 + (start1 - extended_start1) + patch_extra_lines_after
|
||||
extended_start2 = max(1, start2 - patch_lines_before)
|
||||
extended_size2 = size2 + (start2 - extended_start2) + patch_extra_lines_after
|
||||
if extended_start1 - 1 + extended_size1 > len_original_lines:
|
||||
# we cannot extend beyond the original file
|
||||
delta_cap = extended_start1 - 1 + extended_size1 - len_original_lines
|
||||
extended_size1 = max(extended_size1 - delta_cap, size1)
|
||||
extended_size2 = max(extended_size2 - delta_cap, size2)
|
||||
return extended_start1, extended_size1, extended_start2, extended_size2
|
||||
|
||||
if allow_dynamic_context:
|
||||
extended_start1, extended_size1, extended_start2, extended_size2 = \
|
||||
_calc_context_limits(patch_extra_lines_before_dynamic)
|
||||
lines_before = original_lines[extended_start1 - 1:start1 - 1]
|
||||
found_header = False
|
||||
for i, line, in enumerate(lines_before):
|
||||
if section_header in line:
|
||||
found_header = True
|
||||
# Update start and size in one line each
|
||||
extended_start1, extended_start2 = extended_start1 + i, extended_start2 + i
|
||||
extended_size1, extended_size2 = extended_size1 - i, extended_size2 - i
|
||||
# get_logger().debug(f"Found section header in line {i} before the hunk")
|
||||
section_header = ''
|
||||
break
|
||||
if not found_header:
|
||||
# get_logger().debug(f"Section header not found in the extra lines before the hunk")
|
||||
extended_start1, extended_size1, extended_start2, extended_size2 = \
|
||||
_calc_context_limits(patch_extra_lines_before)
|
||||
else:
|
||||
extended_start1, extended_size1, extended_start2, extended_size2 = \
|
||||
_calc_context_limits(patch_extra_lines_before)
|
||||
|
||||
delta_lines = [f' {line}' for line in original_lines[extended_start1 - 1:start1 - 1]]
|
||||
|
||||
# logic to remove section header if its in the extra delta lines (in dynamic context, this is also done)
|
||||
if section_header and not allow_dynamic_context:
|
||||
for line in delta_lines:
|
||||
if section_header in line:
|
||||
section_header = '' # remove section header if it is in the extra delta lines
|
||||
break
|
||||
else:
|
||||
extended_start1 = start1
|
||||
extended_size1 = size1
|
||||
extended_start2 = start2
|
||||
extended_size2 = size2
|
||||
delta_lines = []
|
||||
extended_patch_lines.append('')
|
||||
extended_patch_lines.append(
|
||||
f'@@ -{extended_start1},{extended_size1} '
|
||||
f'+{extended_start2},{extended_size2} @@ {section_header}')
|
||||
extended_patch_lines.extend(delta_lines) # one to zero based
|
||||
continue
|
||||
extended_patch_lines.append(line)
|
||||
except Exception as e:
|
||||
get_logger().warning(f"Failed to extend patch: {e}", artifact={"traceback": traceback.format_exc()})
|
||||
return patch_str
|
||||
|
||||
# finish processing last hunk
|
||||
if start1 != -1 and patch_extra_lines_after > 0 and is_valid_hunk:
|
||||
delta_lines = original_lines[start1 + size1 - 1:start1 + size1 - 1 + patch_extra_lines_after]
|
||||
# add space at the beginning of each extra line
|
||||
delta_lines = [f' {line}' for line in delta_lines]
|
||||
extended_patch_lines.extend(delta_lines)
|
||||
|
||||
extended_patch_str = '\n'.join(extended_patch_lines)
|
||||
return extended_patch_str
|
||||
|
||||
|
||||
def check_if_hunk_lines_matches_to_file(i, original_lines, patch_lines, start1):
|
||||
"""
|
||||
Check if the hunk lines match the original file content. We saw cases where the hunk header line doesn't match the original file content, and then
|
||||
extending the hunk with extra lines before the hunk header can cause the hunk to be invalid.
|
||||
"""
|
||||
is_valid_hunk = True
|
||||
try:
|
||||
if i + 1 < len(patch_lines) and patch_lines[i + 1][0] == ' ': # an existing line in the file
|
||||
if patch_lines[i + 1].strip() != original_lines[start1 - 1].strip():
|
||||
is_valid_hunk = False
|
||||
get_logger().error(
|
||||
f"Invalid hunk in PR, line {start1} in hunk header doesn't match the original file content")
|
||||
except:
|
||||
pass
|
||||
return is_valid_hunk
|
||||
|
||||
|
||||
def extract_hunk_headers(match):
|
||||
res = list(match.groups())
|
||||
for i in range(len(res)):
|
||||
if res[i] is None:
|
||||
@ -55,29 +179,7 @@ def extend_patch(original_file_str, patch_str, num_lines) -> str:
|
||||
start1, size1, size2 = map(int, res[:3])
|
||||
start2 = 0
|
||||
section_header = res[4]
|
||||
extended_start1 = max(1, start1 - num_lines)
|
||||
extended_size1 = size1 + (start1 - extended_start1) + num_lines
|
||||
extended_start2 = max(1, start2 - num_lines)
|
||||
extended_size2 = size2 + (start2 - extended_start2) + num_lines
|
||||
extended_patch_lines.append(
|
||||
f'@@ -{extended_start1},{extended_size1} '
|
||||
f'+{extended_start2},{extended_size2} @@ {section_header}')
|
||||
extended_patch_lines.extend(
|
||||
original_lines[extended_start1 - 1:start1 - 1]) # one to zero based
|
||||
continue
|
||||
extended_patch_lines.append(line)
|
||||
except Exception as e:
|
||||
if get_settings().config.verbosity_level >= 2:
|
||||
get_logger().error(f"Failed to extend patch: {e}")
|
||||
return patch_str
|
||||
|
||||
# finish previous hunk
|
||||
if start1 != -1:
|
||||
extended_patch_lines.extend(
|
||||
original_lines[start1 + size1 - 1:start1 + size1 - 1 + num_lines])
|
||||
|
||||
extended_patch_str = '\n'.join(extended_patch_lines)
|
||||
return extended_patch_str
|
||||
return section_header, size1, size2, start1, start2
|
||||
|
||||
|
||||
def omit_deletion_hunks(patch_lines) -> str:
|
||||
@ -109,6 +211,7 @@ def omit_deletion_hunks(patch_lines) -> str:
|
||||
inside_hunk = True
|
||||
else:
|
||||
temp_hunk.append(line)
|
||||
if line:
|
||||
edit_type = line[0]
|
||||
if edit_type == '+':
|
||||
add_hunk = True
|
||||
@ -183,8 +286,11 @@ __old hunk__
|
||||
line6
|
||||
...
|
||||
"""
|
||||
# if the file was deleted, return a message indicating that the file was deleted
|
||||
if hasattr(file, 'edit_type') and file.edit_type == EDIT_TYPE.DELETED:
|
||||
return f"\n\n## file '{file.filename.strip()}' was deleted\n"
|
||||
|
||||
patch_with_lines_str = f"\n\n## file: '{file.filename.strip()}'\n"
|
||||
patch_with_lines_str = f"\n\n## File: '{file.filename.strip()}'\n"
|
||||
patch_lines = patch.splitlines()
|
||||
RE_HUNK_HEADER = re.compile(
|
||||
r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@[ ]?(.*)")
|
||||
@ -194,21 +300,26 @@ __old hunk__
|
||||
start1, size1, start2, size2 = -1, -1, -1, -1
|
||||
prev_header_line = []
|
||||
header_line = []
|
||||
for line in patch_lines:
|
||||
for line_i, line in enumerate(patch_lines):
|
||||
if 'no newline at end of file' in line.lower():
|
||||
continue
|
||||
|
||||
if line.startswith('@@'):
|
||||
header_line = line
|
||||
match = RE_HUNK_HEADER.match(line)
|
||||
if match and new_content_lines: # found a new hunk, split the previous lines
|
||||
if new_content_lines:
|
||||
if match and (new_content_lines or old_content_lines): # found a new hunk, split the previous lines
|
||||
if prev_header_line:
|
||||
patch_with_lines_str += f'\n{prev_header_line}\n'
|
||||
is_plus_lines = is_minus_lines = False
|
||||
if new_content_lines:
|
||||
is_plus_lines = any([line.startswith('+') for line in new_content_lines])
|
||||
if old_content_lines:
|
||||
is_minus_lines = any([line.startswith('-') for line in old_content_lines])
|
||||
if is_plus_lines or is_minus_lines: # notice 'True' here - we always present __new hunk__ for section, otherwise LLM gets confused
|
||||
patch_with_lines_str = patch_with_lines_str.rstrip() + '\n__new hunk__\n'
|
||||
for i, line_new in enumerate(new_content_lines):
|
||||
patch_with_lines_str += f"{start2 + i} {line_new}\n"
|
||||
if old_content_lines:
|
||||
if is_minus_lines:
|
||||
patch_with_lines_str = patch_with_lines_str.rstrip() + '\n__old hunk__\n'
|
||||
for line_old in old_content_lines:
|
||||
patch_with_lines_str += f"{line_old}\n"
|
||||
@ -217,32 +328,34 @@ __old hunk__
|
||||
if match:
|
||||
prev_header_line = header_line
|
||||
|
||||
res = list(match.groups())
|
||||
for i in range(len(res)):
|
||||
if res[i] is None:
|
||||
res[i] = 0
|
||||
try:
|
||||
start1, size1, start2, size2 = map(int, res[:4])
|
||||
except: # '@@ -0,0 +1 @@' case
|
||||
start1, size1, size2 = map(int, res[:3])
|
||||
start2 = 0
|
||||
section_header, size1, size2, start1, start2 = extract_hunk_headers(match)
|
||||
|
||||
elif line.startswith('+'):
|
||||
new_content_lines.append(line)
|
||||
elif line.startswith('-'):
|
||||
old_content_lines.append(line)
|
||||
else:
|
||||
if not line and line_i: # if this line is empty and the next line is a hunk header, skip it
|
||||
if line_i + 1 < len(patch_lines) and patch_lines[line_i + 1].startswith('@@'):
|
||||
continue
|
||||
elif line_i + 1 == len(patch_lines):
|
||||
continue
|
||||
new_content_lines.append(line)
|
||||
old_content_lines.append(line)
|
||||
|
||||
# finishing last hunk
|
||||
if match and new_content_lines:
|
||||
if new_content_lines:
|
||||
patch_with_lines_str += f'\n{header_line}\n'
|
||||
is_plus_lines = is_minus_lines = False
|
||||
if new_content_lines:
|
||||
is_plus_lines = any([line.startswith('+') for line in new_content_lines])
|
||||
if old_content_lines:
|
||||
is_minus_lines = any([line.startswith('-') for line in old_content_lines])
|
||||
if is_plus_lines or is_minus_lines: # notice 'True' here - we always present __new hunk__ for section, otherwise LLM gets confused
|
||||
patch_with_lines_str = patch_with_lines_str.rstrip() + '\n__new hunk__\n'
|
||||
for i, line_new in enumerate(new_content_lines):
|
||||
patch_with_lines_str += f"{start2 + i} {line_new}\n"
|
||||
if old_content_lines:
|
||||
if is_minus_lines:
|
||||
patch_with_lines_str = patch_with_lines_str.rstrip() + '\n__old hunk__\n'
|
||||
for line_old in old_content_lines:
|
||||
patch_with_lines_str += f"{line_old}\n"
|
||||
@ -251,8 +364,8 @@ __old hunk__
|
||||
|
||||
|
||||
def extract_hunk_lines_from_patch(patch: str, file_name, line_start, line_end, side) -> tuple[str, str]:
|
||||
|
||||
patch_with_lines_str = f"\n\n## file: '{file_name.strip()}'\n\n"
|
||||
try:
|
||||
patch_with_lines_str = f"\n\n## File: '{file_name.strip()}'\n\n"
|
||||
selected_lines = ""
|
||||
patch_lines = patch.splitlines()
|
||||
RE_HUNK_HEADER = re.compile(
|
||||
@ -272,15 +385,7 @@ def extract_hunk_lines_from_patch(patch: str, file_name, line_start, line_end, s
|
||||
|
||||
match = RE_HUNK_HEADER.match(line)
|
||||
|
||||
res = list(match.groups())
|
||||
for i in range(len(res)):
|
||||
if res[i] is None:
|
||||
res[i] = 0
|
||||
try:
|
||||
start1, size1, start2, size2 = map(int, res[:4])
|
||||
except: # '@@ -0,0 +1 @@' case
|
||||
start1, size1, size2 = map(int, res[:3])
|
||||
start2 = 0
|
||||
section_header, size1, size2, start1, start2 = extract_hunk_headers(match)
|
||||
|
||||
# check if line range is in this hunk
|
||||
if side.lower() == 'left':
|
||||
@ -302,5 +407,8 @@ def extract_hunk_lines_from_patch(patch: str, file_name, line_start, line_end, s
|
||||
patch_with_lines_str += line + '\n'
|
||||
if not line.startswith('-'): # currently we don't support /ask line for deleted lines
|
||||
selected_lines_num += 1
|
||||
except Exception as e:
|
||||
get_logger().error(f"Failed to extract hunk lines from patch: {e}", artifact={"traceback": traceback.format_exc()})
|
||||
return "", ""
|
||||
|
||||
return patch_with_lines_str.rstrip(), selected_lines.rstrip()
|
@ -4,8 +4,6 @@ from typing import Dict
|
||||
from pr_agent.config_loader import get_settings
|
||||
|
||||
|
||||
|
||||
|
||||
def filter_bad_extensions(files):
|
||||
# Bad Extensions, source: https://github.com/EleutherAI/github-downloader/blob/345e7c4cbb9e0dc8a0615fd995a08bf9d73b3fe6/download_repo_text.py # noqa: E501
|
||||
bad_extensions = get_settings().bad_extensions.default
|
||||
@ -14,7 +12,9 @@ def filter_bad_extensions(files):
|
||||
return [f for f in files if f.filename is not None and is_valid_file(f.filename, bad_extensions)]
|
||||
|
||||
|
||||
def is_valid_file(filename, bad_extensions=None):
|
||||
def is_valid_file(filename:str, bad_extensions=None) -> bool:
|
||||
if not filename:
|
||||
return False
|
||||
if not bad_extensions:
|
||||
bad_extensions = get_settings().bad_extensions.default
|
||||
if get_settings().config.use_extra_bad_extensions:
|
||||
|
@ -5,14 +5,15 @@ from typing import Callable, List, Tuple
|
||||
|
||||
from github import RateLimitExceededException
|
||||
|
||||
from pr_agent.algo.git_patch_processing import convert_to_hunks_with_lines_numbers, extend_patch, handle_patch_deletions
|
||||
from pr_agent.algo.language_handler import sort_files_by_main_languages
|
||||
from pr_agent.algo.file_filter import filter_ignored
|
||||
from pr_agent.algo.git_patch_processing import (
|
||||
convert_to_hunks_with_lines_numbers, extend_patch, handle_patch_deletions)
|
||||
from pr_agent.algo.language_handler import sort_files_by_main_languages
|
||||
from pr_agent.algo.token_handler import TokenHandler
|
||||
from pr_agent.algo.utils import get_max_tokens, clip_tokens, ModelType
|
||||
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
|
||||
from pr_agent.algo.utils import ModelType, clip_tokens, get_max_tokens, get_weak_model
|
||||
from pr_agent.config_loader import get_settings
|
||||
from pr_agent.git_providers.git_provider import GitProvider
|
||||
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
|
||||
from pr_agent.log import get_logger
|
||||
|
||||
DELETED_FILES_ = "Deleted files:\n"
|
||||
@ -23,8 +24,15 @@ ADDED_FILES_ = "Additional added files (insufficient token budget to process):\n
|
||||
|
||||
OUTPUT_BUFFER_TOKENS_SOFT_THRESHOLD = 1500
|
||||
OUTPUT_BUFFER_TOKENS_HARD_THRESHOLD = 1000
|
||||
MAX_EXTRA_LINES = 10
|
||||
|
||||
|
||||
def cap_and_log_extra_lines(value, direction) -> int:
|
||||
if value > MAX_EXTRA_LINES:
|
||||
get_logger().warning(f"patch_extra_lines_{direction} was {value}, capping to {MAX_EXTRA_LINES}")
|
||||
return MAX_EXTRA_LINES
|
||||
return value
|
||||
|
||||
|
||||
def get_pr_diff(git_provider: GitProvider, token_handler: TokenHandler,
|
||||
model: str,
|
||||
@ -33,9 +41,13 @@ def get_pr_diff(git_provider: GitProvider, token_handler: TokenHandler,
|
||||
large_pr_handling=False,
|
||||
return_remaining_files=False):
|
||||
if disable_extra_lines:
|
||||
PATCH_EXTRA_LINES = 0
|
||||
PATCH_EXTRA_LINES_BEFORE = 0
|
||||
PATCH_EXTRA_LINES_AFTER = 0
|
||||
else:
|
||||
PATCH_EXTRA_LINES = get_settings().config.patch_extra_lines
|
||||
PATCH_EXTRA_LINES_BEFORE = get_settings().config.patch_extra_lines_before
|
||||
PATCH_EXTRA_LINES_AFTER = get_settings().config.patch_extra_lines_after
|
||||
PATCH_EXTRA_LINES_BEFORE = cap_and_log_extra_lines(PATCH_EXTRA_LINES_BEFORE, "before")
|
||||
PATCH_EXTRA_LINES_AFTER = cap_and_log_extra_lines(PATCH_EXTRA_LINES_AFTER, "after")
|
||||
|
||||
try:
|
||||
diff_files_original = git_provider.get_diff_files()
|
||||
@ -64,7 +76,8 @@ def get_pr_diff(git_provider: GitProvider, token_handler: TokenHandler,
|
||||
|
||||
# generate a standard diff string, with patch extension
|
||||
patches_extended, total_tokens, patches_extended_tokens = pr_generate_extended_diff(
|
||||
pr_languages, token_handler, add_line_numbers_to_hunks, patch_extra_lines=PATCH_EXTRA_LINES)
|
||||
pr_languages, token_handler, add_line_numbers_to_hunks,
|
||||
patch_extra_lines_before=PATCH_EXTRA_LINES_BEFORE, patch_extra_lines_after=PATCH_EXTRA_LINES_AFTER)
|
||||
|
||||
# if we are under the limit, return the full diff
|
||||
if total_tokens + OUTPUT_BUFFER_TOKENS_SOFT_THRESHOLD < get_max_tokens(model):
|
||||
@ -72,7 +85,7 @@ def get_pr_diff(git_provider: GitProvider, token_handler: TokenHandler,
|
||||
f"returning full diff.")
|
||||
return "\n".join(patches_extended)
|
||||
|
||||
# if we are over the limit, start pruning
|
||||
# if we are over the limit, start pruning (If we got here, we will not extend the patches with extra lines)
|
||||
get_logger().info(f"Tokens: {total_tokens}, total tokens over limit: {get_max_tokens(model)}, "
|
||||
f"pruning diff.")
|
||||
patches_compressed_list, total_tokens_list, deleted_files_list, remaining_files_list, file_dict, files_in_patches_list = \
|
||||
@ -80,7 +93,7 @@ def get_pr_diff(git_provider: GitProvider, token_handler: TokenHandler,
|
||||
|
||||
if large_pr_handling and len(patches_compressed_list) > 1:
|
||||
get_logger().info(f"Large PR handling mode, and found {len(patches_compressed_list)} patches with original diff.")
|
||||
return "" # return empty string, as we generate multiple patches with a different prompt
|
||||
return "" # return empty string, as we want to generate multiple patches with a different prompt
|
||||
|
||||
# return the first patch
|
||||
patches_compressed = patches_compressed_list[0]
|
||||
@ -105,7 +118,7 @@ def get_pr_diff(git_provider: GitProvider, token_handler: TokenHandler,
|
||||
added_list_str = ADDED_FILES_ + f"\n{filename}"
|
||||
else:
|
||||
added_list_str = added_list_str + f"\n{filename}"
|
||||
elif file_values['edit_type'] == EDIT_TYPE.MODIFIED or EDIT_TYPE.RENAMED:
|
||||
elif file_values['edit_type'] in [EDIT_TYPE.MODIFIED, EDIT_TYPE.RENAMED]:
|
||||
unprocessed_files.append(filename)
|
||||
if not modified_list_str:
|
||||
modified_list_str = MORE_MODIFIED_FILES_ + f"\n{filename}"
|
||||
@ -174,17 +187,8 @@ def get_pr_diff_multiple_patchs(git_provider: GitProvider, token_handler: TokenH
|
||||
def pr_generate_extended_diff(pr_languages: list,
|
||||
token_handler: TokenHandler,
|
||||
add_line_numbers_to_hunks: bool,
|
||||
patch_extra_lines: int = 0) -> Tuple[list, int, list]:
|
||||
"""
|
||||
Generate a standard diff string with patch extension, while counting the number of tokens used and applying diff
|
||||
minimization techniques if needed.
|
||||
|
||||
Args:
|
||||
- pr_languages: A list of dictionaries representing the languages used in the pull request and their corresponding
|
||||
files.
|
||||
- token_handler: An object of the TokenHandler class used for handling tokens in the context of the pull request.
|
||||
- add_line_numbers_to_hunks: A boolean indicating whether to add line numbers to the hunks in the diff.
|
||||
"""
|
||||
patch_extra_lines_before: int = 0,
|
||||
patch_extra_lines_after: int = 0) -> Tuple[list, int, list]:
|
||||
total_tokens = token_handler.prompt_tokens # initial tokens
|
||||
patches_extended = []
|
||||
patches_extended_tokens = []
|
||||
@ -196,14 +200,20 @@ def pr_generate_extended_diff(pr_languages: list,
|
||||
continue
|
||||
|
||||
# extend each patch with extra lines of context
|
||||
extended_patch = extend_patch(original_file_content_str, patch, num_lines=patch_extra_lines)
|
||||
extended_patch = extend_patch(original_file_content_str, patch,
|
||||
patch_extra_lines_before, patch_extra_lines_after, file.filename)
|
||||
if not extended_patch:
|
||||
get_logger().warning(f"Failed to extend patch for file: {file.filename}")
|
||||
continue
|
||||
full_extended_patch = f"\n\n## {file.filename}\n\n{extended_patch}\n"
|
||||
|
||||
if add_line_numbers_to_hunks:
|
||||
full_extended_patch = convert_to_hunks_with_lines_numbers(extended_patch, file)
|
||||
else:
|
||||
full_extended_patch = f"\n\n## File: '{file.filename.strip()}'\n{extended_patch.rstrip()}\n"
|
||||
|
||||
# add AI-summary metadata to the patch
|
||||
if file.ai_file_summary and get_settings().get("config.enable_ai_metadata", False):
|
||||
full_extended_patch = add_ai_summary_top_patch(file, full_extended_patch)
|
||||
|
||||
patch_tokens = token_handler.count_tokens(full_extended_patch)
|
||||
file.tokens = patch_tokens
|
||||
@ -244,6 +254,10 @@ def pr_generate_compressed_diff(top_langs: list, token_handler: TokenHandler, mo
|
||||
if convert_hunks_to_line_numbers:
|
||||
patch = convert_to_hunks_with_lines_numbers(patch, file)
|
||||
|
||||
## add AI-summary metadata to the patch (disabled, since we are in the compressed diff)
|
||||
# if file.ai_file_summary and get_settings().config.get('config.is_auto_command', False):
|
||||
# patch = add_ai_summary_top_patch(file, patch)
|
||||
|
||||
new_patch_tokens = token_handler.count_tokens(patch)
|
||||
file_dict[file.filename] = {'patch': patch, 'tokens': new_patch_tokens, 'edit_type': file.edit_type}
|
||||
|
||||
@ -303,13 +317,13 @@ def generate_full_patch(convert_hunks_to_line_numbers, file_dict, max_tokens_mod
|
||||
# TODO: Option for alternative logic to remove hunks from the patch to reduce the number of tokens
|
||||
# until we meet the requirements
|
||||
if get_settings().config.verbosity_level >= 2:
|
||||
get_logger().warning(f"Patch too large, skipping it, {filename}")
|
||||
get_logger().warning(f"Patch too large, skipping it: '{filename}'")
|
||||
remaining_files_list_new.append(filename)
|
||||
continue
|
||||
|
||||
if patch:
|
||||
if not convert_hunks_to_line_numbers:
|
||||
patch_final = f"\n\n## file: '{filename.strip()}\n\n{patch.strip()}\n'"
|
||||
patch_final = f"\n\n## File: '{filename.strip()}'\n\n{patch.strip()}\n"
|
||||
else:
|
||||
patch_final = "\n\n" + patch.strip()
|
||||
patches.append(patch_final)
|
||||
@ -335,16 +349,14 @@ async def retry_with_fallback_models(f: Callable, model_type: ModelType = ModelT
|
||||
except:
|
||||
get_logger().warning(
|
||||
f"Failed to generate prediction with {model}"
|
||||
f"{(' from deployment ' + deployment_id) if deployment_id else ''}: "
|
||||
f"{traceback.format_exc()}"
|
||||
)
|
||||
if i == len(all_models) - 1: # If it's the last iteration
|
||||
raise # Re-raise the last exception
|
||||
raise Exception(f"Failed to generate prediction with any model of {all_models}")
|
||||
|
||||
|
||||
def _get_all_models(model_type: ModelType = ModelType.REGULAR) -> List[str]:
|
||||
if model_type == ModelType.TURBO:
|
||||
model = get_settings().config.model_turbo
|
||||
if model_type == ModelType.WEAK:
|
||||
model = get_weak_model()
|
||||
else:
|
||||
model = get_settings().config.model
|
||||
fallback_models = get_settings().config.fallback_models
|
||||
@ -405,12 +417,21 @@ def get_pr_multi_diffs(git_provider: GitProvider,
|
||||
for lang in pr_languages:
|
||||
sorted_files.extend(sorted(lang['files'], key=lambda x: x.tokens, reverse=True))
|
||||
|
||||
# Get the maximum number of extra lines before and after the patch
|
||||
PATCH_EXTRA_LINES_BEFORE = get_settings().config.patch_extra_lines_before
|
||||
PATCH_EXTRA_LINES_AFTER = get_settings().config.patch_extra_lines_after
|
||||
PATCH_EXTRA_LINES_BEFORE = cap_and_log_extra_lines(PATCH_EXTRA_LINES_BEFORE, "before")
|
||||
PATCH_EXTRA_LINES_AFTER = cap_and_log_extra_lines(PATCH_EXTRA_LINES_AFTER, "after")
|
||||
|
||||
# try first a single run with standard diff string, with patch extension, and no deletions
|
||||
patches_extended, total_tokens, patches_extended_tokens = pr_generate_extended_diff(
|
||||
pr_languages, token_handler, add_line_numbers_to_hunks=True)
|
||||
pr_languages, token_handler, add_line_numbers_to_hunks=True,
|
||||
patch_extra_lines_before=PATCH_EXTRA_LINES_BEFORE,
|
||||
patch_extra_lines_after=PATCH_EXTRA_LINES_AFTER)
|
||||
|
||||
# if we are under the limit, return the full diff
|
||||
if total_tokens + OUTPUT_BUFFER_TOKENS_SOFT_THRESHOLD < get_max_tokens(model):
|
||||
return ["\n".join(patches_extended)]
|
||||
return ["\n".join(patches_extended)] if patches_extended else []
|
||||
|
||||
patches = []
|
||||
final_diff_list = []
|
||||
@ -434,6 +455,9 @@ def get_pr_multi_diffs(git_provider: GitProvider,
|
||||
continue
|
||||
|
||||
patch = convert_to_hunks_with_lines_numbers(patch, file)
|
||||
# add AI-summary metadata to the patch
|
||||
if file.ai_file_summary and get_settings().get("config.enable_ai_metadata", False):
|
||||
patch = add_ai_summary_top_patch(file, patch)
|
||||
new_patch_tokens = token_handler.count_tokens(patch)
|
||||
|
||||
if patch and (token_handler.prompt_tokens + new_patch_tokens) > get_max_tokens(
|
||||
@ -462,6 +486,10 @@ def get_pr_multi_diffs(git_provider: GitProvider,
|
||||
patches = []
|
||||
total_tokens = token_handler.prompt_tokens
|
||||
call_number += 1
|
||||
if call_number > max_calls: # avoid creating new patches
|
||||
if get_settings().config.verbosity_level >= 2:
|
||||
get_logger().info(f"Reached max calls ({max_calls})")
|
||||
break
|
||||
if get_settings().config.verbosity_level >= 2:
|
||||
get_logger().info(f"Call number: {call_number}")
|
||||
|
||||
@ -477,3 +505,46 @@ def get_pr_multi_diffs(git_provider: GitProvider,
|
||||
final_diff_list.append(final_diff)
|
||||
|
||||
return final_diff_list
|
||||
|
||||
|
||||
def add_ai_metadata_to_diff_files(git_provider, pr_description_files):
|
||||
"""
|
||||
Adds AI metadata to the diff files based on the PR description files (FilePatchInfo.ai_file_summary).
|
||||
"""
|
||||
try:
|
||||
if not pr_description_files:
|
||||
get_logger().warning(f"PR description files are empty.")
|
||||
return
|
||||
available_files = {pr_file['full_file_name'].strip(): pr_file for pr_file in pr_description_files}
|
||||
diff_files = git_provider.get_diff_files()
|
||||
found_any_match = False
|
||||
for file in diff_files:
|
||||
filename = file.filename.strip()
|
||||
if filename in available_files:
|
||||
file.ai_file_summary = available_files[filename]
|
||||
found_any_match = True
|
||||
if not found_any_match:
|
||||
get_logger().error(f"Failed to find any matching files between PR description and diff files.",
|
||||
artifact={"pr_description_files": pr_description_files})
|
||||
except Exception as e:
|
||||
get_logger().error(f"Failed to add AI metadata to diff files: {e}",
|
||||
artifact={"traceback": traceback.format_exc()})
|
||||
|
||||
|
||||
def add_ai_summary_top_patch(file, full_extended_patch):
|
||||
try:
|
||||
# below every instance of '## File: ...' in the patch, add the ai-summary metadata
|
||||
full_extended_patch_lines = full_extended_patch.split("\n")
|
||||
for i, line in enumerate(full_extended_patch_lines):
|
||||
if line.startswith("## File:") or line.startswith("## file:"):
|
||||
full_extended_patch_lines.insert(i + 1,
|
||||
f"### AI-generated changes summary:\n{file.ai_file_summary['long_summary']}")
|
||||
full_extended_patch = "\n".join(full_extended_patch_lines)
|
||||
return full_extended_patch
|
||||
|
||||
# if no '## File: ...' was found
|
||||
return full_extended_patch
|
||||
except Exception as e:
|
||||
get_logger().error(f"Failed to add AI summary to the top of the patch: {e}",
|
||||
artifact={"traceback": traceback.format_exc()})
|
||||
return full_extended_patch
|
||||
|
@ -1,7 +1,10 @@
|
||||
from threading import Lock
|
||||
|
||||
from jinja2 import Environment, StrictUndefined
|
||||
from tiktoken import encoding_for_model, get_encoding
|
||||
|
||||
from pr_agent.config_loader import get_settings
|
||||
from threading import Lock
|
||||
from pr_agent.log import get_logger
|
||||
|
||||
|
||||
class TokenEncoder:
|
||||
@ -62,12 +65,16 @@ class TokenHandler:
|
||||
Returns:
|
||||
The sum of the number of tokens in the system and user strings.
|
||||
"""
|
||||
try:
|
||||
environment = Environment(undefined=StrictUndefined)
|
||||
system_prompt = environment.from_string(system).render(vars)
|
||||
user_prompt = environment.from_string(user).render(vars)
|
||||
system_prompt_tokens = len(encoder.encode(system_prompt))
|
||||
user_prompt_tokens = len(encoder.encode(user_prompt))
|
||||
return system_prompt_tokens + user_prompt_tokens
|
||||
except Exception as e:
|
||||
get_logger().error(f"Error in _get_system_user_tokens: {e}")
|
||||
return 0
|
||||
|
||||
def count_tokens(self, patch: str) -> int:
|
||||
"""
|
||||
|
@ -1,5 +1,6 @@
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class EDIT_TYPE(Enum):
|
||||
@ -21,3 +22,5 @@ class FilePatchInfo:
|
||||
old_filename: str = None
|
||||
num_plus_lines: int = -1
|
||||
num_minus_lines: int = -1
|
||||
language: Optional[str] = None
|
||||
ai_file_summary: str = None
|
||||
|
@ -1,34 +1,60 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import difflib
|
||||
import hashlib
|
||||
import html
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import textwrap
|
||||
import time
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from importlib.metadata import PackageNotFoundError, version
|
||||
from typing import Any, List, Tuple
|
||||
|
||||
import html2text
|
||||
import requests
|
||||
import yaml
|
||||
from pydantic import BaseModel
|
||||
from starlette_context import context
|
||||
|
||||
from pr_agent.algo import MAX_TOKENS
|
||||
from pr_agent.algo.git_patch_processing import extract_hunk_lines_from_patch
|
||||
from pr_agent.algo.token_handler import TokenEncoder
|
||||
from pr_agent.config_loader import get_settings, global_settings
|
||||
from pr_agent.algo.types import FilePatchInfo
|
||||
from pr_agent.config_loader import get_settings, global_settings
|
||||
from pr_agent.log import get_logger
|
||||
|
||||
|
||||
def get_weak_model() -> str:
|
||||
if get_settings().get("config.model_weak"):
|
||||
return get_settings().config.model_weak
|
||||
return get_settings().config.model
|
||||
|
||||
|
||||
class Range(BaseModel):
|
||||
line_start: int # should be 0-indexed
|
||||
line_end: int
|
||||
column_start: int = -1
|
||||
column_end: int = -1
|
||||
|
||||
class ModelType(str, Enum):
|
||||
REGULAR = "regular"
|
||||
TURBO = "turbo"
|
||||
|
||||
WEAK = "weak"
|
||||
|
||||
class PRReviewHeader(str, Enum):
|
||||
REGULAR = "## PR Reviewer Guide"
|
||||
INCREMENTAL = "## Incremental PR Reviewer Guide"
|
||||
|
||||
|
||||
class PRDescriptionHeader(str, Enum):
|
||||
CHANGES_WALKTHROUGH = "### **Changes walkthrough** 📝"
|
||||
|
||||
|
||||
def get_setting(key: str) -> Any:
|
||||
try:
|
||||
key = key.upper()
|
||||
@ -37,7 +63,7 @@ def get_setting(key: str) -> Any:
|
||||
return global_settings.get(key, None)
|
||||
|
||||
|
||||
def emphasize_header(text: str, only_markdown=False) -> str:
|
||||
def emphasize_header(text: str, only_markdown=False, reference_link=None) -> str:
|
||||
try:
|
||||
# Finding the position of the first occurrence of ": "
|
||||
colon_position = text.find(": ")
|
||||
@ -46,7 +72,13 @@ def emphasize_header(text: str, only_markdown=False) -> str:
|
||||
if colon_position != -1:
|
||||
# Everything before the colon (inclusive) is wrapped in <strong> tags
|
||||
if only_markdown:
|
||||
if reference_link:
|
||||
transformed_string = f"[**{text[:colon_position + 1]}**]({reference_link})\n" + text[colon_position + 1:]
|
||||
else:
|
||||
transformed_string = f"**{text[:colon_position + 1]}**\n" + text[colon_position + 1:]
|
||||
else:
|
||||
if reference_link:
|
||||
transformed_string = f"<strong><a href='{reference_link}'>{text[:colon_position + 1]}</a></strong><br>" + text[colon_position + 1:]
|
||||
else:
|
||||
transformed_string = "<strong>" + text[:colon_position + 1] + "</strong>" +'<br>' + text[colon_position + 1:]
|
||||
else:
|
||||
@ -70,7 +102,11 @@ def unique_strings(input_list: List[str]) -> List[str]:
|
||||
seen.add(item)
|
||||
return unique_list
|
||||
|
||||
def convert_to_markdown_v2(output_data: dict, gfm_supported: bool = True, incremental_review=None) -> str:
|
||||
def convert_to_markdown_v2(output_data: dict,
|
||||
gfm_supported: bool = True,
|
||||
incremental_review=None,
|
||||
git_provider=None,
|
||||
files=None) -> str:
|
||||
"""
|
||||
Convert a dictionary of data into markdown format.
|
||||
Args:
|
||||
@ -81,8 +117,8 @@ def convert_to_markdown_v2(output_data: dict, gfm_supported: bool = True, increm
|
||||
|
||||
emojis = {
|
||||
"Can be split": "🔀",
|
||||
"Possible issues": "⚡",
|
||||
"Key issues to review": "⚡",
|
||||
"Recommended focus areas for review": "⚡",
|
||||
"Score": "🏅",
|
||||
"Relevant tests": "🧪",
|
||||
"Focused PR": "✨",
|
||||
@ -91,6 +127,7 @@ def convert_to_markdown_v2(output_data: dict, gfm_supported: bool = True, increm
|
||||
"Insights from user's answers": "📝",
|
||||
"Code feedback": "🤖",
|
||||
"Estimated effort to review [1-5]": "⏱️",
|
||||
"Ticket compliance check": "🎫",
|
||||
}
|
||||
markdown_text = ""
|
||||
if not incremental_review:
|
||||
@ -101,17 +138,21 @@ def convert_to_markdown_v2(output_data: dict, gfm_supported: bool = True, increm
|
||||
if not output_data or not output_data.get('review', {}):
|
||||
return ""
|
||||
|
||||
if get_settings().get("pr_reviewer.enable_intro_text", False):
|
||||
markdown_text += f"Here are some key observations to aid the review process:\n\n"
|
||||
|
||||
if gfm_supported:
|
||||
markdown_text += "<table>\n"
|
||||
|
||||
for key, value in output_data['review'].items():
|
||||
if value is None or value == '' or value == {} or value == []:
|
||||
if key.lower() != 'can_be_split':
|
||||
if key.lower() not in ['can_be_split', 'key_issues_to_review']:
|
||||
continue
|
||||
key_nice = key.replace('_', ' ').capitalize()
|
||||
emoji = emojis.get(key_nice, "")
|
||||
if 'Estimated effort to review' in key_nice:
|
||||
key_nice = 'Estimated effort to review'
|
||||
value = str(value).strip()
|
||||
if value.isnumeric():
|
||||
value_int = int(value)
|
||||
else:
|
||||
@ -129,7 +170,7 @@ def convert_to_markdown_v2(output_data: dict, gfm_supported: bool = True, increm
|
||||
else:
|
||||
markdown_text += f"### {emoji} {key_nice}: {value}\n\n"
|
||||
elif 'relevant tests' in key_nice.lower():
|
||||
value = value.strip().lower()
|
||||
value = str(value).strip().lower()
|
||||
if gfm_supported:
|
||||
markdown_text += f"<tr><td>"
|
||||
if is_value_no(value):
|
||||
@ -137,18 +178,13 @@ def convert_to_markdown_v2(output_data: dict, gfm_supported: bool = True, increm
|
||||
else:
|
||||
markdown_text += f"{emoji} <strong>PR contains tests</strong>"
|
||||
markdown_text += f"</td></tr>\n"
|
||||
else:
|
||||
if gfm_supported:
|
||||
markdown_text += f"<tr><td>"
|
||||
if is_value_no(value):
|
||||
markdown_text += f"{emoji} <strong>No relevant tests</strong>"
|
||||
else:
|
||||
markdown_text += f"{emoji} <strong>PR contains tests</strong>"
|
||||
else:
|
||||
if is_value_no(value):
|
||||
markdown_text += f'### {emoji} No relevant tests\n\n'
|
||||
else:
|
||||
markdown_text += f"### PR contains tests\n\n"
|
||||
markdown_text += f"### {emoji} PR contains tests\n\n"
|
||||
elif 'ticket compliance check' in key_nice.lower():
|
||||
markdown_text = ticket_markdown_logic(emoji, markdown_text, value, gfm_supported)
|
||||
elif 'security concerns' in key_nice.lower():
|
||||
if gfm_supported:
|
||||
markdown_text += f"<tr><td>"
|
||||
@ -164,7 +200,7 @@ def convert_to_markdown_v2(output_data: dict, gfm_supported: bool = True, increm
|
||||
markdown_text += f'### {emoji} No security concerns identified\n\n'
|
||||
else:
|
||||
markdown_text += f"### {emoji} Security concerns\n\n"
|
||||
value = emphasize_header(value.strip())
|
||||
value = emphasize_header(value.strip(), only_markdown=True)
|
||||
markdown_text += f"{value}\n\n"
|
||||
elif 'can be split' in key_nice.lower():
|
||||
if gfm_supported:
|
||||
@ -172,29 +208,56 @@ def convert_to_markdown_v2(output_data: dict, gfm_supported: bool = True, increm
|
||||
markdown_text += process_can_be_split(emoji, value)
|
||||
markdown_text += f"</td></tr>\n"
|
||||
elif 'key issues to review' in key_nice.lower():
|
||||
value = value.strip()
|
||||
# value is a list of issues
|
||||
if is_value_no(value):
|
||||
if gfm_supported:
|
||||
markdown_text += f"<tr><td>"
|
||||
markdown_text += f"{emoji} <strong>No key issues to review</strong>"
|
||||
markdown_text += f"{emoji} <strong>No major issues detected</strong>"
|
||||
markdown_text += f"</td></tr>\n"
|
||||
else:
|
||||
markdown_text += f"### {emoji} No key issues to review\n\n"
|
||||
markdown_text += f"### {emoji} No major issues detected\n\n"
|
||||
else:
|
||||
issues = value.split('\n- ')
|
||||
for i, _ in enumerate(issues):
|
||||
issues[i] = issues[i].strip().strip('-').strip()
|
||||
issues = unique_strings(issues) # remove duplicates
|
||||
issues = value
|
||||
if gfm_supported:
|
||||
markdown_text += f"<tr><td>"
|
||||
markdown_text += f"{emoji} <strong>{key_nice}</strong><br><br>\n\n"
|
||||
# markdown_text += f"{emoji} <strong>{key_nice}</strong><br><br>\n\n"
|
||||
markdown_text += f"{emoji} <strong>Recommended focus areas for review</strong><br><br>\n\n"
|
||||
else:
|
||||
markdown_text += f"### {emoji} Key issues to review:\n\n"
|
||||
markdown_text += f"### {emoji} Recommended focus areas for review\n\n#### \n"
|
||||
for i, issue in enumerate(issues):
|
||||
if not issue:
|
||||
try:
|
||||
if not issue or not isinstance(issue, dict):
|
||||
continue
|
||||
issue = emphasize_header(issue, only_markdown=True)
|
||||
markdown_text += f"{issue}\n\n"
|
||||
relevant_file = issue.get('relevant_file', '').strip()
|
||||
issue_header = issue.get('issue_header', '').strip()
|
||||
if issue_header.lower() == 'possible bug':
|
||||
issue_header = 'Possible Issue' # Make the header less frightening
|
||||
issue_content = issue.get('issue_content', '').strip()
|
||||
start_line = int(str(issue.get('start_line', 0)).strip())
|
||||
end_line = int(str(issue.get('end_line', 0)).strip())
|
||||
|
||||
relevant_lines_str = extract_relevant_lines_str(end_line, files, relevant_file, start_line, dedent=True)
|
||||
if git_provider:
|
||||
reference_link = git_provider.get_line_link(relevant_file, start_line, end_line)
|
||||
else:
|
||||
reference_link = None
|
||||
|
||||
if gfm_supported:
|
||||
if reference_link is not None and len(reference_link) > 0:
|
||||
if relevant_lines_str:
|
||||
issue_str = f"<details><summary><a href='{reference_link}'><strong>{issue_header}</strong></a>\n\n{issue_content}</summary>\n\n{relevant_lines_str}\n\n</details>"
|
||||
else:
|
||||
issue_str = f"<a href='{reference_link}'><strong>{issue_header}</strong></a><br>{issue_content}"
|
||||
else:
|
||||
issue_str = f"<strong>{issue_header}</strong><br>{issue_content}"
|
||||
else:
|
||||
if reference_link is not None and len(reference_link) > 0:
|
||||
issue_str = f"[**{issue_header}**]({reference_link})\n\n{issue_content}\n\n"
|
||||
else:
|
||||
issue_str = f"**{issue_header}**\n\n{issue_content}\n\n"
|
||||
markdown_text += f"{issue_str}\n\n"
|
||||
except Exception as e:
|
||||
get_logger().exception(f"Failed to process 'Recommended focus areas for review': {e}")
|
||||
if gfm_supported:
|
||||
markdown_text += f"</td></tr>\n"
|
||||
else:
|
||||
@ -208,21 +271,142 @@ def convert_to_markdown_v2(output_data: dict, gfm_supported: bool = True, increm
|
||||
if gfm_supported:
|
||||
markdown_text += "</table>\n"
|
||||
|
||||
if 'code_feedback' in output_data:
|
||||
if gfm_supported:
|
||||
markdown_text += f"\n\n"
|
||||
markdown_text += f"<details><summary> <strong>Code feedback:</strong></summary>\n\n"
|
||||
markdown_text += "<hr>"
|
||||
else:
|
||||
markdown_text += f"\n\n### Code feedback:\n\n"
|
||||
for i, value in enumerate(output_data['code_feedback']):
|
||||
if value is None or value == '' or value == {} or value == []:
|
||||
return markdown_text
|
||||
|
||||
|
||||
def extract_relevant_lines_str(end_line, files, relevant_file, start_line, dedent=False) -> str:
|
||||
"""
|
||||
Finds 'relevant_file' in 'files', and extracts the lines from 'start_line' to 'end_line' string from the file content.
|
||||
"""
|
||||
try:
|
||||
relevant_lines_str = ""
|
||||
if files:
|
||||
files = set_file_languages(files)
|
||||
for file in files:
|
||||
if file.filename.strip() == relevant_file:
|
||||
if not file.head_file:
|
||||
# as a fallback, extract relevant lines directly from patch
|
||||
patch = file.patch
|
||||
get_logger().info(f"No content found in file: '{file.filename}' for 'extract_relevant_lines_str'. Using patch instead")
|
||||
_, selected_lines = extract_hunk_lines_from_patch(patch, file.filename, start_line, end_line,side='right')
|
||||
if not selected_lines:
|
||||
get_logger().error(f"Failed to extract relevant lines from patch: {file.filename}")
|
||||
return ""
|
||||
# filter out '-' lines
|
||||
relevant_lines_str = ""
|
||||
for line in selected_lines.splitlines():
|
||||
if line.startswith('-'):
|
||||
continue
|
||||
markdown_text += parse_code_suggestion(value, i, gfm_supported)+"\n\n"
|
||||
if markdown_text.endswith('<hr>'):
|
||||
markdown_text= markdown_text[:-4]
|
||||
relevant_lines_str += line[1:] + '\n'
|
||||
else:
|
||||
relevant_file_lines = file.head_file.splitlines()
|
||||
relevant_lines_str = "\n".join(relevant_file_lines[start_line - 1:end_line])
|
||||
|
||||
if dedent and relevant_lines_str:
|
||||
# Remove the longest leading string of spaces and tabs common to all lines.
|
||||
relevant_lines_str = textwrap.dedent(relevant_lines_str)
|
||||
relevant_lines_str = f"```{file.language}\n{relevant_lines_str}\n```"
|
||||
break
|
||||
|
||||
return relevant_lines_str
|
||||
except Exception as e:
|
||||
get_logger().exception(f"Failed to extract relevant lines: {e}")
|
||||
return ""
|
||||
|
||||
|
||||
def ticket_markdown_logic(emoji, markdown_text, value, gfm_supported) -> str:
|
||||
ticket_compliance_str = ""
|
||||
compliance_emoji = ''
|
||||
# Track compliance levels across all tickets
|
||||
all_compliance_levels = []
|
||||
|
||||
if isinstance(value, list):
|
||||
for ticket_analysis in value:
|
||||
try:
|
||||
ticket_url = ticket_analysis.get('ticket_url', '').strip()
|
||||
explanation = ''
|
||||
ticket_compliance_level = '' # Individual ticket compliance
|
||||
fully_compliant_str = ticket_analysis.get('fully_compliant_requirements', '').strip()
|
||||
not_compliant_str = ticket_analysis.get('not_compliant_requirements', '').strip()
|
||||
requires_further_human_verification = ticket_analysis.get('requires_further_human_verification',
|
||||
'').strip()
|
||||
|
||||
if not fully_compliant_str and not not_compliant_str:
|
||||
get_logger().debug(f"Ticket compliance has no requirements",
|
||||
artifact={'ticket_url': ticket_url})
|
||||
continue
|
||||
|
||||
# Calculate individual ticket compliance level
|
||||
if fully_compliant_str:
|
||||
if not_compliant_str:
|
||||
ticket_compliance_level = 'Partially compliant'
|
||||
else:
|
||||
if not requires_further_human_verification:
|
||||
ticket_compliance_level = 'Fully compliant'
|
||||
else:
|
||||
ticket_compliance_level = 'PR Code Verified'
|
||||
elif not_compliant_str:
|
||||
ticket_compliance_level = 'Not compliant'
|
||||
|
||||
# Store the compliance level for aggregation
|
||||
if ticket_compliance_level:
|
||||
all_compliance_levels.append(ticket_compliance_level)
|
||||
|
||||
# build compliance string
|
||||
if fully_compliant_str:
|
||||
explanation += f"Compliant requirements:\n\n{fully_compliant_str}\n\n"
|
||||
if not_compliant_str:
|
||||
explanation += f"Non-compliant requirements:\n\n{not_compliant_str}\n\n"
|
||||
if requires_further_human_verification:
|
||||
explanation += f"Requires further human verification:\n\n{requires_further_human_verification}\n\n"
|
||||
ticket_compliance_str += f"\n\n**[{ticket_url.split('/')[-1]}]({ticket_url}) - {ticket_compliance_level}**\n\n{explanation}\n\n"
|
||||
|
||||
# for debugging
|
||||
if requires_further_human_verification:
|
||||
get_logger().debug(f"Ticket compliance requires further human verification",
|
||||
artifact={'ticket_url': ticket_url,
|
||||
'requires_further_human_verification': requires_further_human_verification,
|
||||
'compliance_level': ticket_compliance_level})
|
||||
|
||||
except Exception as e:
|
||||
get_logger().exception(f"Failed to process ticket compliance: {e}")
|
||||
continue
|
||||
|
||||
# Calculate overall compliance level and emoji
|
||||
if all_compliance_levels:
|
||||
if all(level == 'Fully compliant' for level in all_compliance_levels):
|
||||
compliance_level = 'Fully compliant'
|
||||
compliance_emoji = '✅'
|
||||
elif all(level == 'PR Code Verified' for level in all_compliance_levels):
|
||||
compliance_level = 'PR Code Verified'
|
||||
compliance_emoji = '✅'
|
||||
elif any(level == 'Not compliant' for level in all_compliance_levels):
|
||||
# If there's a mix of compliant and non-compliant tickets
|
||||
if any(level in ['Fully compliant', 'PR Code Verified'] for level in all_compliance_levels):
|
||||
compliance_level = 'Partially compliant'
|
||||
compliance_emoji = '🔶'
|
||||
else:
|
||||
compliance_level = 'Not compliant'
|
||||
compliance_emoji = '❌'
|
||||
elif any(level == 'Partially compliant' for level in all_compliance_levels):
|
||||
compliance_level = 'Partially compliant'
|
||||
compliance_emoji = '🔶'
|
||||
else:
|
||||
compliance_level = 'PR Code Verified'
|
||||
compliance_emoji = '✅'
|
||||
|
||||
# Set extra statistics outside the ticket loop
|
||||
get_settings().set('config.extra_statistics', {'compliance_level': compliance_level})
|
||||
|
||||
# editing table row for ticket compliance analysis
|
||||
if gfm_supported:
|
||||
markdown_text += f"</details>"
|
||||
markdown_text += f"<tr><td>\n\n"
|
||||
markdown_text += f"**{emoji} Ticket compliance analysis {compliance_emoji}**\n\n"
|
||||
markdown_text += ticket_compliance_str
|
||||
markdown_text += f"</td></tr>\n"
|
||||
else:
|
||||
markdown_text += f"### {emoji} Ticket compliance analysis {compliance_emoji}\n\n"
|
||||
markdown_text += ticket_compliance_str + "\n\n"
|
||||
|
||||
return markdown_text
|
||||
|
||||
@ -450,27 +634,22 @@ def load_large_diff(filename, new_file_content_str: str, original_file_content_s
|
||||
"""
|
||||
Generate a patch for a modified file by comparing the original content of the file with the new content provided as
|
||||
input.
|
||||
|
||||
Args:
|
||||
new_file_content_str: The new content of the file as a string.
|
||||
original_file_content_str: The original content of the file as a string.
|
||||
|
||||
Returns:
|
||||
The generated or provided patch string.
|
||||
|
||||
Raises:
|
||||
None.
|
||||
"""
|
||||
patch = ""
|
||||
if not original_file_content_str and not new_file_content_str:
|
||||
return ""
|
||||
|
||||
try:
|
||||
original_file_content_str = (original_file_content_str or "").rstrip() + "\n"
|
||||
new_file_content_str = (new_file_content_str or "").rstrip() + "\n"
|
||||
diff = difflib.unified_diff(original_file_content_str.splitlines(keepends=True),
|
||||
new_file_content_str.splitlines(keepends=True))
|
||||
if get_settings().config.verbosity_level >= 2 and show_warning:
|
||||
get_logger().warning(f"File was modified, but no patch was found. Manually creating patch: {filename}.")
|
||||
get_logger().info(f"File was modified, but no patch was found. Manually creating patch: {filename}.")
|
||||
patch = ''.join(diff)
|
||||
except Exception:
|
||||
pass
|
||||
return patch
|
||||
except Exception as e:
|
||||
get_logger().exception(f"Failed to generate patch for file: {filename}")
|
||||
return ""
|
||||
|
||||
|
||||
def update_settings_from_args(args: List[str]) -> List[str]:
|
||||
@ -520,15 +699,22 @@ def _fix_key_value(key: str, value: str):
|
||||
|
||||
|
||||
def load_yaml(response_text: str, keys_fix_yaml: List[str] = [], first_key="", last_key="") -> dict:
|
||||
response_text = response_text.removeprefix('```yaml').rstrip('`')
|
||||
response_text = response_text.strip('\n').removeprefix('```yaml').rstrip().removesuffix('```')
|
||||
try:
|
||||
data = yaml.safe_load(response_text)
|
||||
except Exception as e:
|
||||
get_logger().error(f"Failed to parse AI prediction: {e}")
|
||||
get_logger().warning(f"Initial failure to parse AI prediction: {e}")
|
||||
data = try_fix_yaml(response_text, keys_fix_yaml=keys_fix_yaml, first_key=first_key, last_key=last_key)
|
||||
if not data:
|
||||
get_logger().error(f"Failed to parse AI prediction after fallbacks",
|
||||
artifact={'response_text': response_text})
|
||||
else:
|
||||
get_logger().info(f"Successfully parsed AI prediction after fallbacks",
|
||||
artifact={'response_text': response_text})
|
||||
return data
|
||||
|
||||
|
||||
|
||||
def try_fix_yaml(response_text: str,
|
||||
keys_fix_yaml: List[str] = [],
|
||||
first_key="",
|
||||
@ -541,9 +727,9 @@ def try_fix_yaml(response_text: str,
|
||||
response_text_lines_copy = response_text_lines.copy()
|
||||
for i in range(0, len(response_text_lines_copy)):
|
||||
for key in keys_yaml:
|
||||
if key in response_text_lines_copy[i] and not '|-' in response_text_lines_copy[i]:
|
||||
if key in response_text_lines_copy[i] and not '|' in response_text_lines_copy[i]:
|
||||
response_text_lines_copy[i] = response_text_lines_copy[i].replace(f'{key}',
|
||||
f'{key} |-\n ')
|
||||
f'{key} |\n ')
|
||||
try:
|
||||
data = yaml.safe_load('\n'.join(response_text_lines_copy))
|
||||
get_logger().info(f"Successfully parsed AI prediction after adding |-\n")
|
||||
@ -551,14 +737,14 @@ def try_fix_yaml(response_text: str,
|
||||
except:
|
||||
get_logger().info(f"Failed to parse AI prediction after adding |-\n")
|
||||
|
||||
# second fallback - try to extract only range from first ```yaml to ```
|
||||
# second fallback - try to extract only range from first ```yaml to ````
|
||||
snippet_pattern = r'```(yaml)?[\s\S]*?```'
|
||||
snippet = re.search(snippet_pattern, '\n'.join(response_text_lines_copy))
|
||||
if snippet:
|
||||
snippet_text = snippet.group()
|
||||
try:
|
||||
data = yaml.safe_load(snippet_text.removeprefix('```yaml').rstrip('`'))
|
||||
get_logger().info(f"Successfully parsed AI prediction after extracting yaml snippet with second fallback")
|
||||
get_logger().info(f"Successfully parsed AI prediction after extracting yaml snippet")
|
||||
return data
|
||||
except:
|
||||
pass
|
||||
@ -573,6 +759,7 @@ def try_fix_yaml(response_text: str,
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
# forth fallback - try to extract yaml snippet by 'first_key' and 'last_key'
|
||||
# note that 'last_key' can be in practice a key that is not the last key in the yaml snippet.
|
||||
# it just needs to be some inner key, so we can look for newlines after it
|
||||
@ -635,14 +822,16 @@ def get_user_labels(current_labels: List[str] = None):
|
||||
Only keep labels that has been added by the user
|
||||
"""
|
||||
try:
|
||||
enable_custom_labels = get_settings().config.get('enable_custom_labels', False)
|
||||
custom_labels = get_settings().get('custom_labels', [])
|
||||
if current_labels is None:
|
||||
current_labels = []
|
||||
user_labels = []
|
||||
for label in current_labels:
|
||||
if label.lower() in ['bug fix', 'tests', 'enhancement', 'documentation', 'other']:
|
||||
continue
|
||||
if get_settings().config.enable_custom_labels:
|
||||
if label in get_settings().custom_labels:
|
||||
if enable_custom_labels:
|
||||
if label in custom_labels:
|
||||
continue
|
||||
user_labels.append(label)
|
||||
if user_labels:
|
||||
@ -654,15 +843,25 @@ def get_user_labels(current_labels: List[str] = None):
|
||||
|
||||
|
||||
def get_max_tokens(model):
|
||||
"""
|
||||
Get the maximum number of tokens allowed for a model.
|
||||
logic:
|
||||
(1) If the model is in './pr_agent/algo/__init__.py', use the value from there.
|
||||
(2) else, the user needs to define explicitly 'config.custom_model_max_tokens'
|
||||
|
||||
For both cases, we further limit the number of tokens to 'config.max_model_tokens' if it is set.
|
||||
This aims to improve the algorithmic quality, as the AI model degrades in performance when the input is too long.
|
||||
"""
|
||||
settings = get_settings()
|
||||
if model in MAX_TOKENS:
|
||||
max_tokens_model = MAX_TOKENS[model]
|
||||
elif settings.config.custom_model_max_tokens > 0:
|
||||
max_tokens_model = settings.config.custom_model_max_tokens
|
||||
else:
|
||||
raise Exception(f"MAX_TOKENS must be set for model {model} in ./pr_agent/algo/__init__.py")
|
||||
raise Exception(f"Ensure {model} is defined in MAX_TOKENS in ./pr_agent/algo/__init__.py or set a positive value for it in config.custom_model_max_tokens")
|
||||
|
||||
if settings.config.max_model_tokens:
|
||||
if settings.config.max_model_tokens and settings.config.max_model_tokens > 0:
|
||||
max_tokens_model = min(settings.config.max_model_tokens, max_tokens_model)
|
||||
# get_logger().debug(f"limiting max tokens to {max_tokens_model}")
|
||||
return max_tokens_model
|
||||
|
||||
|
||||
@ -714,6 +913,7 @@ def replace_code_tags(text):
|
||||
"""
|
||||
Replace odd instances of ` with <code> and even instances of ` with </code>
|
||||
"""
|
||||
text = html.escape(text)
|
||||
parts = text.split('`')
|
||||
for i in range(1, len(parts), 2):
|
||||
parts[i] = '<code>' + parts[i] + '</code>'
|
||||
@ -730,6 +930,9 @@ def find_line_number_of_relevant_line_in_file(diff_files: List[FilePatchInfo],
|
||||
re_hunk_header = re.compile(
|
||||
r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@[ ]?(.*)")
|
||||
|
||||
if not diff_files:
|
||||
return position, absolute_position
|
||||
|
||||
for file in diff_files:
|
||||
if file.filename and (file.filename.strip() == relevant_file):
|
||||
patch = file.patch
|
||||
@ -791,56 +994,64 @@ def find_line_number_of_relevant_line_in_file(diff_files: List[FilePatchInfo],
|
||||
break
|
||||
return position, absolute_position
|
||||
|
||||
def validate_and_await_rate_limit(rate_limit_status=None, git_provider=None, get_rate_limit_status_func=None):
|
||||
if git_provider and not rate_limit_status:
|
||||
rate_limit_status = {'resources': git_provider.github_client.get_rate_limit().raw_data}
|
||||
def get_rate_limit_status(github_token) -> dict:
|
||||
GITHUB_API_URL = get_settings(use_context=False).get("GITHUB.BASE_URL", "https://api.github.com").rstrip("/") # "https://api.github.com"
|
||||
# GITHUB_API_URL = "https://api.github.com"
|
||||
RATE_LIMIT_URL = f"{GITHUB_API_URL}/rate_limit"
|
||||
HEADERS = {
|
||||
"Accept": "application/vnd.github.v3+json",
|
||||
"Authorization": f"token {github_token}"
|
||||
}
|
||||
|
||||
if not rate_limit_status:
|
||||
rate_limit_status = get_rate_limit_status_func()
|
||||
response = requests.get(RATE_LIMIT_URL, headers=HEADERS)
|
||||
try:
|
||||
rate_limit_info = response.json()
|
||||
if rate_limit_info.get('message') == 'Rate limiting is not enabled.': # for github enterprise
|
||||
return {'resources': {}}
|
||||
response.raise_for_status() # Check for HTTP errors
|
||||
except: # retry
|
||||
time.sleep(0.1)
|
||||
response = requests.get(RATE_LIMIT_URL, headers=HEADERS)
|
||||
return response.json()
|
||||
return rate_limit_info
|
||||
|
||||
|
||||
def validate_rate_limit_github(github_token, installation_id=None, threshold=0.1) -> bool:
|
||||
try:
|
||||
rate_limit_status = get_rate_limit_status(github_token)
|
||||
if installation_id:
|
||||
get_logger().debug(f"installation_id: {installation_id}, Rate limit status: {rate_limit_status['rate']}")
|
||||
# validate that the rate limit is not exceeded
|
||||
# validate that the rate limit is not exceeded
|
||||
is_rate_limit = False
|
||||
for key, value in rate_limit_status['resources'].items():
|
||||
if value['remaining'] == 0:
|
||||
print(f"key: {key}, value: {value}")
|
||||
is_rate_limit = True
|
||||
if value['remaining'] < value['limit'] * threshold:
|
||||
get_logger().error(f"key: {key}, value: {value}")
|
||||
return False
|
||||
return True
|
||||
except Exception as e:
|
||||
get_logger().error(f"Error in rate limit {e}",
|
||||
artifact={"traceback": traceback.format_exc()})
|
||||
return True
|
||||
|
||||
|
||||
def validate_and_await_rate_limit(github_token):
|
||||
try:
|
||||
rate_limit_status = get_rate_limit_status(github_token)
|
||||
# validate that the rate limit is not exceeded
|
||||
for key, value in rate_limit_status['resources'].items():
|
||||
if value['remaining'] < value['limit'] // 80:
|
||||
get_logger().error(f"key: {key}, value: {value}")
|
||||
sleep_time_sec = value['reset'] - datetime.now().timestamp()
|
||||
sleep_time_hour = sleep_time_sec / 3600.0
|
||||
print(f"Rate limit exceeded. Sleeping for {sleep_time_hour} hours")
|
||||
get_logger().error(f"Rate limit exceeded. Sleeping for {sleep_time_hour} hours")
|
||||
if sleep_time_sec > 0:
|
||||
time.sleep(sleep_time_sec + 1)
|
||||
|
||||
if git_provider:
|
||||
rate_limit_status = {'resources': git_provider.github_client.get_rate_limit().raw_data}
|
||||
else:
|
||||
rate_limit_status = get_rate_limit_status_func()
|
||||
|
||||
return is_rate_limit
|
||||
|
||||
|
||||
def get_largest_component(pr_url):
|
||||
from pr_agent.tools.pr_analyzer import PRAnalyzer
|
||||
publish_output = get_settings().config.publish_output
|
||||
get_settings().config.publish_output = False # disable publish output
|
||||
analyzer = PRAnalyzer(pr_url)
|
||||
methods_dict_files = analyzer.run_sync()
|
||||
get_settings().config.publish_output = publish_output
|
||||
max_lines_changed = 0
|
||||
file_b = ""
|
||||
component_name_b = ""
|
||||
for file in methods_dict_files:
|
||||
for method in methods_dict_files[file]:
|
||||
try:
|
||||
if methods_dict_files[file][method]['num_plus_lines'] > max_lines_changed:
|
||||
max_lines_changed = methods_dict_files[file][method]['num_plus_lines']
|
||||
file_b = file
|
||||
component_name_b = method
|
||||
rate_limit_status = get_rate_limit_status(github_token)
|
||||
return rate_limit_status
|
||||
except:
|
||||
pass
|
||||
if component_name_b:
|
||||
get_logger().info(f"Using the largest changed component: '{component_name_b}'")
|
||||
return component_name_b, file_b
|
||||
else:
|
||||
return None, None
|
||||
get_logger().error("Error in rate limit")
|
||||
return None
|
||||
|
||||
|
||||
def github_action_output(output_data: dict, key_name: str):
|
||||
try:
|
||||
@ -856,21 +1067,24 @@ def github_action_output(output_data: dict, key_name: str):
|
||||
|
||||
|
||||
def show_relevant_configurations(relevant_section: str) -> str:
|
||||
forbidden_keys = ['ai_disclaimer', 'ai_disclaimer_title', 'ANALYTICS_FOLDER', 'secret_provider',
|
||||
skip_keys = ['ai_disclaimer', 'ai_disclaimer_title', 'ANALYTICS_FOLDER', 'secret_provider', "skip_keys", "app_id", "redirect",
|
||||
'trial_prefix_message', 'no_eligible_message', 'identity_provider', 'ALLOWED_REPOS','APP_NAME']
|
||||
extra_skip_keys = get_settings().config.get('config.skip_keys', [])
|
||||
if extra_skip_keys:
|
||||
skip_keys.extend(extra_skip_keys)
|
||||
|
||||
markdown_text = ""
|
||||
markdown_text += "\n<hr>\n<details> <summary><strong>🛠️ Relevant configurations:</strong></summary> \n\n"
|
||||
markdown_text +="<br>These are the relevant [configurations](https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml) for this tool:\n\n"
|
||||
markdown_text += f"**[config**]\n```yaml\n\n"
|
||||
for key, value in get_settings().config.items():
|
||||
if key in forbidden_keys:
|
||||
if key in skip_keys:
|
||||
continue
|
||||
markdown_text += f"{key}: {value}\n"
|
||||
markdown_text += "\n```\n"
|
||||
markdown_text += f"\n**[{relevant_section}]**\n```yaml\n\n"
|
||||
for key, value in get_settings().get(relevant_section, {}).items():
|
||||
if key in forbidden_keys:
|
||||
if key in skip_keys:
|
||||
continue
|
||||
markdown_text += f"{key}: {value}\n"
|
||||
markdown_text += "\n```"
|
||||
@ -878,9 +1092,148 @@ def show_relevant_configurations(relevant_section: str) -> str:
|
||||
return markdown_text
|
||||
|
||||
def is_value_no(value):
|
||||
if value is None:
|
||||
if not value:
|
||||
return True
|
||||
value_str = str(value).strip().lower()
|
||||
if value_str == 'no' or value_str == 'none' or value_str == 'false':
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def set_pr_string(repo_name, pr_number):
|
||||
return f"{repo_name}#{pr_number}"
|
||||
|
||||
|
||||
def string_to_uniform_number(s: str) -> float:
|
||||
"""
|
||||
Convert a string to a uniform number in the range [0, 1].
|
||||
The uniform distribution is achieved by the nature of the SHA-256 hash function, which produces a uniformly distributed hash value over its output space.
|
||||
"""
|
||||
# Generate a hash of the string
|
||||
hash_object = hashlib.sha256(s.encode())
|
||||
# Convert the hash to an integer
|
||||
hash_int = int(hash_object.hexdigest(), 16)
|
||||
# Normalize the integer to the range [0, 1]
|
||||
max_hash_int = 2 ** 256 - 1
|
||||
uniform_number = float(hash_int) / max_hash_int
|
||||
return uniform_number
|
||||
|
||||
|
||||
def process_description(description_full: str) -> Tuple[str, List]:
|
||||
if not description_full:
|
||||
return "", []
|
||||
|
||||
description_split = description_full.split(PRDescriptionHeader.CHANGES_WALKTHROUGH.value)
|
||||
base_description_str = description_split[0]
|
||||
changes_walkthrough_str = ""
|
||||
files = []
|
||||
if len(description_split) > 1:
|
||||
changes_walkthrough_str = description_split[1]
|
||||
else:
|
||||
get_logger().debug("No changes walkthrough found")
|
||||
|
||||
try:
|
||||
if changes_walkthrough_str:
|
||||
# get the end of the table
|
||||
if '</table>\n\n___' in changes_walkthrough_str:
|
||||
end = changes_walkthrough_str.index("</table>\n\n___")
|
||||
elif '\n___' in changes_walkthrough_str:
|
||||
end = changes_walkthrough_str.index("\n___")
|
||||
else:
|
||||
end = len(changes_walkthrough_str)
|
||||
changes_walkthrough_str = changes_walkthrough_str[:end]
|
||||
|
||||
h = html2text.HTML2Text()
|
||||
h.body_width = 0 # Disable line wrapping
|
||||
|
||||
# find all the files
|
||||
pattern = r'<tr>\s*<td>\s*(<details>\s*<summary>(.*?)</summary>(.*?)</details>)\s*</td>'
|
||||
files_found = re.findall(pattern, changes_walkthrough_str, re.DOTALL)
|
||||
for file_data in files_found:
|
||||
try:
|
||||
if isinstance(file_data, tuple):
|
||||
file_data = file_data[0]
|
||||
pattern = r'<details>\s*<summary><strong>(.*?)</strong>\s*<dd><code>(.*?)</code>.*?</summary>\s*<hr>\s*(.*?)\s*<li>(.*?)</details>'
|
||||
res = re.search(pattern, file_data, re.DOTALL)
|
||||
if not res or res.lastindex != 4:
|
||||
pattern_back = r'<details>\s*<summary><strong>(.*?)</strong><dd><code>(.*?)</code>.*?</summary>\s*<hr>\s*(.*?)\n\n\s*(.*?)</details>'
|
||||
res = re.search(pattern_back, file_data, re.DOTALL)
|
||||
if not res or res.lastindex != 4:
|
||||
pattern_back = r'<details>\s*<summary><strong>(.*?)</strong>\s*<dd><code>(.*?)</code>.*?</summary>\s*<hr>\s*(.*?)\s*-\s*(.*?)\s*</details>' # looking for hypen ('- ')
|
||||
res = re.search(pattern_back, file_data, re.DOTALL)
|
||||
if res and res.lastindex == 4:
|
||||
short_filename = res.group(1).strip()
|
||||
short_summary = res.group(2).strip()
|
||||
long_filename = res.group(3).strip()
|
||||
long_summary = res.group(4).strip()
|
||||
long_summary = long_summary.replace('<br> *', '\n*').replace('<br>','').replace('\n','<br>')
|
||||
long_summary = h.handle(long_summary).strip()
|
||||
if long_summary.startswith('\\-'):
|
||||
long_summary = "* " + long_summary[2:]
|
||||
elif not long_summary.startswith('*'):
|
||||
long_summary = f"* {long_summary}"
|
||||
|
||||
files.append({
|
||||
'short_file_name': short_filename,
|
||||
'full_file_name': long_filename,
|
||||
'short_summary': short_summary,
|
||||
'long_summary': long_summary
|
||||
})
|
||||
else:
|
||||
if '<code>...</code>' in file_data:
|
||||
pass # PR with many files. some did not get analyzed
|
||||
else:
|
||||
get_logger().error(f"Failed to parse description", artifact={'description': file_data})
|
||||
except Exception as e:
|
||||
get_logger().exception(f"Failed to process description: {e}", artifact={'description': file_data})
|
||||
|
||||
|
||||
except Exception as e:
|
||||
get_logger().exception(f"Failed to process description: {e}")
|
||||
|
||||
return base_description_str, files
|
||||
|
||||
def get_version() -> str:
|
||||
# First check pyproject.toml if running directly out of repository
|
||||
if os.path.exists("pyproject.toml"):
|
||||
if sys.version_info >= (3, 11):
|
||||
import tomllib
|
||||
with open("pyproject.toml", "rb") as f:
|
||||
data = tomllib.load(f)
|
||||
if "project" in data and "version" in data["project"]:
|
||||
return data["project"]["version"]
|
||||
else:
|
||||
get_logger().warning("Version not found in pyproject.toml")
|
||||
else:
|
||||
get_logger().warning("Unable to determine local version from pyproject.toml")
|
||||
|
||||
# Otherwise get the installed pip package version
|
||||
try:
|
||||
return version('pr-agent')
|
||||
except PackageNotFoundError:
|
||||
get_logger().warning("Unable to find package named 'pr-agent'")
|
||||
return "unknown"
|
||||
|
||||
|
||||
def set_file_languages(diff_files) -> List[FilePatchInfo]:
|
||||
try:
|
||||
# if the language is already set, do not change it
|
||||
if hasattr(diff_files[0], 'language') and diff_files[0].language:
|
||||
return diff_files
|
||||
|
||||
# map file extensions to programming languages
|
||||
language_extension_map_org = get_settings().language_extension_map_org
|
||||
extension_to_language = {}
|
||||
for language, extensions in language_extension_map_org.items():
|
||||
for ext in extensions:
|
||||
extension_to_language[ext] = language
|
||||
for file in diff_files:
|
||||
extension_s = '.' + file.filename.rsplit('.')[-1]
|
||||
language_name = "txt"
|
||||
if extension_s and (extension_s in extension_to_language):
|
||||
language_name = extension_to_language[extension_s]
|
||||
file.language = language_name.lower()
|
||||
except Exception as e:
|
||||
get_logger().exception(f"Failed to set file languages: {e}")
|
||||
|
||||
return diff_files
|
||||
|
@ -3,8 +3,9 @@ import asyncio
|
||||
import os
|
||||
|
||||
from pr_agent.agent.pr_agent import PRAgent, commands
|
||||
from pr_agent.algo.utils import get_version
|
||||
from pr_agent.config_loader import get_settings
|
||||
from pr_agent.log import setup_logger
|
||||
from pr_agent.log import get_logger, setup_logger
|
||||
|
||||
log_level = os.environ.get("LOG_LEVEL", "INFO")
|
||||
setup_logger(log_level)
|
||||
@ -45,6 +46,7 @@ def set_parser():
|
||||
To edit any configuration parameter from 'configuration.toml', just add -config_path=<value>.
|
||||
For example: 'python cli.py --pr_url=... review --pr_reviewer.extra_instructions="focus on the file: ..."'
|
||||
""")
|
||||
parser.add_argument('--version', action='version', version=f'pr-agent {get_version()}')
|
||||
parser.add_argument('--pr_url', type=str, help='The URL of the PR to review', default=None)
|
||||
parser.add_argument('--issue_url', type=str, help='The URL of the Issue to review', default=None)
|
||||
parser.add_argument('command', type=str, help='The', choices=commands, default='review')
|
||||
@ -71,10 +73,21 @@ def run(inargs=None, args=None):
|
||||
|
||||
command = args.command.lower()
|
||||
get_settings().set("CONFIG.CLI_MODE", True)
|
||||
|
||||
async def inner():
|
||||
if args.issue_url:
|
||||
result = asyncio.run(PRAgent().handle_request(args.issue_url, [command] + args.rest))
|
||||
result = await asyncio.create_task(PRAgent().handle_request(args.issue_url, [command] + args.rest))
|
||||
else:
|
||||
result = asyncio.run(PRAgent().handle_request(args.pr_url, [command] + args.rest))
|
||||
result = await asyncio.create_task(PRAgent().handle_request(args.pr_url, [command] + args.rest))
|
||||
|
||||
if get_settings().litellm.get("enable_callbacks", False):
|
||||
# There may be additional events on the event queue from the run above. If there are give them time to complete.
|
||||
get_logger().debug("Waiting for event queue to complete")
|
||||
await asyncio.wait([task for task in asyncio.all_tasks() if task is not asyncio.current_task()])
|
||||
|
||||
return result
|
||||
|
||||
result = asyncio.run(inner())
|
||||
if not result:
|
||||
parser.print_help()
|
||||
|
||||
|
@ -12,7 +12,6 @@ global_settings = Dynaconf(
|
||||
envvar_prefix=False,
|
||||
merge_enabled=True,
|
||||
settings_files=[join(current_dir, f) for f in [
|
||||
"settings/.secrets.toml",
|
||||
"settings/configuration.toml",
|
||||
"settings/ignore.toml",
|
||||
"settings/language_extensions.toml",
|
||||
@ -27,8 +26,10 @@ global_settings = Dynaconf(
|
||||
"settings/pr_update_changelog_prompts.toml",
|
||||
"settings/pr_custom_labels.toml",
|
||||
"settings/pr_add_docs.toml",
|
||||
"settings/custom_labels.toml",
|
||||
"settings/pr_help_prompts.toml",
|
||||
"settings/.secrets.toml",
|
||||
"settings_prod/.secrets.toml",
|
||||
"settings/custom_labels.toml"
|
||||
]]
|
||||
)
|
||||
|
||||
|
@ -1,14 +1,16 @@
|
||||
from starlette_context import context
|
||||
|
||||
from pr_agent.config_loader import get_settings
|
||||
from pr_agent.git_providers.azuredevops_provider import AzureDevopsProvider
|
||||
from pr_agent.git_providers.bitbucket_provider import BitbucketProvider
|
||||
from pr_agent.git_providers.bitbucket_server_provider import BitbucketServerProvider
|
||||
from pr_agent.git_providers.bitbucket_server_provider import \
|
||||
BitbucketServerProvider
|
||||
from pr_agent.git_providers.codecommit_provider import CodeCommitProvider
|
||||
from pr_agent.git_providers.gerrit_provider import GerritProvider
|
||||
from pr_agent.git_providers.git_provider import GitProvider
|
||||
from pr_agent.git_providers.github_provider import GithubProvider
|
||||
from pr_agent.git_providers.gitlab_provider import GitLabProvider
|
||||
from pr_agent.git_providers.local_git_provider import LocalGitProvider
|
||||
from pr_agent.git_providers.azuredevops_provider import AzureDevopsProvider
|
||||
from pr_agent.git_providers.gerrit_provider import GerritProvider
|
||||
from starlette_context import context
|
||||
|
||||
_GIT_PROVIDERS = {
|
||||
'github': GithubProvider,
|
||||
|
@ -2,33 +2,33 @@ import os
|
||||
from typing import Optional, Tuple
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from ..algo.file_filter import filter_ignored
|
||||
from ..log import get_logger
|
||||
from ..algo.language_handler import is_valid_file
|
||||
from ..algo.utils import clip_tokens, find_line_number_of_relevant_line_in_file, load_large_diff
|
||||
from ..config_loader import get_settings
|
||||
from .git_provider import GitProvider
|
||||
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
|
||||
|
||||
from ..algo.file_filter import filter_ignored
|
||||
from ..algo.language_handler import is_valid_file
|
||||
from ..algo.utils import (PRDescriptionHeader, clip_tokens,
|
||||
find_line_number_of_relevant_line_in_file,
|
||||
load_large_diff)
|
||||
from ..config_loader import get_settings
|
||||
from ..log import get_logger
|
||||
from .git_provider import GitProvider
|
||||
|
||||
AZURE_DEVOPS_AVAILABLE = True
|
||||
ADO_APP_CLIENT_DEFAULT_ID = "499b84ac-1321-427f-aa17-267ca6975798/.default"
|
||||
MAX_PR_DESCRIPTION_AZURE_LENGTH = 4000-1
|
||||
|
||||
try:
|
||||
# noinspection PyUnresolvedReferences
|
||||
from msrest.authentication import BasicAuthentication
|
||||
# noinspection PyUnresolvedReferences
|
||||
from azure.devops.connection import Connection
|
||||
# noinspection PyUnresolvedReferences
|
||||
from azure.identity import DefaultAzureCredential
|
||||
# noinspection PyUnresolvedReferences
|
||||
from azure.devops.v7_1.git.models import (
|
||||
Comment,
|
||||
CommentThread,
|
||||
GitVersionDescriptor,
|
||||
from azure.devops.v7_1.git.models import (Comment, CommentThread,
|
||||
GitPullRequest,
|
||||
GitPullRequestIterationChanges,
|
||||
)
|
||||
GitVersionDescriptor)
|
||||
# noinspection PyUnresolvedReferences
|
||||
from azure.identity import DefaultAzureCredential
|
||||
from msrest.authentication import BasicAuthentication
|
||||
except ImportError:
|
||||
AZURE_DEVOPS_AVAILABLE = False
|
||||
|
||||
@ -67,14 +67,12 @@ class AzureDevopsProvider(GitProvider):
|
||||
relevant_lines_end = suggestion['relevant_lines_end']
|
||||
|
||||
if not relevant_lines_start or relevant_lines_start == -1:
|
||||
if get_settings().config.verbosity_level >= 2:
|
||||
get_logger().exception(
|
||||
get_logger().warning(
|
||||
f"Failed to publish code suggestion, relevant_lines_start is {relevant_lines_start}")
|
||||
continue
|
||||
|
||||
if relevant_lines_end < relevant_lines_start:
|
||||
if get_settings().config.verbosity_level >= 2:
|
||||
get_logger().exception(f"Failed to publish code suggestion, "
|
||||
get_logger().warning(f"Failed to publish code suggestion, "
|
||||
f"relevant_lines_end is {relevant_lines_end} and "
|
||||
f"relevant_lines_start is {relevant_lines_start}")
|
||||
continue
|
||||
@ -95,9 +93,11 @@ class AzureDevopsProvider(GitProvider):
|
||||
"side": "RIGHT",
|
||||
}
|
||||
post_parameters_list.append(post_parameters)
|
||||
if not post_parameters_list:
|
||||
return False
|
||||
|
||||
try:
|
||||
for post_parameters in post_parameters_list:
|
||||
try:
|
||||
comment = Comment(content=post_parameters["body"], comment_type=1)
|
||||
thread = CommentThread(comments=[comment],
|
||||
thread_context={
|
||||
@ -117,15 +117,11 @@ class AzureDevopsProvider(GitProvider):
|
||||
repository_id=self.repo_slug,
|
||||
pull_request_id=self.pr_num
|
||||
)
|
||||
if get_settings().config.verbosity_level >= 2:
|
||||
get_logger().info(
|
||||
f"Published code suggestion on {self.pr_num} at {post_parameters['path']}"
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
if get_settings().config.verbosity_level >= 2:
|
||||
get_logger().error(f"Failed to publish code suggestion, error: {e}")
|
||||
return False
|
||||
get_logger().warning(f"Azure failed to publish code suggestion, error: {e}")
|
||||
return True
|
||||
|
||||
|
||||
|
||||
def get_pr_description_full(self) -> str:
|
||||
return self.pr.description
|
||||
@ -165,7 +161,7 @@ class AzureDevopsProvider(GitProvider):
|
||||
pull_request_id=self.pr_num,
|
||||
)
|
||||
except Exception as e:
|
||||
get_logger().exception(f"Failed to publish labels, error: {e}")
|
||||
get_logger().warning(f"Failed to publish labels, error: {e}")
|
||||
|
||||
def get_pr_labels(self, update=False):
|
||||
try:
|
||||
@ -316,7 +312,7 @@ class AzureDevopsProvider(GitProvider):
|
||||
|
||||
new_file_content_str = new_file_content_str.content
|
||||
except Exception as error:
|
||||
get_logger().error(f"Failed to retrieve new file content of {file} at version {version}. Error: {str(error)}")
|
||||
get_logger().error(f"Failed to retrieve new file content of {file} at version {version}", error=error)
|
||||
# get_logger().error(
|
||||
# "Failed to retrieve new file content of %s at version %s. Error: %s",
|
||||
# file,
|
||||
@ -330,12 +326,15 @@ class AzureDevopsProvider(GitProvider):
|
||||
edit_type = EDIT_TYPE.ADDED
|
||||
elif diff_types[file] == "delete":
|
||||
edit_type = EDIT_TYPE.DELETED
|
||||
elif diff_types[file] == "rename":
|
||||
elif "rename" in diff_types[file]: # diff_type can be `rename` | `edit, rename`
|
||||
edit_type = EDIT_TYPE.RENAMED
|
||||
|
||||
version = GitVersionDescriptor(
|
||||
version=base_sha.commit_id, version_type="commit"
|
||||
)
|
||||
if edit_type == EDIT_TYPE.ADDED or edit_type == EDIT_TYPE.RENAMED:
|
||||
original_file_content_str = ""
|
||||
else:
|
||||
try:
|
||||
original_file_content_str = self.azure_devops_client.get_item(
|
||||
repository_id=self.repo_slug,
|
||||
@ -347,7 +346,7 @@ class AzureDevopsProvider(GitProvider):
|
||||
)
|
||||
original_file_content_str = original_file_content_str.content
|
||||
except Exception as error:
|
||||
get_logger().error(f"Failed to retrieve original file content of {file} at version {version}. Error: {str(error)}")
|
||||
get_logger().error(f"Failed to retrieve original file content of {file} at version {version}", error=error)
|
||||
original_file_content_str = ""
|
||||
|
||||
patch = load_large_diff(
|
||||
@ -375,12 +374,15 @@ class AzureDevopsProvider(GitProvider):
|
||||
self.diff_files = diff_files
|
||||
return diff_files
|
||||
except Exception as e:
|
||||
print(f"Error: {str(e)}")
|
||||
get_logger().exception(f"Failed to get diff files, error: {e}")
|
||||
return []
|
||||
|
||||
def publish_comment(self, pr_comment: str, is_temporary: bool = False, thread_context=None):
|
||||
if is_temporary and not get_settings().config.publish_output_progress:
|
||||
get_logger().debug(f"Skipping publish_comment for temporary comment: {pr_comment}")
|
||||
return None
|
||||
comment = Comment(content=pr_comment)
|
||||
thread = CommentThread(comments=[comment], thread_context=thread_context, status=1)
|
||||
thread = CommentThread(comments=[comment], thread_context=thread_context, status=5)
|
||||
thread_response = self.azure_devops_client.create_thread(
|
||||
comment_thread=thread,
|
||||
project=self.workspace_slug,
|
||||
@ -401,7 +403,7 @@ class AzureDevopsProvider(GitProvider):
|
||||
pr_body = pr_body[:ind]
|
||||
|
||||
if len(pr_body) > MAX_PR_DESCRIPTION_AZURE_LENGTH:
|
||||
changes_walkthrough_text = '## **Changes walkthrough**'
|
||||
changes_walkthrough_text = PRDescriptionHeader.CHANGES_WALKTHROUGH.value
|
||||
ind = pr_body.find(changes_walkthrough_text)
|
||||
if ind != -1:
|
||||
pr_body = pr_body[:ind]
|
||||
@ -432,7 +434,7 @@ class AzureDevopsProvider(GitProvider):
|
||||
except Exception as e:
|
||||
get_logger().exception(f"Failed to remove temp comments, error: {e}")
|
||||
|
||||
def publish_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
|
||||
def publish_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str, original_suggestion=None):
|
||||
self.publish_inline_comments([self.create_inline_comment(body, relevant_file, relevant_line_in_file)])
|
||||
|
||||
|
||||
@ -516,19 +518,20 @@ class AzureDevopsProvider(GitProvider):
|
||||
source_branch = pr_info.source_ref_name.split("/")[-1]
|
||||
return source_branch
|
||||
|
||||
def get_pr_description(self, *, full: bool = True) -> str:
|
||||
max_tokens = get_settings().get("CONFIG.MAX_DESCRIPTION_TOKENS", None)
|
||||
if max_tokens:
|
||||
return clip_tokens(self.pr.description, max_tokens)
|
||||
return self.pr.description
|
||||
|
||||
def get_user_id(self):
|
||||
return 0
|
||||
|
||||
def get_issue_comments(self):
|
||||
raise NotImplementedError(
|
||||
"Azure DevOps provider does not support issue comments yet"
|
||||
)
|
||||
threads = self.azure_devops_client.get_threads(repository_id=self.repo_slug, pull_request_id=self.pr_num, project=self.workspace_slug)
|
||||
threads.reverse()
|
||||
comment_list = []
|
||||
for thread in threads:
|
||||
for comment in thread.comments:
|
||||
if comment.content and comment not in comment_list:
|
||||
comment.body = comment.content
|
||||
comment.thread_id = thread.id
|
||||
comment_list.append(comment)
|
||||
return comment_list
|
||||
|
||||
def add_eyes_reaction(self, issue_comment_id: int, disable_eyes: bool = False) -> Optional[int]:
|
||||
return True
|
||||
@ -541,18 +544,20 @@ class AzureDevopsProvider(GitProvider):
|
||||
parsed_url = urlparse(pr_url)
|
||||
|
||||
path_parts = parsed_url.path.strip("/").split("/")
|
||||
|
||||
if len(path_parts) < 6 or path_parts[4] != "pullrequest":
|
||||
if "pullrequest" not in path_parts:
|
||||
raise ValueError(
|
||||
"The provided URL does not appear to be a Azure DevOps PR URL"
|
||||
)
|
||||
|
||||
if len(path_parts) == 6: # "https://dev.azure.com/organization/project/_git/repo/pullrequest/1"
|
||||
workspace_slug = path_parts[1]
|
||||
repo_slug = path_parts[3]
|
||||
try:
|
||||
pr_number = int(path_parts[5])
|
||||
except ValueError as e:
|
||||
raise ValueError("Unable to convert PR number to integer") from e
|
||||
elif len(path_parts) == 5: # 'https://organization.visualstudio.com/project/_git/repo/pullrequest/1'
|
||||
workspace_slug = path_parts[0]
|
||||
repo_slug = path_parts[2]
|
||||
pr_number = int(path_parts[4])
|
||||
else:
|
||||
raise ValueError("The provided URL does not appear to be a Azure DevOps PR URL")
|
||||
|
||||
return workspace_slug, repo_slug, pr_number
|
||||
|
||||
@ -612,3 +617,5 @@ class AzureDevopsProvider(GitProvider):
|
||||
get_logger().error(f"Failed to get pr id, error: {e}")
|
||||
return ""
|
||||
|
||||
def publish_file_comments(self, file_comments: list) -> bool:
|
||||
pass
|
||||
|
@ -1,4 +1,6 @@
|
||||
import difflib
|
||||
import json
|
||||
import re
|
||||
from typing import Optional, Tuple
|
||||
from urllib.parse import urlparse
|
||||
|
||||
@ -6,13 +8,20 @@ import requests
|
||||
from atlassian.bitbucket import Cloud
|
||||
from starlette_context import context
|
||||
|
||||
from pr_agent.algo.types import FilePatchInfo, EDIT_TYPE
|
||||
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
|
||||
|
||||
from ..algo.file_filter import filter_ignored
|
||||
from ..algo.language_handler import is_valid_file
|
||||
from ..algo.utils import find_line_number_of_relevant_line_in_file
|
||||
from ..config_loader import get_settings
|
||||
from ..log import get_logger
|
||||
from .git_provider import GitProvider
|
||||
from .git_provider import MAX_FILES_ALLOWED_FULL, GitProvider
|
||||
|
||||
|
||||
def _gef_filename(diff):
|
||||
if diff.new.path:
|
||||
return diff.new.path
|
||||
return diff.old.path
|
||||
|
||||
|
||||
class BitbucketProvider(GitProvider):
|
||||
@ -30,6 +39,7 @@ class BitbucketProvider(GitProvider):
|
||||
s.headers["Content-Type"] = "application/json"
|
||||
self.headers = s.headers
|
||||
self.bitbucket_client = Cloud(session=s)
|
||||
self.max_comment_length = 31000
|
||||
self.workspace_slug = None
|
||||
self.repo_slug = None
|
||||
self.repo = None
|
||||
@ -39,6 +49,7 @@ class BitbucketProvider(GitProvider):
|
||||
self.temp_comments = []
|
||||
self.incremental = incremental
|
||||
self.diff_files = None
|
||||
self.git_files = None
|
||||
if pr_url:
|
||||
self.set_pr(pr_url)
|
||||
self.bitbucket_comment_api_url = self.pr._BitbucketBase__data["links"]["comments"]["href"]
|
||||
@ -63,19 +74,33 @@ class BitbucketProvider(GitProvider):
|
||||
post_parameters_list = []
|
||||
for suggestion in code_suggestions:
|
||||
body = suggestion["body"]
|
||||
original_suggestion = suggestion.get('original_suggestion', None) # needed for diff code
|
||||
if original_suggestion:
|
||||
try:
|
||||
existing_code = original_suggestion['existing_code'].rstrip() + "\n"
|
||||
improved_code = original_suggestion['improved_code'].rstrip() + "\n"
|
||||
diff = difflib.unified_diff(existing_code.split('\n'),
|
||||
improved_code.split('\n'), n=999)
|
||||
patch_orig = "\n".join(diff)
|
||||
patch = "\n".join(patch_orig.splitlines()[5:]).strip('\n')
|
||||
diff_code = f"\n\n```diff\n{patch.rstrip()}\n```"
|
||||
# replace ```suggestion ... ``` with diff_code, using regex:
|
||||
body = re.sub(r'```suggestion.*?```', diff_code, body, flags=re.DOTALL)
|
||||
except Exception as e:
|
||||
get_logger().exception(f"Bitbucket failed to get diff code for publishing, error: {e}")
|
||||
continue
|
||||
|
||||
relevant_file = suggestion["relevant_file"]
|
||||
relevant_lines_start = suggestion["relevant_lines_start"]
|
||||
relevant_lines_end = suggestion["relevant_lines_end"]
|
||||
|
||||
if not relevant_lines_start or relevant_lines_start == -1:
|
||||
if get_settings().config.verbosity_level >= 2:
|
||||
get_logger().exception(
|
||||
f"Failed to publish code suggestion, relevant_lines_start is {relevant_lines_start}"
|
||||
)
|
||||
continue
|
||||
|
||||
if relevant_lines_end < relevant_lines_start:
|
||||
if get_settings().config.verbosity_level >= 2:
|
||||
get_logger().exception(
|
||||
f"Failed to publish code suggestion, "
|
||||
f"relevant_lines_end is {relevant_lines_end} and "
|
||||
@ -104,12 +129,15 @@ class BitbucketProvider(GitProvider):
|
||||
self.publish_inline_comments(post_parameters_list)
|
||||
return True
|
||||
except Exception as e:
|
||||
if get_settings().config.verbosity_level >= 2:
|
||||
get_logger().error(f"Failed to publish code suggestion, error: {e}")
|
||||
get_logger().error(f"Bitbucket failed to publish code suggestion, error: {e}")
|
||||
return False
|
||||
|
||||
def publish_file_comments(self, file_comments: list) -> bool:
|
||||
pass
|
||||
|
||||
def is_supported(self, capability: str) -> bool:
|
||||
if capability in ['get_issue_comments', 'publish_inline_comments', 'get_labels', 'gfm_markdown']:
|
||||
if capability in ['get_issue_comments', 'publish_inline_comments', 'get_labels', 'gfm_markdown',
|
||||
'publish_file_comments']:
|
||||
return False
|
||||
return True
|
||||
|
||||
@ -118,7 +146,17 @@ class BitbucketProvider(GitProvider):
|
||||
self.pr = self._get_pr()
|
||||
|
||||
def get_files(self):
|
||||
return [diff.new.path for diff in self.pr.diffstat()]
|
||||
try:
|
||||
git_files = context.get("git_files", None)
|
||||
if git_files:
|
||||
return git_files
|
||||
self.git_files = [_gef_filename(diff) for diff in self.pr.diffstat()]
|
||||
context["git_files"] = self.git_files
|
||||
return self.git_files
|
||||
except Exception:
|
||||
if not self.git_files:
|
||||
self.git_files = [_gef_filename(diff) for diff in self.pr.diffstat()]
|
||||
return self.git_files
|
||||
|
||||
def get_diff_files(self) -> list[FilePatchInfo]:
|
||||
if self.diff_files:
|
||||
@ -129,34 +167,111 @@ class BitbucketProvider(GitProvider):
|
||||
if diffs != diffs_original:
|
||||
try:
|
||||
names_original = [d.new.path for d in diffs_original]
|
||||
names_filtered = [d.new.path for d in diffs]
|
||||
names_kept = [d.new.path for d in diffs]
|
||||
names_filtered = list(set(names_original) - set(names_kept))
|
||||
get_logger().info(f"Filtered out [ignore] files for PR", extra={
|
||||
'original_files': names_original,
|
||||
'filtered_files': names_filtered
|
||||
'names_kept': names_kept,
|
||||
'names_filtered': names_filtered
|
||||
|
||||
})
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
diff_split = [
|
||||
"diff --git%s" % x for x in self.pr.diff().split("diff --git") if x.strip()
|
||||
]
|
||||
# get the pr patches
|
||||
try:
|
||||
pr_patches = self.pr.diff()
|
||||
except Exception as e:
|
||||
# Try different encodings if UTF-8 fails
|
||||
get_logger().warning(f"Failed to decode PR patch with utf-8, error: {e}")
|
||||
encodings_to_try = ['iso-8859-1', 'latin-1', 'ascii', 'utf-16']
|
||||
pr_patches = None
|
||||
for encoding in encodings_to_try:
|
||||
try:
|
||||
pr_patches = self.pr.diff(encoding=encoding)
|
||||
get_logger().info(f"Successfully decoded PR patch with encoding {encoding}")
|
||||
break
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
|
||||
if pr_patches is None:
|
||||
raise ValueError(f"Failed to decode PR patch with encodings {encodings_to_try}")
|
||||
|
||||
diff_split = ["diff --git" + x for x in pr_patches.split("diff --git") if x.strip()]
|
||||
# filter all elements of 'diff_split' that are of indices in 'diffs_original' that are not in 'diffs'
|
||||
if len(diff_split) > len(diffs) and len(diffs_original) == len(diff_split):
|
||||
diff_split = [diff_split[i] for i in range(len(diff_split)) if diffs_original[i] in diffs]
|
||||
if len(diff_split) != len(diffs):
|
||||
get_logger().error(f"Error - failed to split the diff into {len(diffs)} parts")
|
||||
return []
|
||||
# bitbucket diff has a header for each file, we need to remove it:
|
||||
# "diff --git filename
|
||||
# new file mode 100644 (optional)
|
||||
# index caa56f0..61528d7 100644
|
||||
# --- a/pr_agent/cli_pip.py
|
||||
# +++ b/pr_agent/cli_pip.py
|
||||
# @@ -... @@"
|
||||
for i, _ in enumerate(diff_split):
|
||||
diff_split_lines = diff_split[i].splitlines()
|
||||
if (len(diff_split_lines) >= 6) and \
|
||||
((diff_split_lines[2].startswith("---") and
|
||||
diff_split_lines[3].startswith("+++") and
|
||||
diff_split_lines[4].startswith("@@")) or
|
||||
(diff_split_lines[3].startswith("---") and # new or deleted file
|
||||
diff_split_lines[4].startswith("+++") and
|
||||
diff_split_lines[5].startswith("@@"))):
|
||||
diff_split[i] = "\n".join(diff_split_lines[4:])
|
||||
else:
|
||||
if diffs[i].data.get('lines_added', 0) == 0 and diffs[i].data.get('lines_removed', 0) == 0:
|
||||
diff_split[i] = ""
|
||||
elif len(diff_split_lines) <= 3:
|
||||
diff_split[i] = ""
|
||||
get_logger().info(f"Disregarding empty diff for file {_gef_filename(diffs[i])}")
|
||||
else:
|
||||
get_logger().warning(f"Bitbucket failed to get diff for file {_gef_filename(diffs[i])}")
|
||||
diff_split[i] = ""
|
||||
|
||||
invalid_files_names = []
|
||||
diff_files = []
|
||||
counter_valid = 0
|
||||
# get full files
|
||||
for index, diff in enumerate(diffs):
|
||||
if not is_valid_file(diff.new.path):
|
||||
invalid_files_names.append(diff.new.path)
|
||||
file_path = _gef_filename(diff)
|
||||
if not is_valid_file(file_path):
|
||||
invalid_files_names.append(file_path)
|
||||
continue
|
||||
|
||||
try:
|
||||
counter_valid += 1
|
||||
if get_settings().get("bitbucket_app.avoid_full_files", False):
|
||||
original_file_content_str = ""
|
||||
new_file_content_str = ""
|
||||
elif counter_valid < MAX_FILES_ALLOWED_FULL // 2: # factor 2 because bitbucket has limited API calls
|
||||
if diff.old.get_data("links"):
|
||||
original_file_content_str = self._get_pr_file_content(
|
||||
diff.old.get_data("links")
|
||||
)
|
||||
new_file_content_str = self._get_pr_file_content(diff.new.get_data("links"))
|
||||
diff.old.get_data("links")['self']['href'])
|
||||
else:
|
||||
original_file_content_str = ""
|
||||
if diff.new.get_data("links"):
|
||||
new_file_content_str = self._get_pr_file_content(diff.new.get_data("links")['self']['href'])
|
||||
else:
|
||||
new_file_content_str = ""
|
||||
else:
|
||||
if counter_valid == MAX_FILES_ALLOWED_FULL // 2:
|
||||
get_logger().info(
|
||||
f"Bitbucket too many files in PR, will avoid loading full content for rest of files")
|
||||
original_file_content_str = ""
|
||||
new_file_content_str = ""
|
||||
except Exception as e:
|
||||
get_logger().exception(f"Error - bitbucket failed to get file content, error: {e}")
|
||||
original_file_content_str = ""
|
||||
new_file_content_str = ""
|
||||
|
||||
file_patch_canonic_structure = FilePatchInfo(
|
||||
original_file_content_str,
|
||||
new_file_content_str,
|
||||
diff_split[index],
|
||||
diff.new.path,
|
||||
file_path,
|
||||
)
|
||||
|
||||
if diff.data['status'] == 'added':
|
||||
@ -170,8 +285,7 @@ class BitbucketProvider(GitProvider):
|
||||
diff_files.append(file_patch_canonic_structure)
|
||||
|
||||
if invalid_files_names:
|
||||
get_logger().info(f"Invalid file names: {invalid_files_names}")
|
||||
|
||||
get_logger().info(f"Disregarding files with invalid extensions:\n{invalid_files_names}")
|
||||
|
||||
self.diff_files = diff_files
|
||||
return diff_files
|
||||
@ -211,6 +325,10 @@ class BitbucketProvider(GitProvider):
|
||||
self.publish_comment(pr_comment)
|
||||
|
||||
def publish_comment(self, pr_comment: str, is_temporary: bool = False):
|
||||
if is_temporary and not get_settings().config.publish_output_progress:
|
||||
get_logger().debug(f"Skipping publish_comment for temporary comment: {pr_comment}")
|
||||
return None
|
||||
pr_comment = self.limit_output_characters(pr_comment, self.max_comment_length)
|
||||
comment = self.pr.comment(pr_comment)
|
||||
if is_temporary:
|
||||
self.temp_comments.append(comment["id"])
|
||||
@ -218,6 +336,7 @@ class BitbucketProvider(GitProvider):
|
||||
|
||||
def edit_comment(self, comment, body: str):
|
||||
try:
|
||||
body = self.limit_output_characters(body, self.max_comment_length)
|
||||
comment.update(body)
|
||||
except Exception as e:
|
||||
get_logger().exception(f"Failed to update comment, error: {e}")
|
||||
@ -236,10 +355,13 @@ class BitbucketProvider(GitProvider):
|
||||
get_logger().exception(f"Failed to remove comment, error: {e}")
|
||||
|
||||
# function to create_inline_comment
|
||||
def create_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str, absolute_position: int = None):
|
||||
def create_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str,
|
||||
absolute_position: int = None):
|
||||
body = self.limit_output_characters(body, self.max_comment_length)
|
||||
position, absolute_position = find_line_number_of_relevant_line_in_file(self.get_diff_files(),
|
||||
relevant_file.strip('`'),
|
||||
relevant_line_in_file, absolute_position)
|
||||
relevant_line_in_file,
|
||||
absolute_position)
|
||||
if position == -1:
|
||||
if get_settings().config.verbosity_level >= 2:
|
||||
get_logger().info(f"Could not find position for {relevant_file} {relevant_line_in_file}")
|
||||
@ -249,8 +371,8 @@ class BitbucketProvider(GitProvider):
|
||||
path = relevant_file.strip()
|
||||
return dict(body=body, path=path, position=absolute_position) if subject_type == "LINE" else {}
|
||||
|
||||
|
||||
def publish_inline_comment(self, comment: str, from_line: int, file: str):
|
||||
def publish_inline_comment(self, comment: str, from_line: int, file: str, original_suggestion=None):
|
||||
comment = self.limit_output_characters(comment, self.max_comment_length)
|
||||
payload = json.dumps({
|
||||
"content": {
|
||||
"raw": comment,
|
||||
@ -314,6 +436,9 @@ class BitbucketProvider(GitProvider):
|
||||
def get_pr_branch(self):
|
||||
return self.pr.source_branch
|
||||
|
||||
def get_pr_owner_id(self) -> str | None:
|
||||
return self.workspace_slug
|
||||
|
||||
def get_pr_description_full(self):
|
||||
return self.pr.description
|
||||
|
||||
@ -380,7 +505,6 @@ class BitbucketProvider(GitProvider):
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
|
||||
def create_or_update_pr_file(self, file_path: str, branch: str, contents="", message="") -> None:
|
||||
url = (f"https://api.bitbucket.org/2.0/repositories/{self.workspace_slug}/{self.repo_slug}/src/")
|
||||
if not message:
|
||||
@ -400,6 +524,13 @@ class BitbucketProvider(GitProvider):
|
||||
get_logger().exception(f"Failed to create empty file {file_path} in branch {branch}")
|
||||
|
||||
def _get_pr_file_content(self, remote_link: str):
|
||||
try:
|
||||
response = requests.request("GET", remote_link, headers=self.headers)
|
||||
if response.status_code == 404: # not found
|
||||
return ""
|
||||
contents = response.text
|
||||
return contents
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
def get_commit_messages(self):
|
||||
|
@ -1,34 +1,28 @@
|
||||
import json
|
||||
import difflib
|
||||
import re
|
||||
|
||||
from packaging.version import parse as parse_version
|
||||
from typing import Optional, Tuple
|
||||
from urllib.parse import quote_plus, urlparse
|
||||
|
||||
import requests
|
||||
from atlassian.bitbucket import Bitbucket
|
||||
from starlette_context import context
|
||||
from requests.exceptions import HTTPError
|
||||
|
||||
from .git_provider import GitProvider
|
||||
from ..algo.types import EDIT_TYPE, FilePatchInfo
|
||||
from ..algo.git_patch_processing import decode_if_bytes
|
||||
from ..algo.language_handler import is_valid_file
|
||||
from ..algo.utils import load_large_diff, find_line_number_of_relevant_line_in_file
|
||||
from ..algo.types import EDIT_TYPE, FilePatchInfo
|
||||
from ..algo.utils import (find_line_number_of_relevant_line_in_file,
|
||||
load_large_diff)
|
||||
from ..config_loader import get_settings
|
||||
from ..log import get_logger
|
||||
from .git_provider import GitProvider
|
||||
|
||||
|
||||
class BitbucketServerProvider(GitProvider):
|
||||
def __init__(
|
||||
self, pr_url: Optional[str] = None, incremental: Optional[bool] = False
|
||||
self, pr_url: Optional[str] = None, incremental: Optional[bool] = False,
|
||||
bitbucket_client: Optional[Bitbucket] = None,
|
||||
):
|
||||
s = requests.Session()
|
||||
try:
|
||||
bearer = context.get("bitbucket_bearer_token", None)
|
||||
s.headers["Authorization"] = f"Bearer {bearer}"
|
||||
except Exception:
|
||||
s.headers[
|
||||
"Authorization"
|
||||
] = f'Bearer {get_settings().get("BITBUCKET_SERVER.BEARER_TOKEN", None)}'
|
||||
|
||||
s.headers["Content-Type"] = "application/json"
|
||||
self.headers = s.headers
|
||||
self.bitbucket_server_url = None
|
||||
self.workspace_slug = None
|
||||
self.repo_slug = None
|
||||
@ -42,22 +36,28 @@ class BitbucketServerProvider(GitProvider):
|
||||
self.bitbucket_pull_request_api_url = pr_url
|
||||
|
||||
self.bitbucket_server_url = self._parse_bitbucket_server(url=pr_url)
|
||||
self.bitbucket_client = Bitbucket(url=self.bitbucket_server_url,
|
||||
token=get_settings().get("BITBUCKET_SERVER.BEARER_TOKEN", None))
|
||||
self.bitbucket_client = bitbucket_client or Bitbucket(url=self.bitbucket_server_url,
|
||||
token=get_settings().get("BITBUCKET_SERVER.BEARER_TOKEN",
|
||||
None))
|
||||
try:
|
||||
self.bitbucket_api_version = parse_version(self.bitbucket_client.get("rest/api/1.0/application-properties").get('version'))
|
||||
except Exception:
|
||||
self.bitbucket_api_version = None
|
||||
|
||||
if pr_url:
|
||||
self.set_pr(pr_url)
|
||||
|
||||
def get_repo_settings(self):
|
||||
try:
|
||||
url = (f"{self.bitbucket_server_url}/projects/{self.workspace_slug}/repos/{self.repo_slug}/src/"
|
||||
f"{self.pr.destination_branch}/.pr_agent.toml")
|
||||
response = requests.request("GET", url, headers=self.headers)
|
||||
if response.status_code == 404: # not found
|
||||
content = self.bitbucket_client.get_content_of_file(self.workspace_slug, self.repo_slug, ".pr_agent.toml", self.get_pr_branch())
|
||||
|
||||
return content
|
||||
except Exception as e:
|
||||
if isinstance(e, HTTPError):
|
||||
if e.response.status_code == 404: # not found
|
||||
return ""
|
||||
contents = response.text.encode('utf-8')
|
||||
return contents
|
||||
except Exception:
|
||||
|
||||
get_logger().error(f"Failed to load .pr_agent.toml file, error: {e}")
|
||||
return ""
|
||||
|
||||
def get_pr_id(self):
|
||||
@ -70,20 +70,33 @@ class BitbucketServerProvider(GitProvider):
|
||||
post_parameters_list = []
|
||||
for suggestion in code_suggestions:
|
||||
body = suggestion["body"]
|
||||
original_suggestion = suggestion.get('original_suggestion', None) # needed for diff code
|
||||
if original_suggestion:
|
||||
try:
|
||||
existing_code = original_suggestion['existing_code'].rstrip() + "\n"
|
||||
improved_code = original_suggestion['improved_code'].rstrip() + "\n"
|
||||
diff = difflib.unified_diff(existing_code.split('\n'),
|
||||
improved_code.split('\n'), n=999)
|
||||
patch_orig = "\n".join(diff)
|
||||
patch = "\n".join(patch_orig.splitlines()[5:]).strip('\n')
|
||||
diff_code = f"\n\n```diff\n{patch.rstrip()}\n```"
|
||||
# replace ```suggestion ... ``` with diff_code, using regex:
|
||||
body = re.sub(r'```suggestion.*?```', diff_code, body, flags=re.DOTALL)
|
||||
except Exception as e:
|
||||
get_logger().exception(f"Bitbucket failed to get diff code for publishing, error: {e}")
|
||||
continue
|
||||
relevant_file = suggestion["relevant_file"]
|
||||
relevant_lines_start = suggestion["relevant_lines_start"]
|
||||
relevant_lines_end = suggestion["relevant_lines_end"]
|
||||
|
||||
if not relevant_lines_start or relevant_lines_start == -1:
|
||||
if get_settings().config.verbosity_level >= 2:
|
||||
get_logger().exception(
|
||||
get_logger().warning(
|
||||
f"Failed to publish code suggestion, relevant_lines_start is {relevant_lines_start}"
|
||||
)
|
||||
continue
|
||||
|
||||
if relevant_lines_end < relevant_lines_start:
|
||||
if get_settings().config.verbosity_level >= 2:
|
||||
get_logger().exception(
|
||||
get_logger().warning(
|
||||
f"Failed to publish code suggestion, "
|
||||
f"relevant_lines_end is {relevant_lines_end} and "
|
||||
f"relevant_lines_start is {relevant_lines_start}"
|
||||
@ -91,6 +104,8 @@ class BitbucketServerProvider(GitProvider):
|
||||
continue
|
||||
|
||||
if relevant_lines_end > relevant_lines_start:
|
||||
# Bitbucket does not support multi-line suggestions so use a code block instead - https://jira.atlassian.com/browse/BSERV-4553
|
||||
body = body.replace("```suggestion", "```")
|
||||
post_parameters = {
|
||||
"body": body,
|
||||
"path": relevant_file,
|
||||
@ -115,8 +130,11 @@ class BitbucketServerProvider(GitProvider):
|
||||
get_logger().error(f"Failed to publish code suggestion, error: {e}")
|
||||
return False
|
||||
|
||||
def publish_file_comments(self, file_comments: list) -> bool:
|
||||
pass
|
||||
|
||||
def is_supported(self, capability: str) -> bool:
|
||||
if capability in ['get_issue_comments', 'get_labels', 'gfm_markdown']:
|
||||
if capability in ['get_issue_comments', 'get_labels', 'gfm_markdown', 'publish_file_comments']:
|
||||
return False
|
||||
return True
|
||||
|
||||
@ -131,7 +149,7 @@ class BitbucketServerProvider(GitProvider):
|
||||
self.repo_slug,
|
||||
path,
|
||||
commit_id)
|
||||
except requests.HTTPError as e:
|
||||
except HTTPError as e:
|
||||
get_logger().debug(f"File {path} not found at commit id: {commit_id}")
|
||||
return file_content
|
||||
|
||||
@ -140,13 +158,51 @@ class BitbucketServerProvider(GitProvider):
|
||||
diffstat = [change["path"]['toString'] for change in changes]
|
||||
return diffstat
|
||||
|
||||
#gets the best common ancestor: https://git-scm.com/docs/git-merge-base
|
||||
@staticmethod
|
||||
def get_best_common_ancestor(source_commits_list, destination_commits_list, guaranteed_common_ancestor) -> str:
|
||||
destination_commit_hashes = {commit['id'] for commit in destination_commits_list} | {guaranteed_common_ancestor}
|
||||
|
||||
for commit in source_commits_list:
|
||||
for parent_commit in commit['parents']:
|
||||
if parent_commit['id'] in destination_commit_hashes:
|
||||
return parent_commit['id']
|
||||
|
||||
return guaranteed_common_ancestor
|
||||
|
||||
def get_diff_files(self) -> list[FilePatchInfo]:
|
||||
if self.diff_files:
|
||||
return self.diff_files
|
||||
|
||||
base_sha = self.pr.toRef['latestCommit']
|
||||
head_sha = self.pr.fromRef['latestCommit']
|
||||
|
||||
# if Bitbucket api version is >= 8.16 then use the merge-base api for 2-way diff calculation
|
||||
if self.bitbucket_api_version is not None and self.bitbucket_api_version >= parse_version("8.16"):
|
||||
try:
|
||||
base_sha = self.bitbucket_client.get(self._get_merge_base())['id']
|
||||
except Exception as e:
|
||||
get_logger().error(f"Failed to get the best common ancestor for PR: {self.pr_url}, \nerror: {e}")
|
||||
raise e
|
||||
else:
|
||||
source_commits_list = list(self.bitbucket_client.get_pull_requests_commits(
|
||||
self.workspace_slug,
|
||||
self.repo_slug,
|
||||
self.pr_num
|
||||
))
|
||||
# if Bitbucket api version is None or < 7.0 then do a simple diff with a guaranteed common ancestor
|
||||
base_sha = source_commits_list[-1]['parents'][0]['id']
|
||||
# if Bitbucket api version is 7.0-8.15 then use 2-way diff functionality for the base_sha
|
||||
if self.bitbucket_api_version is not None and self.bitbucket_api_version >= parse_version("7.0"):
|
||||
try:
|
||||
destination_commits = list(
|
||||
self.bitbucket_client.get_commits(self.workspace_slug, self.repo_slug, base_sha,
|
||||
self.pr.toRef['latestCommit']))
|
||||
base_sha = self.get_best_common_ancestor(source_commits_list, destination_commits, base_sha)
|
||||
except Exception as e:
|
||||
get_logger().error(
|
||||
f"Failed to get the commit list for calculating best common ancestor for PR: {self.pr_url}, \nerror: {e}")
|
||||
raise e
|
||||
|
||||
diff_files = []
|
||||
original_file_content_str = ""
|
||||
new_file_content_str = ""
|
||||
@ -162,27 +218,23 @@ class BitbucketServerProvider(GitProvider):
|
||||
case 'ADD':
|
||||
edit_type = EDIT_TYPE.ADDED
|
||||
new_file_content_str = self.get_file(file_path, head_sha)
|
||||
if isinstance(new_file_content_str, (bytes, bytearray)):
|
||||
new_file_content_str = new_file_content_str.decode("utf-8")
|
||||
new_file_content_str = decode_if_bytes(new_file_content_str)
|
||||
original_file_content_str = ""
|
||||
case 'DELETE':
|
||||
edit_type = EDIT_TYPE.DELETED
|
||||
new_file_content_str = ""
|
||||
original_file_content_str = self.get_file(file_path, base_sha)
|
||||
if isinstance(original_file_content_str, (bytes, bytearray)):
|
||||
original_file_content_str = original_file_content_str.decode("utf-8")
|
||||
original_file_content_str = decode_if_bytes(original_file_content_str)
|
||||
case 'RENAME':
|
||||
edit_type = EDIT_TYPE.RENAMED
|
||||
case _:
|
||||
edit_type = EDIT_TYPE.MODIFIED
|
||||
original_file_content_str = self.get_file(file_path, base_sha)
|
||||
if isinstance(original_file_content_str, (bytes, bytearray)):
|
||||
original_file_content_str = original_file_content_str.decode("utf-8")
|
||||
original_file_content_str = decode_if_bytes(original_file_content_str)
|
||||
new_file_content_str = self.get_file(file_path, head_sha)
|
||||
if isinstance(new_file_content_str, (bytes, bytearray)):
|
||||
new_file_content_str = new_file_content_str.decode("utf-8")
|
||||
new_file_content_str = decode_if_bytes(new_file_content_str)
|
||||
|
||||
patch = load_large_diff(file_path, new_file_content_str, original_file_content_str)
|
||||
patch = load_large_diff(file_path, new_file_content_str, original_file_content_str, show_warning=False)
|
||||
|
||||
diff_files.append(
|
||||
FilePatchInfo(
|
||||
@ -230,7 +282,7 @@ class BitbucketServerProvider(GitProvider):
|
||||
path = relevant_file.strip()
|
||||
return dict(body=body, path=path, position=absolute_position) if subject_type == "LINE" else {}
|
||||
|
||||
def publish_inline_comment(self, comment: str, from_line: int, file: str):
|
||||
def publish_inline_comment(self, comment: str, from_line: int, file: str, original_suggestion=None):
|
||||
payload = {
|
||||
"text": comment,
|
||||
"severity": "NORMAL",
|
||||
@ -244,11 +296,18 @@ class BitbucketServerProvider(GitProvider):
|
||||
}
|
||||
|
||||
try:
|
||||
requests.post(url=self._get_pr_comments_url(), json=payload, headers=self.headers).raise_for_status()
|
||||
self.bitbucket_client.post(self._get_pr_comments_path(), data=payload)
|
||||
except Exception as e:
|
||||
get_logger().error(f"Failed to publish inline comment to '{file}' at line {from_line}, error: {e}")
|
||||
raise e
|
||||
|
||||
def get_line_link(self, relevant_file: str, relevant_line_start: int, relevant_line_end: int = None) -> str:
|
||||
if relevant_line_start == -1:
|
||||
link = f"{self.pr_url}/diff#{quote_plus(relevant_file)}"
|
||||
else:
|
||||
link = f"{self.pr_url}/diff#{quote_plus(relevant_file)}?t={relevant_line_start}"
|
||||
return link
|
||||
|
||||
def generate_link_to_relevant_line_number(self, suggestion) -> str:
|
||||
try:
|
||||
relevant_file = suggestion['relevant_file'].strip('`').strip("'").rstrip()
|
||||
@ -301,6 +360,9 @@ class BitbucketServerProvider(GitProvider):
|
||||
def get_pr_branch(self):
|
||||
return self.pr.fromRef['displayId']
|
||||
|
||||
def get_pr_owner_id(self) -> str | None:
|
||||
return self.workspace_slug
|
||||
|
||||
def get_pr_description_full(self):
|
||||
if hasattr(self.pr, "description"):
|
||||
return self.pr.description
|
||||
@ -323,19 +385,47 @@ class BitbucketServerProvider(GitProvider):
|
||||
|
||||
@staticmethod
|
||||
def _parse_bitbucket_server(url: str) -> str:
|
||||
# pr url format: f"{bitbucket_server}/projects/{project_name}/repos/{repository_name}/pull-requests/{pr_id}"
|
||||
parsed_url = urlparse(url)
|
||||
server_path = parsed_url.path.split("/projects/")
|
||||
if len(server_path) > 1:
|
||||
server_path = server_path[0].strip("/")
|
||||
return f"{parsed_url.scheme}://{parsed_url.netloc}/{server_path}".strip("/")
|
||||
return f"{parsed_url.scheme}://{parsed_url.netloc}"
|
||||
|
||||
@staticmethod
|
||||
def _parse_pr_url(pr_url: str) -> Tuple[str, str, int]:
|
||||
# pr url format: f"{bitbucket_server}/projects/{project_name}/repos/{repository_name}/pull-requests/{pr_id}"
|
||||
parsed_url = urlparse(pr_url)
|
||||
|
||||
path_parts = parsed_url.path.strip("/").split("/")
|
||||
if len(path_parts) < 6 or path_parts[4] != "pull-requests":
|
||||
|
||||
try:
|
||||
projects_index = path_parts.index("projects")
|
||||
except ValueError:
|
||||
projects_index = -1
|
||||
|
||||
try:
|
||||
users_index = path_parts.index("users")
|
||||
except ValueError:
|
||||
users_index = -1
|
||||
|
||||
if projects_index == -1 and users_index == -1:
|
||||
raise ValueError(f"The provided URL '{pr_url}' does not appear to be a Bitbucket PR URL")
|
||||
|
||||
if projects_index != -1:
|
||||
path_parts = path_parts[projects_index:]
|
||||
else:
|
||||
path_parts = path_parts[users_index:]
|
||||
|
||||
if len(path_parts) < 6 or path_parts[2] != "repos" or path_parts[4] != "pull-requests":
|
||||
raise ValueError(
|
||||
f"The provided URL '{pr_url}' does not appear to be a Bitbucket PR URL"
|
||||
)
|
||||
|
||||
workspace_slug = path_parts[1]
|
||||
if users_index != -1:
|
||||
workspace_slug = f"~{workspace_slug}"
|
||||
repo_slug = path_parts[3]
|
||||
try:
|
||||
pr_number = int(path_parts[5])
|
||||
@ -350,15 +440,20 @@ class BitbucketServerProvider(GitProvider):
|
||||
return self.repo
|
||||
|
||||
def _get_pr(self):
|
||||
pr = self.bitbucket_client.get_pull_request(self.workspace_slug, self.repo_slug, pull_request_id=self.pr_num)
|
||||
try:
|
||||
pr = self.bitbucket_client.get_pull_request(self.workspace_slug, self.repo_slug,
|
||||
pull_request_id=self.pr_num)
|
||||
return type('new_dict', (object,), pr)
|
||||
except Exception as e:
|
||||
get_logger().error(f"Failed to get pull request, error: {e}")
|
||||
raise e
|
||||
|
||||
def _get_pr_file_content(self, remote_link: str):
|
||||
return ""
|
||||
|
||||
def get_commit_messages(self):
|
||||
def get_commit_messages(self):
|
||||
raise NotImplementedError("Get commit messages function not implemented yet.")
|
||||
return ""
|
||||
|
||||
# bitbucket does not support labels
|
||||
def publish_description(self, pr_title: str, description: str):
|
||||
payload = {
|
||||
@ -373,7 +468,6 @@ class BitbucketServerProvider(GitProvider):
|
||||
get_logger().error(f"Failed to update pull request, error: {e}")
|
||||
raise e
|
||||
|
||||
|
||||
# bitbucket does not support labels
|
||||
def publish_labels(self, pr_types: list):
|
||||
pass
|
||||
@ -382,5 +476,8 @@ class BitbucketServerProvider(GitProvider):
|
||||
def get_pr_labels(self, update=False):
|
||||
pass
|
||||
|
||||
def _get_pr_comments_url(self):
|
||||
return f"{self.bitbucket_server_url}/rest/api/latest/projects/{self.workspace_slug}/repos/{self.repo_slug}/pull-requests/{self.pr_num}/comments"
|
||||
def _get_pr_comments_path(self):
|
||||
return f"rest/api/latest/projects/{self.workspace_slug}/repos/{self.repo_slug}/pull-requests/{self.pr_num}/comments"
|
||||
|
||||
def _get_merge_base(self):
|
||||
return f"rest/api/latest/projects/{self.workspace_slug}/repos/{self.repo_slug}/pull-requests/{self.pr_num}/merge-base"
|
||||
|
@ -4,13 +4,15 @@ from collections import Counter
|
||||
from typing import List, Optional, Tuple
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from pr_agent.git_providers.codecommit_client import CodeCommitClient
|
||||
from pr_agent.algo.language_handler import is_valid_file
|
||||
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
|
||||
from pr_agent.git_providers.codecommit_client import CodeCommitClient
|
||||
|
||||
from ..algo.utils import load_large_diff
|
||||
from .git_provider import GitProvider
|
||||
from ..config_loader import get_settings
|
||||
from ..log import get_logger
|
||||
from pr_agent.algo.language_handler import is_valid_file
|
||||
from .git_provider import GitProvider
|
||||
|
||||
|
||||
class PullRequestCCMimic:
|
||||
"""
|
||||
@ -225,7 +227,7 @@ class CodeCommitProvider(GitProvider):
|
||||
def remove_comment(self, comment):
|
||||
return "" # not implemented yet
|
||||
|
||||
def publish_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
|
||||
def publish_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str, original_suggestion=None):
|
||||
# https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/codecommit/client/post_comment_for_compared_commit.html
|
||||
raise NotImplementedError("CodeCommit provider does not support publishing inline comments yet")
|
||||
|
||||
|
@ -12,9 +12,9 @@ import requests
|
||||
import urllib3.util
|
||||
from git import Repo
|
||||
|
||||
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
|
||||
from pr_agent.config_loader import get_settings
|
||||
from pr_agent.git_providers.git_provider import GitProvider
|
||||
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
|
||||
from pr_agent.git_providers.local_git_provider import PullRequestMimic
|
||||
from pr_agent.log import get_logger
|
||||
|
||||
@ -376,7 +376,7 @@ class GerritProvider(GitProvider):
|
||||
'provider')
|
||||
|
||||
def publish_inline_comment(self, body: str, relevant_file: str,
|
||||
relevant_line_in_file: str):
|
||||
relevant_line_in_file: str, original_suggestion=None):
|
||||
raise NotImplementedError(
|
||||
'Publishing inline comments is not implemented for the gerrit '
|
||||
'provider')
|
||||
|
@ -1,12 +1,13 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
# enum EDIT_TYPE (ADDED, DELETED, MODIFIED, RENAMED)
|
||||
from typing import Optional
|
||||
|
||||
from pr_agent.config_loader import get_settings
|
||||
from pr_agent.algo.types import FilePatchInfo
|
||||
from pr_agent.algo.utils import Range, process_description
|
||||
from pr_agent.config_loader import get_settings
|
||||
from pr_agent.log import get_logger
|
||||
|
||||
MAX_FILES_ALLOWED_FULL = 50
|
||||
|
||||
class GitProvider(ABC):
|
||||
@abstractmethod
|
||||
@ -51,16 +52,28 @@ class GitProvider(ABC):
|
||||
def edit_comment(self, comment, body: str):
|
||||
pass
|
||||
|
||||
def edit_comment_from_comment_id(self, comment_id: int, body: str):
|
||||
pass
|
||||
|
||||
def get_comment_body_from_comment_id(self, comment_id: int) -> str:
|
||||
pass
|
||||
|
||||
def reply_to_comment_from_comment_id(self, comment_id: int, body: str):
|
||||
pass
|
||||
|
||||
def get_pr_description(self, *, full: bool = True) -> str:
|
||||
from pr_agent.config_loader import get_settings
|
||||
def get_pr_description(self, full: bool = True, split_changes_walkthrough=False) -> str or tuple:
|
||||
from pr_agent.algo.utils import clip_tokens
|
||||
from pr_agent.config_loader import get_settings
|
||||
max_tokens_description = get_settings().get("CONFIG.MAX_DESCRIPTION_TOKENS", None)
|
||||
description = self.get_pr_description_full() if full else self.get_user_description()
|
||||
if split_changes_walkthrough:
|
||||
description, files = process_description(description)
|
||||
if max_tokens_description:
|
||||
return clip_tokens(description, max_tokens_description)
|
||||
description = clip_tokens(description, max_tokens_description)
|
||||
return description, files
|
||||
else:
|
||||
if max_tokens_description:
|
||||
description = clip_tokens(description, max_tokens_description)
|
||||
return description
|
||||
|
||||
def get_user_description(self) -> str:
|
||||
@ -74,6 +87,7 @@ class GitProvider(ABC):
|
||||
# if the existing description wasn't generated by the pr-agent, just return it as-is
|
||||
if not self._is_generated_by_pr_agent(description_lowercase):
|
||||
get_logger().info(f"Existing description was not generated by the pr-agent")
|
||||
self.user_description = description
|
||||
return description
|
||||
|
||||
# if the existing description was generated by the pr-agent, but it doesn't contain a user description,
|
||||
@ -120,12 +134,18 @@ class GitProvider(ABC):
|
||||
def get_repo_settings(self):
|
||||
pass
|
||||
|
||||
def get_workspace_name(self):
|
||||
return ""
|
||||
|
||||
def get_pr_id(self):
|
||||
return ""
|
||||
|
||||
def get_line_link(self, relevant_file: str, relevant_line_start: int, relevant_line_end: int = None) -> str:
|
||||
return ""
|
||||
|
||||
def get_lines_link_original_file(self, filepath:str, component_range: Range) -> str:
|
||||
return ""
|
||||
|
||||
#### comments operations ####
|
||||
@abstractmethod
|
||||
def publish_comment(self, pr_comment: str, is_temporary: bool = False):
|
||||
@ -166,8 +186,9 @@ class GitProvider(ABC):
|
||||
pass
|
||||
self.publish_comment(pr_comment)
|
||||
|
||||
|
||||
@abstractmethod
|
||||
def publish_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
|
||||
def publish_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str, original_suggestion=None):
|
||||
pass
|
||||
|
||||
def create_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str,
|
||||
@ -238,6 +259,9 @@ class GitProvider(ABC):
|
||||
except Exception as e:
|
||||
return -1
|
||||
|
||||
def limit_output_characters(self, output: str, max_chars: int):
|
||||
return output[:max_chars] + '...' if len(output) > max_chars else output
|
||||
|
||||
|
||||
def get_main_pr_language(languages, files) -> str:
|
||||
"""
|
||||
@ -308,6 +332,8 @@ def get_main_pr_language(languages, files) -> str:
|
||||
return main_language_str
|
||||
|
||||
|
||||
|
||||
|
||||
class IncrementalPR:
|
||||
def __init__(self, is_incremental: bool = False):
|
||||
self.is_incremental = is_incremental
|
||||
|
@ -1,21 +1,30 @@
|
||||
import time
|
||||
import copy
|
||||
import difflib
|
||||
import hashlib
|
||||
import itertools
|
||||
import re
|
||||
import time
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
from typing import Optional, Tuple
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from github import AppAuthentication, Auth, Github, GithubException
|
||||
from github import AppAuthentication, Auth, Github
|
||||
from retry import retry
|
||||
from starlette_context import context
|
||||
|
||||
from ..algo.file_filter import filter_ignored
|
||||
from ..algo.git_patch_processing import extract_hunk_headers
|
||||
from ..algo.language_handler import is_valid_file
|
||||
from ..algo.utils import PRReviewHeader, load_large_diff, clip_tokens, find_line_number_of_relevant_line_in_file
|
||||
from ..algo.types import EDIT_TYPE
|
||||
from ..algo.utils import (PRReviewHeader, Range, clip_tokens,
|
||||
find_line_number_of_relevant_line_in_file,
|
||||
load_large_diff, set_file_languages)
|
||||
from ..config_loader import get_settings
|
||||
from ..log import get_logger
|
||||
from ..servers.utils import RateLimitExceeded
|
||||
from .git_provider import GitProvider, IncrementalPR
|
||||
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
|
||||
from .git_provider import (MAX_FILES_ALLOWED_FULL, FilePatchInfo, GitProvider,
|
||||
IncrementalPR)
|
||||
|
||||
|
||||
class GithubProvider(GitProvider):
|
||||
@ -25,7 +34,8 @@ class GithubProvider(GitProvider):
|
||||
self.installation_id = context.get("installation_id", None)
|
||||
except Exception:
|
||||
self.installation_id = None
|
||||
self.base_url = get_settings().get("GITHUB.BASE_URL", "https://api.github.com").rstrip("/")
|
||||
self.max_comment_chars = 65000
|
||||
self.base_url = get_settings().get("GITHUB.BASE_URL", "https://api.github.com").rstrip("/") # "https://api.github.com"
|
||||
self.base_url_html = self.base_url.split("api/")[0].rstrip("/") if "api/" in self.base_url else "https://github.com"
|
||||
self.github_client = self._get_github_client()
|
||||
self.repo = None
|
||||
@ -164,23 +174,62 @@ class GithubProvider(GitProvider):
|
||||
|
||||
diff_files = []
|
||||
invalid_files_names = []
|
||||
is_close_to_rate_limit = False
|
||||
|
||||
# The base.sha will point to the current state of the base branch (including parallel merges), not the original base commit when the PR was created
|
||||
# We can fix this by finding the merge base commit between the PR head and base branches
|
||||
# Note that The pr.head.sha is actually correct as is - it points to the latest commit in your PR branch.
|
||||
# This SHA isn't affected by parallel merges to the base branch since it's specific to your PR's branch.
|
||||
repo = self.repo_obj
|
||||
pr = self.pr
|
||||
try:
|
||||
compare = repo.compare(pr.base.sha, pr.head.sha) # communication with GitHub
|
||||
merge_base_commit = compare.merge_base_commit
|
||||
except Exception as e:
|
||||
get_logger().error(f"Failed to get merge base commit: {e}")
|
||||
merge_base_commit = pr.base
|
||||
if merge_base_commit.sha != pr.base.sha:
|
||||
get_logger().info(
|
||||
f"Using merge base commit {merge_base_commit.sha} instead of base commit ")
|
||||
|
||||
counter_valid = 0
|
||||
for file in files:
|
||||
if not is_valid_file(file.filename):
|
||||
invalid_files_names.append(file.filename)
|
||||
continue
|
||||
|
||||
new_file_content_str = self._get_pr_file_content(file, self.pr.head.sha) # communication with GitHub
|
||||
patch = file.patch
|
||||
if is_close_to_rate_limit:
|
||||
new_file_content_str = ""
|
||||
original_file_content_str = ""
|
||||
else:
|
||||
# allow only a limited number of files to be fully loaded. We can manage the rest with diffs only
|
||||
counter_valid += 1
|
||||
avoid_load = False
|
||||
if counter_valid >= MAX_FILES_ALLOWED_FULL and patch and not self.incremental.is_incremental:
|
||||
avoid_load = True
|
||||
if counter_valid == MAX_FILES_ALLOWED_FULL:
|
||||
get_logger().info(f"Too many files in PR, will avoid loading full content for rest of files")
|
||||
|
||||
if avoid_load:
|
||||
new_file_content_str = ""
|
||||
else:
|
||||
new_file_content_str = self._get_pr_file_content(file, self.pr.head.sha) # communication with GitHub
|
||||
|
||||
if self.incremental.is_incremental and self.unreviewed_files_set:
|
||||
original_file_content_str = self._get_pr_file_content(file, self.incremental.last_seen_commit_sha)
|
||||
patch = load_large_diff(file.filename, new_file_content_str, original_file_content_str)
|
||||
self.unreviewed_files_set[file.filename] = patch
|
||||
else:
|
||||
original_file_content_str = self._get_pr_file_content(file, self.pr.base.sha)
|
||||
if avoid_load:
|
||||
original_file_content_str = ""
|
||||
else:
|
||||
original_file_content_str = self._get_pr_file_content(file, merge_base_commit.sha)
|
||||
# original_file_content_str = self._get_pr_file_content(file, self.pr.base.sha)
|
||||
if not patch:
|
||||
patch = load_large_diff(file.filename, new_file_content_str, original_file_content_str)
|
||||
|
||||
|
||||
if file.status == 'added':
|
||||
edit_type = EDIT_TYPE.ADDED
|
||||
elif file.status == 'removed':
|
||||
@ -194,9 +243,14 @@ class GithubProvider(GitProvider):
|
||||
edit_type = EDIT_TYPE.UNKNOWN
|
||||
|
||||
# count number of lines added and removed
|
||||
if hasattr(file, 'additions') and hasattr(file, 'deletions'):
|
||||
num_plus_lines = file.additions
|
||||
num_minus_lines = file.deletions
|
||||
else:
|
||||
patch_lines = patch.splitlines(keepends=True)
|
||||
num_plus_lines = len([line for line in patch_lines if line.startswith('+')])
|
||||
num_minus_lines = len([line for line in patch_lines if line.startswith('-')])
|
||||
|
||||
file_patch_canonical_structure = FilePatchInfo(original_file_content_str, new_file_content_str, patch,
|
||||
file.filename, edit_type=edit_type,
|
||||
num_plus_lines=num_plus_lines,
|
||||
@ -213,8 +267,9 @@ class GithubProvider(GitProvider):
|
||||
|
||||
return diff_files
|
||||
|
||||
except GithubException.RateLimitExceededException as e:
|
||||
get_logger().error(f"Rate limit exceeded for GitHub API. Original message: {e}")
|
||||
except Exception as e:
|
||||
get_logger().error(f"Failing to get diff files: {e}",
|
||||
artifact={"traceback": traceback.format_exc()})
|
||||
raise RateLimitExceeded("Rate limit exceeded for GitHub API.") from e
|
||||
|
||||
def publish_description(self, pr_title: str, pr_body: str):
|
||||
@ -236,8 +291,8 @@ class GithubProvider(GitProvider):
|
||||
def publish_comment(self, pr_comment: str, is_temporary: bool = False):
|
||||
if is_temporary and not get_settings().config.publish_output_progress:
|
||||
get_logger().debug(f"Skipping publish_comment for temporary comment: {pr_comment}")
|
||||
return
|
||||
|
||||
return None
|
||||
pr_comment = self.limit_output_characters(pr_comment, self.max_comment_chars)
|
||||
response = self.pr.create_issue_comment(pr_comment)
|
||||
if hasattr(response, "user") and hasattr(response.user, "login"):
|
||||
self.github_user_id = response.user.login
|
||||
@ -247,18 +302,19 @@ class GithubProvider(GitProvider):
|
||||
self.pr.comments_list.append(response)
|
||||
return response
|
||||
|
||||
def publish_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
|
||||
def publish_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str, original_suggestion=None):
|
||||
body = self.limit_output_characters(body, self.max_comment_chars)
|
||||
self.publish_inline_comments([self.create_inline_comment(body, relevant_file, relevant_line_in_file)])
|
||||
|
||||
|
||||
def create_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str,
|
||||
absolute_position: int = None):
|
||||
body = self.limit_output_characters(body, self.max_comment_chars)
|
||||
position, absolute_position = find_line_number_of_relevant_line_in_file(self.diff_files,
|
||||
relevant_file.strip('`'),
|
||||
relevant_line_in_file,
|
||||
absolute_position)
|
||||
if position == -1:
|
||||
if get_settings().config.verbosity_level >= 2:
|
||||
get_logger().info(f"Could not find position for {relevant_file} {relevant_line_in_file}")
|
||||
subject_type = "FILE"
|
||||
else:
|
||||
@ -271,11 +327,9 @@ class GithubProvider(GitProvider):
|
||||
# publish all comments in a single message
|
||||
self.pr.create_review(commit=self.last_commit_id, comments=comments)
|
||||
except Exception as e:
|
||||
if get_settings().config.verbosity_level >= 2:
|
||||
get_logger().error(f"Failed to publish inline comments")
|
||||
get_logger().info(f"Initially failed to publish inline comments as committable")
|
||||
|
||||
if (getattr(e, "status", None) == 422
|
||||
and get_settings().github.publish_inline_comments_fallback_with_verification and not disable_fallback):
|
||||
if (getattr(e, "status", None) == 422 and not disable_fallback):
|
||||
pass # continue to try _publish_inline_comments_fallback_with_verification
|
||||
else:
|
||||
raise e # will end up with publishing the comments one by one
|
||||
@ -283,7 +337,6 @@ class GithubProvider(GitProvider):
|
||||
try:
|
||||
self._publish_inline_comments_fallback_with_verification(comments)
|
||||
except Exception as e:
|
||||
if get_settings().config.verbosity_level >= 2:
|
||||
get_logger().error(f"Failed to publish inline code comments fallback, error: {e}")
|
||||
raise e
|
||||
|
||||
@ -309,10 +362,8 @@ class GithubProvider(GitProvider):
|
||||
for comment in fixed_comments_as_one_liner:
|
||||
try:
|
||||
self.publish_inline_comments([comment], disable_fallback=True)
|
||||
if get_settings().config.verbosity_level >= 2:
|
||||
get_logger().info(f"Published invalid comment as a single line comment: {comment}")
|
||||
except:
|
||||
if get_settings().config.verbosity_level >= 2:
|
||||
get_logger().error(f"Failed to publish invalid comment as a single line comment: {comment}")
|
||||
|
||||
def _verify_code_comment(self, comment: dict):
|
||||
@ -371,7 +422,6 @@ class GithubProvider(GitProvider):
|
||||
if fixed_comment != comment:
|
||||
fixed_comments.append(fixed_comment)
|
||||
except Exception as e:
|
||||
if get_settings().config.verbosity_level >= 2:
|
||||
get_logger().error(f"Failed to fix inline comment, error: {e}")
|
||||
return fixed_comments
|
||||
|
||||
@ -380,20 +430,21 @@ class GithubProvider(GitProvider):
|
||||
Publishes code suggestions as comments on the PR.
|
||||
"""
|
||||
post_parameters_list = []
|
||||
for suggestion in code_suggestions:
|
||||
|
||||
code_suggestions_validated = self.validate_comments_inside_hunks(code_suggestions)
|
||||
|
||||
for suggestion in code_suggestions_validated:
|
||||
body = suggestion['body']
|
||||
relevant_file = suggestion['relevant_file']
|
||||
relevant_lines_start = suggestion['relevant_lines_start']
|
||||
relevant_lines_end = suggestion['relevant_lines_end']
|
||||
|
||||
if not relevant_lines_start or relevant_lines_start == -1:
|
||||
if get_settings().config.verbosity_level >= 2:
|
||||
get_logger().exception(
|
||||
f"Failed to publish code suggestion, relevant_lines_start is {relevant_lines_start}")
|
||||
continue
|
||||
|
||||
if relevant_lines_end < relevant_lines_start:
|
||||
if get_settings().config.verbosity_level >= 2:
|
||||
get_logger().exception(f"Failed to publish code suggestion, "
|
||||
f"relevant_lines_end is {relevant_lines_end} and "
|
||||
f"relevant_lines_start is {relevant_lines_start}")
|
||||
@ -420,16 +471,28 @@ class GithubProvider(GitProvider):
|
||||
self.publish_inline_comments(post_parameters_list)
|
||||
return True
|
||||
except Exception as e:
|
||||
if get_settings().config.verbosity_level >= 2:
|
||||
get_logger().error(f"Failed to publish code suggestion, error: {e}")
|
||||
return False
|
||||
|
||||
def edit_comment(self, comment, body: str):
|
||||
body = self.limit_output_characters(body, self.max_comment_chars)
|
||||
comment.edit(body=body)
|
||||
|
||||
def edit_comment_from_comment_id(self, comment_id: int, body: str):
|
||||
try:
|
||||
# self.pr.get_issue_comment(comment_id).edit(body)
|
||||
body = self.limit_output_characters(body, self.max_comment_chars)
|
||||
headers, data_patch = self.pr._requester.requestJsonAndCheck(
|
||||
"PATCH", f"{self.base_url}/repos/{self.repo}/issues/comments/{comment_id}",
|
||||
input={"body": body}
|
||||
)
|
||||
except Exception as e:
|
||||
get_logger().exception(f"Failed to edit comment, error: {e}")
|
||||
|
||||
def reply_to_comment_from_comment_id(self, comment_id: int, body: str):
|
||||
try:
|
||||
# self.pr.get_issue_comment(comment_id).edit(body)
|
||||
body = self.limit_output_characters(body, self.max_comment_chars)
|
||||
headers, data_patch = self.pr._requester.requestJsonAndCheck(
|
||||
"POST", f"{self.base_url}/repos/{self.repo}/pulls/{self.pr_num}/comments/{comment_id}/replies",
|
||||
input={"body": body}
|
||||
@ -437,6 +500,51 @@ class GithubProvider(GitProvider):
|
||||
except Exception as e:
|
||||
get_logger().exception(f"Failed to reply comment, error: {e}")
|
||||
|
||||
def get_comment_body_from_comment_id(self, comment_id: int):
|
||||
try:
|
||||
# self.pr.get_issue_comment(comment_id).edit(body)
|
||||
headers, data_patch = self.pr._requester.requestJsonAndCheck(
|
||||
"GET", f"{self.base_url}/repos/{self.repo}/issues/comments/{comment_id}"
|
||||
)
|
||||
return data_patch.get("body","")
|
||||
except Exception as e:
|
||||
get_logger().exception(f"Failed to edit comment, error: {e}")
|
||||
return None
|
||||
|
||||
def publish_file_comments(self, file_comments: list) -> bool:
|
||||
try:
|
||||
headers, existing_comments = self.pr._requester.requestJsonAndCheck(
|
||||
"GET", f"{self.pr.url}/comments"
|
||||
)
|
||||
for comment in file_comments:
|
||||
comment['commit_id'] = self.last_commit_id.sha
|
||||
comment['body'] = self.limit_output_characters(comment['body'], self.max_comment_chars)
|
||||
|
||||
found = False
|
||||
for existing_comment in existing_comments:
|
||||
comment['commit_id'] = self.last_commit_id.sha
|
||||
our_app_name = get_settings().get("GITHUB.APP_NAME", "")
|
||||
same_comment_creator = False
|
||||
if self.deployment_type == 'app':
|
||||
same_comment_creator = our_app_name.lower() in existing_comment['user']['login'].lower()
|
||||
elif self.deployment_type == 'user':
|
||||
same_comment_creator = self.github_user_id == existing_comment['user']['login']
|
||||
if existing_comment['subject_type'] == 'file' and comment['path'] == existing_comment['path'] and same_comment_creator:
|
||||
|
||||
headers, data_patch = self.pr._requester.requestJsonAndCheck(
|
||||
"PATCH", f"{self.base_url}/repos/{self.repo}/pulls/comments/{existing_comment['id']}", input={"body":comment['body']}
|
||||
)
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
headers, data_post = self.pr._requester.requestJsonAndCheck(
|
||||
"POST", f"{self.pr.url}/comments", input=comment
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
get_logger().error(f"Failed to publish diffview file summary, error: {e}")
|
||||
return False
|
||||
|
||||
def remove_initial_comment(self):
|
||||
try:
|
||||
for comment in getattr(self.pr, 'comments_list', []):
|
||||
@ -461,6 +569,11 @@ class GithubProvider(GitProvider):
|
||||
def get_pr_branch(self):
|
||||
return self.pr.head.ref
|
||||
|
||||
def get_pr_owner_id(self) -> str | None:
|
||||
if not self.repo:
|
||||
return None
|
||||
return self.repo.split('/')[0]
|
||||
|
||||
def get_pr_description_full(self):
|
||||
return self.pr.body
|
||||
|
||||
@ -495,6 +608,9 @@ class GithubProvider(GitProvider):
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
def get_workspace_name(self):
|
||||
return self.repo.split('/')[0]
|
||||
|
||||
def add_eyes_reaction(self, issue_comment_id: int, disable_eyes: bool = False) -> Optional[int]:
|
||||
if disable_eyes:
|
||||
return None
|
||||
@ -505,7 +621,7 @@ class GithubProvider(GitProvider):
|
||||
)
|
||||
return data_patch.get("id", None)
|
||||
except Exception as e:
|
||||
get_logger().exception(f"Failed to add eyes reaction, error: {e}")
|
||||
get_logger().warning(f"Failed to add eyes reaction, error: {e}")
|
||||
return None
|
||||
|
||||
def remove_reaction(self, issue_comment_id: int, reaction_id: str) -> bool:
|
||||
@ -520,15 +636,14 @@ class GithubProvider(GitProvider):
|
||||
get_logger().exception(f"Failed to remove eyes reaction, error: {e}")
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _parse_pr_url(pr_url: str) -> Tuple[str, int]:
|
||||
def _parse_pr_url(self, pr_url: str) -> Tuple[str, int]:
|
||||
parsed_url = urlparse(pr_url)
|
||||
|
||||
if 'github.com' not in parsed_url.netloc:
|
||||
raise ValueError("The provided URL is not a valid GitHub URL")
|
||||
if parsed_url.path.startswith('/api/v3'):
|
||||
parsed_url = urlparse(pr_url.replace("/api/v3", ""))
|
||||
|
||||
path_parts = parsed_url.path.strip('/').split('/')
|
||||
if 'api.github.com' in parsed_url.netloc:
|
||||
if 'api.github.com' in parsed_url.netloc or '/api/v3' in pr_url:
|
||||
if len(path_parts) < 5 or path_parts[3] != 'pulls':
|
||||
raise ValueError("The provided URL does not appear to be a GitHub PR URL")
|
||||
repo_name = '/'.join(path_parts[1:3])
|
||||
@ -549,8 +664,7 @@ class GithubProvider(GitProvider):
|
||||
|
||||
return repo_name, pr_number
|
||||
|
||||
@staticmethod
|
||||
def _parse_issue_url(issue_url: str) -> Tuple[str, int]:
|
||||
def _parse_issue_url(self, issue_url: str) -> Tuple[str, int]:
|
||||
parsed_url = urlparse(issue_url)
|
||||
|
||||
if 'github.com' not in parsed_url.netloc:
|
||||
@ -658,7 +772,7 @@ class GithubProvider(GitProvider):
|
||||
"PUT", f"{self.pr.issue_url}/labels", input=post_parameters
|
||||
)
|
||||
except Exception as e:
|
||||
get_logger().exception(f"Failed to publish labels, error: {e}")
|
||||
get_logger().warning(f"Failed to publish labels, error: {e}")
|
||||
|
||||
def get_pr_labels(self, update=False):
|
||||
try:
|
||||
@ -676,7 +790,7 @@ class GithubProvider(GitProvider):
|
||||
|
||||
def get_repo_labels(self):
|
||||
labels = self.repo_obj.get_labels()
|
||||
return [label for label in labels]
|
||||
return [label for label in itertools.islice(labels, 50)]
|
||||
|
||||
def get_commit_messages(self):
|
||||
"""
|
||||
@ -716,7 +830,6 @@ class GithubProvider(GitProvider):
|
||||
link = f"{self.base_url_html}/{self.repo}/pull/{self.pr_num}/files#diff-{sha_file}R{absolute_position}"
|
||||
return link
|
||||
except Exception as e:
|
||||
if get_settings().config.verbosity_level >= 2:
|
||||
get_logger().info(f"Failed adding line link, error: {e}")
|
||||
|
||||
return ""
|
||||
@ -731,6 +844,32 @@ class GithubProvider(GitProvider):
|
||||
link = f"{self.base_url_html}/{self.repo}/pull/{self.pr_num}/files#diff-{sha_file}R{relevant_line_start}"
|
||||
return link
|
||||
|
||||
def get_lines_link_original_file(self, filepath: str, component_range: Range) -> str:
|
||||
"""
|
||||
Returns the link to the original file on GitHub that corresponds to the given filepath and component range.
|
||||
|
||||
Args:
|
||||
filepath (str): The path of the file.
|
||||
component_range (Range): The range of lines that represent the component.
|
||||
|
||||
Returns:
|
||||
str: The link to the original file on GitHub.
|
||||
|
||||
Example:
|
||||
>>> filepath = "path/to/file.py"
|
||||
>>> component_range = Range(line_start=10, line_end=20)
|
||||
>>> link = get_lines_link_original_file(filepath, component_range)
|
||||
>>> print(link)
|
||||
"https://github.com/{repo}/blob/{commit_sha}/{filepath}/#L11-L21"
|
||||
"""
|
||||
line_start = component_range.line_start + 1
|
||||
line_end = component_range.line_end + 1
|
||||
# link = (f"https://github.com/{self.repo}/blob/{self.last_commit_id.sha}/{filepath}/"
|
||||
# f"#L{line_start}-L{line_end}")
|
||||
link = (f"{self.base_url_html}/{self.repo}/blob/{self.last_commit_id.sha}/{filepath}/"
|
||||
f"#L{line_start}-L{line_end}")
|
||||
|
||||
return link
|
||||
|
||||
def get_pr_id(self):
|
||||
try:
|
||||
@ -751,3 +890,89 @@ class GithubProvider(GitProvider):
|
||||
|
||||
def calc_pr_statistics(self, pull_request_data: dict):
|
||||
return {}
|
||||
|
||||
def validate_comments_inside_hunks(self, code_suggestions):
|
||||
"""
|
||||
validate that all committable comments are inside PR hunks - this is a must for committable comments in GitHub
|
||||
"""
|
||||
code_suggestions_copy = copy.deepcopy(code_suggestions)
|
||||
diff_files = self.get_diff_files()
|
||||
RE_HUNK_HEADER = re.compile(
|
||||
r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@[ ]?(.*)")
|
||||
|
||||
diff_files = set_file_languages(diff_files)
|
||||
|
||||
for suggestion in code_suggestions_copy:
|
||||
try:
|
||||
relevant_file_path = suggestion['relevant_file']
|
||||
for file in diff_files:
|
||||
if file.filename == relevant_file_path:
|
||||
|
||||
# generate on-demand the patches range for the relevant file
|
||||
patch_str = file.patch
|
||||
if not hasattr(file, 'patches_range'):
|
||||
file.patches_range = []
|
||||
patch_lines = patch_str.splitlines()
|
||||
for i, line in enumerate(patch_lines):
|
||||
if line.startswith('@@'):
|
||||
match = RE_HUNK_HEADER.match(line)
|
||||
# identify hunk header
|
||||
if match:
|
||||
section_header, size1, size2, start1, start2 = extract_hunk_headers(match)
|
||||
file.patches_range.append({'start': start2, 'end': start2 + size2 - 1})
|
||||
|
||||
patches_range = file.patches_range
|
||||
comment_start_line = suggestion.get('relevant_lines_start', None)
|
||||
comment_end_line = suggestion.get('relevant_lines_end', None)
|
||||
original_suggestion = suggestion.get('original_suggestion', None) # needed for diff code
|
||||
if not comment_start_line or not comment_end_line or not original_suggestion:
|
||||
continue
|
||||
|
||||
# check if the comment is inside a valid hunk
|
||||
is_valid_hunk = False
|
||||
min_distance = float('inf')
|
||||
patch_range_min = None
|
||||
# find the hunk that contains the comment, or the closest one
|
||||
for i, patch_range in enumerate(patches_range):
|
||||
d1 = comment_start_line - patch_range['start']
|
||||
d2 = patch_range['end'] - comment_end_line
|
||||
if d1 >= 0 and d2 >= 0: # found a valid hunk
|
||||
is_valid_hunk = True
|
||||
min_distance = 0
|
||||
patch_range_min = patch_range
|
||||
break
|
||||
elif d1 * d2 <= 0: # comment is possibly inside the hunk
|
||||
d1_clip = abs(min(0, d1))
|
||||
d2_clip = abs(min(0, d2))
|
||||
d = max(d1_clip, d2_clip)
|
||||
if d < min_distance:
|
||||
patch_range_min = patch_range
|
||||
min_distance = min(min_distance, d)
|
||||
if not is_valid_hunk:
|
||||
if min_distance < 10: # 10 lines - a reasonable distance to consider the comment inside the hunk
|
||||
# make the suggestion non-committable, yet multi line
|
||||
suggestion['relevant_lines_start'] = max(suggestion['relevant_lines_start'], patch_range_min['start'])
|
||||
suggestion['relevant_lines_end'] = min(suggestion['relevant_lines_end'], patch_range_min['end'])
|
||||
body = suggestion['body'].strip()
|
||||
|
||||
# present new diff code in collapsible
|
||||
existing_code = original_suggestion['existing_code'].rstrip() + "\n"
|
||||
improved_code = original_suggestion['improved_code'].rstrip() + "\n"
|
||||
diff = difflib.unified_diff(existing_code.split('\n'),
|
||||
improved_code.split('\n'), n=999)
|
||||
patch_orig = "\n".join(diff)
|
||||
patch = "\n".join(patch_orig.splitlines()[5:]).strip('\n')
|
||||
diff_code = f"\n\n<details><summary>New proposed code:</summary>\n\n```diff\n{patch.rstrip()}\n```"
|
||||
# replace ```suggestion ... ``` with diff_code, using regex:
|
||||
body = re.sub(r'```suggestion.*?```', diff_code, body, flags=re.DOTALL)
|
||||
body += "\n\n</details>"
|
||||
suggestion['body'] = body
|
||||
get_logger().info(f"Comment was moved to a valid hunk, "
|
||||
f"start_line={suggestion['relevant_lines_start']}, end_line={suggestion['relevant_lines_end']}, file={file.filename}")
|
||||
else:
|
||||
get_logger().error(f"Comment is not inside a valid hunk, "
|
||||
f"start_line={suggestion['relevant_lines_start']}, end_line={suggestion['relevant_lines_end']}, file={file.filename}")
|
||||
except Exception as e:
|
||||
get_logger().error(f"Failed to process patch for committable comment, error: {e}")
|
||||
return code_suggestions_copy
|
||||
|
||||
|
@ -1,18 +1,23 @@
|
||||
import difflib
|
||||
import hashlib
|
||||
import re
|
||||
from typing import Optional, Tuple
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import gitlab
|
||||
import requests
|
||||
from gitlab import GitlabGetError
|
||||
|
||||
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
|
||||
|
||||
from ..algo.file_filter import filter_ignored
|
||||
from ..algo.language_handler import is_valid_file
|
||||
from ..algo.utils import load_large_diff, clip_tokens, find_line_number_of_relevant_line_in_file
|
||||
from ..algo.utils import (clip_tokens,
|
||||
find_line_number_of_relevant_line_in_file,
|
||||
load_large_diff)
|
||||
from ..config_loader import get_settings
|
||||
from .git_provider import GitProvider
|
||||
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
|
||||
from ..log import get_logger
|
||||
from .git_provider import MAX_FILES_ALLOWED_FULL, GitProvider
|
||||
|
||||
|
||||
class DiffNotFoundError(Exception):
|
||||
@ -25,6 +30,7 @@ class GitLabProvider(GitProvider):
|
||||
gitlab_url = get_settings().get("GITLAB.URL", None)
|
||||
if not gitlab_url:
|
||||
raise ValueError("GitLab URL is not set in the config file")
|
||||
self.gitlab_url = gitlab_url
|
||||
gitlab_access_token = get_settings().get("GITLAB.PERSONAL_ACCESS_TOKEN", None)
|
||||
if not gitlab_access_token:
|
||||
raise ValueError("GitLab personal access token is not set in the config file")
|
||||
@ -32,6 +38,7 @@ class GitLabProvider(GitProvider):
|
||||
url=gitlab_url,
|
||||
oauth_token=gitlab_access_token
|
||||
)
|
||||
self.max_comment_chars = 65000
|
||||
self.id_project = None
|
||||
self.id_mr = None
|
||||
self.mr = None
|
||||
@ -45,7 +52,8 @@ class GitLabProvider(GitProvider):
|
||||
self.incremental = incremental
|
||||
|
||||
def is_supported(self, capability: str) -> bool:
|
||||
if capability in ['get_issue_comments', 'create_inline_comment', 'publish_inline_comments']: # gfm_markdown is supported in gitlab !
|
||||
if capability in ['get_issue_comments', 'create_inline_comment', 'publish_inline_comments',
|
||||
'publish_file_comments']: # gfm_markdown is supported in gitlab !
|
||||
return False
|
||||
return True
|
||||
|
||||
@ -101,13 +109,23 @@ class GitLabProvider(GitProvider):
|
||||
|
||||
diff_files = []
|
||||
invalid_files_names = []
|
||||
counter_valid = 0
|
||||
for diff in diffs:
|
||||
if not is_valid_file(diff['new_path']):
|
||||
invalid_files_names.append(diff['new_path'])
|
||||
continue
|
||||
|
||||
# allow only a limited number of files to be fully loaded. We can manage the rest with diffs only
|
||||
counter_valid += 1
|
||||
if counter_valid < MAX_FILES_ALLOWED_FULL or not diff['diff']:
|
||||
original_file_content_str = self.get_pr_file_content(diff['old_path'], self.mr.diff_refs['base_sha'])
|
||||
new_file_content_str = self.get_pr_file_content(diff['new_path'], self.mr.diff_refs['head_sha'])
|
||||
else:
|
||||
if counter_valid == MAX_FILES_ALLOWED_FULL:
|
||||
get_logger().info(f"Too many files in PR, will avoid loading full content for rest of files")
|
||||
original_file_content_str = ''
|
||||
new_file_content_str = ''
|
||||
|
||||
try:
|
||||
if isinstance(original_file_content_str, bytes):
|
||||
original_file_content_str = bytes.decode(original_file_content_str, 'utf-8')
|
||||
@ -176,28 +194,36 @@ class GitLabProvider(GitProvider):
|
||||
self.publish_persistent_comment_full(pr_comment, initial_header, update_header, name, final_update_message)
|
||||
|
||||
def publish_comment(self, mr_comment: str, is_temporary: bool = False):
|
||||
if is_temporary and not get_settings().config.publish_output_progress:
|
||||
get_logger().debug(f"Skipping publish_comment for temporary comment: {mr_comment}")
|
||||
return None
|
||||
mr_comment = self.limit_output_characters(mr_comment, self.max_comment_chars)
|
||||
comment = self.mr.notes.create({'body': mr_comment})
|
||||
if is_temporary:
|
||||
self.temp_comments.append(comment)
|
||||
return comment
|
||||
|
||||
def edit_comment(self, comment, body: str):
|
||||
body = self.limit_output_characters(body, self.max_comment_chars)
|
||||
self.mr.notes.update(comment.id,{'body': body} )
|
||||
|
||||
def edit_comment_from_comment_id(self, comment_id: int, body: str):
|
||||
body = self.limit_output_characters(body, self.max_comment_chars)
|
||||
comment = self.mr.notes.get(comment_id)
|
||||
comment.body = body
|
||||
comment.save()
|
||||
|
||||
def reply_to_comment_from_comment_id(self, comment_id: int, body: str):
|
||||
body = self.limit_output_characters(body, self.max_comment_chars)
|
||||
discussion = self.mr.discussions.get(comment_id)
|
||||
discussion.notes.create({'body': body})
|
||||
|
||||
def publish_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
|
||||
def publish_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str, original_suggestion=None):
|
||||
body = self.limit_output_characters(body, self.max_comment_chars)
|
||||
edit_type, found, source_line_no, target_file, target_line_no = self.search_line(relevant_file,
|
||||
relevant_line_in_file)
|
||||
self.send_inline_comment(body, edit_type, found, relevant_file, relevant_line_in_file, source_line_no,
|
||||
target_file, target_line_no)
|
||||
target_file, target_line_no, original_suggestion)
|
||||
|
||||
def create_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str, absolute_position: int = None):
|
||||
raise NotImplementedError("Gitlab provider does not support creating inline comments yet")
|
||||
@ -206,11 +232,13 @@ class GitLabProvider(GitProvider):
|
||||
raise NotImplementedError("Gitlab provider does not support publishing inline comments yet")
|
||||
|
||||
def get_comment_body_from_comment_id(self, comment_id: int):
|
||||
comment = self.mr.notes.get(comment_id)
|
||||
comment = self.mr.notes.get(comment_id).body
|
||||
return comment
|
||||
|
||||
def send_inline_comment(self,body: str,edit_type: str,found: bool,relevant_file: str,relevant_line_in_file: int,
|
||||
source_line_no: int, target_file: str,target_line_no: int) -> None:
|
||||
def send_inline_comment(self, body: str, edit_type: str, found: bool, relevant_file: str,
|
||||
relevant_line_in_file: str,
|
||||
source_line_no: int, target_file: str, target_line_no: int,
|
||||
original_suggestion=None) -> None:
|
||||
if not found:
|
||||
get_logger().info(f"Could not find position for {relevant_file} {relevant_line_in_file}")
|
||||
else:
|
||||
@ -230,14 +258,67 @@ class GitLabProvider(GitProvider):
|
||||
else:
|
||||
pos_obj['new_line'] = target_line_no - 1
|
||||
pos_obj['old_line'] = source_line_no - 1
|
||||
get_logger().debug(f"Creating comment in {self.id_mr} with body {body} and position {pos_obj}")
|
||||
get_logger().debug(f"Creating comment in MR {self.id_mr} with body {body} and position {pos_obj}")
|
||||
try:
|
||||
self.mr.discussions.create({'body': body, 'position': pos_obj})
|
||||
except Exception as e:
|
||||
get_logger().debug(
|
||||
f"Failed to create comment in {self.id_mr} with position {pos_obj} (probably not a '+' line)")
|
||||
try:
|
||||
# fallback - create a general note on the file in the MR
|
||||
if 'suggestion_orig_location' in original_suggestion:
|
||||
line_start = original_suggestion['suggestion_orig_location']['start_line']
|
||||
line_end = original_suggestion['suggestion_orig_location']['end_line']
|
||||
old_code_snippet = original_suggestion['prev_code_snippet']
|
||||
new_code_snippet = original_suggestion['new_code_snippet']
|
||||
content = original_suggestion['suggestion_summary']
|
||||
label = original_suggestion['category']
|
||||
if 'score' in original_suggestion:
|
||||
score = original_suggestion['score']
|
||||
else:
|
||||
score = 7
|
||||
else:
|
||||
line_start = original_suggestion['relevant_lines_start']
|
||||
line_end = original_suggestion['relevant_lines_end']
|
||||
old_code_snippet = original_suggestion['existing_code']
|
||||
new_code_snippet = original_suggestion['improved_code']
|
||||
content = original_suggestion['suggestion_content']
|
||||
label = original_suggestion['label']
|
||||
score = original_suggestion.get('score', 7)
|
||||
|
||||
def get_relevant_diff(self, relevant_file: str, relevant_line_in_file: int) -> Optional[dict]:
|
||||
if hasattr(self, 'main_language'):
|
||||
language = self.main_language
|
||||
else:
|
||||
language = ''
|
||||
link = self.get_line_link(relevant_file, line_start, line_end)
|
||||
body_fallback =f"**Suggestion:** {content} [{label}, importance: {score}]\n\n"
|
||||
body_fallback +=f"\n\n<details><summary>[{target_file.filename} [{line_start}-{line_end}]]({link}):</summary>\n\n"
|
||||
body_fallback += f"\n\n___\n\n`(Cannot implement directly - GitLab API allows committable suggestions strictly on MR diff lines)`"
|
||||
body_fallback+="</details>\n\n"
|
||||
diff_patch = difflib.unified_diff(old_code_snippet.split('\n'),
|
||||
new_code_snippet.split('\n'), n=999)
|
||||
patch_orig = "\n".join(diff_patch)
|
||||
patch = "\n".join(patch_orig.splitlines()[5:]).strip('\n')
|
||||
diff_code = f"\n\n```diff\n{patch.rstrip()}\n```"
|
||||
body_fallback += diff_code
|
||||
|
||||
# Create a general note on the file in the MR
|
||||
self.mr.notes.create({
|
||||
'body': body_fallback,
|
||||
'position': {
|
||||
'base_sha': diff.base_commit_sha,
|
||||
'start_sha': diff.start_commit_sha,
|
||||
'head_sha': diff.head_commit_sha,
|
||||
'position_type': 'text',
|
||||
'file_path': f'{target_file.filename}',
|
||||
}
|
||||
})
|
||||
get_logger().debug(f"Created fallback comment in MR {self.id_mr} with position {pos_obj}")
|
||||
|
||||
# get_logger().debug(
|
||||
# f"Failed to create comment in MR {self.id_mr} with position {pos_obj} (probably not a '+' line)")
|
||||
except Exception as e:
|
||||
get_logger().exception(f"Failed to create comment in MR {self.id_mr}")
|
||||
|
||||
def get_relevant_diff(self, relevant_file: str, relevant_line_in_file: str) -> Optional[dict]:
|
||||
changes = self.mr.changes() # Retrieve the changes for the merge request once
|
||||
if not changes:
|
||||
get_logger().error('No changes found for the merge request.')
|
||||
@ -257,6 +338,10 @@ class GitLabProvider(GitProvider):
|
||||
def publish_code_suggestions(self, code_suggestions: list) -> bool:
|
||||
for suggestion in code_suggestions:
|
||||
try:
|
||||
if suggestion and 'original_suggestion' in suggestion:
|
||||
original_suggestion = suggestion['original_suggestion']
|
||||
else:
|
||||
original_suggestion = suggestion
|
||||
body = suggestion['body']
|
||||
relevant_file = suggestion['relevant_file']
|
||||
relevant_lines_start = suggestion['relevant_lines_start']
|
||||
@ -277,19 +362,22 @@ class GitLabProvider(GitProvider):
|
||||
# edit_type, found, source_line_no, target_file, target_line_no = self.find_in_file(target_file,
|
||||
# relevant_line_in_file)
|
||||
# for code suggestions, we want to edit the new code
|
||||
source_line_no = None
|
||||
source_line_no = -1
|
||||
target_line_no = relevant_lines_start + 1
|
||||
found = True
|
||||
edit_type = 'addition'
|
||||
|
||||
self.send_inline_comment(body, edit_type, found, relevant_file, relevant_line_in_file, source_line_no,
|
||||
target_file, target_line_no)
|
||||
target_file, target_line_no, original_suggestion)
|
||||
except Exception as e:
|
||||
get_logger().exception(f"Could not publish code suggestion:\nsuggestion: {suggestion}\nerror: {e}")
|
||||
|
||||
# note that we publish suggestions one-by-one. so, if one fails, the rest will still be published
|
||||
return True
|
||||
|
||||
def publish_file_comments(self, file_comments: list) -> bool:
|
||||
pass
|
||||
|
||||
def search_line(self, relevant_file, relevant_line_in_file):
|
||||
target_file = None
|
||||
|
||||
@ -367,6 +455,15 @@ class GitLabProvider(GitProvider):
|
||||
def get_pr_branch(self):
|
||||
return self.mr.source_branch
|
||||
|
||||
def get_pr_owner_id(self) -> str | None:
|
||||
if not self.gitlab_url or 'gitlab.com' in self.gitlab_url:
|
||||
if not self.id_project:
|
||||
return None
|
||||
return self.id_project.split('/')[0]
|
||||
# extract host name
|
||||
host = urlparse(self.gitlab_url).hostname
|
||||
return host
|
||||
|
||||
def get_pr_description_full(self):
|
||||
return self.mr.description
|
||||
|
||||
@ -380,6 +477,9 @@ class GitLabProvider(GitProvider):
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
def get_workspace_name(self):
|
||||
return self.id_project.split('/')[0]
|
||||
|
||||
def add_eyes_reaction(self, issue_comment_id: int, disable_eyes: bool = False) -> Optional[int]:
|
||||
return True
|
||||
|
||||
@ -423,7 +523,7 @@ class GitLabProvider(GitProvider):
|
||||
self.mr.labels = list(set(pr_types))
|
||||
self.mr.save()
|
||||
except Exception as e:
|
||||
get_logger().exception(f"Failed to publish labels, error: {e}")
|
||||
get_logger().warning(f"Failed to publish labels, error: {e}")
|
||||
|
||||
def publish_inline_comments(self, comments: list[dict]):
|
||||
pass
|
||||
@ -462,7 +562,7 @@ class GitLabProvider(GitProvider):
|
||||
if relevant_line_start == -1:
|
||||
link = f"{self.gl.url}/{self.id_project}/-/blob/{self.mr.source_branch}/{relevant_file}?ref_type=heads"
|
||||
elif relevant_line_end:
|
||||
link = f"{self.gl.url}/{self.id_project}/-/blob/{self.mr.source_branch}/{relevant_file}?ref_type=heads#L{relevant_line_start}-L{relevant_line_end}"
|
||||
link = f"{self.gl.url}/{self.id_project}/-/blob/{self.mr.source_branch}/{relevant_file}?ref_type=heads#L{relevant_line_start}-{relevant_line_end}"
|
||||
else:
|
||||
link = f"{self.gl.url}/{self.id_project}/-/blob/{self.mr.source_branch}/{relevant_file}?ref_type=heads#L{relevant_line_start}"
|
||||
return link
|
||||
|
@ -4,9 +4,9 @@ from typing import List
|
||||
|
||||
from git import Repo
|
||||
|
||||
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
|
||||
from pr_agent.config_loader import _find_repository_root, get_settings
|
||||
from pr_agent.git_providers.git_provider import GitProvider
|
||||
from pr_agent.algo.types import EDIT_TYPE, FilePatchInfo
|
||||
from pr_agent.log import get_logger
|
||||
|
||||
|
||||
@ -119,7 +119,7 @@ class LocalGitProvider(GitProvider):
|
||||
# Write the string to the file
|
||||
file.write(pr_comment)
|
||||
|
||||
def publish_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str):
|
||||
def publish_inline_comment(self, body: str, relevant_file: str, relevant_line_in_file: str, original_suggestion=None):
|
||||
raise NotImplementedError('Publishing inline comments is not implemented for the local git provider')
|
||||
|
||||
def publish_inline_comments(self, comments: list[dict]):
|
||||
@ -141,6 +141,18 @@ class LocalGitProvider(GitProvider):
|
||||
def remove_comment(self, comment):
|
||||
pass # Not applicable to the local git provider, but required by the interface
|
||||
|
||||
def add_eyes_reaction(self, comment):
|
||||
pass # Not applicable to the local git provider, but required by the interface
|
||||
|
||||
def get_commit_messages(self):
|
||||
pass # Not applicable to the local git provider, but required by the interface
|
||||
|
||||
def get_repo_settings(self):
|
||||
pass # Not applicable to the local git provider, but required by the interface
|
||||
|
||||
def remove_reaction(self, comment):
|
||||
pass # Not applicable to the local git provider, but required by the interface
|
||||
|
||||
def get_languages(self):
|
||||
"""
|
||||
Calculate percentage of languages in repository. Used for hunk prioritisation.
|
||||
|
@ -3,11 +3,12 @@ import os
|
||||
import tempfile
|
||||
|
||||
from dynaconf import Dynaconf
|
||||
from starlette_context import context
|
||||
|
||||
from pr_agent.config_loader import get_settings
|
||||
from pr_agent.git_providers import get_git_provider, get_git_provider_with_context
|
||||
from pr_agent.git_providers import (get_git_provider,
|
||||
get_git_provider_with_context)
|
||||
from pr_agent.log import get_logger
|
||||
from starlette_context import context
|
||||
|
||||
|
||||
def apply_repo_settings(pr_url):
|
||||
@ -27,8 +28,11 @@ def apply_repo_settings(pr_url):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
error_local = None
|
||||
if repo_settings:
|
||||
repo_settings_file = None
|
||||
category = 'local'
|
||||
try:
|
||||
fd, repo_settings_file = tempfile.mkstemp(suffix='.toml')
|
||||
os.write(fd, repo_settings)
|
||||
new_settings = Dynaconf(settings_files=[repo_settings_file])
|
||||
@ -39,6 +43,12 @@ def apply_repo_settings(pr_url):
|
||||
get_settings().unset(section)
|
||||
get_settings().set(section, section_dict, merge=False)
|
||||
get_logger().info(f"Applying repo settings:\n{new_settings.as_dict()}")
|
||||
except Exception as e:
|
||||
get_logger().warning(f"Failed to apply repo {category} settings, error: {str(e)}")
|
||||
error_local = {'error': str(e), 'settings': repo_settings, 'category': category}
|
||||
|
||||
if error_local:
|
||||
handle_configurations_errors([error_local], git_provider)
|
||||
except Exception as e:
|
||||
get_logger().exception("Failed to apply repo settings", e)
|
||||
finally:
|
||||
@ -47,3 +57,47 @@ def apply_repo_settings(pr_url):
|
||||
os.remove(repo_settings_file)
|
||||
except Exception as e:
|
||||
get_logger().error(f"Failed to remove temporary settings file {repo_settings_file}", e)
|
||||
|
||||
# enable switching models with a short definition
|
||||
if get_settings().config.model.lower() == 'claude-3-5-sonnet':
|
||||
set_claude_model()
|
||||
|
||||
|
||||
def handle_configurations_errors(config_errors, git_provider):
|
||||
try:
|
||||
if not any(config_errors):
|
||||
return
|
||||
|
||||
for err in config_errors:
|
||||
if err:
|
||||
configuration_file_content = err['settings'].decode()
|
||||
err_message = err['error']
|
||||
config_type = err['category']
|
||||
header = f"❌ **PR-Agent failed to apply '{config_type}' repo settings**"
|
||||
body = f"{header}\n\nThe configuration file needs to be a valid [TOML](https://qodo-merge-docs.qodo.ai/usage-guide/configuration_options/), please fix it.\n\n"
|
||||
body += f"___\n\n**Error message:**\n`{err_message}`\n\n"
|
||||
if git_provider.is_supported("gfm_markdown"):
|
||||
body += f"\n\n<details><summary>Configuration content:</summary>\n\n```toml\n{configuration_file_content}\n```\n\n</details>"
|
||||
else:
|
||||
body += f"\n\n**Configuration content:**\n\n```toml\n{configuration_file_content}\n```\n\n"
|
||||
get_logger().warning(f"Sending a 'configuration error' comment to the PR", artifact={'body': body})
|
||||
# git_provider.publish_comment(body)
|
||||
if hasattr(git_provider, 'publish_persistent_comment'):
|
||||
git_provider.publish_persistent_comment(body,
|
||||
initial_header=header,
|
||||
update_header=False,
|
||||
final_update_message=False)
|
||||
else:
|
||||
git_provider.publish_comment(body)
|
||||
except Exception as e:
|
||||
get_logger().exception(f"Failed to handle configurations errors", e)
|
||||
|
||||
|
||||
def set_claude_model():
|
||||
"""
|
||||
set the claude-sonnet-3.5 model easily (even by users), just by stating: --config.model='claude-3-5-sonnet'
|
||||
"""
|
||||
model_claude = "bedrock/anthropic.claude-3-5-sonnet-20240620-v1:0"
|
||||
get_settings().set('config.model', model_claude)
|
||||
get_settings().set('config.model_weak', model_claude)
|
||||
get_settings().set('config.fallback_models', [model_claude])
|
||||
|
@ -1,5 +1,6 @@
|
||||
from pr_agent.config_loader import get_settings
|
||||
from pr_agent.identity_providers.default_identity_provider import DefaultIdentityProvider
|
||||
from pr_agent.identity_providers.default_identity_provider import \
|
||||
DefaultIdentityProvider
|
||||
|
||||
_IDENTITY_PROVIDERS = {
|
||||
'default': DefaultIdentityProvider
|
||||
|
@ -1,4 +1,5 @@
|
||||
from pr_agent.identity_providers.identity_provider import Eligibility, IdentityProvider
|
||||
from pr_agent.identity_providers.identity_provider import (Eligibility,
|
||||
IdentityProvider)
|
||||
|
||||
|
||||
class DefaultIdentityProvider(IdentityProvider):
|
||||
|
@ -8,12 +8,10 @@ def get_secret_provider():
|
||||
provider_id = get_settings().config.secret_provider
|
||||
if provider_id == 'google_cloud_storage':
|
||||
try:
|
||||
from pr_agent.secret_providers.google_cloud_storage_secret_provider import GoogleCloudStorageSecretProvider
|
||||
from pr_agent.secret_providers.google_cloud_storage_secret_provider import \
|
||||
GoogleCloudStorageSecretProvider
|
||||
return GoogleCloudStorageSecretProvider()
|
||||
except Exception as e:
|
||||
raise ValueError(f"Failed to initialize google_cloud_storage secret provider {provider_id}") from e
|
||||
else:
|
||||
raise ValueError("Unknown SECRET_PROVIDER")
|
||||
|
||||
|
||||
|
||||
|
@ -22,7 +22,7 @@ class GoogleCloudStorageSecretProvider(SecretProvider):
|
||||
blob = self.bucket.blob(secret_name)
|
||||
return blob.download_as_string()
|
||||
except Exception as e:
|
||||
get_logger().error(f"Failed to get secret {secret_name} from Google Cloud Storage: {e}")
|
||||
get_logger().warning(f"Failed to get secret {secret_name} from Google Cloud Storage: {e}")
|
||||
return ""
|
||||
|
||||
def store_secret(self, secret_name: str, secret_value: str):
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user