diff --git a/locales/ca/README.md b/locales/ca/README.md index 5712bb949b..6f48f051ad 100644 --- a/locales/ca/README.md +++ b/locales/ca/README.md @@ -181,37 +181,35 @@ Ens encanten les contribucions de la comunitat! Comenceu llegint el nostre [CONT Gràcies a tots els nostres col·laboradors que han ajudat a millorar Roo Code! - -| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| -| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| ColemanRoo
ColemanRoo
| hannesrudolph
hannesrudolph
| KJ7LNW
KJ7LNW
| stea9499
stea9499
| canrobins13
canrobins13
| joemanley201
joemanley201
| -| System233
System233
| nissa-seru
nissa-seru
| jquanton
jquanton
| NyxJae
NyxJae
| MuriloFP
MuriloFP
| d-oit
d-oit
| -| punkpeye
punkpeye
| wkordalski
wkordalski
| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| monotykamary
monotykamary
| elianiva
elianiva
| feifei325
feifei325
| -| zhangtony239
zhangtony239
| cannuri
cannuri
| lloydchang
lloydchang
| sachasayan
sachasayan
| vigneshsubbiah16
vigneshsubbiah16
| qdaxb
qdaxb
| -| Szpadel
Szpadel
| diarmidmackenzie
diarmidmackenzie
| psv2522
psv2522
| Premshay
Premshay
| lupuletic
lupuletic
| shariqriazz
shariqriazz
| -| xyOz-dev
xyOz-dev
| pugazhendhi-m
pugazhendhi-m
| aheizi
aheizi
| olweraltuve
olweraltuve
| jr
jr
| dtrugman
dtrugman
| -| nbihan-mediware
nbihan-mediware
| PeterDaveHello
PeterDaveHello
| RaySinner
RaySinner
| afshawnlotfi
afshawnlotfi
| emshvac
emshvac
| kyle-apex
kyle-apex
| -| pdecat
pdecat
| Lunchb0ne
Lunchb0ne
| vagadiya
vagadiya
| arthurauffray
arthurauffray
| upamune
upamune
| StevenTCramer
StevenTCramer
| -| sammcj
sammcj
| p12tic
p12tic
| gtaylor
gtaylor
| aitoroses
aitoroses
| noritaka1166
noritaka1166
| philfung
philfung
| -| ross
ross
| heyseth
heyseth
| taisukeoe
taisukeoe
| dlab-anton
dlab-anton
| eonghk
eonghk
| teddyOOXX
teddyOOXX
| -| vincentsong
vincentsong
| yongjer
yongjer
| SmartManoj
SmartManoj
| ashktn
ashktn
| franekp
franekp
| yt3trees
yt3trees
| -| benzntech
benzntech
| anton-otee
anton-otee
| axkirillov
axkirillov
| bramburn
bramburn
| hassoncs
hassoncs
| ChuKhaLi
ChuKhaLi
| -| snoyiatk
snoyiatk
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| Chenjiayuan195
Chenjiayuan195
| julionav
julionav
| SplittyDev
SplittyDev
| -| mdp
mdp
| napter
napter
| hatsu38
hatsu38
| hongzio
hongzio
| im47cn
im47cn
| shoopapa
shoopapa
| -| jwcraig
jwcraig
| kinandan
kinandan
| kohii
kohii
| lightrabbit
lightrabbit
| olup
olup
| nevermorec
nevermorec
| -| GOODBOY008
GOODBOY008
| dqroid
dqroid
| dairui1
dairui1
| bannzai
bannzai
| axmo
axmo
| avtc
avtc
| -| asychin
asychin
| amittell
amittell
| Yoshino-Yukitaro
Yoshino-Yukitaro
| Yikai-Liao
Yikai-Liao
| PretzelVector
PretzelVector
| zetaloop
zetaloop
| -| zeozeozeo
zeozeozeo
| cdlliuy
cdlliuy
| student20880
student20880
| shohei-ihaya
shohei-ihaya
| shaybc
shaybc
| seedlord
seedlord
| -| samir-nimbly
samir-nimbly
| ronyblum
ronyblum
| robertheadley
robertheadley
| refactorthis
refactorthis
| pokutuna
pokutuna
| philipnext
philipnext
| -| oprstchn
oprstchn
| nobu007
nobu007
| mosleyit
mosleyit
| moqimoqidea
moqimoqidea
| mlopezr
mlopezr
| mecab
mecab
| -| zxdvd
zxdvd
| hesara
hesara
| DeXtroTip
DeXtroTip
| pfitz
pfitz
| celestial-vault
celestial-vault
| linegel
linegel
| -| dbasclpy
dbasclpy
| Deon588
Deon588
| dleen
dleen
| chadgauth
chadgauth
| olearycrew
olearycrew
| bogdan0083
bogdan0083
| -| Atlogit
Atlogit
| atlasgong
atlasgong
| andreastempsch
andreastempsch
| alasano
alasano
| QuinsZouls
QuinsZouls
| HadesArchitect
HadesArchitect
| -| alarno
alarno
| adamwlarson
adamwlarson
| AMHesch
AMHesch
| vladstudio
vladstudio
| NamesMT
NamesMT
| tmsjngx0
tmsjngx0
| -| tgfjt
tgfjt
| maekawataiki
maekawataiki
| samsilveira
samsilveira
| mr-ryan-james
mr-ryan-james
| 01Rian
01Rian
| RSO
RSO
| -| R-omk
R-omk
| Sarke
Sarke
| kvokka
kvokka
| ecmasx
ecmasx
| marvijo-code
marvijo-code
| mamertofabian
mamertofabian
| -| monkeyDluffy6017
monkeyDluffy6017
| libertyteeth
libertyteeth
| shtse8
shtse8
| ksze
ksze
| Jdo300
Jdo300
| | - +|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| +|:---:|:---:|:---:|:---:|:---:|:---:| +|ColemanRoo
ColemanRoo
|hannesrudolph
hannesrudolph
|KJ7LNW
KJ7LNW
|stea9499
stea9499
|canrobins13
canrobins13
|joemanley201
joemanley201
| +|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
| +|punkpeye
punkpeye
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|elianiva
elianiva
|feifei325
feifei325
| +|zhangtony239
zhangtony239
|cannuri
cannuri
|lloydchang
lloydchang
|sachasayan
sachasayan
|vigneshsubbiah16
vigneshsubbiah16
|qdaxb
qdaxb
| +|Szpadel
Szpadel
|diarmidmackenzie
diarmidmackenzie
|psv2522
psv2522
|Premshay
Premshay
|lupuletic
lupuletic
|shariqriazz
shariqriazz
| +|xyOz-dev
xyOz-dev
|pugazhendhi-m
pugazhendhi-m
|aheizi
aheizi
|olweraltuve
olweraltuve
|jr
jr
|dtrugman
dtrugman
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|RaySinner
RaySinner
|afshawnlotfi
afshawnlotfi
|emshvac
emshvac
|kyle-apex
kyle-apex
| +|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|vagadiya
vagadiya
|arthurauffray
arthurauffray
|upamune
upamune
|StevenTCramer
StevenTCramer
| +|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
|aitoroses
aitoroses
|noritaka1166
noritaka1166
|philfung
philfung
| +|ross
ross
|heyseth
heyseth
|taisukeoe
taisukeoe
|dlab-anton
dlab-anton
|eonghk
eonghk
|teddyOOXX
teddyOOXX
| +|vincentsong
vincentsong
|yongjer
yongjer
|SmartManoj
SmartManoj
|ashktn
ashktn
|franekp
franekp
|yt3trees
yt3trees
| +|benzntech
benzntech
|anton-otee
anton-otee
|axkirillov
axkirillov
|bramburn
bramburn
|hassoncs
hassoncs
|ChuKhaLi
ChuKhaLi
| +|snoyiatk
snoyiatk
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
|julionav
julionav
|SplittyDev
SplittyDev
| +|mdp
mdp
|napter
napter
|hatsu38
hatsu38
|hongzio
hongzio
|im47cn
im47cn
|shoopapa
shoopapa
| +|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|nevermorec
nevermorec
| +|GOODBOY008
GOODBOY008
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
|avtc
avtc
| +|asychin
asychin
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|Yikai-Liao
Yikai-Liao
|PretzelVector
PretzelVector
|zetaloop
zetaloop
| +|zeozeozeo
zeozeozeo
|cdlliuy
cdlliuy
|student20880
student20880
|shohei-ihaya
shohei-ihaya
|shaybc
shaybc
|seedlord
seedlord
| +|samir-nimbly
samir-nimbly
|ronyblum
ronyblum
|robertheadley
robertheadley
|refactorthis
refactorthis
|pokutuna
pokutuna
|philipnext
philipnext
| +|oprstchn
oprstchn
|nobu007
nobu007
|mosleyit
mosleyit
|moqimoqidea
moqimoqidea
|mlopezr
mlopezr
|mecab
mecab
| +|zxdvd
zxdvd
|hesara
hesara
|DeXtroTip
DeXtroTip
|pfitz
pfitz
|celestial-vault
celestial-vault
|linegel
linegel
| +|dbasclpy
dbasclpy
|Deon588
Deon588
|dleen
dleen
|chadgauth
chadgauth
|olearycrew
olearycrew
|bogdan0083
bogdan0083
| +|Atlogit
Atlogit
|atlasgong
atlasgong
|andreastempsch
andreastempsch
|alasano
alasano
|QuinsZouls
QuinsZouls
|HadesArchitect
HadesArchitect
| +|alarno
alarno
|adamwlarson
adamwlarson
|AMHesch
AMHesch
|vladstudio
vladstudio
|NamesMT
NamesMT
|tmsjngx0
tmsjngx0
| +|tgfjt
tgfjt
|maekawataiki
maekawataiki
|samsilveira
samsilveira
|mr-ryan-james
mr-ryan-james
|01Rian
01Rian
|RSO
RSO
| +|R-omk
R-omk
|Sarke
Sarke
|kvokka
kvokka
|ecmasx
ecmasx
|marvijo-code
marvijo-code
|mamertofabian
mamertofabian
| +|monkeyDluffy6017
monkeyDluffy6017
|libertyteeth
libertyteeth
|shtse8
shtse8
|ksze
ksze
|Jdo300
Jdo300
| | ## Llicència diff --git a/locales/de/README.md b/locales/de/README.md index 470af39456..0a9eec3a76 100644 --- a/locales/de/README.md +++ b/locales/de/README.md @@ -181,37 +181,35 @@ Wir lieben Community-Beiträge! Beginnen Sie mit dem Lesen unserer [CONTRIBUTING Danke an alle unsere Mitwirkenden, die geholfen haben, Roo Code zu verbessern! - -| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| -| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| ColemanRoo
ColemanRoo
| hannesrudolph
hannesrudolph
| KJ7LNW
KJ7LNW
| stea9499
stea9499
| canrobins13
canrobins13
| joemanley201
joemanley201
| -| System233
System233
| nissa-seru
nissa-seru
| jquanton
jquanton
| NyxJae
NyxJae
| MuriloFP
MuriloFP
| d-oit
d-oit
| -| punkpeye
punkpeye
| wkordalski
wkordalski
| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| monotykamary
monotykamary
| elianiva
elianiva
| feifei325
feifei325
| -| zhangtony239
zhangtony239
| cannuri
cannuri
| lloydchang
lloydchang
| sachasayan
sachasayan
| vigneshsubbiah16
vigneshsubbiah16
| qdaxb
qdaxb
| -| Szpadel
Szpadel
| diarmidmackenzie
diarmidmackenzie
| psv2522
psv2522
| Premshay
Premshay
| lupuletic
lupuletic
| shariqriazz
shariqriazz
| -| xyOz-dev
xyOz-dev
| pugazhendhi-m
pugazhendhi-m
| aheizi
aheizi
| olweraltuve
olweraltuve
| jr
jr
| dtrugman
dtrugman
| -| nbihan-mediware
nbihan-mediware
| PeterDaveHello
PeterDaveHello
| RaySinner
RaySinner
| afshawnlotfi
afshawnlotfi
| emshvac
emshvac
| kyle-apex
kyle-apex
| -| pdecat
pdecat
| Lunchb0ne
Lunchb0ne
| vagadiya
vagadiya
| arthurauffray
arthurauffray
| upamune
upamune
| StevenTCramer
StevenTCramer
| -| sammcj
sammcj
| p12tic
p12tic
| gtaylor
gtaylor
| aitoroses
aitoroses
| noritaka1166
noritaka1166
| philfung
philfung
| -| ross
ross
| heyseth
heyseth
| taisukeoe
taisukeoe
| dlab-anton
dlab-anton
| eonghk
eonghk
| teddyOOXX
teddyOOXX
| -| vincentsong
vincentsong
| yongjer
yongjer
| SmartManoj
SmartManoj
| ashktn
ashktn
| franekp
franekp
| yt3trees
yt3trees
| -| benzntech
benzntech
| anton-otee
anton-otee
| axkirillov
axkirillov
| bramburn
bramburn
| hassoncs
hassoncs
| ChuKhaLi
ChuKhaLi
| -| snoyiatk
snoyiatk
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| Chenjiayuan195
Chenjiayuan195
| julionav
julionav
| SplittyDev
SplittyDev
| -| mdp
mdp
| napter
napter
| hatsu38
hatsu38
| hongzio
hongzio
| im47cn
im47cn
| shoopapa
shoopapa
| -| jwcraig
jwcraig
| kinandan
kinandan
| kohii
kohii
| lightrabbit
lightrabbit
| olup
olup
| nevermorec
nevermorec
| -| GOODBOY008
GOODBOY008
| dqroid
dqroid
| dairui1
dairui1
| bannzai
bannzai
| axmo
axmo
| avtc
avtc
| -| asychin
asychin
| amittell
amittell
| Yoshino-Yukitaro
Yoshino-Yukitaro
| Yikai-Liao
Yikai-Liao
| PretzelVector
PretzelVector
| zetaloop
zetaloop
| -| zeozeozeo
zeozeozeo
| cdlliuy
cdlliuy
| student20880
student20880
| shohei-ihaya
shohei-ihaya
| shaybc
shaybc
| seedlord
seedlord
| -| samir-nimbly
samir-nimbly
| ronyblum
ronyblum
| robertheadley
robertheadley
| refactorthis
refactorthis
| pokutuna
pokutuna
| philipnext
philipnext
| -| oprstchn
oprstchn
| nobu007
nobu007
| mosleyit
mosleyit
| moqimoqidea
moqimoqidea
| mlopezr
mlopezr
| mecab
mecab
| -| zxdvd
zxdvd
| hesara
hesara
| DeXtroTip
DeXtroTip
| pfitz
pfitz
| celestial-vault
celestial-vault
| linegel
linegel
| -| dbasclpy
dbasclpy
| Deon588
Deon588
| dleen
dleen
| chadgauth
chadgauth
| olearycrew
olearycrew
| bogdan0083
bogdan0083
| -| Atlogit
Atlogit
| atlasgong
atlasgong
| andreastempsch
andreastempsch
| alasano
alasano
| QuinsZouls
QuinsZouls
| HadesArchitect
HadesArchitect
| -| alarno
alarno
| adamwlarson
adamwlarson
| AMHesch
AMHesch
| vladstudio
vladstudio
| NamesMT
NamesMT
| tmsjngx0
tmsjngx0
| -| tgfjt
tgfjt
| maekawataiki
maekawataiki
| samsilveira
samsilveira
| mr-ryan-james
mr-ryan-james
| 01Rian
01Rian
| RSO
RSO
| -| R-omk
R-omk
| Sarke
Sarke
| kvokka
kvokka
| ecmasx
ecmasx
| marvijo-code
marvijo-code
| mamertofabian
mamertofabian
| -| monkeyDluffy6017
monkeyDluffy6017
| libertyteeth
libertyteeth
| shtse8
shtse8
| ksze
ksze
| Jdo300
Jdo300
| | - +|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| +|:---:|:---:|:---:|:---:|:---:|:---:| +|ColemanRoo
ColemanRoo
|hannesrudolph
hannesrudolph
|KJ7LNW
KJ7LNW
|stea9499
stea9499
|canrobins13
canrobins13
|joemanley201
joemanley201
| +|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
| +|punkpeye
punkpeye
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|elianiva
elianiva
|feifei325
feifei325
| +|zhangtony239
zhangtony239
|cannuri
cannuri
|lloydchang
lloydchang
|sachasayan
sachasayan
|vigneshsubbiah16
vigneshsubbiah16
|qdaxb
qdaxb
| +|Szpadel
Szpadel
|diarmidmackenzie
diarmidmackenzie
|psv2522
psv2522
|Premshay
Premshay
|lupuletic
lupuletic
|shariqriazz
shariqriazz
| +|xyOz-dev
xyOz-dev
|pugazhendhi-m
pugazhendhi-m
|aheizi
aheizi
|olweraltuve
olweraltuve
|jr
jr
|dtrugman
dtrugman
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|RaySinner
RaySinner
|afshawnlotfi
afshawnlotfi
|emshvac
emshvac
|kyle-apex
kyle-apex
| +|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|vagadiya
vagadiya
|arthurauffray
arthurauffray
|upamune
upamune
|StevenTCramer
StevenTCramer
| +|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
|aitoroses
aitoroses
|noritaka1166
noritaka1166
|philfung
philfung
| +|ross
ross
|heyseth
heyseth
|taisukeoe
taisukeoe
|dlab-anton
dlab-anton
|eonghk
eonghk
|teddyOOXX
teddyOOXX
| +|vincentsong
vincentsong
|yongjer
yongjer
|SmartManoj
SmartManoj
|ashktn
ashktn
|franekp
franekp
|yt3trees
yt3trees
| +|benzntech
benzntech
|anton-otee
anton-otee
|axkirillov
axkirillov
|bramburn
bramburn
|hassoncs
hassoncs
|ChuKhaLi
ChuKhaLi
| +|snoyiatk
snoyiatk
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
|julionav
julionav
|SplittyDev
SplittyDev
| +|mdp
mdp
|napter
napter
|hatsu38
hatsu38
|hongzio
hongzio
|im47cn
im47cn
|shoopapa
shoopapa
| +|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|nevermorec
nevermorec
| +|GOODBOY008
GOODBOY008
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
|avtc
avtc
| +|asychin
asychin
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|Yikai-Liao
Yikai-Liao
|PretzelVector
PretzelVector
|zetaloop
zetaloop
| +|zeozeozeo
zeozeozeo
|cdlliuy
cdlliuy
|student20880
student20880
|shohei-ihaya
shohei-ihaya
|shaybc
shaybc
|seedlord
seedlord
| +|samir-nimbly
samir-nimbly
|ronyblum
ronyblum
|robertheadley
robertheadley
|refactorthis
refactorthis
|pokutuna
pokutuna
|philipnext
philipnext
| +|oprstchn
oprstchn
|nobu007
nobu007
|mosleyit
mosleyit
|moqimoqidea
moqimoqidea
|mlopezr
mlopezr
|mecab
mecab
| +|zxdvd
zxdvd
|hesara
hesara
|DeXtroTip
DeXtroTip
|pfitz
pfitz
|celestial-vault
celestial-vault
|linegel
linegel
| +|dbasclpy
dbasclpy
|Deon588
Deon588
|dleen
dleen
|chadgauth
chadgauth
|olearycrew
olearycrew
|bogdan0083
bogdan0083
| +|Atlogit
Atlogit
|atlasgong
atlasgong
|andreastempsch
andreastempsch
|alasano
alasano
|QuinsZouls
QuinsZouls
|HadesArchitect
HadesArchitect
| +|alarno
alarno
|adamwlarson
adamwlarson
|AMHesch
AMHesch
|vladstudio
vladstudio
|NamesMT
NamesMT
|tmsjngx0
tmsjngx0
| +|tgfjt
tgfjt
|maekawataiki
maekawataiki
|samsilveira
samsilveira
|mr-ryan-james
mr-ryan-james
|01Rian
01Rian
|RSO
RSO
| +|R-omk
R-omk
|Sarke
Sarke
|kvokka
kvokka
|ecmasx
ecmasx
|marvijo-code
marvijo-code
|mamertofabian
mamertofabian
| +|monkeyDluffy6017
monkeyDluffy6017
|libertyteeth
libertyteeth
|shtse8
shtse8
|ksze
ksze
|Jdo300
Jdo300
| | ## Lizenz diff --git a/locales/es/README.md b/locales/es/README.md index b2e04fe08f..3f01d6d337 100644 --- a/locales/es/README.md +++ b/locales/es/README.md @@ -181,37 +181,35 @@ Usamos [changesets](https://github.com/changesets/changesets) para versionar y p ¡Gracias a todos nuestros colaboradores que han ayudado a mejorar Roo Code! - -| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| -| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| ColemanRoo
ColemanRoo
| hannesrudolph
hannesrudolph
| KJ7LNW
KJ7LNW
| stea9499
stea9499
| canrobins13
canrobins13
| joemanley201
joemanley201
| -| System233
System233
| nissa-seru
nissa-seru
| jquanton
jquanton
| NyxJae
NyxJae
| MuriloFP
MuriloFP
| d-oit
d-oit
| -| punkpeye
punkpeye
| wkordalski
wkordalski
| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| monotykamary
monotykamary
| elianiva
elianiva
| feifei325
feifei325
| -| zhangtony239
zhangtony239
| cannuri
cannuri
| lloydchang
lloydchang
| sachasayan
sachasayan
| vigneshsubbiah16
vigneshsubbiah16
| qdaxb
qdaxb
| -| Szpadel
Szpadel
| diarmidmackenzie
diarmidmackenzie
| psv2522
psv2522
| Premshay
Premshay
| lupuletic
lupuletic
| shariqriazz
shariqriazz
| -| xyOz-dev
xyOz-dev
| pugazhendhi-m
pugazhendhi-m
| aheizi
aheizi
| olweraltuve
olweraltuve
| jr
jr
| dtrugman
dtrugman
| -| nbihan-mediware
nbihan-mediware
| PeterDaveHello
PeterDaveHello
| RaySinner
RaySinner
| afshawnlotfi
afshawnlotfi
| emshvac
emshvac
| kyle-apex
kyle-apex
| -| pdecat
pdecat
| Lunchb0ne
Lunchb0ne
| vagadiya
vagadiya
| arthurauffray
arthurauffray
| upamune
upamune
| StevenTCramer
StevenTCramer
| -| sammcj
sammcj
| p12tic
p12tic
| gtaylor
gtaylor
| aitoroses
aitoroses
| noritaka1166
noritaka1166
| philfung
philfung
| -| ross
ross
| heyseth
heyseth
| taisukeoe
taisukeoe
| dlab-anton
dlab-anton
| eonghk
eonghk
| teddyOOXX
teddyOOXX
| -| vincentsong
vincentsong
| yongjer
yongjer
| SmartManoj
SmartManoj
| ashktn
ashktn
| franekp
franekp
| yt3trees
yt3trees
| -| benzntech
benzntech
| anton-otee
anton-otee
| axkirillov
axkirillov
| bramburn
bramburn
| hassoncs
hassoncs
| ChuKhaLi
ChuKhaLi
| -| snoyiatk
snoyiatk
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| Chenjiayuan195
Chenjiayuan195
| julionav
julionav
| SplittyDev
SplittyDev
| -| mdp
mdp
| napter
napter
| hatsu38
hatsu38
| hongzio
hongzio
| im47cn
im47cn
| shoopapa
shoopapa
| -| jwcraig
jwcraig
| kinandan
kinandan
| kohii
kohii
| lightrabbit
lightrabbit
| olup
olup
| nevermorec
nevermorec
| -| GOODBOY008
GOODBOY008
| dqroid
dqroid
| dairui1
dairui1
| bannzai
bannzai
| axmo
axmo
| avtc
avtc
| -| asychin
asychin
| amittell
amittell
| Yoshino-Yukitaro
Yoshino-Yukitaro
| Yikai-Liao
Yikai-Liao
| PretzelVector
PretzelVector
| zetaloop
zetaloop
| -| zeozeozeo
zeozeozeo
| cdlliuy
cdlliuy
| student20880
student20880
| shohei-ihaya
shohei-ihaya
| shaybc
shaybc
| seedlord
seedlord
| -| samir-nimbly
samir-nimbly
| ronyblum
ronyblum
| robertheadley
robertheadley
| refactorthis
refactorthis
| pokutuna
pokutuna
| philipnext
philipnext
| -| oprstchn
oprstchn
| nobu007
nobu007
| mosleyit
mosleyit
| moqimoqidea
moqimoqidea
| mlopezr
mlopezr
| mecab
mecab
| -| zxdvd
zxdvd
| hesara
hesara
| DeXtroTip
DeXtroTip
| pfitz
pfitz
| celestial-vault
celestial-vault
| linegel
linegel
| -| dbasclpy
dbasclpy
| Deon588
Deon588
| dleen
dleen
| chadgauth
chadgauth
| olearycrew
olearycrew
| bogdan0083
bogdan0083
| -| Atlogit
Atlogit
| atlasgong
atlasgong
| andreastempsch
andreastempsch
| alasano
alasano
| QuinsZouls
QuinsZouls
| HadesArchitect
HadesArchitect
| -| alarno
alarno
| adamwlarson
adamwlarson
| AMHesch
AMHesch
| vladstudio
vladstudio
| NamesMT
NamesMT
| tmsjngx0
tmsjngx0
| -| tgfjt
tgfjt
| maekawataiki
maekawataiki
| samsilveira
samsilveira
| mr-ryan-james
mr-ryan-james
| 01Rian
01Rian
| RSO
RSO
| -| R-omk
R-omk
| Sarke
Sarke
| kvokka
kvokka
| ecmasx
ecmasx
| marvijo-code
marvijo-code
| mamertofabian
mamertofabian
| -| monkeyDluffy6017
monkeyDluffy6017
| libertyteeth
libertyteeth
| shtse8
shtse8
| ksze
ksze
| Jdo300
Jdo300
| | - +|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| +|:---:|:---:|:---:|:---:|:---:|:---:| +|ColemanRoo
ColemanRoo
|hannesrudolph
hannesrudolph
|KJ7LNW
KJ7LNW
|stea9499
stea9499
|canrobins13
canrobins13
|joemanley201
joemanley201
| +|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
| +|punkpeye
punkpeye
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|elianiva
elianiva
|feifei325
feifei325
| +|zhangtony239
zhangtony239
|cannuri
cannuri
|lloydchang
lloydchang
|sachasayan
sachasayan
|vigneshsubbiah16
vigneshsubbiah16
|qdaxb
qdaxb
| +|Szpadel
Szpadel
|diarmidmackenzie
diarmidmackenzie
|psv2522
psv2522
|Premshay
Premshay
|lupuletic
lupuletic
|shariqriazz
shariqriazz
| +|xyOz-dev
xyOz-dev
|pugazhendhi-m
pugazhendhi-m
|aheizi
aheizi
|olweraltuve
olweraltuve
|jr
jr
|dtrugman
dtrugman
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|RaySinner
RaySinner
|afshawnlotfi
afshawnlotfi
|emshvac
emshvac
|kyle-apex
kyle-apex
| +|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|vagadiya
vagadiya
|arthurauffray
arthurauffray
|upamune
upamune
|StevenTCramer
StevenTCramer
| +|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
|aitoroses
aitoroses
|noritaka1166
noritaka1166
|philfung
philfung
| +|ross
ross
|heyseth
heyseth
|taisukeoe
taisukeoe
|dlab-anton
dlab-anton
|eonghk
eonghk
|teddyOOXX
teddyOOXX
| +|vincentsong
vincentsong
|yongjer
yongjer
|SmartManoj
SmartManoj
|ashktn
ashktn
|franekp
franekp
|yt3trees
yt3trees
| +|benzntech
benzntech
|anton-otee
anton-otee
|axkirillov
axkirillov
|bramburn
bramburn
|hassoncs
hassoncs
|ChuKhaLi
ChuKhaLi
| +|snoyiatk
snoyiatk
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
|julionav
julionav
|SplittyDev
SplittyDev
| +|mdp
mdp
|napter
napter
|hatsu38
hatsu38
|hongzio
hongzio
|im47cn
im47cn
|shoopapa
shoopapa
| +|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|nevermorec
nevermorec
| +|GOODBOY008
GOODBOY008
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
|avtc
avtc
| +|asychin
asychin
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|Yikai-Liao
Yikai-Liao
|PretzelVector
PretzelVector
|zetaloop
zetaloop
| +|zeozeozeo
zeozeozeo
|cdlliuy
cdlliuy
|student20880
student20880
|shohei-ihaya
shohei-ihaya
|shaybc
shaybc
|seedlord
seedlord
| +|samir-nimbly
samir-nimbly
|ronyblum
ronyblum
|robertheadley
robertheadley
|refactorthis
refactorthis
|pokutuna
pokutuna
|philipnext
philipnext
| +|oprstchn
oprstchn
|nobu007
nobu007
|mosleyit
mosleyit
|moqimoqidea
moqimoqidea
|mlopezr
mlopezr
|mecab
mecab
| +|zxdvd
zxdvd
|hesara
hesara
|DeXtroTip
DeXtroTip
|pfitz
pfitz
|celestial-vault
celestial-vault
|linegel
linegel
| +|dbasclpy
dbasclpy
|Deon588
Deon588
|dleen
dleen
|chadgauth
chadgauth
|olearycrew
olearycrew
|bogdan0083
bogdan0083
| +|Atlogit
Atlogit
|atlasgong
atlasgong
|andreastempsch
andreastempsch
|alasano
alasano
|QuinsZouls
QuinsZouls
|HadesArchitect
HadesArchitect
| +|alarno
alarno
|adamwlarson
adamwlarson
|AMHesch
AMHesch
|vladstudio
vladstudio
|NamesMT
NamesMT
|tmsjngx0
tmsjngx0
| +|tgfjt
tgfjt
|maekawataiki
maekawataiki
|samsilveira
samsilveira
|mr-ryan-james
mr-ryan-james
|01Rian
01Rian
|RSO
RSO
| +|R-omk
R-omk
|Sarke
Sarke
|kvokka
kvokka
|ecmasx
ecmasx
|marvijo-code
marvijo-code
|mamertofabian
mamertofabian
| +|monkeyDluffy6017
monkeyDluffy6017
|libertyteeth
libertyteeth
|shtse8
shtse8
|ksze
ksze
|Jdo300
Jdo300
| | ## Licencia diff --git a/locales/fr/README.md b/locales/fr/README.md index 301e655c64..2f4e725f34 100644 --- a/locales/fr/README.md +++ b/locales/fr/README.md @@ -181,37 +181,35 @@ Nous adorons les contributions de la communauté ! Commencez par lire notre [CON Merci à tous nos contributeurs qui ont aidé à améliorer Roo Code ! - -| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| -| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| ColemanRoo
ColemanRoo
| hannesrudolph
hannesrudolph
| KJ7LNW
KJ7LNW
| stea9499
stea9499
| canrobins13
canrobins13
| joemanley201
joemanley201
| -| System233
System233
| nissa-seru
nissa-seru
| jquanton
jquanton
| NyxJae
NyxJae
| MuriloFP
MuriloFP
| d-oit
d-oit
| -| punkpeye
punkpeye
| wkordalski
wkordalski
| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| monotykamary
monotykamary
| elianiva
elianiva
| feifei325
feifei325
| -| zhangtony239
zhangtony239
| cannuri
cannuri
| lloydchang
lloydchang
| sachasayan
sachasayan
| vigneshsubbiah16
vigneshsubbiah16
| qdaxb
qdaxb
| -| Szpadel
Szpadel
| diarmidmackenzie
diarmidmackenzie
| psv2522
psv2522
| Premshay
Premshay
| lupuletic
lupuletic
| shariqriazz
shariqriazz
| -| xyOz-dev
xyOz-dev
| pugazhendhi-m
pugazhendhi-m
| aheizi
aheizi
| olweraltuve
olweraltuve
| jr
jr
| dtrugman
dtrugman
| -| nbihan-mediware
nbihan-mediware
| PeterDaveHello
PeterDaveHello
| RaySinner
RaySinner
| afshawnlotfi
afshawnlotfi
| emshvac
emshvac
| kyle-apex
kyle-apex
| -| pdecat
pdecat
| Lunchb0ne
Lunchb0ne
| vagadiya
vagadiya
| arthurauffray
arthurauffray
| upamune
upamune
| StevenTCramer
StevenTCramer
| -| sammcj
sammcj
| p12tic
p12tic
| gtaylor
gtaylor
| aitoroses
aitoroses
| noritaka1166
noritaka1166
| philfung
philfung
| -| ross
ross
| heyseth
heyseth
| taisukeoe
taisukeoe
| dlab-anton
dlab-anton
| eonghk
eonghk
| teddyOOXX
teddyOOXX
| -| vincentsong
vincentsong
| yongjer
yongjer
| SmartManoj
SmartManoj
| ashktn
ashktn
| franekp
franekp
| yt3trees
yt3trees
| -| benzntech
benzntech
| anton-otee
anton-otee
| axkirillov
axkirillov
| bramburn
bramburn
| hassoncs
hassoncs
| ChuKhaLi
ChuKhaLi
| -| snoyiatk
snoyiatk
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| Chenjiayuan195
Chenjiayuan195
| julionav
julionav
| SplittyDev
SplittyDev
| -| mdp
mdp
| napter
napter
| hatsu38
hatsu38
| hongzio
hongzio
| im47cn
im47cn
| shoopapa
shoopapa
| -| jwcraig
jwcraig
| kinandan
kinandan
| kohii
kohii
| lightrabbit
lightrabbit
| olup
olup
| nevermorec
nevermorec
| -| GOODBOY008
GOODBOY008
| dqroid
dqroid
| dairui1
dairui1
| bannzai
bannzai
| axmo
axmo
| avtc
avtc
| -| asychin
asychin
| amittell
amittell
| Yoshino-Yukitaro
Yoshino-Yukitaro
| Yikai-Liao
Yikai-Liao
| PretzelVector
PretzelVector
| zetaloop
zetaloop
| -| zeozeozeo
zeozeozeo
| cdlliuy
cdlliuy
| student20880
student20880
| shohei-ihaya
shohei-ihaya
| shaybc
shaybc
| seedlord
seedlord
| -| samir-nimbly
samir-nimbly
| ronyblum
ronyblum
| robertheadley
robertheadley
| refactorthis
refactorthis
| pokutuna
pokutuna
| philipnext
philipnext
| -| oprstchn
oprstchn
| nobu007
nobu007
| mosleyit
mosleyit
| moqimoqidea
moqimoqidea
| mlopezr
mlopezr
| mecab
mecab
| -| zxdvd
zxdvd
| hesara
hesara
| DeXtroTip
DeXtroTip
| pfitz
pfitz
| celestial-vault
celestial-vault
| linegel
linegel
| -| dbasclpy
dbasclpy
| Deon588
Deon588
| dleen
dleen
| chadgauth
chadgauth
| olearycrew
olearycrew
| bogdan0083
bogdan0083
| -| Atlogit
Atlogit
| atlasgong
atlasgong
| andreastempsch
andreastempsch
| alasano
alasano
| QuinsZouls
QuinsZouls
| HadesArchitect
HadesArchitect
| -| alarno
alarno
| adamwlarson
adamwlarson
| AMHesch
AMHesch
| vladstudio
vladstudio
| NamesMT
NamesMT
| tmsjngx0
tmsjngx0
| -| tgfjt
tgfjt
| maekawataiki
maekawataiki
| samsilveira
samsilveira
| mr-ryan-james
mr-ryan-james
| 01Rian
01Rian
| RSO
RSO
| -| R-omk
R-omk
| Sarke
Sarke
| kvokka
kvokka
| ecmasx
ecmasx
| marvijo-code
marvijo-code
| mamertofabian
mamertofabian
| -| monkeyDluffy6017
monkeyDluffy6017
| libertyteeth
libertyteeth
| shtse8
shtse8
| ksze
ksze
| Jdo300
Jdo300
| | - +|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| +|:---:|:---:|:---:|:---:|:---:|:---:| +|ColemanRoo
ColemanRoo
|hannesrudolph
hannesrudolph
|KJ7LNW
KJ7LNW
|stea9499
stea9499
|canrobins13
canrobins13
|joemanley201
joemanley201
| +|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
| +|punkpeye
punkpeye
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|elianiva
elianiva
|feifei325
feifei325
| +|zhangtony239
zhangtony239
|cannuri
cannuri
|lloydchang
lloydchang
|sachasayan
sachasayan
|vigneshsubbiah16
vigneshsubbiah16
|qdaxb
qdaxb
| +|Szpadel
Szpadel
|diarmidmackenzie
diarmidmackenzie
|psv2522
psv2522
|Premshay
Premshay
|lupuletic
lupuletic
|shariqriazz
shariqriazz
| +|xyOz-dev
xyOz-dev
|pugazhendhi-m
pugazhendhi-m
|aheizi
aheizi
|olweraltuve
olweraltuve
|jr
jr
|dtrugman
dtrugman
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|RaySinner
RaySinner
|afshawnlotfi
afshawnlotfi
|emshvac
emshvac
|kyle-apex
kyle-apex
| +|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|vagadiya
vagadiya
|arthurauffray
arthurauffray
|upamune
upamune
|StevenTCramer
StevenTCramer
| +|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
|aitoroses
aitoroses
|noritaka1166
noritaka1166
|philfung
philfung
| +|ross
ross
|heyseth
heyseth
|taisukeoe
taisukeoe
|dlab-anton
dlab-anton
|eonghk
eonghk
|teddyOOXX
teddyOOXX
| +|vincentsong
vincentsong
|yongjer
yongjer
|SmartManoj
SmartManoj
|ashktn
ashktn
|franekp
franekp
|yt3trees
yt3trees
| +|benzntech
benzntech
|anton-otee
anton-otee
|axkirillov
axkirillov
|bramburn
bramburn
|hassoncs
hassoncs
|ChuKhaLi
ChuKhaLi
| +|snoyiatk
snoyiatk
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
|julionav
julionav
|SplittyDev
SplittyDev
| +|mdp
mdp
|napter
napter
|hatsu38
hatsu38
|hongzio
hongzio
|im47cn
im47cn
|shoopapa
shoopapa
| +|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|nevermorec
nevermorec
| +|GOODBOY008
GOODBOY008
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
|avtc
avtc
| +|asychin
asychin
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|Yikai-Liao
Yikai-Liao
|PretzelVector
PretzelVector
|zetaloop
zetaloop
| +|zeozeozeo
zeozeozeo
|cdlliuy
cdlliuy
|student20880
student20880
|shohei-ihaya
shohei-ihaya
|shaybc
shaybc
|seedlord
seedlord
| +|samir-nimbly
samir-nimbly
|ronyblum
ronyblum
|robertheadley
robertheadley
|refactorthis
refactorthis
|pokutuna
pokutuna
|philipnext
philipnext
| +|oprstchn
oprstchn
|nobu007
nobu007
|mosleyit
mosleyit
|moqimoqidea
moqimoqidea
|mlopezr
mlopezr
|mecab
mecab
| +|zxdvd
zxdvd
|hesara
hesara
|DeXtroTip
DeXtroTip
|pfitz
pfitz
|celestial-vault
celestial-vault
|linegel
linegel
| +|dbasclpy
dbasclpy
|Deon588
Deon588
|dleen
dleen
|chadgauth
chadgauth
|olearycrew
olearycrew
|bogdan0083
bogdan0083
| +|Atlogit
Atlogit
|atlasgong
atlasgong
|andreastempsch
andreastempsch
|alasano
alasano
|QuinsZouls
QuinsZouls
|HadesArchitect
HadesArchitect
| +|alarno
alarno
|adamwlarson
adamwlarson
|AMHesch
AMHesch
|vladstudio
vladstudio
|NamesMT
NamesMT
|tmsjngx0
tmsjngx0
| +|tgfjt
tgfjt
|maekawataiki
maekawataiki
|samsilveira
samsilveira
|mr-ryan-james
mr-ryan-james
|01Rian
01Rian
|RSO
RSO
| +|R-omk
R-omk
|Sarke
Sarke
|kvokka
kvokka
|ecmasx
ecmasx
|marvijo-code
marvijo-code
|mamertofabian
mamertofabian
| +|monkeyDluffy6017
monkeyDluffy6017
|libertyteeth
libertyteeth
|shtse8
shtse8
|ksze
ksze
|Jdo300
Jdo300
| | ## Licence diff --git a/locales/hi/README.md b/locales/hi/README.md index 14994a8c34..5ae9873ba4 100644 --- a/locales/hi/README.md +++ b/locales/hi/README.md @@ -181,37 +181,35 @@ code --install-extension bin/roo-cline-.vsix Roo Code को बेहतर बनाने में मदद करने वाले हमारे सभी योगदानकर्ताओं को धन्यवाद! - -| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| -| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| ColemanRoo
ColemanRoo
| hannesrudolph
hannesrudolph
| KJ7LNW
KJ7LNW
| stea9499
stea9499
| canrobins13
canrobins13
| joemanley201
joemanley201
| -| System233
System233
| nissa-seru
nissa-seru
| jquanton
jquanton
| NyxJae
NyxJae
| MuriloFP
MuriloFP
| d-oit
d-oit
| -| punkpeye
punkpeye
| wkordalski
wkordalski
| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| monotykamary
monotykamary
| elianiva
elianiva
| feifei325
feifei325
| -| zhangtony239
zhangtony239
| cannuri
cannuri
| lloydchang
lloydchang
| sachasayan
sachasayan
| vigneshsubbiah16
vigneshsubbiah16
| qdaxb
qdaxb
| -| Szpadel
Szpadel
| diarmidmackenzie
diarmidmackenzie
| psv2522
psv2522
| Premshay
Premshay
| lupuletic
lupuletic
| shariqriazz
shariqriazz
| -| xyOz-dev
xyOz-dev
| pugazhendhi-m
pugazhendhi-m
| aheizi
aheizi
| olweraltuve
olweraltuve
| jr
jr
| dtrugman
dtrugman
| -| nbihan-mediware
nbihan-mediware
| PeterDaveHello
PeterDaveHello
| RaySinner
RaySinner
| afshawnlotfi
afshawnlotfi
| emshvac
emshvac
| kyle-apex
kyle-apex
| -| pdecat
pdecat
| Lunchb0ne
Lunchb0ne
| vagadiya
vagadiya
| arthurauffray
arthurauffray
| upamune
upamune
| StevenTCramer
StevenTCramer
| -| sammcj
sammcj
| p12tic
p12tic
| gtaylor
gtaylor
| aitoroses
aitoroses
| noritaka1166
noritaka1166
| philfung
philfung
| -| ross
ross
| heyseth
heyseth
| taisukeoe
taisukeoe
| dlab-anton
dlab-anton
| eonghk
eonghk
| teddyOOXX
teddyOOXX
| -| vincentsong
vincentsong
| yongjer
yongjer
| SmartManoj
SmartManoj
| ashktn
ashktn
| franekp
franekp
| yt3trees
yt3trees
| -| benzntech
benzntech
| anton-otee
anton-otee
| axkirillov
axkirillov
| bramburn
bramburn
| hassoncs
hassoncs
| ChuKhaLi
ChuKhaLi
| -| snoyiatk
snoyiatk
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| Chenjiayuan195
Chenjiayuan195
| julionav
julionav
| SplittyDev
SplittyDev
| -| mdp
mdp
| napter
napter
| hatsu38
hatsu38
| hongzio
hongzio
| im47cn
im47cn
| shoopapa
shoopapa
| -| jwcraig
jwcraig
| kinandan
kinandan
| kohii
kohii
| lightrabbit
lightrabbit
| olup
olup
| nevermorec
nevermorec
| -| GOODBOY008
GOODBOY008
| dqroid
dqroid
| dairui1
dairui1
| bannzai
bannzai
| axmo
axmo
| avtc
avtc
| -| asychin
asychin
| amittell
amittell
| Yoshino-Yukitaro
Yoshino-Yukitaro
| Yikai-Liao
Yikai-Liao
| PretzelVector
PretzelVector
| zetaloop
zetaloop
| -| zeozeozeo
zeozeozeo
| cdlliuy
cdlliuy
| student20880
student20880
| shohei-ihaya
shohei-ihaya
| shaybc
shaybc
| seedlord
seedlord
| -| samir-nimbly
samir-nimbly
| ronyblum
ronyblum
| robertheadley
robertheadley
| refactorthis
refactorthis
| pokutuna
pokutuna
| philipnext
philipnext
| -| oprstchn
oprstchn
| nobu007
nobu007
| mosleyit
mosleyit
| moqimoqidea
moqimoqidea
| mlopezr
mlopezr
| mecab
mecab
| -| zxdvd
zxdvd
| hesara
hesara
| DeXtroTip
DeXtroTip
| pfitz
pfitz
| celestial-vault
celestial-vault
| linegel
linegel
| -| dbasclpy
dbasclpy
| Deon588
Deon588
| dleen
dleen
| chadgauth
chadgauth
| olearycrew
olearycrew
| bogdan0083
bogdan0083
| -| Atlogit
Atlogit
| atlasgong
atlasgong
| andreastempsch
andreastempsch
| alasano
alasano
| QuinsZouls
QuinsZouls
| HadesArchitect
HadesArchitect
| -| alarno
alarno
| adamwlarson
adamwlarson
| AMHesch
AMHesch
| vladstudio
vladstudio
| NamesMT
NamesMT
| tmsjngx0
tmsjngx0
| -| tgfjt
tgfjt
| maekawataiki
maekawataiki
| samsilveira
samsilveira
| mr-ryan-james
mr-ryan-james
| 01Rian
01Rian
| RSO
RSO
| -| R-omk
R-omk
| Sarke
Sarke
| kvokka
kvokka
| ecmasx
ecmasx
| marvijo-code
marvijo-code
| mamertofabian
mamertofabian
| -| monkeyDluffy6017
monkeyDluffy6017
| libertyteeth
libertyteeth
| shtse8
shtse8
| ksze
ksze
| Jdo300
Jdo300
| | - +|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| +|:---:|:---:|:---:|:---:|:---:|:---:| +|ColemanRoo
ColemanRoo
|hannesrudolph
hannesrudolph
|KJ7LNW
KJ7LNW
|stea9499
stea9499
|canrobins13
canrobins13
|joemanley201
joemanley201
| +|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
| +|punkpeye
punkpeye
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|elianiva
elianiva
|feifei325
feifei325
| +|zhangtony239
zhangtony239
|cannuri
cannuri
|lloydchang
lloydchang
|sachasayan
sachasayan
|vigneshsubbiah16
vigneshsubbiah16
|qdaxb
qdaxb
| +|Szpadel
Szpadel
|diarmidmackenzie
diarmidmackenzie
|psv2522
psv2522
|Premshay
Premshay
|lupuletic
lupuletic
|shariqriazz
shariqriazz
| +|xyOz-dev
xyOz-dev
|pugazhendhi-m
pugazhendhi-m
|aheizi
aheizi
|olweraltuve
olweraltuve
|jr
jr
|dtrugman
dtrugman
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|RaySinner
RaySinner
|afshawnlotfi
afshawnlotfi
|emshvac
emshvac
|kyle-apex
kyle-apex
| +|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|vagadiya
vagadiya
|arthurauffray
arthurauffray
|upamune
upamune
|StevenTCramer
StevenTCramer
| +|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
|aitoroses
aitoroses
|noritaka1166
noritaka1166
|philfung
philfung
| +|ross
ross
|heyseth
heyseth
|taisukeoe
taisukeoe
|dlab-anton
dlab-anton
|eonghk
eonghk
|teddyOOXX
teddyOOXX
| +|vincentsong
vincentsong
|yongjer
yongjer
|SmartManoj
SmartManoj
|ashktn
ashktn
|franekp
franekp
|yt3trees
yt3trees
| +|benzntech
benzntech
|anton-otee
anton-otee
|axkirillov
axkirillov
|bramburn
bramburn
|hassoncs
hassoncs
|ChuKhaLi
ChuKhaLi
| +|snoyiatk
snoyiatk
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
|julionav
julionav
|SplittyDev
SplittyDev
| +|mdp
mdp
|napter
napter
|hatsu38
hatsu38
|hongzio
hongzio
|im47cn
im47cn
|shoopapa
shoopapa
| +|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|nevermorec
nevermorec
| +|GOODBOY008
GOODBOY008
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
|avtc
avtc
| +|asychin
asychin
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|Yikai-Liao
Yikai-Liao
|PretzelVector
PretzelVector
|zetaloop
zetaloop
| +|zeozeozeo
zeozeozeo
|cdlliuy
cdlliuy
|student20880
student20880
|shohei-ihaya
shohei-ihaya
|shaybc
shaybc
|seedlord
seedlord
| +|samir-nimbly
samir-nimbly
|ronyblum
ronyblum
|robertheadley
robertheadley
|refactorthis
refactorthis
|pokutuna
pokutuna
|philipnext
philipnext
| +|oprstchn
oprstchn
|nobu007
nobu007
|mosleyit
mosleyit
|moqimoqidea
moqimoqidea
|mlopezr
mlopezr
|mecab
mecab
| +|zxdvd
zxdvd
|hesara
hesara
|DeXtroTip
DeXtroTip
|pfitz
pfitz
|celestial-vault
celestial-vault
|linegel
linegel
| +|dbasclpy
dbasclpy
|Deon588
Deon588
|dleen
dleen
|chadgauth
chadgauth
|olearycrew
olearycrew
|bogdan0083
bogdan0083
| +|Atlogit
Atlogit
|atlasgong
atlasgong
|andreastempsch
andreastempsch
|alasano
alasano
|QuinsZouls
QuinsZouls
|HadesArchitect
HadesArchitect
| +|alarno
alarno
|adamwlarson
adamwlarson
|AMHesch
AMHesch
|vladstudio
vladstudio
|NamesMT
NamesMT
|tmsjngx0
tmsjngx0
| +|tgfjt
tgfjt
|maekawataiki
maekawataiki
|samsilveira
samsilveira
|mr-ryan-james
mr-ryan-james
|01Rian
01Rian
|RSO
RSO
| +|R-omk
R-omk
|Sarke
Sarke
|kvokka
kvokka
|ecmasx
ecmasx
|marvijo-code
marvijo-code
|mamertofabian
mamertofabian
| +|monkeyDluffy6017
monkeyDluffy6017
|libertyteeth
libertyteeth
|shtse8
shtse8
|ksze
ksze
|Jdo300
Jdo300
| | ## लाइसेंस diff --git a/locales/it/README.md b/locales/it/README.md index f3c773f1e2..37b1ca1d25 100644 --- a/locales/it/README.md +++ b/locales/it/README.md @@ -181,37 +181,35 @@ Amiamo i contributi della community! Inizia leggendo il nostro [CONTRIBUTING.md] Grazie a tutti i nostri contributori che hanno aiutato a migliorare Roo Code! - -| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| -| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| ColemanRoo
ColemanRoo
| hannesrudolph
hannesrudolph
| KJ7LNW
KJ7LNW
| stea9499
stea9499
| canrobins13
canrobins13
| joemanley201
joemanley201
| -| System233
System233
| nissa-seru
nissa-seru
| jquanton
jquanton
| NyxJae
NyxJae
| MuriloFP
MuriloFP
| d-oit
d-oit
| -| punkpeye
punkpeye
| wkordalski
wkordalski
| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| monotykamary
monotykamary
| elianiva
elianiva
| feifei325
feifei325
| -| zhangtony239
zhangtony239
| cannuri
cannuri
| lloydchang
lloydchang
| sachasayan
sachasayan
| vigneshsubbiah16
vigneshsubbiah16
| qdaxb
qdaxb
| -| Szpadel
Szpadel
| diarmidmackenzie
diarmidmackenzie
| psv2522
psv2522
| Premshay
Premshay
| lupuletic
lupuletic
| shariqriazz
shariqriazz
| -| xyOz-dev
xyOz-dev
| pugazhendhi-m
pugazhendhi-m
| aheizi
aheizi
| olweraltuve
olweraltuve
| jr
jr
| dtrugman
dtrugman
| -| nbihan-mediware
nbihan-mediware
| PeterDaveHello
PeterDaveHello
| RaySinner
RaySinner
| afshawnlotfi
afshawnlotfi
| emshvac
emshvac
| kyle-apex
kyle-apex
| -| pdecat
pdecat
| Lunchb0ne
Lunchb0ne
| vagadiya
vagadiya
| arthurauffray
arthurauffray
| upamune
upamune
| StevenTCramer
StevenTCramer
| -| sammcj
sammcj
| p12tic
p12tic
| gtaylor
gtaylor
| aitoroses
aitoroses
| noritaka1166
noritaka1166
| philfung
philfung
| -| ross
ross
| heyseth
heyseth
| taisukeoe
taisukeoe
| dlab-anton
dlab-anton
| eonghk
eonghk
| teddyOOXX
teddyOOXX
| -| vincentsong
vincentsong
| yongjer
yongjer
| SmartManoj
SmartManoj
| ashktn
ashktn
| franekp
franekp
| yt3trees
yt3trees
| -| benzntech
benzntech
| anton-otee
anton-otee
| axkirillov
axkirillov
| bramburn
bramburn
| hassoncs
hassoncs
| ChuKhaLi
ChuKhaLi
| -| snoyiatk
snoyiatk
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| Chenjiayuan195
Chenjiayuan195
| julionav
julionav
| SplittyDev
SplittyDev
| -| mdp
mdp
| napter
napter
| hatsu38
hatsu38
| hongzio
hongzio
| im47cn
im47cn
| shoopapa
shoopapa
| -| jwcraig
jwcraig
| kinandan
kinandan
| kohii
kohii
| lightrabbit
lightrabbit
| olup
olup
| nevermorec
nevermorec
| -| GOODBOY008
GOODBOY008
| dqroid
dqroid
| dairui1
dairui1
| bannzai
bannzai
| axmo
axmo
| avtc
avtc
| -| asychin
asychin
| amittell
amittell
| Yoshino-Yukitaro
Yoshino-Yukitaro
| Yikai-Liao
Yikai-Liao
| PretzelVector
PretzelVector
| zetaloop
zetaloop
| -| zeozeozeo
zeozeozeo
| cdlliuy
cdlliuy
| student20880
student20880
| shohei-ihaya
shohei-ihaya
| shaybc
shaybc
| seedlord
seedlord
| -| samir-nimbly
samir-nimbly
| ronyblum
ronyblum
| robertheadley
robertheadley
| refactorthis
refactorthis
| pokutuna
pokutuna
| philipnext
philipnext
| -| oprstchn
oprstchn
| nobu007
nobu007
| mosleyit
mosleyit
| moqimoqidea
moqimoqidea
| mlopezr
mlopezr
| mecab
mecab
| -| zxdvd
zxdvd
| hesara
hesara
| DeXtroTip
DeXtroTip
| pfitz
pfitz
| celestial-vault
celestial-vault
| linegel
linegel
| -| dbasclpy
dbasclpy
| Deon588
Deon588
| dleen
dleen
| chadgauth
chadgauth
| olearycrew
olearycrew
| bogdan0083
bogdan0083
| -| Atlogit
Atlogit
| atlasgong
atlasgong
| andreastempsch
andreastempsch
| alasano
alasano
| QuinsZouls
QuinsZouls
| HadesArchitect
HadesArchitect
| -| alarno
alarno
| adamwlarson
adamwlarson
| AMHesch
AMHesch
| vladstudio
vladstudio
| NamesMT
NamesMT
| tmsjngx0
tmsjngx0
| -| tgfjt
tgfjt
| maekawataiki
maekawataiki
| samsilveira
samsilveira
| mr-ryan-james
mr-ryan-james
| 01Rian
01Rian
| RSO
RSO
| -| R-omk
R-omk
| Sarke
Sarke
| kvokka
kvokka
| ecmasx
ecmasx
| marvijo-code
marvijo-code
| mamertofabian
mamertofabian
| -| monkeyDluffy6017
monkeyDluffy6017
| libertyteeth
libertyteeth
| shtse8
shtse8
| ksze
ksze
| Jdo300
Jdo300
| | - +|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| +|:---:|:---:|:---:|:---:|:---:|:---:| +|ColemanRoo
ColemanRoo
|hannesrudolph
hannesrudolph
|KJ7LNW
KJ7LNW
|stea9499
stea9499
|canrobins13
canrobins13
|joemanley201
joemanley201
| +|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
| +|punkpeye
punkpeye
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|elianiva
elianiva
|feifei325
feifei325
| +|zhangtony239
zhangtony239
|cannuri
cannuri
|lloydchang
lloydchang
|sachasayan
sachasayan
|vigneshsubbiah16
vigneshsubbiah16
|qdaxb
qdaxb
| +|Szpadel
Szpadel
|diarmidmackenzie
diarmidmackenzie
|psv2522
psv2522
|Premshay
Premshay
|lupuletic
lupuletic
|shariqriazz
shariqriazz
| +|xyOz-dev
xyOz-dev
|pugazhendhi-m
pugazhendhi-m
|aheizi
aheizi
|olweraltuve
olweraltuve
|jr
jr
|dtrugman
dtrugman
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|RaySinner
RaySinner
|afshawnlotfi
afshawnlotfi
|emshvac
emshvac
|kyle-apex
kyle-apex
| +|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|vagadiya
vagadiya
|arthurauffray
arthurauffray
|upamune
upamune
|StevenTCramer
StevenTCramer
| +|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
|aitoroses
aitoroses
|noritaka1166
noritaka1166
|philfung
philfung
| +|ross
ross
|heyseth
heyseth
|taisukeoe
taisukeoe
|dlab-anton
dlab-anton
|eonghk
eonghk
|teddyOOXX
teddyOOXX
| +|vincentsong
vincentsong
|yongjer
yongjer
|SmartManoj
SmartManoj
|ashktn
ashktn
|franekp
franekp
|yt3trees
yt3trees
| +|benzntech
benzntech
|anton-otee
anton-otee
|axkirillov
axkirillov
|bramburn
bramburn
|hassoncs
hassoncs
|ChuKhaLi
ChuKhaLi
| +|snoyiatk
snoyiatk
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
|julionav
julionav
|SplittyDev
SplittyDev
| +|mdp
mdp
|napter
napter
|hatsu38
hatsu38
|hongzio
hongzio
|im47cn
im47cn
|shoopapa
shoopapa
| +|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|nevermorec
nevermorec
| +|GOODBOY008
GOODBOY008
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
|avtc
avtc
| +|asychin
asychin
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|Yikai-Liao
Yikai-Liao
|PretzelVector
PretzelVector
|zetaloop
zetaloop
| +|zeozeozeo
zeozeozeo
|cdlliuy
cdlliuy
|student20880
student20880
|shohei-ihaya
shohei-ihaya
|shaybc
shaybc
|seedlord
seedlord
| +|samir-nimbly
samir-nimbly
|ronyblum
ronyblum
|robertheadley
robertheadley
|refactorthis
refactorthis
|pokutuna
pokutuna
|philipnext
philipnext
| +|oprstchn
oprstchn
|nobu007
nobu007
|mosleyit
mosleyit
|moqimoqidea
moqimoqidea
|mlopezr
mlopezr
|mecab
mecab
| +|zxdvd
zxdvd
|hesara
hesara
|DeXtroTip
DeXtroTip
|pfitz
pfitz
|celestial-vault
celestial-vault
|linegel
linegel
| +|dbasclpy
dbasclpy
|Deon588
Deon588
|dleen
dleen
|chadgauth
chadgauth
|olearycrew
olearycrew
|bogdan0083
bogdan0083
| +|Atlogit
Atlogit
|atlasgong
atlasgong
|andreastempsch
andreastempsch
|alasano
alasano
|QuinsZouls
QuinsZouls
|HadesArchitect
HadesArchitect
| +|alarno
alarno
|adamwlarson
adamwlarson
|AMHesch
AMHesch
|vladstudio
vladstudio
|NamesMT
NamesMT
|tmsjngx0
tmsjngx0
| +|tgfjt
tgfjt
|maekawataiki
maekawataiki
|samsilveira
samsilveira
|mr-ryan-james
mr-ryan-james
|01Rian
01Rian
|RSO
RSO
| +|R-omk
R-omk
|Sarke
Sarke
|kvokka
kvokka
|ecmasx
ecmasx
|marvijo-code
marvijo-code
|mamertofabian
mamertofabian
| +|monkeyDluffy6017
monkeyDluffy6017
|libertyteeth
libertyteeth
|shtse8
shtse8
|ksze
ksze
|Jdo300
Jdo300
| | ## Licenza diff --git a/locales/ja/README.md b/locales/ja/README.md index c89d607042..8356f3b3d1 100644 --- a/locales/ja/README.md +++ b/locales/ja/README.md @@ -181,37 +181,35 @@ code --install-extension bin/roo-cline-.vsix Roo Codeの改善に貢献してくれたすべての貢献者に感謝します! - -| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| -| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| ColemanRoo
ColemanRoo
| hannesrudolph
hannesrudolph
| KJ7LNW
KJ7LNW
| stea9499
stea9499
| canrobins13
canrobins13
| joemanley201
joemanley201
| -| System233
System233
| nissa-seru
nissa-seru
| jquanton
jquanton
| NyxJae
NyxJae
| MuriloFP
MuriloFP
| d-oit
d-oit
| -| punkpeye
punkpeye
| wkordalski
wkordalski
| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| monotykamary
monotykamary
| elianiva
elianiva
| feifei325
feifei325
| -| zhangtony239
zhangtony239
| cannuri
cannuri
| lloydchang
lloydchang
| sachasayan
sachasayan
| vigneshsubbiah16
vigneshsubbiah16
| qdaxb
qdaxb
| -| Szpadel
Szpadel
| diarmidmackenzie
diarmidmackenzie
| psv2522
psv2522
| Premshay
Premshay
| lupuletic
lupuletic
| shariqriazz
shariqriazz
| -| xyOz-dev
xyOz-dev
| pugazhendhi-m
pugazhendhi-m
| aheizi
aheizi
| olweraltuve
olweraltuve
| jr
jr
| dtrugman
dtrugman
| -| nbihan-mediware
nbihan-mediware
| PeterDaveHello
PeterDaveHello
| RaySinner
RaySinner
| afshawnlotfi
afshawnlotfi
| emshvac
emshvac
| kyle-apex
kyle-apex
| -| pdecat
pdecat
| Lunchb0ne
Lunchb0ne
| vagadiya
vagadiya
| arthurauffray
arthurauffray
| upamune
upamune
| StevenTCramer
StevenTCramer
| -| sammcj
sammcj
| p12tic
p12tic
| gtaylor
gtaylor
| aitoroses
aitoroses
| noritaka1166
noritaka1166
| philfung
philfung
| -| ross
ross
| heyseth
heyseth
| taisukeoe
taisukeoe
| dlab-anton
dlab-anton
| eonghk
eonghk
| teddyOOXX
teddyOOXX
| -| vincentsong
vincentsong
| yongjer
yongjer
| SmartManoj
SmartManoj
| ashktn
ashktn
| franekp
franekp
| yt3trees
yt3trees
| -| benzntech
benzntech
| anton-otee
anton-otee
| axkirillov
axkirillov
| bramburn
bramburn
| hassoncs
hassoncs
| ChuKhaLi
ChuKhaLi
| -| snoyiatk
snoyiatk
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| Chenjiayuan195
Chenjiayuan195
| julionav
julionav
| SplittyDev
SplittyDev
| -| mdp
mdp
| napter
napter
| hatsu38
hatsu38
| hongzio
hongzio
| im47cn
im47cn
| shoopapa
shoopapa
| -| jwcraig
jwcraig
| kinandan
kinandan
| kohii
kohii
| lightrabbit
lightrabbit
| olup
olup
| nevermorec
nevermorec
| -| GOODBOY008
GOODBOY008
| dqroid
dqroid
| dairui1
dairui1
| bannzai
bannzai
| axmo
axmo
| avtc
avtc
| -| asychin
asychin
| amittell
amittell
| Yoshino-Yukitaro
Yoshino-Yukitaro
| Yikai-Liao
Yikai-Liao
| PretzelVector
PretzelVector
| zetaloop
zetaloop
| -| zeozeozeo
zeozeozeo
| cdlliuy
cdlliuy
| student20880
student20880
| shohei-ihaya
shohei-ihaya
| shaybc
shaybc
| seedlord
seedlord
| -| samir-nimbly
samir-nimbly
| ronyblum
ronyblum
| robertheadley
robertheadley
| refactorthis
refactorthis
| pokutuna
pokutuna
| philipnext
philipnext
| -| oprstchn
oprstchn
| nobu007
nobu007
| mosleyit
mosleyit
| moqimoqidea
moqimoqidea
| mlopezr
mlopezr
| mecab
mecab
| -| zxdvd
zxdvd
| hesara
hesara
| DeXtroTip
DeXtroTip
| pfitz
pfitz
| celestial-vault
celestial-vault
| linegel
linegel
| -| dbasclpy
dbasclpy
| Deon588
Deon588
| dleen
dleen
| chadgauth
chadgauth
| olearycrew
olearycrew
| bogdan0083
bogdan0083
| -| Atlogit
Atlogit
| atlasgong
atlasgong
| andreastempsch
andreastempsch
| alasano
alasano
| QuinsZouls
QuinsZouls
| HadesArchitect
HadesArchitect
| -| alarno
alarno
| adamwlarson
adamwlarson
| AMHesch
AMHesch
| vladstudio
vladstudio
| NamesMT
NamesMT
| tmsjngx0
tmsjngx0
| -| tgfjt
tgfjt
| maekawataiki
maekawataiki
| samsilveira
samsilveira
| mr-ryan-james
mr-ryan-james
| 01Rian
01Rian
| RSO
RSO
| -| R-omk
R-omk
| Sarke
Sarke
| kvokka
kvokka
| ecmasx
ecmasx
| marvijo-code
marvijo-code
| mamertofabian
mamertofabian
| -| monkeyDluffy6017
monkeyDluffy6017
| libertyteeth
libertyteeth
| shtse8
shtse8
| ksze
ksze
| Jdo300
Jdo300
| | - +|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| +|:---:|:---:|:---:|:---:|:---:|:---:| +|ColemanRoo
ColemanRoo
|hannesrudolph
hannesrudolph
|KJ7LNW
KJ7LNW
|stea9499
stea9499
|canrobins13
canrobins13
|joemanley201
joemanley201
| +|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
| +|punkpeye
punkpeye
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|elianiva
elianiva
|feifei325
feifei325
| +|zhangtony239
zhangtony239
|cannuri
cannuri
|lloydchang
lloydchang
|sachasayan
sachasayan
|vigneshsubbiah16
vigneshsubbiah16
|qdaxb
qdaxb
| +|Szpadel
Szpadel
|diarmidmackenzie
diarmidmackenzie
|psv2522
psv2522
|Premshay
Premshay
|lupuletic
lupuletic
|shariqriazz
shariqriazz
| +|xyOz-dev
xyOz-dev
|pugazhendhi-m
pugazhendhi-m
|aheizi
aheizi
|olweraltuve
olweraltuve
|jr
jr
|dtrugman
dtrugman
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|RaySinner
RaySinner
|afshawnlotfi
afshawnlotfi
|emshvac
emshvac
|kyle-apex
kyle-apex
| +|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|vagadiya
vagadiya
|arthurauffray
arthurauffray
|upamune
upamune
|StevenTCramer
StevenTCramer
| +|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
|aitoroses
aitoroses
|noritaka1166
noritaka1166
|philfung
philfung
| +|ross
ross
|heyseth
heyseth
|taisukeoe
taisukeoe
|dlab-anton
dlab-anton
|eonghk
eonghk
|teddyOOXX
teddyOOXX
| +|vincentsong
vincentsong
|yongjer
yongjer
|SmartManoj
SmartManoj
|ashktn
ashktn
|franekp
franekp
|yt3trees
yt3trees
| +|benzntech
benzntech
|anton-otee
anton-otee
|axkirillov
axkirillov
|bramburn
bramburn
|hassoncs
hassoncs
|ChuKhaLi
ChuKhaLi
| +|snoyiatk
snoyiatk
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
|julionav
julionav
|SplittyDev
SplittyDev
| +|mdp
mdp
|napter
napter
|hatsu38
hatsu38
|hongzio
hongzio
|im47cn
im47cn
|shoopapa
shoopapa
| +|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|nevermorec
nevermorec
| +|GOODBOY008
GOODBOY008
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
|avtc
avtc
| +|asychin
asychin
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|Yikai-Liao
Yikai-Liao
|PretzelVector
PretzelVector
|zetaloop
zetaloop
| +|zeozeozeo
zeozeozeo
|cdlliuy
cdlliuy
|student20880
student20880
|shohei-ihaya
shohei-ihaya
|shaybc
shaybc
|seedlord
seedlord
| +|samir-nimbly
samir-nimbly
|ronyblum
ronyblum
|robertheadley
robertheadley
|refactorthis
refactorthis
|pokutuna
pokutuna
|philipnext
philipnext
| +|oprstchn
oprstchn
|nobu007
nobu007
|mosleyit
mosleyit
|moqimoqidea
moqimoqidea
|mlopezr
mlopezr
|mecab
mecab
| +|zxdvd
zxdvd
|hesara
hesara
|DeXtroTip
DeXtroTip
|pfitz
pfitz
|celestial-vault
celestial-vault
|linegel
linegel
| +|dbasclpy
dbasclpy
|Deon588
Deon588
|dleen
dleen
|chadgauth
chadgauth
|olearycrew
olearycrew
|bogdan0083
bogdan0083
| +|Atlogit
Atlogit
|atlasgong
atlasgong
|andreastempsch
andreastempsch
|alasano
alasano
|QuinsZouls
QuinsZouls
|HadesArchitect
HadesArchitect
| +|alarno
alarno
|adamwlarson
adamwlarson
|AMHesch
AMHesch
|vladstudio
vladstudio
|NamesMT
NamesMT
|tmsjngx0
tmsjngx0
| +|tgfjt
tgfjt
|maekawataiki
maekawataiki
|samsilveira
samsilveira
|mr-ryan-james
mr-ryan-james
|01Rian
01Rian
|RSO
RSO
| +|R-omk
R-omk
|Sarke
Sarke
|kvokka
kvokka
|ecmasx
ecmasx
|marvijo-code
marvijo-code
|mamertofabian
mamertofabian
| +|monkeyDluffy6017
monkeyDluffy6017
|libertyteeth
libertyteeth
|shtse8
shtse8
|ksze
ksze
|Jdo300
Jdo300
| | ## ライセンス diff --git a/locales/ko/README.md b/locales/ko/README.md index 90a527e20d..0a6f458764 100644 --- a/locales/ko/README.md +++ b/locales/ko/README.md @@ -181,37 +181,35 @@ code --install-extension bin/roo-cline-.vsix Roo Code를 더 좋게 만드는 데 도움을 준 모든 기여자에게 감사드립니다! - -| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| -| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| ColemanRoo
ColemanRoo
| hannesrudolph
hannesrudolph
| KJ7LNW
KJ7LNW
| stea9499
stea9499
| canrobins13
canrobins13
| joemanley201
joemanley201
| -| System233
System233
| nissa-seru
nissa-seru
| jquanton
jquanton
| NyxJae
NyxJae
| MuriloFP
MuriloFP
| d-oit
d-oit
| -| punkpeye
punkpeye
| wkordalski
wkordalski
| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| monotykamary
monotykamary
| elianiva
elianiva
| feifei325
feifei325
| -| zhangtony239
zhangtony239
| cannuri
cannuri
| lloydchang
lloydchang
| sachasayan
sachasayan
| vigneshsubbiah16
vigneshsubbiah16
| qdaxb
qdaxb
| -| Szpadel
Szpadel
| diarmidmackenzie
diarmidmackenzie
| psv2522
psv2522
| Premshay
Premshay
| lupuletic
lupuletic
| shariqriazz
shariqriazz
| -| xyOz-dev
xyOz-dev
| pugazhendhi-m
pugazhendhi-m
| aheizi
aheizi
| olweraltuve
olweraltuve
| jr
jr
| dtrugman
dtrugman
| -| nbihan-mediware
nbihan-mediware
| PeterDaveHello
PeterDaveHello
| RaySinner
RaySinner
| afshawnlotfi
afshawnlotfi
| emshvac
emshvac
| kyle-apex
kyle-apex
| -| pdecat
pdecat
| Lunchb0ne
Lunchb0ne
| vagadiya
vagadiya
| arthurauffray
arthurauffray
| upamune
upamune
| StevenTCramer
StevenTCramer
| -| sammcj
sammcj
| p12tic
p12tic
| gtaylor
gtaylor
| aitoroses
aitoroses
| noritaka1166
noritaka1166
| philfung
philfung
| -| ross
ross
| heyseth
heyseth
| taisukeoe
taisukeoe
| dlab-anton
dlab-anton
| eonghk
eonghk
| teddyOOXX
teddyOOXX
| -| vincentsong
vincentsong
| yongjer
yongjer
| SmartManoj
SmartManoj
| ashktn
ashktn
| franekp
franekp
| yt3trees
yt3trees
| -| benzntech
benzntech
| anton-otee
anton-otee
| axkirillov
axkirillov
| bramburn
bramburn
| hassoncs
hassoncs
| ChuKhaLi
ChuKhaLi
| -| snoyiatk
snoyiatk
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| Chenjiayuan195
Chenjiayuan195
| julionav
julionav
| SplittyDev
SplittyDev
| -| mdp
mdp
| napter
napter
| hatsu38
hatsu38
| hongzio
hongzio
| im47cn
im47cn
| shoopapa
shoopapa
| -| jwcraig
jwcraig
| kinandan
kinandan
| kohii
kohii
| lightrabbit
lightrabbit
| olup
olup
| nevermorec
nevermorec
| -| GOODBOY008
GOODBOY008
| dqroid
dqroid
| dairui1
dairui1
| bannzai
bannzai
| axmo
axmo
| avtc
avtc
| -| asychin
asychin
| amittell
amittell
| Yoshino-Yukitaro
Yoshino-Yukitaro
| Yikai-Liao
Yikai-Liao
| PretzelVector
PretzelVector
| zetaloop
zetaloop
| -| zeozeozeo
zeozeozeo
| cdlliuy
cdlliuy
| student20880
student20880
| shohei-ihaya
shohei-ihaya
| shaybc
shaybc
| seedlord
seedlord
| -| samir-nimbly
samir-nimbly
| ronyblum
ronyblum
| robertheadley
robertheadley
| refactorthis
refactorthis
| pokutuna
pokutuna
| philipnext
philipnext
| -| oprstchn
oprstchn
| nobu007
nobu007
| mosleyit
mosleyit
| moqimoqidea
moqimoqidea
| mlopezr
mlopezr
| mecab
mecab
| -| zxdvd
zxdvd
| hesara
hesara
| DeXtroTip
DeXtroTip
| pfitz
pfitz
| celestial-vault
celestial-vault
| linegel
linegel
| -| dbasclpy
dbasclpy
| Deon588
Deon588
| dleen
dleen
| chadgauth
chadgauth
| olearycrew
olearycrew
| bogdan0083
bogdan0083
| -| Atlogit
Atlogit
| atlasgong
atlasgong
| andreastempsch
andreastempsch
| alasano
alasano
| QuinsZouls
QuinsZouls
| HadesArchitect
HadesArchitect
| -| alarno
alarno
| adamwlarson
adamwlarson
| AMHesch
AMHesch
| vladstudio
vladstudio
| NamesMT
NamesMT
| tmsjngx0
tmsjngx0
| -| tgfjt
tgfjt
| maekawataiki
maekawataiki
| samsilveira
samsilveira
| mr-ryan-james
mr-ryan-james
| 01Rian
01Rian
| RSO
RSO
| -| R-omk
R-omk
| Sarke
Sarke
| kvokka
kvokka
| ecmasx
ecmasx
| marvijo-code
marvijo-code
| mamertofabian
mamertofabian
| -| monkeyDluffy6017
monkeyDluffy6017
| libertyteeth
libertyteeth
| shtse8
shtse8
| ksze
ksze
| Jdo300
Jdo300
| | - +|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| +|:---:|:---:|:---:|:---:|:---:|:---:| +|ColemanRoo
ColemanRoo
|hannesrudolph
hannesrudolph
|KJ7LNW
KJ7LNW
|stea9499
stea9499
|canrobins13
canrobins13
|joemanley201
joemanley201
| +|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
| +|punkpeye
punkpeye
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|elianiva
elianiva
|feifei325
feifei325
| +|zhangtony239
zhangtony239
|cannuri
cannuri
|lloydchang
lloydchang
|sachasayan
sachasayan
|vigneshsubbiah16
vigneshsubbiah16
|qdaxb
qdaxb
| +|Szpadel
Szpadel
|diarmidmackenzie
diarmidmackenzie
|psv2522
psv2522
|Premshay
Premshay
|lupuletic
lupuletic
|shariqriazz
shariqriazz
| +|xyOz-dev
xyOz-dev
|pugazhendhi-m
pugazhendhi-m
|aheizi
aheizi
|olweraltuve
olweraltuve
|jr
jr
|dtrugman
dtrugman
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|RaySinner
RaySinner
|afshawnlotfi
afshawnlotfi
|emshvac
emshvac
|kyle-apex
kyle-apex
| +|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|vagadiya
vagadiya
|arthurauffray
arthurauffray
|upamune
upamune
|StevenTCramer
StevenTCramer
| +|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
|aitoroses
aitoroses
|noritaka1166
noritaka1166
|philfung
philfung
| +|ross
ross
|heyseth
heyseth
|taisukeoe
taisukeoe
|dlab-anton
dlab-anton
|eonghk
eonghk
|teddyOOXX
teddyOOXX
| +|vincentsong
vincentsong
|yongjer
yongjer
|SmartManoj
SmartManoj
|ashktn
ashktn
|franekp
franekp
|yt3trees
yt3trees
| +|benzntech
benzntech
|anton-otee
anton-otee
|axkirillov
axkirillov
|bramburn
bramburn
|hassoncs
hassoncs
|ChuKhaLi
ChuKhaLi
| +|snoyiatk
snoyiatk
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
|julionav
julionav
|SplittyDev
SplittyDev
| +|mdp
mdp
|napter
napter
|hatsu38
hatsu38
|hongzio
hongzio
|im47cn
im47cn
|shoopapa
shoopapa
| +|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|nevermorec
nevermorec
| +|GOODBOY008
GOODBOY008
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
|avtc
avtc
| +|asychin
asychin
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|Yikai-Liao
Yikai-Liao
|PretzelVector
PretzelVector
|zetaloop
zetaloop
| +|zeozeozeo
zeozeozeo
|cdlliuy
cdlliuy
|student20880
student20880
|shohei-ihaya
shohei-ihaya
|shaybc
shaybc
|seedlord
seedlord
| +|samir-nimbly
samir-nimbly
|ronyblum
ronyblum
|robertheadley
robertheadley
|refactorthis
refactorthis
|pokutuna
pokutuna
|philipnext
philipnext
| +|oprstchn
oprstchn
|nobu007
nobu007
|mosleyit
mosleyit
|moqimoqidea
moqimoqidea
|mlopezr
mlopezr
|mecab
mecab
| +|zxdvd
zxdvd
|hesara
hesara
|DeXtroTip
DeXtroTip
|pfitz
pfitz
|celestial-vault
celestial-vault
|linegel
linegel
| +|dbasclpy
dbasclpy
|Deon588
Deon588
|dleen
dleen
|chadgauth
chadgauth
|olearycrew
olearycrew
|bogdan0083
bogdan0083
| +|Atlogit
Atlogit
|atlasgong
atlasgong
|andreastempsch
andreastempsch
|alasano
alasano
|QuinsZouls
QuinsZouls
|HadesArchitect
HadesArchitect
| +|alarno
alarno
|adamwlarson
adamwlarson
|AMHesch
AMHesch
|vladstudio
vladstudio
|NamesMT
NamesMT
|tmsjngx0
tmsjngx0
| +|tgfjt
tgfjt
|maekawataiki
maekawataiki
|samsilveira
samsilveira
|mr-ryan-james
mr-ryan-james
|01Rian
01Rian
|RSO
RSO
| +|R-omk
R-omk
|Sarke
Sarke
|kvokka
kvokka
|ecmasx
ecmasx
|marvijo-code
marvijo-code
|mamertofabian
mamertofabian
| +|monkeyDluffy6017
monkeyDluffy6017
|libertyteeth
libertyteeth
|shtse8
shtse8
|ksze
ksze
|Jdo300
Jdo300
| | ## 라이선스 diff --git a/locales/nl/README.md b/locales/nl/README.md index db5c27edc8..422812de5a 100644 --- a/locales/nl/README.md +++ b/locales/nl/README.md @@ -182,37 +182,35 @@ We houden van bijdragen uit de community! Begin met het lezen van onze [CONTRIBU Dank aan alle bijdragers die Roo Code beter hebben gemaakt! - -| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| -| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| ColemanRoo
ColemanRoo
| hannesrudolph
hannesrudolph
| KJ7LNW
KJ7LNW
| stea9499
stea9499
| canrobins13
canrobins13
| joemanley201
joemanley201
| -| System233
System233
| nissa-seru
nissa-seru
| jquanton
jquanton
| NyxJae
NyxJae
| MuriloFP
MuriloFP
| d-oit
d-oit
| -| punkpeye
punkpeye
| wkordalski
wkordalski
| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| monotykamary
monotykamary
| elianiva
elianiva
| feifei325
feifei325
| -| zhangtony239
zhangtony239
| cannuri
cannuri
| lloydchang
lloydchang
| sachasayan
sachasayan
| vigneshsubbiah16
vigneshsubbiah16
| qdaxb
qdaxb
| -| Szpadel
Szpadel
| diarmidmackenzie
diarmidmackenzie
| psv2522
psv2522
| Premshay
Premshay
| lupuletic
lupuletic
| shariqriazz
shariqriazz
| -| xyOz-dev
xyOz-dev
| pugazhendhi-m
pugazhendhi-m
| aheizi
aheizi
| olweraltuve
olweraltuve
| jr
jr
| dtrugman
dtrugman
| -| nbihan-mediware
nbihan-mediware
| PeterDaveHello
PeterDaveHello
| RaySinner
RaySinner
| afshawnlotfi
afshawnlotfi
| emshvac
emshvac
| kyle-apex
kyle-apex
| -| pdecat
pdecat
| Lunchb0ne
Lunchb0ne
| vagadiya
vagadiya
| arthurauffray
arthurauffray
| upamune
upamune
| StevenTCramer
StevenTCramer
| -| sammcj
sammcj
| p12tic
p12tic
| gtaylor
gtaylor
| aitoroses
aitoroses
| noritaka1166
noritaka1166
| philfung
philfung
| -| ross
ross
| heyseth
heyseth
| taisukeoe
taisukeoe
| dlab-anton
dlab-anton
| eonghk
eonghk
| teddyOOXX
teddyOOXX
| -| vincentsong
vincentsong
| yongjer
yongjer
| SmartManoj
SmartManoj
| ashktn
ashktn
| franekp
franekp
| yt3trees
yt3trees
| -| benzntech
benzntech
| anton-otee
anton-otee
| axkirillov
axkirillov
| bramburn
bramburn
| hassoncs
hassoncs
| ChuKhaLi
ChuKhaLi
| -| snoyiatk
snoyiatk
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| Chenjiayuan195
Chenjiayuan195
| julionav
julionav
| SplittyDev
SplittyDev
| -| mdp
mdp
| napter
napter
| hatsu38
hatsu38
| hongzio
hongzio
| im47cn
im47cn
| shoopapa
shoopapa
| -| jwcraig
jwcraig
| kinandan
kinandan
| kohii
kohii
| lightrabbit
lightrabbit
| olup
olup
| nevermorec
nevermorec
| -| GOODBOY008
GOODBOY008
| dqroid
dqroid
| dairui1
dairui1
| bannzai
bannzai
| axmo
axmo
| avtc
avtc
| -| asychin
asychin
| amittell
amittell
| Yoshino-Yukitaro
Yoshino-Yukitaro
| Yikai-Liao
Yikai-Liao
| PretzelVector
PretzelVector
| zetaloop
zetaloop
| -| zeozeozeo
zeozeozeo
| cdlliuy
cdlliuy
| student20880
student20880
| shohei-ihaya
shohei-ihaya
| shaybc
shaybc
| seedlord
seedlord
| -| samir-nimbly
samir-nimbly
| ronyblum
ronyblum
| robertheadley
robertheadley
| refactorthis
refactorthis
| pokutuna
pokutuna
| philipnext
philipnext
| -| oprstchn
oprstchn
| nobu007
nobu007
| mosleyit
mosleyit
| moqimoqidea
moqimoqidea
| mlopezr
mlopezr
| mecab
mecab
| -| zxdvd
zxdvd
| hesara
hesara
| DeXtroTip
DeXtroTip
| pfitz
pfitz
| celestial-vault
celestial-vault
| linegel
linegel
| -| dbasclpy
dbasclpy
| Deon588
Deon588
| dleen
dleen
| chadgauth
chadgauth
| olearycrew
olearycrew
| bogdan0083
bogdan0083
| -| Atlogit
Atlogit
| atlasgong
atlasgong
| andreastempsch
andreastempsch
| alasano
alasano
| QuinsZouls
QuinsZouls
| HadesArchitect
HadesArchitect
| -| alarno
alarno
| adamwlarson
adamwlarson
| AMHesch
AMHesch
| vladstudio
vladstudio
| NamesMT
NamesMT
| tmsjngx0
tmsjngx0
| -| tgfjt
tgfjt
| maekawataiki
maekawataiki
| samsilveira
samsilveira
| mr-ryan-james
mr-ryan-james
| 01Rian
01Rian
| RSO
RSO
| -| R-omk
R-omk
| Sarke
Sarke
| kvokka
kvokka
| ecmasx
ecmasx
| marvijo-code
marvijo-code
| mamertofabian
mamertofabian
| -| monkeyDluffy6017
monkeyDluffy6017
| libertyteeth
libertyteeth
| shtse8
shtse8
| ksze
ksze
| Jdo300
Jdo300
| | - +|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| +|:---:|:---:|:---:|:---:|:---:|:---:| +|ColemanRoo
ColemanRoo
|hannesrudolph
hannesrudolph
|KJ7LNW
KJ7LNW
|stea9499
stea9499
|canrobins13
canrobins13
|joemanley201
joemanley201
| +|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
| +|punkpeye
punkpeye
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|elianiva
elianiva
|feifei325
feifei325
| +|zhangtony239
zhangtony239
|cannuri
cannuri
|lloydchang
lloydchang
|sachasayan
sachasayan
|vigneshsubbiah16
vigneshsubbiah16
|qdaxb
qdaxb
| +|Szpadel
Szpadel
|diarmidmackenzie
diarmidmackenzie
|psv2522
psv2522
|Premshay
Premshay
|lupuletic
lupuletic
|shariqriazz
shariqriazz
| +|xyOz-dev
xyOz-dev
|pugazhendhi-m
pugazhendhi-m
|aheizi
aheizi
|olweraltuve
olweraltuve
|jr
jr
|dtrugman
dtrugman
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|RaySinner
RaySinner
|afshawnlotfi
afshawnlotfi
|emshvac
emshvac
|kyle-apex
kyle-apex
| +|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|vagadiya
vagadiya
|arthurauffray
arthurauffray
|upamune
upamune
|StevenTCramer
StevenTCramer
| +|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
|aitoroses
aitoroses
|noritaka1166
noritaka1166
|philfung
philfung
| +|ross
ross
|heyseth
heyseth
|taisukeoe
taisukeoe
|dlab-anton
dlab-anton
|eonghk
eonghk
|teddyOOXX
teddyOOXX
| +|vincentsong
vincentsong
|yongjer
yongjer
|SmartManoj
SmartManoj
|ashktn
ashktn
|franekp
franekp
|yt3trees
yt3trees
| +|benzntech
benzntech
|anton-otee
anton-otee
|axkirillov
axkirillov
|bramburn
bramburn
|hassoncs
hassoncs
|ChuKhaLi
ChuKhaLi
| +|snoyiatk
snoyiatk
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
|julionav
julionav
|SplittyDev
SplittyDev
| +|mdp
mdp
|napter
napter
|hatsu38
hatsu38
|hongzio
hongzio
|im47cn
im47cn
|shoopapa
shoopapa
| +|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|nevermorec
nevermorec
| +|GOODBOY008
GOODBOY008
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
|avtc
avtc
| +|asychin
asychin
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|Yikai-Liao
Yikai-Liao
|PretzelVector
PretzelVector
|zetaloop
zetaloop
| +|zeozeozeo
zeozeozeo
|cdlliuy
cdlliuy
|student20880
student20880
|shohei-ihaya
shohei-ihaya
|shaybc
shaybc
|seedlord
seedlord
| +|samir-nimbly
samir-nimbly
|ronyblum
ronyblum
|robertheadley
robertheadley
|refactorthis
refactorthis
|pokutuna
pokutuna
|philipnext
philipnext
| +|oprstchn
oprstchn
|nobu007
nobu007
|mosleyit
mosleyit
|moqimoqidea
moqimoqidea
|mlopezr
mlopezr
|mecab
mecab
| +|zxdvd
zxdvd
|hesara
hesara
|DeXtroTip
DeXtroTip
|pfitz
pfitz
|celestial-vault
celestial-vault
|linegel
linegel
| +|dbasclpy
dbasclpy
|Deon588
Deon588
|dleen
dleen
|chadgauth
chadgauth
|olearycrew
olearycrew
|bogdan0083
bogdan0083
| +|Atlogit
Atlogit
|atlasgong
atlasgong
|andreastempsch
andreastempsch
|alasano
alasano
|QuinsZouls
QuinsZouls
|HadesArchitect
HadesArchitect
| +|alarno
alarno
|adamwlarson
adamwlarson
|AMHesch
AMHesch
|vladstudio
vladstudio
|NamesMT
NamesMT
|tmsjngx0
tmsjngx0
| +|tgfjt
tgfjt
|maekawataiki
maekawataiki
|samsilveira
samsilveira
|mr-ryan-james
mr-ryan-james
|01Rian
01Rian
|RSO
RSO
| +|R-omk
R-omk
|Sarke
Sarke
|kvokka
kvokka
|ecmasx
ecmasx
|marvijo-code
marvijo-code
|mamertofabian
mamertofabian
| +|monkeyDluffy6017
monkeyDluffy6017
|libertyteeth
libertyteeth
|shtse8
shtse8
|ksze
ksze
|Jdo300
Jdo300
| | ## Licentie diff --git a/locales/pl/README.md b/locales/pl/README.md index ce990816da..6d5f5611cb 100644 --- a/locales/pl/README.md +++ b/locales/pl/README.md @@ -181,37 +181,35 @@ Kochamy wkład społeczności! Zacznij od przeczytania naszego [CONTRIBUTING.md] Dziękujemy wszystkim naszym współtwórcom, którzy pomogli ulepszyć Roo Code! - -| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| -| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| ColemanRoo
ColemanRoo
| hannesrudolph
hannesrudolph
| KJ7LNW
KJ7LNW
| stea9499
stea9499
| canrobins13
canrobins13
| joemanley201
joemanley201
| -| System233
System233
| nissa-seru
nissa-seru
| jquanton
jquanton
| NyxJae
NyxJae
| MuriloFP
MuriloFP
| d-oit
d-oit
| -| punkpeye
punkpeye
| wkordalski
wkordalski
| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| monotykamary
monotykamary
| elianiva
elianiva
| feifei325
feifei325
| -| zhangtony239
zhangtony239
| cannuri
cannuri
| lloydchang
lloydchang
| sachasayan
sachasayan
| vigneshsubbiah16
vigneshsubbiah16
| qdaxb
qdaxb
| -| Szpadel
Szpadel
| diarmidmackenzie
diarmidmackenzie
| psv2522
psv2522
| Premshay
Premshay
| lupuletic
lupuletic
| shariqriazz
shariqriazz
| -| xyOz-dev
xyOz-dev
| pugazhendhi-m
pugazhendhi-m
| aheizi
aheizi
| olweraltuve
olweraltuve
| jr
jr
| dtrugman
dtrugman
| -| nbihan-mediware
nbihan-mediware
| PeterDaveHello
PeterDaveHello
| RaySinner
RaySinner
| afshawnlotfi
afshawnlotfi
| emshvac
emshvac
| kyle-apex
kyle-apex
| -| pdecat
pdecat
| Lunchb0ne
Lunchb0ne
| vagadiya
vagadiya
| arthurauffray
arthurauffray
| upamune
upamune
| StevenTCramer
StevenTCramer
| -| sammcj
sammcj
| p12tic
p12tic
| gtaylor
gtaylor
| aitoroses
aitoroses
| noritaka1166
noritaka1166
| philfung
philfung
| -| ross
ross
| heyseth
heyseth
| taisukeoe
taisukeoe
| dlab-anton
dlab-anton
| eonghk
eonghk
| teddyOOXX
teddyOOXX
| -| vincentsong
vincentsong
| yongjer
yongjer
| SmartManoj
SmartManoj
| ashktn
ashktn
| franekp
franekp
| yt3trees
yt3trees
| -| benzntech
benzntech
| anton-otee
anton-otee
| axkirillov
axkirillov
| bramburn
bramburn
| hassoncs
hassoncs
| ChuKhaLi
ChuKhaLi
| -| snoyiatk
snoyiatk
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| Chenjiayuan195
Chenjiayuan195
| julionav
julionav
| SplittyDev
SplittyDev
| -| mdp
mdp
| napter
napter
| hatsu38
hatsu38
| hongzio
hongzio
| im47cn
im47cn
| shoopapa
shoopapa
| -| jwcraig
jwcraig
| kinandan
kinandan
| kohii
kohii
| lightrabbit
lightrabbit
| olup
olup
| nevermorec
nevermorec
| -| GOODBOY008
GOODBOY008
| dqroid
dqroid
| dairui1
dairui1
| bannzai
bannzai
| axmo
axmo
| avtc
avtc
| -| asychin
asychin
| amittell
amittell
| Yoshino-Yukitaro
Yoshino-Yukitaro
| Yikai-Liao
Yikai-Liao
| PretzelVector
PretzelVector
| zetaloop
zetaloop
| -| zeozeozeo
zeozeozeo
| cdlliuy
cdlliuy
| student20880
student20880
| shohei-ihaya
shohei-ihaya
| shaybc
shaybc
| seedlord
seedlord
| -| samir-nimbly
samir-nimbly
| ronyblum
ronyblum
| robertheadley
robertheadley
| refactorthis
refactorthis
| pokutuna
pokutuna
| philipnext
philipnext
| -| oprstchn
oprstchn
| nobu007
nobu007
| mosleyit
mosleyit
| moqimoqidea
moqimoqidea
| mlopezr
mlopezr
| mecab
mecab
| -| zxdvd
zxdvd
| hesara
hesara
| DeXtroTip
DeXtroTip
| pfitz
pfitz
| celestial-vault
celestial-vault
| linegel
linegel
| -| dbasclpy
dbasclpy
| Deon588
Deon588
| dleen
dleen
| chadgauth
chadgauth
| olearycrew
olearycrew
| bogdan0083
bogdan0083
| -| Atlogit
Atlogit
| atlasgong
atlasgong
| andreastempsch
andreastempsch
| alasano
alasano
| QuinsZouls
QuinsZouls
| HadesArchitect
HadesArchitect
| -| alarno
alarno
| adamwlarson
adamwlarson
| AMHesch
AMHesch
| vladstudio
vladstudio
| NamesMT
NamesMT
| tmsjngx0
tmsjngx0
| -| tgfjt
tgfjt
| maekawataiki
maekawataiki
| samsilveira
samsilveira
| mr-ryan-james
mr-ryan-james
| 01Rian
01Rian
| RSO
RSO
| -| R-omk
R-omk
| Sarke
Sarke
| kvokka
kvokka
| ecmasx
ecmasx
| marvijo-code
marvijo-code
| mamertofabian
mamertofabian
| -| monkeyDluffy6017
monkeyDluffy6017
| libertyteeth
libertyteeth
| shtse8
shtse8
| ksze
ksze
| Jdo300
Jdo300
| | - +|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| +|:---:|:---:|:---:|:---:|:---:|:---:| +|ColemanRoo
ColemanRoo
|hannesrudolph
hannesrudolph
|KJ7LNW
KJ7LNW
|stea9499
stea9499
|canrobins13
canrobins13
|joemanley201
joemanley201
| +|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
| +|punkpeye
punkpeye
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|elianiva
elianiva
|feifei325
feifei325
| +|zhangtony239
zhangtony239
|cannuri
cannuri
|lloydchang
lloydchang
|sachasayan
sachasayan
|vigneshsubbiah16
vigneshsubbiah16
|qdaxb
qdaxb
| +|Szpadel
Szpadel
|diarmidmackenzie
diarmidmackenzie
|psv2522
psv2522
|Premshay
Premshay
|lupuletic
lupuletic
|shariqriazz
shariqriazz
| +|xyOz-dev
xyOz-dev
|pugazhendhi-m
pugazhendhi-m
|aheizi
aheizi
|olweraltuve
olweraltuve
|jr
jr
|dtrugman
dtrugman
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|RaySinner
RaySinner
|afshawnlotfi
afshawnlotfi
|emshvac
emshvac
|kyle-apex
kyle-apex
| +|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|vagadiya
vagadiya
|arthurauffray
arthurauffray
|upamune
upamune
|StevenTCramer
StevenTCramer
| +|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
|aitoroses
aitoroses
|noritaka1166
noritaka1166
|philfung
philfung
| +|ross
ross
|heyseth
heyseth
|taisukeoe
taisukeoe
|dlab-anton
dlab-anton
|eonghk
eonghk
|teddyOOXX
teddyOOXX
| +|vincentsong
vincentsong
|yongjer
yongjer
|SmartManoj
SmartManoj
|ashktn
ashktn
|franekp
franekp
|yt3trees
yt3trees
| +|benzntech
benzntech
|anton-otee
anton-otee
|axkirillov
axkirillov
|bramburn
bramburn
|hassoncs
hassoncs
|ChuKhaLi
ChuKhaLi
| +|snoyiatk
snoyiatk
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
|julionav
julionav
|SplittyDev
SplittyDev
| +|mdp
mdp
|napter
napter
|hatsu38
hatsu38
|hongzio
hongzio
|im47cn
im47cn
|shoopapa
shoopapa
| +|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|nevermorec
nevermorec
| +|GOODBOY008
GOODBOY008
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
|avtc
avtc
| +|asychin
asychin
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|Yikai-Liao
Yikai-Liao
|PretzelVector
PretzelVector
|zetaloop
zetaloop
| +|zeozeozeo
zeozeozeo
|cdlliuy
cdlliuy
|student20880
student20880
|shohei-ihaya
shohei-ihaya
|shaybc
shaybc
|seedlord
seedlord
| +|samir-nimbly
samir-nimbly
|ronyblum
ronyblum
|robertheadley
robertheadley
|refactorthis
refactorthis
|pokutuna
pokutuna
|philipnext
philipnext
| +|oprstchn
oprstchn
|nobu007
nobu007
|mosleyit
mosleyit
|moqimoqidea
moqimoqidea
|mlopezr
mlopezr
|mecab
mecab
| +|zxdvd
zxdvd
|hesara
hesara
|DeXtroTip
DeXtroTip
|pfitz
pfitz
|celestial-vault
celestial-vault
|linegel
linegel
| +|dbasclpy
dbasclpy
|Deon588
Deon588
|dleen
dleen
|chadgauth
chadgauth
|olearycrew
olearycrew
|bogdan0083
bogdan0083
| +|Atlogit
Atlogit
|atlasgong
atlasgong
|andreastempsch
andreastempsch
|alasano
alasano
|QuinsZouls
QuinsZouls
|HadesArchitect
HadesArchitect
| +|alarno
alarno
|adamwlarson
adamwlarson
|AMHesch
AMHesch
|vladstudio
vladstudio
|NamesMT
NamesMT
|tmsjngx0
tmsjngx0
| +|tgfjt
tgfjt
|maekawataiki
maekawataiki
|samsilveira
samsilveira
|mr-ryan-james
mr-ryan-james
|01Rian
01Rian
|RSO
RSO
| +|R-omk
R-omk
|Sarke
Sarke
|kvokka
kvokka
|ecmasx
ecmasx
|marvijo-code
marvijo-code
|mamertofabian
mamertofabian
| +|monkeyDluffy6017
monkeyDluffy6017
|libertyteeth
libertyteeth
|shtse8
shtse8
|ksze
ksze
|Jdo300
Jdo300
| | ## Licencja diff --git a/locales/pt-BR/README.md b/locales/pt-BR/README.md index 34fe1360c4..d31502d85f 100644 --- a/locales/pt-BR/README.md +++ b/locales/pt-BR/README.md @@ -181,37 +181,35 @@ Adoramos contribuições da comunidade! Comece lendo nosso [CONTRIBUTING.md](CON Obrigado a todos os nossos contribuidores que ajudaram a tornar o Roo Code melhor! - -| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| -| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| ColemanRoo
ColemanRoo
| hannesrudolph
hannesrudolph
| KJ7LNW
KJ7LNW
| stea9499
stea9499
| canrobins13
canrobins13
| joemanley201
joemanley201
| -| System233
System233
| nissa-seru
nissa-seru
| jquanton
jquanton
| NyxJae
NyxJae
| MuriloFP
MuriloFP
| d-oit
d-oit
| -| punkpeye
punkpeye
| wkordalski
wkordalski
| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| monotykamary
monotykamary
| elianiva
elianiva
| feifei325
feifei325
| -| zhangtony239
zhangtony239
| cannuri
cannuri
| lloydchang
lloydchang
| sachasayan
sachasayan
| vigneshsubbiah16
vigneshsubbiah16
| qdaxb
qdaxb
| -| Szpadel
Szpadel
| diarmidmackenzie
diarmidmackenzie
| psv2522
psv2522
| Premshay
Premshay
| lupuletic
lupuletic
| shariqriazz
shariqriazz
| -| xyOz-dev
xyOz-dev
| pugazhendhi-m
pugazhendhi-m
| aheizi
aheizi
| olweraltuve
olweraltuve
| jr
jr
| dtrugman
dtrugman
| -| nbihan-mediware
nbihan-mediware
| PeterDaveHello
PeterDaveHello
| RaySinner
RaySinner
| afshawnlotfi
afshawnlotfi
| emshvac
emshvac
| kyle-apex
kyle-apex
| -| pdecat
pdecat
| Lunchb0ne
Lunchb0ne
| vagadiya
vagadiya
| arthurauffray
arthurauffray
| upamune
upamune
| StevenTCramer
StevenTCramer
| -| sammcj
sammcj
| p12tic
p12tic
| gtaylor
gtaylor
| aitoroses
aitoroses
| noritaka1166
noritaka1166
| philfung
philfung
| -| ross
ross
| heyseth
heyseth
| taisukeoe
taisukeoe
| dlab-anton
dlab-anton
| eonghk
eonghk
| teddyOOXX
teddyOOXX
| -| vincentsong
vincentsong
| yongjer
yongjer
| SmartManoj
SmartManoj
| ashktn
ashktn
| franekp
franekp
| yt3trees
yt3trees
| -| benzntech
benzntech
| anton-otee
anton-otee
| axkirillov
axkirillov
| bramburn
bramburn
| hassoncs
hassoncs
| ChuKhaLi
ChuKhaLi
| -| snoyiatk
snoyiatk
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| Chenjiayuan195
Chenjiayuan195
| julionav
julionav
| SplittyDev
SplittyDev
| -| mdp
mdp
| napter
napter
| hatsu38
hatsu38
| hongzio
hongzio
| im47cn
im47cn
| shoopapa
shoopapa
| -| jwcraig
jwcraig
| kinandan
kinandan
| kohii
kohii
| lightrabbit
lightrabbit
| olup
olup
| nevermorec
nevermorec
| -| GOODBOY008
GOODBOY008
| dqroid
dqroid
| dairui1
dairui1
| bannzai
bannzai
| axmo
axmo
| avtc
avtc
| -| asychin
asychin
| amittell
amittell
| Yoshino-Yukitaro
Yoshino-Yukitaro
| Yikai-Liao
Yikai-Liao
| PretzelVector
PretzelVector
| zetaloop
zetaloop
| -| zeozeozeo
zeozeozeo
| cdlliuy
cdlliuy
| student20880
student20880
| shohei-ihaya
shohei-ihaya
| shaybc
shaybc
| seedlord
seedlord
| -| samir-nimbly
samir-nimbly
| ronyblum
ronyblum
| robertheadley
robertheadley
| refactorthis
refactorthis
| pokutuna
pokutuna
| philipnext
philipnext
| -| oprstchn
oprstchn
| nobu007
nobu007
| mosleyit
mosleyit
| moqimoqidea
moqimoqidea
| mlopezr
mlopezr
| mecab
mecab
| -| zxdvd
zxdvd
| hesara
hesara
| DeXtroTip
DeXtroTip
| pfitz
pfitz
| celestial-vault
celestial-vault
| linegel
linegel
| -| dbasclpy
dbasclpy
| Deon588
Deon588
| dleen
dleen
| chadgauth
chadgauth
| olearycrew
olearycrew
| bogdan0083
bogdan0083
| -| Atlogit
Atlogit
| atlasgong
atlasgong
| andreastempsch
andreastempsch
| alasano
alasano
| QuinsZouls
QuinsZouls
| HadesArchitect
HadesArchitect
| -| alarno
alarno
| adamwlarson
adamwlarson
| AMHesch
AMHesch
| vladstudio
vladstudio
| NamesMT
NamesMT
| tmsjngx0
tmsjngx0
| -| tgfjt
tgfjt
| maekawataiki
maekawataiki
| samsilveira
samsilveira
| mr-ryan-james
mr-ryan-james
| 01Rian
01Rian
| RSO
RSO
| -| R-omk
R-omk
| Sarke
Sarke
| kvokka
kvokka
| ecmasx
ecmasx
| marvijo-code
marvijo-code
| mamertofabian
mamertofabian
| -| monkeyDluffy6017
monkeyDluffy6017
| libertyteeth
libertyteeth
| shtse8
shtse8
| ksze
ksze
| Jdo300
Jdo300
| | - +|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| +|:---:|:---:|:---:|:---:|:---:|:---:| +|ColemanRoo
ColemanRoo
|hannesrudolph
hannesrudolph
|KJ7LNW
KJ7LNW
|stea9499
stea9499
|canrobins13
canrobins13
|joemanley201
joemanley201
| +|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
| +|punkpeye
punkpeye
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|elianiva
elianiva
|feifei325
feifei325
| +|zhangtony239
zhangtony239
|cannuri
cannuri
|lloydchang
lloydchang
|sachasayan
sachasayan
|vigneshsubbiah16
vigneshsubbiah16
|qdaxb
qdaxb
| +|Szpadel
Szpadel
|diarmidmackenzie
diarmidmackenzie
|psv2522
psv2522
|Premshay
Premshay
|lupuletic
lupuletic
|shariqriazz
shariqriazz
| +|xyOz-dev
xyOz-dev
|pugazhendhi-m
pugazhendhi-m
|aheizi
aheizi
|olweraltuve
olweraltuve
|jr
jr
|dtrugman
dtrugman
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|RaySinner
RaySinner
|afshawnlotfi
afshawnlotfi
|emshvac
emshvac
|kyle-apex
kyle-apex
| +|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|vagadiya
vagadiya
|arthurauffray
arthurauffray
|upamune
upamune
|StevenTCramer
StevenTCramer
| +|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
|aitoroses
aitoroses
|noritaka1166
noritaka1166
|philfung
philfung
| +|ross
ross
|heyseth
heyseth
|taisukeoe
taisukeoe
|dlab-anton
dlab-anton
|eonghk
eonghk
|teddyOOXX
teddyOOXX
| +|vincentsong
vincentsong
|yongjer
yongjer
|SmartManoj
SmartManoj
|ashktn
ashktn
|franekp
franekp
|yt3trees
yt3trees
| +|benzntech
benzntech
|anton-otee
anton-otee
|axkirillov
axkirillov
|bramburn
bramburn
|hassoncs
hassoncs
|ChuKhaLi
ChuKhaLi
| +|snoyiatk
snoyiatk
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
|julionav
julionav
|SplittyDev
SplittyDev
| +|mdp
mdp
|napter
napter
|hatsu38
hatsu38
|hongzio
hongzio
|im47cn
im47cn
|shoopapa
shoopapa
| +|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|nevermorec
nevermorec
| +|GOODBOY008
GOODBOY008
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
|avtc
avtc
| +|asychin
asychin
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|Yikai-Liao
Yikai-Liao
|PretzelVector
PretzelVector
|zetaloop
zetaloop
| +|zeozeozeo
zeozeozeo
|cdlliuy
cdlliuy
|student20880
student20880
|shohei-ihaya
shohei-ihaya
|shaybc
shaybc
|seedlord
seedlord
| +|samir-nimbly
samir-nimbly
|ronyblum
ronyblum
|robertheadley
robertheadley
|refactorthis
refactorthis
|pokutuna
pokutuna
|philipnext
philipnext
| +|oprstchn
oprstchn
|nobu007
nobu007
|mosleyit
mosleyit
|moqimoqidea
moqimoqidea
|mlopezr
mlopezr
|mecab
mecab
| +|zxdvd
zxdvd
|hesara
hesara
|DeXtroTip
DeXtroTip
|pfitz
pfitz
|celestial-vault
celestial-vault
|linegel
linegel
| +|dbasclpy
dbasclpy
|Deon588
Deon588
|dleen
dleen
|chadgauth
chadgauth
|olearycrew
olearycrew
|bogdan0083
bogdan0083
| +|Atlogit
Atlogit
|atlasgong
atlasgong
|andreastempsch
andreastempsch
|alasano
alasano
|QuinsZouls
QuinsZouls
|HadesArchitect
HadesArchitect
| +|alarno
alarno
|adamwlarson
adamwlarson
|AMHesch
AMHesch
|vladstudio
vladstudio
|NamesMT
NamesMT
|tmsjngx0
tmsjngx0
| +|tgfjt
tgfjt
|maekawataiki
maekawataiki
|samsilveira
samsilveira
|mr-ryan-james
mr-ryan-james
|01Rian
01Rian
|RSO
RSO
| +|R-omk
R-omk
|Sarke
Sarke
|kvokka
kvokka
|ecmasx
ecmasx
|marvijo-code
marvijo-code
|mamertofabian
mamertofabian
| +|monkeyDluffy6017
monkeyDluffy6017
|libertyteeth
libertyteeth
|shtse8
shtse8
|ksze
ksze
|Jdo300
Jdo300
| | ## Licença diff --git a/locales/ru/README.md b/locales/ru/README.md index 9c3b764e8f..020e91fc09 100644 --- a/locales/ru/README.md +++ b/locales/ru/README.md @@ -183,37 +183,35 @@ code --install-extension bin/roo-cline-.vsix Спасибо всем нашим участникам, которые помогли сделать Roo Code лучше! - -| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| -| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| ColemanRoo
ColemanRoo
| hannesrudolph
hannesrudolph
| KJ7LNW
KJ7LNW
| stea9499
stea9499
| canrobins13
canrobins13
| joemanley201
joemanley201
| -| System233
System233
| nissa-seru
nissa-seru
| jquanton
jquanton
| NyxJae
NyxJae
| MuriloFP
MuriloFP
| d-oit
d-oit
| -| punkpeye
punkpeye
| wkordalski
wkordalski
| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| monotykamary
monotykamary
| elianiva
elianiva
| feifei325
feifei325
| -| zhangtony239
zhangtony239
| cannuri
cannuri
| lloydchang
lloydchang
| sachasayan
sachasayan
| vigneshsubbiah16
vigneshsubbiah16
| qdaxb
qdaxb
| -| Szpadel
Szpadel
| diarmidmackenzie
diarmidmackenzie
| psv2522
psv2522
| Premshay
Premshay
| lupuletic
lupuletic
| shariqriazz
shariqriazz
| -| xyOz-dev
xyOz-dev
| pugazhendhi-m
pugazhendhi-m
| aheizi
aheizi
| olweraltuve
olweraltuve
| jr
jr
| dtrugman
dtrugman
| -| nbihan-mediware
nbihan-mediware
| PeterDaveHello
PeterDaveHello
| RaySinner
RaySinner
| afshawnlotfi
afshawnlotfi
| emshvac
emshvac
| kyle-apex
kyle-apex
| -| pdecat
pdecat
| Lunchb0ne
Lunchb0ne
| vagadiya
vagadiya
| arthurauffray
arthurauffray
| upamune
upamune
| StevenTCramer
StevenTCramer
| -| sammcj
sammcj
| p12tic
p12tic
| gtaylor
gtaylor
| aitoroses
aitoroses
| noritaka1166
noritaka1166
| philfung
philfung
| -| ross
ross
| heyseth
heyseth
| taisukeoe
taisukeoe
| dlab-anton
dlab-anton
| eonghk
eonghk
| teddyOOXX
teddyOOXX
| -| vincentsong
vincentsong
| yongjer
yongjer
| SmartManoj
SmartManoj
| ashktn
ashktn
| franekp
franekp
| yt3trees
yt3trees
| -| benzntech
benzntech
| anton-otee
anton-otee
| axkirillov
axkirillov
| bramburn
bramburn
| hassoncs
hassoncs
| ChuKhaLi
ChuKhaLi
| -| snoyiatk
snoyiatk
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| Chenjiayuan195
Chenjiayuan195
| julionav
julionav
| SplittyDev
SplittyDev
| -| mdp
mdp
| napter
napter
| hatsu38
hatsu38
| hongzio
hongzio
| im47cn
im47cn
| shoopapa
shoopapa
| -| jwcraig
jwcraig
| kinandan
kinandan
| kohii
kohii
| lightrabbit
lightrabbit
| olup
olup
| nevermorec
nevermorec
| -| GOODBOY008
GOODBOY008
| dqroid
dqroid
| dairui1
dairui1
| bannzai
bannzai
| axmo
axmo
| avtc
avtc
| -| asychin
asychin
| amittell
amittell
| Yoshino-Yukitaro
Yoshino-Yukitaro
| Yikai-Liao
Yikai-Liao
| PretzelVector
PretzelVector
| zetaloop
zetaloop
| -| zeozeozeo
zeozeozeo
| cdlliuy
cdlliuy
| student20880
student20880
| shohei-ihaya
shohei-ihaya
| shaybc
shaybc
| seedlord
seedlord
| -| samir-nimbly
samir-nimbly
| ronyblum
ronyblum
| robertheadley
robertheadley
| refactorthis
refactorthis
| pokutuna
pokutuna
| philipnext
philipnext
| -| oprstchn
oprstchn
| nobu007
nobu007
| mosleyit
mosleyit
| moqimoqidea
moqimoqidea
| mlopezr
mlopezr
| mecab
mecab
| -| zxdvd
zxdvd
| hesara
hesara
| DeXtroTip
DeXtroTip
| pfitz
pfitz
| celestial-vault
celestial-vault
| linegel
linegel
| -| dbasclpy
dbasclpy
| Deon588
Deon588
| dleen
dleen
| chadgauth
chadgauth
| olearycrew
olearycrew
| bogdan0083
bogdan0083
| -| Atlogit
Atlogit
| atlasgong
atlasgong
| andreastempsch
andreastempsch
| alasano
alasano
| QuinsZouls
QuinsZouls
| HadesArchitect
HadesArchitect
| -| alarno
alarno
| adamwlarson
adamwlarson
| AMHesch
AMHesch
| vladstudio
vladstudio
| NamesMT
NamesMT
| tmsjngx0
tmsjngx0
| -| tgfjt
tgfjt
| maekawataiki
maekawataiki
| samsilveira
samsilveira
| mr-ryan-james
mr-ryan-james
| 01Rian
01Rian
| RSO
RSO
| -| R-omk
R-omk
| Sarke
Sarke
| kvokka
kvokka
| ecmasx
ecmasx
| marvijo-code
marvijo-code
| mamertofabian
mamertofabian
| -| monkeyDluffy6017
monkeyDluffy6017
| libertyteeth
libertyteeth
| shtse8
shtse8
| ksze
ksze
| Jdo300
Jdo300
| | - +|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| +|:---:|:---:|:---:|:---:|:---:|:---:| +|ColemanRoo
ColemanRoo
|hannesrudolph
hannesrudolph
|KJ7LNW
KJ7LNW
|stea9499
stea9499
|canrobins13
canrobins13
|joemanley201
joemanley201
| +|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
| +|punkpeye
punkpeye
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|elianiva
elianiva
|feifei325
feifei325
| +|zhangtony239
zhangtony239
|cannuri
cannuri
|lloydchang
lloydchang
|sachasayan
sachasayan
|vigneshsubbiah16
vigneshsubbiah16
|qdaxb
qdaxb
| +|Szpadel
Szpadel
|diarmidmackenzie
diarmidmackenzie
|psv2522
psv2522
|Premshay
Premshay
|lupuletic
lupuletic
|shariqriazz
shariqriazz
| +|xyOz-dev
xyOz-dev
|pugazhendhi-m
pugazhendhi-m
|aheizi
aheizi
|olweraltuve
olweraltuve
|jr
jr
|dtrugman
dtrugman
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|RaySinner
RaySinner
|afshawnlotfi
afshawnlotfi
|emshvac
emshvac
|kyle-apex
kyle-apex
| +|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|vagadiya
vagadiya
|arthurauffray
arthurauffray
|upamune
upamune
|StevenTCramer
StevenTCramer
| +|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
|aitoroses
aitoroses
|noritaka1166
noritaka1166
|philfung
philfung
| +|ross
ross
|heyseth
heyseth
|taisukeoe
taisukeoe
|dlab-anton
dlab-anton
|eonghk
eonghk
|teddyOOXX
teddyOOXX
| +|vincentsong
vincentsong
|yongjer
yongjer
|SmartManoj
SmartManoj
|ashktn
ashktn
|franekp
franekp
|yt3trees
yt3trees
| +|benzntech
benzntech
|anton-otee
anton-otee
|axkirillov
axkirillov
|bramburn
bramburn
|hassoncs
hassoncs
|ChuKhaLi
ChuKhaLi
| +|snoyiatk
snoyiatk
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
|julionav
julionav
|SplittyDev
SplittyDev
| +|mdp
mdp
|napter
napter
|hatsu38
hatsu38
|hongzio
hongzio
|im47cn
im47cn
|shoopapa
shoopapa
| +|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|nevermorec
nevermorec
| +|GOODBOY008
GOODBOY008
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
|avtc
avtc
| +|asychin
asychin
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|Yikai-Liao
Yikai-Liao
|PretzelVector
PretzelVector
|zetaloop
zetaloop
| +|zeozeozeo
zeozeozeo
|cdlliuy
cdlliuy
|student20880
student20880
|shohei-ihaya
shohei-ihaya
|shaybc
shaybc
|seedlord
seedlord
| +|samir-nimbly
samir-nimbly
|ronyblum
ronyblum
|robertheadley
robertheadley
|refactorthis
refactorthis
|pokutuna
pokutuna
|philipnext
philipnext
| +|oprstchn
oprstchn
|nobu007
nobu007
|mosleyit
mosleyit
|moqimoqidea
moqimoqidea
|mlopezr
mlopezr
|mecab
mecab
| +|zxdvd
zxdvd
|hesara
hesara
|DeXtroTip
DeXtroTip
|pfitz
pfitz
|celestial-vault
celestial-vault
|linegel
linegel
| +|dbasclpy
dbasclpy
|Deon588
Deon588
|dleen
dleen
|chadgauth
chadgauth
|olearycrew
olearycrew
|bogdan0083
bogdan0083
| +|Atlogit
Atlogit
|atlasgong
atlasgong
|andreastempsch
andreastempsch
|alasano
alasano
|QuinsZouls
QuinsZouls
|HadesArchitect
HadesArchitect
| +|alarno
alarno
|adamwlarson
adamwlarson
|AMHesch
AMHesch
|vladstudio
vladstudio
|NamesMT
NamesMT
|tmsjngx0
tmsjngx0
| +|tgfjt
tgfjt
|maekawataiki
maekawataiki
|samsilveira
samsilveira
|mr-ryan-james
mr-ryan-james
|01Rian
01Rian
|RSO
RSO
| +|R-omk
R-omk
|Sarke
Sarke
|kvokka
kvokka
|ecmasx
ecmasx
|marvijo-code
marvijo-code
|mamertofabian
mamertofabian
| +|monkeyDluffy6017
monkeyDluffy6017
|libertyteeth
libertyteeth
|shtse8
shtse8
|ksze
ksze
|Jdo300
Jdo300
| | ## Лицензия diff --git a/locales/tr/README.md b/locales/tr/README.md index 298b88b9e3..661150e036 100644 --- a/locales/tr/README.md +++ b/locales/tr/README.md @@ -181,37 +181,35 @@ Topluluk katkılarını seviyoruz! [CONTRIBUTING.md](CONTRIBUTING.md) dosyasın Roo Code'u daha iyi hale getirmeye yardımcı olan tüm katkıda bulunanlara teşekkür ederiz! - -| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| -| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| ColemanRoo
ColemanRoo
| hannesrudolph
hannesrudolph
| KJ7LNW
KJ7LNW
| stea9499
stea9499
| canrobins13
canrobins13
| joemanley201
joemanley201
| -| System233
System233
| nissa-seru
nissa-seru
| jquanton
jquanton
| NyxJae
NyxJae
| MuriloFP
MuriloFP
| d-oit
d-oit
| -| punkpeye
punkpeye
| wkordalski
wkordalski
| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| monotykamary
monotykamary
| elianiva
elianiva
| feifei325
feifei325
| -| zhangtony239
zhangtony239
| cannuri
cannuri
| lloydchang
lloydchang
| sachasayan
sachasayan
| vigneshsubbiah16
vigneshsubbiah16
| qdaxb
qdaxb
| -| Szpadel
Szpadel
| diarmidmackenzie
diarmidmackenzie
| psv2522
psv2522
| Premshay
Premshay
| lupuletic
lupuletic
| shariqriazz
shariqriazz
| -| xyOz-dev
xyOz-dev
| pugazhendhi-m
pugazhendhi-m
| aheizi
aheizi
| olweraltuve
olweraltuve
| jr
jr
| dtrugman
dtrugman
| -| nbihan-mediware
nbihan-mediware
| PeterDaveHello
PeterDaveHello
| RaySinner
RaySinner
| afshawnlotfi
afshawnlotfi
| emshvac
emshvac
| kyle-apex
kyle-apex
| -| pdecat
pdecat
| Lunchb0ne
Lunchb0ne
| vagadiya
vagadiya
| arthurauffray
arthurauffray
| upamune
upamune
| StevenTCramer
StevenTCramer
| -| sammcj
sammcj
| p12tic
p12tic
| gtaylor
gtaylor
| aitoroses
aitoroses
| noritaka1166
noritaka1166
| philfung
philfung
| -| ross
ross
| heyseth
heyseth
| taisukeoe
taisukeoe
| dlab-anton
dlab-anton
| eonghk
eonghk
| teddyOOXX
teddyOOXX
| -| vincentsong
vincentsong
| yongjer
yongjer
| SmartManoj
SmartManoj
| ashktn
ashktn
| franekp
franekp
| yt3trees
yt3trees
| -| benzntech
benzntech
| anton-otee
anton-otee
| axkirillov
axkirillov
| bramburn
bramburn
| hassoncs
hassoncs
| ChuKhaLi
ChuKhaLi
| -| snoyiatk
snoyiatk
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| Chenjiayuan195
Chenjiayuan195
| julionav
julionav
| SplittyDev
SplittyDev
| -| mdp
mdp
| napter
napter
| hatsu38
hatsu38
| hongzio
hongzio
| im47cn
im47cn
| shoopapa
shoopapa
| -| jwcraig
jwcraig
| kinandan
kinandan
| kohii
kohii
| lightrabbit
lightrabbit
| olup
olup
| nevermorec
nevermorec
| -| GOODBOY008
GOODBOY008
| dqroid
dqroid
| dairui1
dairui1
| bannzai
bannzai
| axmo
axmo
| avtc
avtc
| -| asychin
asychin
| amittell
amittell
| Yoshino-Yukitaro
Yoshino-Yukitaro
| Yikai-Liao
Yikai-Liao
| PretzelVector
PretzelVector
| zetaloop
zetaloop
| -| zeozeozeo
zeozeozeo
| cdlliuy
cdlliuy
| student20880
student20880
| shohei-ihaya
shohei-ihaya
| shaybc
shaybc
| seedlord
seedlord
| -| samir-nimbly
samir-nimbly
| ronyblum
ronyblum
| robertheadley
robertheadley
| refactorthis
refactorthis
| pokutuna
pokutuna
| philipnext
philipnext
| -| oprstchn
oprstchn
| nobu007
nobu007
| mosleyit
mosleyit
| moqimoqidea
moqimoqidea
| mlopezr
mlopezr
| mecab
mecab
| -| zxdvd
zxdvd
| hesara
hesara
| DeXtroTip
DeXtroTip
| pfitz
pfitz
| celestial-vault
celestial-vault
| linegel
linegel
| -| dbasclpy
dbasclpy
| Deon588
Deon588
| dleen
dleen
| chadgauth
chadgauth
| olearycrew
olearycrew
| bogdan0083
bogdan0083
| -| Atlogit
Atlogit
| atlasgong
atlasgong
| andreastempsch
andreastempsch
| alasano
alasano
| QuinsZouls
QuinsZouls
| HadesArchitect
HadesArchitect
| -| alarno
alarno
| adamwlarson
adamwlarson
| AMHesch
AMHesch
| vladstudio
vladstudio
| NamesMT
NamesMT
| tmsjngx0
tmsjngx0
| -| tgfjt
tgfjt
| maekawataiki
maekawataiki
| samsilveira
samsilveira
| mr-ryan-james
mr-ryan-james
| 01Rian
01Rian
| RSO
RSO
| -| R-omk
R-omk
| Sarke
Sarke
| kvokka
kvokka
| ecmasx
ecmasx
| marvijo-code
marvijo-code
| mamertofabian
mamertofabian
| -| monkeyDluffy6017
monkeyDluffy6017
| libertyteeth
libertyteeth
| shtse8
shtse8
| ksze
ksze
| Jdo300
Jdo300
| | - +|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| +|:---:|:---:|:---:|:---:|:---:|:---:| +|ColemanRoo
ColemanRoo
|hannesrudolph
hannesrudolph
|KJ7LNW
KJ7LNW
|stea9499
stea9499
|canrobins13
canrobins13
|joemanley201
joemanley201
| +|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
| +|punkpeye
punkpeye
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|elianiva
elianiva
|feifei325
feifei325
| +|zhangtony239
zhangtony239
|cannuri
cannuri
|lloydchang
lloydchang
|sachasayan
sachasayan
|vigneshsubbiah16
vigneshsubbiah16
|qdaxb
qdaxb
| +|Szpadel
Szpadel
|diarmidmackenzie
diarmidmackenzie
|psv2522
psv2522
|Premshay
Premshay
|lupuletic
lupuletic
|shariqriazz
shariqriazz
| +|xyOz-dev
xyOz-dev
|pugazhendhi-m
pugazhendhi-m
|aheizi
aheizi
|olweraltuve
olweraltuve
|jr
jr
|dtrugman
dtrugman
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|RaySinner
RaySinner
|afshawnlotfi
afshawnlotfi
|emshvac
emshvac
|kyle-apex
kyle-apex
| +|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|vagadiya
vagadiya
|arthurauffray
arthurauffray
|upamune
upamune
|StevenTCramer
StevenTCramer
| +|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
|aitoroses
aitoroses
|noritaka1166
noritaka1166
|philfung
philfung
| +|ross
ross
|heyseth
heyseth
|taisukeoe
taisukeoe
|dlab-anton
dlab-anton
|eonghk
eonghk
|teddyOOXX
teddyOOXX
| +|vincentsong
vincentsong
|yongjer
yongjer
|SmartManoj
SmartManoj
|ashktn
ashktn
|franekp
franekp
|yt3trees
yt3trees
| +|benzntech
benzntech
|anton-otee
anton-otee
|axkirillov
axkirillov
|bramburn
bramburn
|hassoncs
hassoncs
|ChuKhaLi
ChuKhaLi
| +|snoyiatk
snoyiatk
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
|julionav
julionav
|SplittyDev
SplittyDev
| +|mdp
mdp
|napter
napter
|hatsu38
hatsu38
|hongzio
hongzio
|im47cn
im47cn
|shoopapa
shoopapa
| +|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|nevermorec
nevermorec
| +|GOODBOY008
GOODBOY008
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
|avtc
avtc
| +|asychin
asychin
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|Yikai-Liao
Yikai-Liao
|PretzelVector
PretzelVector
|zetaloop
zetaloop
| +|zeozeozeo
zeozeozeo
|cdlliuy
cdlliuy
|student20880
student20880
|shohei-ihaya
shohei-ihaya
|shaybc
shaybc
|seedlord
seedlord
| +|samir-nimbly
samir-nimbly
|ronyblum
ronyblum
|robertheadley
robertheadley
|refactorthis
refactorthis
|pokutuna
pokutuna
|philipnext
philipnext
| +|oprstchn
oprstchn
|nobu007
nobu007
|mosleyit
mosleyit
|moqimoqidea
moqimoqidea
|mlopezr
mlopezr
|mecab
mecab
| +|zxdvd
zxdvd
|hesara
hesara
|DeXtroTip
DeXtroTip
|pfitz
pfitz
|celestial-vault
celestial-vault
|linegel
linegel
| +|dbasclpy
dbasclpy
|Deon588
Deon588
|dleen
dleen
|chadgauth
chadgauth
|olearycrew
olearycrew
|bogdan0083
bogdan0083
| +|Atlogit
Atlogit
|atlasgong
atlasgong
|andreastempsch
andreastempsch
|alasano
alasano
|QuinsZouls
QuinsZouls
|HadesArchitect
HadesArchitect
| +|alarno
alarno
|adamwlarson
adamwlarson
|AMHesch
AMHesch
|vladstudio
vladstudio
|NamesMT
NamesMT
|tmsjngx0
tmsjngx0
| +|tgfjt
tgfjt
|maekawataiki
maekawataiki
|samsilveira
samsilveira
|mr-ryan-james
mr-ryan-james
|01Rian
01Rian
|RSO
RSO
| +|R-omk
R-omk
|Sarke
Sarke
|kvokka
kvokka
|ecmasx
ecmasx
|marvijo-code
marvijo-code
|mamertofabian
mamertofabian
| +|monkeyDluffy6017
monkeyDluffy6017
|libertyteeth
libertyteeth
|shtse8
shtse8
|ksze
ksze
|Jdo300
Jdo300
| | ## Lisans diff --git a/locales/vi/README.md b/locales/vi/README.md index 73ba117ea1..6bb7683587 100644 --- a/locales/vi/README.md +++ b/locales/vi/README.md @@ -181,37 +181,35 @@ Chúng tôi rất hoan nghênh đóng góp từ cộng đồng! Bắt đầu b Cảm ơn tất cả những người đóng góp đã giúp cải thiện Roo Code! - -| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| -| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| ColemanRoo
ColemanRoo
| hannesrudolph
hannesrudolph
| KJ7LNW
KJ7LNW
| stea9499
stea9499
| canrobins13
canrobins13
| joemanley201
joemanley201
| -| System233
System233
| nissa-seru
nissa-seru
| jquanton
jquanton
| NyxJae
NyxJae
| MuriloFP
MuriloFP
| d-oit
d-oit
| -| punkpeye
punkpeye
| wkordalski
wkordalski
| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| monotykamary
monotykamary
| elianiva
elianiva
| feifei325
feifei325
| -| zhangtony239
zhangtony239
| cannuri
cannuri
| lloydchang
lloydchang
| sachasayan
sachasayan
| vigneshsubbiah16
vigneshsubbiah16
| qdaxb
qdaxb
| -| Szpadel
Szpadel
| diarmidmackenzie
diarmidmackenzie
| psv2522
psv2522
| Premshay
Premshay
| lupuletic
lupuletic
| shariqriazz
shariqriazz
| -| xyOz-dev
xyOz-dev
| pugazhendhi-m
pugazhendhi-m
| aheizi
aheizi
| olweraltuve
olweraltuve
| jr
jr
| dtrugman
dtrugman
| -| nbihan-mediware
nbihan-mediware
| PeterDaveHello
PeterDaveHello
| RaySinner
RaySinner
| afshawnlotfi
afshawnlotfi
| emshvac
emshvac
| kyle-apex
kyle-apex
| -| pdecat
pdecat
| Lunchb0ne
Lunchb0ne
| vagadiya
vagadiya
| arthurauffray
arthurauffray
| upamune
upamune
| StevenTCramer
StevenTCramer
| -| sammcj
sammcj
| p12tic
p12tic
| gtaylor
gtaylor
| aitoroses
aitoroses
| noritaka1166
noritaka1166
| philfung
philfung
| -| ross
ross
| heyseth
heyseth
| taisukeoe
taisukeoe
| dlab-anton
dlab-anton
| eonghk
eonghk
| teddyOOXX
teddyOOXX
| -| vincentsong
vincentsong
| yongjer
yongjer
| SmartManoj
SmartManoj
| ashktn
ashktn
| franekp
franekp
| yt3trees
yt3trees
| -| benzntech
benzntech
| anton-otee
anton-otee
| axkirillov
axkirillov
| bramburn
bramburn
| hassoncs
hassoncs
| ChuKhaLi
ChuKhaLi
| -| snoyiatk
snoyiatk
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| Chenjiayuan195
Chenjiayuan195
| julionav
julionav
| SplittyDev
SplittyDev
| -| mdp
mdp
| napter
napter
| hatsu38
hatsu38
| hongzio
hongzio
| im47cn
im47cn
| shoopapa
shoopapa
| -| jwcraig
jwcraig
| kinandan
kinandan
| kohii
kohii
| lightrabbit
lightrabbit
| olup
olup
| nevermorec
nevermorec
| -| GOODBOY008
GOODBOY008
| dqroid
dqroid
| dairui1
dairui1
| bannzai
bannzai
| axmo
axmo
| avtc
avtc
| -| asychin
asychin
| amittell
amittell
| Yoshino-Yukitaro
Yoshino-Yukitaro
| Yikai-Liao
Yikai-Liao
| PretzelVector
PretzelVector
| zetaloop
zetaloop
| -| zeozeozeo
zeozeozeo
| cdlliuy
cdlliuy
| student20880
student20880
| shohei-ihaya
shohei-ihaya
| shaybc
shaybc
| seedlord
seedlord
| -| samir-nimbly
samir-nimbly
| ronyblum
ronyblum
| robertheadley
robertheadley
| refactorthis
refactorthis
| pokutuna
pokutuna
| philipnext
philipnext
| -| oprstchn
oprstchn
| nobu007
nobu007
| mosleyit
mosleyit
| moqimoqidea
moqimoqidea
| mlopezr
mlopezr
| mecab
mecab
| -| zxdvd
zxdvd
| hesara
hesara
| DeXtroTip
DeXtroTip
| pfitz
pfitz
| celestial-vault
celestial-vault
| linegel
linegel
| -| dbasclpy
dbasclpy
| Deon588
Deon588
| dleen
dleen
| chadgauth
chadgauth
| olearycrew
olearycrew
| bogdan0083
bogdan0083
| -| Atlogit
Atlogit
| atlasgong
atlasgong
| andreastempsch
andreastempsch
| alasano
alasano
| QuinsZouls
QuinsZouls
| HadesArchitect
HadesArchitect
| -| alarno
alarno
| adamwlarson
adamwlarson
| AMHesch
AMHesch
| vladstudio
vladstudio
| NamesMT
NamesMT
| tmsjngx0
tmsjngx0
| -| tgfjt
tgfjt
| maekawataiki
maekawataiki
| samsilveira
samsilveira
| mr-ryan-james
mr-ryan-james
| 01Rian
01Rian
| RSO
RSO
| -| R-omk
R-omk
| Sarke
Sarke
| kvokka
kvokka
| ecmasx
ecmasx
| marvijo-code
marvijo-code
| mamertofabian
mamertofabian
| -| monkeyDluffy6017
monkeyDluffy6017
| libertyteeth
libertyteeth
| shtse8
shtse8
| ksze
ksze
| Jdo300
Jdo300
| | - +|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| +|:---:|:---:|:---:|:---:|:---:|:---:| +|ColemanRoo
ColemanRoo
|hannesrudolph
hannesrudolph
|KJ7LNW
KJ7LNW
|stea9499
stea9499
|canrobins13
canrobins13
|joemanley201
joemanley201
| +|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
| +|punkpeye
punkpeye
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|elianiva
elianiva
|feifei325
feifei325
| +|zhangtony239
zhangtony239
|cannuri
cannuri
|lloydchang
lloydchang
|sachasayan
sachasayan
|vigneshsubbiah16
vigneshsubbiah16
|qdaxb
qdaxb
| +|Szpadel
Szpadel
|diarmidmackenzie
diarmidmackenzie
|psv2522
psv2522
|Premshay
Premshay
|lupuletic
lupuletic
|shariqriazz
shariqriazz
| +|xyOz-dev
xyOz-dev
|pugazhendhi-m
pugazhendhi-m
|aheizi
aheizi
|olweraltuve
olweraltuve
|jr
jr
|dtrugman
dtrugman
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|RaySinner
RaySinner
|afshawnlotfi
afshawnlotfi
|emshvac
emshvac
|kyle-apex
kyle-apex
| +|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|vagadiya
vagadiya
|arthurauffray
arthurauffray
|upamune
upamune
|StevenTCramer
StevenTCramer
| +|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
|aitoroses
aitoroses
|noritaka1166
noritaka1166
|philfung
philfung
| +|ross
ross
|heyseth
heyseth
|taisukeoe
taisukeoe
|dlab-anton
dlab-anton
|eonghk
eonghk
|teddyOOXX
teddyOOXX
| +|vincentsong
vincentsong
|yongjer
yongjer
|SmartManoj
SmartManoj
|ashktn
ashktn
|franekp
franekp
|yt3trees
yt3trees
| +|benzntech
benzntech
|anton-otee
anton-otee
|axkirillov
axkirillov
|bramburn
bramburn
|hassoncs
hassoncs
|ChuKhaLi
ChuKhaLi
| +|snoyiatk
snoyiatk
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
|julionav
julionav
|SplittyDev
SplittyDev
| +|mdp
mdp
|napter
napter
|hatsu38
hatsu38
|hongzio
hongzio
|im47cn
im47cn
|shoopapa
shoopapa
| +|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|nevermorec
nevermorec
| +|GOODBOY008
GOODBOY008
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
|avtc
avtc
| +|asychin
asychin
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|Yikai-Liao
Yikai-Liao
|PretzelVector
PretzelVector
|zetaloop
zetaloop
| +|zeozeozeo
zeozeozeo
|cdlliuy
cdlliuy
|student20880
student20880
|shohei-ihaya
shohei-ihaya
|shaybc
shaybc
|seedlord
seedlord
| +|samir-nimbly
samir-nimbly
|ronyblum
ronyblum
|robertheadley
robertheadley
|refactorthis
refactorthis
|pokutuna
pokutuna
|philipnext
philipnext
| +|oprstchn
oprstchn
|nobu007
nobu007
|mosleyit
mosleyit
|moqimoqidea
moqimoqidea
|mlopezr
mlopezr
|mecab
mecab
| +|zxdvd
zxdvd
|hesara
hesara
|DeXtroTip
DeXtroTip
|pfitz
pfitz
|celestial-vault
celestial-vault
|linegel
linegel
| +|dbasclpy
dbasclpy
|Deon588
Deon588
|dleen
dleen
|chadgauth
chadgauth
|olearycrew
olearycrew
|bogdan0083
bogdan0083
| +|Atlogit
Atlogit
|atlasgong
atlasgong
|andreastempsch
andreastempsch
|alasano
alasano
|QuinsZouls
QuinsZouls
|HadesArchitect
HadesArchitect
| +|alarno
alarno
|adamwlarson
adamwlarson
|AMHesch
AMHesch
|vladstudio
vladstudio
|NamesMT
NamesMT
|tmsjngx0
tmsjngx0
| +|tgfjt
tgfjt
|maekawataiki
maekawataiki
|samsilveira
samsilveira
|mr-ryan-james
mr-ryan-james
|01Rian
01Rian
|RSO
RSO
| +|R-omk
R-omk
|Sarke
Sarke
|kvokka
kvokka
|ecmasx
ecmasx
|marvijo-code
marvijo-code
|mamertofabian
mamertofabian
| +|monkeyDluffy6017
monkeyDluffy6017
|libertyteeth
libertyteeth
|shtse8
shtse8
|ksze
ksze
|Jdo300
Jdo300
| | ## Giấy Phép diff --git a/locales/zh-CN/README.md b/locales/zh-CN/README.md index 1ec1b51d2b..837a66ddea 100644 --- a/locales/zh-CN/README.md +++ b/locales/zh-CN/README.md @@ -181,37 +181,35 @@ code --install-extension bin/roo-cline-.vsix 感谢所有帮助改进 Roo Code 的贡献者! - -| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| -| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| ColemanRoo
ColemanRoo
| hannesrudolph
hannesrudolph
| KJ7LNW
KJ7LNW
| stea9499
stea9499
| canrobins13
canrobins13
| joemanley201
joemanley201
| -| System233
System233
| nissa-seru
nissa-seru
| jquanton
jquanton
| NyxJae
NyxJae
| MuriloFP
MuriloFP
| d-oit
d-oit
| -| punkpeye
punkpeye
| wkordalski
wkordalski
| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| monotykamary
monotykamary
| elianiva
elianiva
| feifei325
feifei325
| -| zhangtony239
zhangtony239
| cannuri
cannuri
| lloydchang
lloydchang
| sachasayan
sachasayan
| vigneshsubbiah16
vigneshsubbiah16
| qdaxb
qdaxb
| -| Szpadel
Szpadel
| diarmidmackenzie
diarmidmackenzie
| psv2522
psv2522
| Premshay
Premshay
| lupuletic
lupuletic
| shariqriazz
shariqriazz
| -| xyOz-dev
xyOz-dev
| pugazhendhi-m
pugazhendhi-m
| aheizi
aheizi
| olweraltuve
olweraltuve
| jr
jr
| dtrugman
dtrugman
| -| nbihan-mediware
nbihan-mediware
| PeterDaveHello
PeterDaveHello
| RaySinner
RaySinner
| afshawnlotfi
afshawnlotfi
| emshvac
emshvac
| kyle-apex
kyle-apex
| -| pdecat
pdecat
| Lunchb0ne
Lunchb0ne
| vagadiya
vagadiya
| arthurauffray
arthurauffray
| upamune
upamune
| StevenTCramer
StevenTCramer
| -| sammcj
sammcj
| p12tic
p12tic
| gtaylor
gtaylor
| aitoroses
aitoroses
| noritaka1166
noritaka1166
| philfung
philfung
| -| ross
ross
| heyseth
heyseth
| taisukeoe
taisukeoe
| dlab-anton
dlab-anton
| eonghk
eonghk
| teddyOOXX
teddyOOXX
| -| vincentsong
vincentsong
| yongjer
yongjer
| SmartManoj
SmartManoj
| ashktn
ashktn
| franekp
franekp
| yt3trees
yt3trees
| -| benzntech
benzntech
| anton-otee
anton-otee
| axkirillov
axkirillov
| bramburn
bramburn
| hassoncs
hassoncs
| ChuKhaLi
ChuKhaLi
| -| snoyiatk
snoyiatk
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| Chenjiayuan195
Chenjiayuan195
| julionav
julionav
| SplittyDev
SplittyDev
| -| mdp
mdp
| napter
napter
| hatsu38
hatsu38
| hongzio
hongzio
| im47cn
im47cn
| shoopapa
shoopapa
| -| jwcraig
jwcraig
| kinandan
kinandan
| kohii
kohii
| lightrabbit
lightrabbit
| olup
olup
| nevermorec
nevermorec
| -| GOODBOY008
GOODBOY008
| dqroid
dqroid
| dairui1
dairui1
| bannzai
bannzai
| axmo
axmo
| avtc
avtc
| -| asychin
asychin
| amittell
amittell
| Yoshino-Yukitaro
Yoshino-Yukitaro
| Yikai-Liao
Yikai-Liao
| PretzelVector
PretzelVector
| zetaloop
zetaloop
| -| zeozeozeo
zeozeozeo
| cdlliuy
cdlliuy
| student20880
student20880
| shohei-ihaya
shohei-ihaya
| shaybc
shaybc
| seedlord
seedlord
| -| samir-nimbly
samir-nimbly
| ronyblum
ronyblum
| robertheadley
robertheadley
| refactorthis
refactorthis
| pokutuna
pokutuna
| philipnext
philipnext
| -| oprstchn
oprstchn
| nobu007
nobu007
| mosleyit
mosleyit
| moqimoqidea
moqimoqidea
| mlopezr
mlopezr
| mecab
mecab
| -| zxdvd
zxdvd
| hesara
hesara
| DeXtroTip
DeXtroTip
| pfitz
pfitz
| celestial-vault
celestial-vault
| linegel
linegel
| -| dbasclpy
dbasclpy
| Deon588
Deon588
| dleen
dleen
| chadgauth
chadgauth
| olearycrew
olearycrew
| bogdan0083
bogdan0083
| -| Atlogit
Atlogit
| atlasgong
atlasgong
| andreastempsch
andreastempsch
| alasano
alasano
| QuinsZouls
QuinsZouls
| HadesArchitect
HadesArchitect
| -| alarno
alarno
| adamwlarson
adamwlarson
| AMHesch
AMHesch
| vladstudio
vladstudio
| NamesMT
NamesMT
| tmsjngx0
tmsjngx0
| -| tgfjt
tgfjt
| maekawataiki
maekawataiki
| samsilveira
samsilveira
| mr-ryan-james
mr-ryan-james
| 01Rian
01Rian
| RSO
RSO
| -| R-omk
R-omk
| Sarke
Sarke
| kvokka
kvokka
| ecmasx
ecmasx
| marvijo-code
marvijo-code
| mamertofabian
mamertofabian
| -| monkeyDluffy6017
monkeyDluffy6017
| libertyteeth
libertyteeth
| shtse8
shtse8
| ksze
ksze
| Jdo300
Jdo300
| | - +|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| +|:---:|:---:|:---:|:---:|:---:|:---:| +|ColemanRoo
ColemanRoo
|hannesrudolph
hannesrudolph
|KJ7LNW
KJ7LNW
|stea9499
stea9499
|canrobins13
canrobins13
|joemanley201
joemanley201
| +|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
| +|punkpeye
punkpeye
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|elianiva
elianiva
|feifei325
feifei325
| +|zhangtony239
zhangtony239
|cannuri
cannuri
|lloydchang
lloydchang
|sachasayan
sachasayan
|vigneshsubbiah16
vigneshsubbiah16
|qdaxb
qdaxb
| +|Szpadel
Szpadel
|diarmidmackenzie
diarmidmackenzie
|psv2522
psv2522
|Premshay
Premshay
|lupuletic
lupuletic
|shariqriazz
shariqriazz
| +|xyOz-dev
xyOz-dev
|pugazhendhi-m
pugazhendhi-m
|aheizi
aheizi
|olweraltuve
olweraltuve
|jr
jr
|dtrugman
dtrugman
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|RaySinner
RaySinner
|afshawnlotfi
afshawnlotfi
|emshvac
emshvac
|kyle-apex
kyle-apex
| +|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|vagadiya
vagadiya
|arthurauffray
arthurauffray
|upamune
upamune
|StevenTCramer
StevenTCramer
| +|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
|aitoroses
aitoroses
|noritaka1166
noritaka1166
|philfung
philfung
| +|ross
ross
|heyseth
heyseth
|taisukeoe
taisukeoe
|dlab-anton
dlab-anton
|eonghk
eonghk
|teddyOOXX
teddyOOXX
| +|vincentsong
vincentsong
|yongjer
yongjer
|SmartManoj
SmartManoj
|ashktn
ashktn
|franekp
franekp
|yt3trees
yt3trees
| +|benzntech
benzntech
|anton-otee
anton-otee
|axkirillov
axkirillov
|bramburn
bramburn
|hassoncs
hassoncs
|ChuKhaLi
ChuKhaLi
| +|snoyiatk
snoyiatk
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
|julionav
julionav
|SplittyDev
SplittyDev
| +|mdp
mdp
|napter
napter
|hatsu38
hatsu38
|hongzio
hongzio
|im47cn
im47cn
|shoopapa
shoopapa
| +|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|nevermorec
nevermorec
| +|GOODBOY008
GOODBOY008
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
|avtc
avtc
| +|asychin
asychin
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|Yikai-Liao
Yikai-Liao
|PretzelVector
PretzelVector
|zetaloop
zetaloop
| +|zeozeozeo
zeozeozeo
|cdlliuy
cdlliuy
|student20880
student20880
|shohei-ihaya
shohei-ihaya
|shaybc
shaybc
|seedlord
seedlord
| +|samir-nimbly
samir-nimbly
|ronyblum
ronyblum
|robertheadley
robertheadley
|refactorthis
refactorthis
|pokutuna
pokutuna
|philipnext
philipnext
| +|oprstchn
oprstchn
|nobu007
nobu007
|mosleyit
mosleyit
|moqimoqidea
moqimoqidea
|mlopezr
mlopezr
|mecab
mecab
| +|zxdvd
zxdvd
|hesara
hesara
|DeXtroTip
DeXtroTip
|pfitz
pfitz
|celestial-vault
celestial-vault
|linegel
linegel
| +|dbasclpy
dbasclpy
|Deon588
Deon588
|dleen
dleen
|chadgauth
chadgauth
|olearycrew
olearycrew
|bogdan0083
bogdan0083
| +|Atlogit
Atlogit
|atlasgong
atlasgong
|andreastempsch
andreastempsch
|alasano
alasano
|QuinsZouls
QuinsZouls
|HadesArchitect
HadesArchitect
| +|alarno
alarno
|adamwlarson
adamwlarson
|AMHesch
AMHesch
|vladstudio
vladstudio
|NamesMT
NamesMT
|tmsjngx0
tmsjngx0
| +|tgfjt
tgfjt
|maekawataiki
maekawataiki
|samsilveira
samsilveira
|mr-ryan-james
mr-ryan-james
|01Rian
01Rian
|RSO
RSO
| +|R-omk
R-omk
|Sarke
Sarke
|kvokka
kvokka
|ecmasx
ecmasx
|marvijo-code
marvijo-code
|mamertofabian
mamertofabian
| +|monkeyDluffy6017
monkeyDluffy6017
|libertyteeth
libertyteeth
|shtse8
shtse8
|ksze
ksze
|Jdo300
Jdo300
| | ## 许可证 diff --git a/locales/zh-TW/README.md b/locales/zh-TW/README.md index 5db380053a..de740f8073 100644 --- a/locales/zh-TW/README.md +++ b/locales/zh-TW/README.md @@ -182,37 +182,35 @@ code --install-extension bin/roo-cline-.vsix 感謝所有幫助改進 Roo Code 的貢獻者! - -| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| -| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| ColemanRoo
ColemanRoo
| hannesrudolph
hannesrudolph
| KJ7LNW
KJ7LNW
| stea9499
stea9499
| canrobins13
canrobins13
| joemanley201
joemanley201
| -| System233
System233
| nissa-seru
nissa-seru
| jquanton
jquanton
| NyxJae
NyxJae
| MuriloFP
MuriloFP
| d-oit
d-oit
| -| punkpeye
punkpeye
| wkordalski
wkordalski
| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| monotykamary
monotykamary
| elianiva
elianiva
| feifei325
feifei325
| -| zhangtony239
zhangtony239
| cannuri
cannuri
| lloydchang
lloydchang
| sachasayan
sachasayan
| vigneshsubbiah16
vigneshsubbiah16
| qdaxb
qdaxb
| -| Szpadel
Szpadel
| diarmidmackenzie
diarmidmackenzie
| psv2522
psv2522
| Premshay
Premshay
| lupuletic
lupuletic
| shariqriazz
shariqriazz
| -| xyOz-dev
xyOz-dev
| pugazhendhi-m
pugazhendhi-m
| aheizi
aheizi
| olweraltuve
olweraltuve
| jr
jr
| dtrugman
dtrugman
| -| nbihan-mediware
nbihan-mediware
| PeterDaveHello
PeterDaveHello
| RaySinner
RaySinner
| afshawnlotfi
afshawnlotfi
| emshvac
emshvac
| kyle-apex
kyle-apex
| -| pdecat
pdecat
| Lunchb0ne
Lunchb0ne
| vagadiya
vagadiya
| arthurauffray
arthurauffray
| upamune
upamune
| StevenTCramer
StevenTCramer
| -| sammcj
sammcj
| p12tic
p12tic
| gtaylor
gtaylor
| aitoroses
aitoroses
| noritaka1166
noritaka1166
| philfung
philfung
| -| ross
ross
| heyseth
heyseth
| taisukeoe
taisukeoe
| dlab-anton
dlab-anton
| eonghk
eonghk
| teddyOOXX
teddyOOXX
| -| vincentsong
vincentsong
| yongjer
yongjer
| SmartManoj
SmartManoj
| ashktn
ashktn
| franekp
franekp
| yt3trees
yt3trees
| -| benzntech
benzntech
| anton-otee
anton-otee
| axkirillov
axkirillov
| bramburn
bramburn
| hassoncs
hassoncs
| ChuKhaLi
ChuKhaLi
| -| snoyiatk
snoyiatk
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| Chenjiayuan195
Chenjiayuan195
| julionav
julionav
| SplittyDev
SplittyDev
| -| mdp
mdp
| napter
napter
| hatsu38
hatsu38
| hongzio
hongzio
| im47cn
im47cn
| shoopapa
shoopapa
| -| jwcraig
jwcraig
| kinandan
kinandan
| kohii
kohii
| lightrabbit
lightrabbit
| olup
olup
| nevermorec
nevermorec
| -| GOODBOY008
GOODBOY008
| dqroid
dqroid
| dairui1
dairui1
| bannzai
bannzai
| axmo
axmo
| avtc
avtc
| -| asychin
asychin
| amittell
amittell
| Yoshino-Yukitaro
Yoshino-Yukitaro
| Yikai-Liao
Yikai-Liao
| PretzelVector
PretzelVector
| zetaloop
zetaloop
| -| zeozeozeo
zeozeozeo
| cdlliuy
cdlliuy
| student20880
student20880
| shohei-ihaya
shohei-ihaya
| shaybc
shaybc
| seedlord
seedlord
| -| samir-nimbly
samir-nimbly
| ronyblum
ronyblum
| robertheadley
robertheadley
| refactorthis
refactorthis
| pokutuna
pokutuna
| philipnext
philipnext
| -| oprstchn
oprstchn
| nobu007
nobu007
| mosleyit
mosleyit
| moqimoqidea
moqimoqidea
| mlopezr
mlopezr
| mecab
mecab
| -| zxdvd
zxdvd
| hesara
hesara
| DeXtroTip
DeXtroTip
| pfitz
pfitz
| celestial-vault
celestial-vault
| linegel
linegel
| -| dbasclpy
dbasclpy
| Deon588
Deon588
| dleen
dleen
| chadgauth
chadgauth
| olearycrew
olearycrew
| bogdan0083
bogdan0083
| -| Atlogit
Atlogit
| atlasgong
atlasgong
| andreastempsch
andreastempsch
| alasano
alasano
| QuinsZouls
QuinsZouls
| HadesArchitect
HadesArchitect
| -| alarno
alarno
| adamwlarson
adamwlarson
| AMHesch
AMHesch
| vladstudio
vladstudio
| NamesMT
NamesMT
| tmsjngx0
tmsjngx0
| -| tgfjt
tgfjt
| maekawataiki
maekawataiki
| samsilveira
samsilveira
| mr-ryan-james
mr-ryan-james
| 01Rian
01Rian
| RSO
RSO
| -| R-omk
R-omk
| Sarke
Sarke
| kvokka
kvokka
| ecmasx
ecmasx
| marvijo-code
marvijo-code
| mamertofabian
mamertofabian
| -| monkeyDluffy6017
monkeyDluffy6017
| libertyteeth
libertyteeth
| shtse8
shtse8
| ksze
ksze
| Jdo300
Jdo300
| | - +|mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| +|:---:|:---:|:---:|:---:|:---:|:---:| +|ColemanRoo
ColemanRoo
|hannesrudolph
hannesrudolph
|KJ7LNW
KJ7LNW
|stea9499
stea9499
|canrobins13
canrobins13
|joemanley201
joemanley201
| +|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|d-oit
d-oit
| +|punkpeye
punkpeye
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|monotykamary
monotykamary
|elianiva
elianiva
|feifei325
feifei325
| +|zhangtony239
zhangtony239
|cannuri
cannuri
|lloydchang
lloydchang
|sachasayan
sachasayan
|vigneshsubbiah16
vigneshsubbiah16
|qdaxb
qdaxb
| +|Szpadel
Szpadel
|diarmidmackenzie
diarmidmackenzie
|psv2522
psv2522
|Premshay
Premshay
|lupuletic
lupuletic
|shariqriazz
shariqriazz
| +|xyOz-dev
xyOz-dev
|pugazhendhi-m
pugazhendhi-m
|aheizi
aheizi
|olweraltuve
olweraltuve
|jr
jr
|dtrugman
dtrugman
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|RaySinner
RaySinner
|afshawnlotfi
afshawnlotfi
|emshvac
emshvac
|kyle-apex
kyle-apex
| +|pdecat
pdecat
|Lunchb0ne
Lunchb0ne
|vagadiya
vagadiya
|arthurauffray
arthurauffray
|upamune
upamune
|StevenTCramer
StevenTCramer
| +|sammcj
sammcj
|p12tic
p12tic
|gtaylor
gtaylor
|aitoroses
aitoroses
|noritaka1166
noritaka1166
|philfung
philfung
| +|ross
ross
|heyseth
heyseth
|taisukeoe
taisukeoe
|dlab-anton
dlab-anton
|eonghk
eonghk
|teddyOOXX
teddyOOXX
| +|vincentsong
vincentsong
|yongjer
yongjer
|SmartManoj
SmartManoj
|ashktn
ashktn
|franekp
franekp
|yt3trees
yt3trees
| +|benzntech
benzntech
|anton-otee
anton-otee
|axkirillov
axkirillov
|bramburn
bramburn
|hassoncs
hassoncs
|ChuKhaLi
ChuKhaLi
| +|snoyiatk
snoyiatk
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
|julionav
julionav
|SplittyDev
SplittyDev
| +|mdp
mdp
|napter
napter
|hatsu38
hatsu38
|hongzio
hongzio
|im47cn
im47cn
|shoopapa
shoopapa
| +|jwcraig
jwcraig
|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|nevermorec
nevermorec
| +|GOODBOY008
GOODBOY008
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|axmo
axmo
|avtc
avtc
| +|asychin
asychin
|amittell
amittell
|Yoshino-Yukitaro
Yoshino-Yukitaro
|Yikai-Liao
Yikai-Liao
|PretzelVector
PretzelVector
|zetaloop
zetaloop
| +|zeozeozeo
zeozeozeo
|cdlliuy
cdlliuy
|student20880
student20880
|shohei-ihaya
shohei-ihaya
|shaybc
shaybc
|seedlord
seedlord
| +|samir-nimbly
samir-nimbly
|ronyblum
ronyblum
|robertheadley
robertheadley
|refactorthis
refactorthis
|pokutuna
pokutuna
|philipnext
philipnext
| +|oprstchn
oprstchn
|nobu007
nobu007
|mosleyit
mosleyit
|moqimoqidea
moqimoqidea
|mlopezr
mlopezr
|mecab
mecab
| +|zxdvd
zxdvd
|hesara
hesara
|DeXtroTip
DeXtroTip
|pfitz
pfitz
|celestial-vault
celestial-vault
|linegel
linegel
| +|dbasclpy
dbasclpy
|Deon588
Deon588
|dleen
dleen
|chadgauth
chadgauth
|olearycrew
olearycrew
|bogdan0083
bogdan0083
| +|Atlogit
Atlogit
|atlasgong
atlasgong
|andreastempsch
andreastempsch
|alasano
alasano
|QuinsZouls
QuinsZouls
|HadesArchitect
HadesArchitect
| +|alarno
alarno
|adamwlarson
adamwlarson
|AMHesch
AMHesch
|vladstudio
vladstudio
|NamesMT
NamesMT
|tmsjngx0
tmsjngx0
| +|tgfjt
tgfjt
|maekawataiki
maekawataiki
|samsilveira
samsilveira
|mr-ryan-james
mr-ryan-james
|01Rian
01Rian
|RSO
RSO
| +|R-omk
R-omk
|Sarke
Sarke
|kvokka
kvokka
|ecmasx
ecmasx
|marvijo-code
marvijo-code
|mamertofabian
mamertofabian
| +|monkeyDluffy6017
monkeyDluffy6017
|libertyteeth
libertyteeth
|shtse8
shtse8
|ksze
ksze
|Jdo300
Jdo300
| | ## 授權 diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6d36ecb30f..1cc1b60fe6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -179,9 +179,18 @@ importers: '@modelcontextprotocol/sdk': specifier: ^1.9.0 version: 1.11.2 + '@qdrant/js-client-rest': + specifier: ^1.14.0 + version: 1.14.0(typescript@5.8.3) + '@types/lodash.debounce': + specifier: ^4.0.9 + version: 4.0.9 '@vscode/codicons': specifier: ^0.0.36 version: 0.0.36 + async-mutex: + specifier: ^0.5.0 + version: 0.5.0 axios: specifier: ^1.7.4 version: 1.9.0 @@ -233,6 +242,9 @@ importers: isbinaryfile: specifier: ^5.0.2 version: 5.0.4 + lodash.debounce: + specifier: ^4.0.8 + version: 4.0.8 mammoth: specifier: ^1.8.0 version: 1.9.0 @@ -251,6 +263,9 @@ importers: os-name: specifier: ^6.0.0 version: 6.0.0 + p-limit: + specifier: ^6.2.0 + version: 6.2.0 p-wait-for: specifier: ^5.0.2 version: 5.0.2 @@ -314,6 +329,9 @@ importers: turndown: specifier: ^7.2.0 version: 7.2.0 + uuid: + specifier: ^11.1.0 + version: 11.1.0 vscode-material-icons: specifier: ^0.1.1 version: 0.1.1 @@ -1364,6 +1382,10 @@ packages: resolution: {integrity: sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@fastify/busboy@2.1.1': + resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} + engines: {node: '>=14'} + '@floating-ui/core@1.7.0': resolution: {integrity: sha512-FRdBLykrPPA6P76GGGqlex/e7fbe0F1ykgxHYNXQsH/iTEtjMj/f9bpY5oQqbjt5VgZvgz/uKXbGuROijh3VLA==} @@ -1623,6 +1645,16 @@ packages: engines: {node: '>=18'} hasBin: true + '@qdrant/js-client-rest@1.14.0': + resolution: {integrity: sha512-2sM2g17FSkN2sNCSeAfqxHRr+SPEVnUQLXBjVv/whm4YQ4JjZ53Jiy1iShk95G+xBf3hKBhJdj8itRnor03IYw==} + engines: {node: '>=18.0.0', pnpm: '>=8'} + peerDependencies: + typescript: '>=4.7' + + '@qdrant/openapi-typescript-fetch@1.2.6': + resolution: {integrity: sha512-oQG/FejNpItrxRHoyctYvT3rwGZOnK4jr3JdppO/c78ktDvkWiPXPHNsrDf33K9sZdRb6PR7gi4noIapu5q4HA==} + engines: {node: '>=18.0.0', pnpm: '>=8'} + '@radix-ui/number@1.1.1': resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} @@ -2164,6 +2196,9 @@ packages: '@sec-ant/readable-stream@0.4.1': resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} + '@sevinf/maybe@0.5.0': + resolution: {integrity: sha512-ARhyoYDnY1LES3vYI0fiG6e9esWfTNcXcO6+MPJJXcnyMV3bim4lnFt45VXouV7y82F4x3YH8nOQ6VztuvUiWg==} + '@shikijs/core@3.4.1': resolution: {integrity: sha512-GCqSd3KXRTKX1sViP7fIyyyf6do2QVg+fTd4IT00ucYCVSKiSN8HbFbfyjGsoZePNKWcQqXe4U4rrz2IVldG5A==} @@ -2957,6 +2992,12 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/lodash.debounce@4.0.9': + resolution: {integrity: sha512-Ma5JcgTREwpLRwMM+XwBR7DaWe96nC38uCBDFKZWbNKD+osjVzdpnUSwBcqCptrp16sSOLBAUb50Car5I0TCsQ==} + + '@types/lodash@4.17.17': + resolution: {integrity: sha512-RRVJ+J3J+WmyOTqnz3PiBLA501eKwXl2noseKOrNo/6+XEHjTAxO4xHvxQB6QuNm+s4WRbn6rSiap8+EA+ykFQ==} + '@types/mdast@3.0.15': resolution: {integrity: sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==} @@ -3390,6 +3431,9 @@ packages: resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} engines: {node: '>= 0.4'} + async-mutex@0.5.0: + resolution: {integrity: sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==} + async@3.2.6: resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} @@ -5905,6 +5949,9 @@ packages: lodash-es@4.17.21: resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + lodash.includes@4.3.0: resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} @@ -6572,6 +6619,10 @@ packages: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} + p-limit@6.2.0: + resolution: {integrity: sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==} + engines: {node: '>=18'} + p-locate@4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} @@ -8019,6 +8070,10 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici@5.28.5: + resolution: {integrity: sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA==} + engines: {node: '>=14.0'} + undici@6.21.3: resolution: {integrity: sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==} engines: {node: '>=18.17'} @@ -8550,6 +8605,10 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + yocto-queue@1.2.1: + resolution: {integrity: sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==} + engines: {node: '>=12.20'} + yoctocolors@2.1.1: resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==} engines: {node: '>=18'} @@ -9693,6 +9752,8 @@ snapshots: '@eslint/core': 0.14.0 levn: 0.4.1 + '@fastify/busboy@2.1.1': {} + '@floating-ui/core@1.7.0': dependencies: '@floating-ui/utils': 0.2.9 @@ -10112,6 +10173,15 @@ snapshots: - bare-buffer - supports-color + '@qdrant/js-client-rest@1.14.0(typescript@5.8.3)': + dependencies: + '@qdrant/openapi-typescript-fetch': 1.2.6 + '@sevinf/maybe': 0.5.0 + typescript: 5.8.3 + undici: 5.28.5 + + '@qdrant/openapi-typescript-fetch@1.2.6': {} + '@radix-ui/number@1.1.1': {} '@radix-ui/primitive@1.1.2': {} @@ -10625,6 +10695,8 @@ snapshots: '@sec-ant/readable-stream@0.4.1': {} + '@sevinf/maybe@0.5.0': {} + '@shikijs/core@3.4.1': dependencies: '@shikijs/types': 3.4.1 @@ -11659,6 +11731,12 @@ snapshots: '@types/json-schema@7.0.15': {} + '@types/lodash.debounce@4.0.9': + dependencies: + '@types/lodash': 4.17.17 + + '@types/lodash@4.17.17': {} + '@types/mdast@3.0.15': dependencies: '@types/unist': 2.0.11 @@ -12179,6 +12257,10 @@ snapshots: async-function@1.0.0: {} + async-mutex@0.5.0: + dependencies: + tslib: 2.8.1 + async@3.2.6: {} asynckit@0.4.0: {} @@ -15279,6 +15361,8 @@ snapshots: lodash-es@4.17.21: {} + lodash.debounce@4.0.8: {} + lodash.includes@4.3.0: {} lodash.isboolean@3.0.3: {} @@ -16236,6 +16320,10 @@ snapshots: dependencies: yocto-queue: 0.1.0 + p-limit@6.2.0: + dependencies: + yocto-queue: 1.2.1 + p-locate@4.1.0: dependencies: p-limit: 2.3.0 @@ -17904,6 +17992,10 @@ snapshots: undici-types@6.21.0: {} + undici@5.28.5: + dependencies: + '@fastify/busboy': 2.1.1 + undici@6.21.3: {} unicorn-magic@0.3.0: {} @@ -18536,6 +18628,8 @@ snapshots: yocto-queue@0.1.0: {} + yocto-queue@1.2.1: {} + yoctocolors@2.1.1: {} zod-to-json-schema@3.24.5(zod@3.24.4): diff --git a/src/__mocks__/p-limit.js b/src/__mocks__/p-limit.js new file mode 100644 index 0000000000..063fb1c2eb --- /dev/null +++ b/src/__mocks__/p-limit.js @@ -0,0 +1,18 @@ +// Mock implementation of p-limit for Jest tests +// p-limit is a utility for limiting the number of concurrent promises + +const pLimit = (concurrency) => { + // Return a function that just executes the passed function immediately + // In tests, we don't need actual concurrency limiting + return (fn) => { + if (typeof fn === "function") { + return fn() + } + return fn + } +} + +// Set default export +pLimit.default = pLimit + +module.exports = pLimit diff --git a/src/activate/registerCommands.ts b/src/activate/registerCommands.ts index b6c7b54779..fc18e96d54 100644 --- a/src/activate/registerCommands.ts +++ b/src/activate/registerCommands.ts @@ -9,6 +9,7 @@ import { telemetryService } from "../services/telemetry/TelemetryService" import { registerHumanRelayCallback, unregisterHumanRelayCallback, handleHumanRelayResponse } from "./humanRelay" import { handleNewTask } from "./handleTask" +import { CodeIndexManager } from "../services/code-index/manager" /** * Helper to get the visible ClineProvider instance or log if not found. @@ -184,7 +185,8 @@ export const openClineInNewTab = async ({ context, outputChannel }: Omit editor.viewColumn || 0)) // Check if there are any visible text editors, otherwise open a new group diff --git a/src/core/assistant-message/presentAssistantMessage.ts b/src/core/assistant-message/presentAssistantMessage.ts index ef2bb04963..6d37063457 100644 --- a/src/core/assistant-message/presentAssistantMessage.ts +++ b/src/core/assistant-message/presentAssistantMessage.ts @@ -32,6 +32,7 @@ import { checkpointSave } from "../checkpoints" import { formatResponse } from "../prompts/responses" import { validateToolUse } from "../tools/validateToolUse" import { Task } from "../task/Task" +import { codebaseSearchTool } from "../tools/codebaseSearchTool" /** * Processes and presents assistant message content to the user interface. @@ -185,6 +186,8 @@ export async function presentAssistantMessage(cline: Task) { return `[${block.name}]` case "switch_mode": return `[${block.name} to '${block.params.mode_slug}'${block.params.reason ? ` because: ${block.params.reason}` : ""}]` + case "codebase_search": // Add case for the new tool + return `[${block.name} for '${block.params.query}']` case "new_task": { const mode = block.params.mode ?? defaultModeSlug const message = block.params.message ?? "(no message)" @@ -402,6 +405,9 @@ export async function presentAssistantMessage(cline: Task) { case "list_files": await listFilesTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag) break + case "codebase_search": + await codebaseSearchTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag) + break case "list_code_definition_names": await listCodeDefinitionNamesTool( cline, diff --git a/src/core/prompts/__tests__/system.test.ts b/src/core/prompts/__tests__/system.test.ts index 7f480dd69d..3647d2d859 100644 --- a/src/core/prompts/__tests__/system.test.ts +++ b/src/core/prompts/__tests__/system.test.ts @@ -85,6 +85,28 @@ jest.mock("vscode", () => ({ env: { language: "en", }, + workspace: { + workspaceFolders: [ + { + uri: { + fsPath: "/test/path", + }, + }, + ], + getWorkspaceFolder: jest.fn().mockReturnValue({ + uri: { + fsPath: "/test/path", + }, + }), + }, + window: { + activeTextEditor: undefined, + }, + EventEmitter: jest.fn().mockImplementation(() => ({ + event: jest.fn(), + fire: jest.fn(), + dispose: jest.fn(), + })), })) jest.mock("../../../utils/shell", () => ({ @@ -343,6 +365,29 @@ describe("SYSTEM_PROMPT", () => { // Mock vscode.env.language const vscode = jest.requireMock("vscode") vscode.env = { language: "es" } + // Ensure workspace mock is maintained + vscode.workspace = { + workspaceFolders: [ + { + uri: { + fsPath: "/test/path", + }, + }, + ], + getWorkspaceFolder: jest.fn().mockReturnValue({ + uri: { + fsPath: "/test/path", + }, + }), + } + vscode.window = { + activeTextEditor: undefined, + } + vscode.EventEmitter = jest.fn().mockImplementation(() => ({ + event: jest.fn(), + fire: jest.fn(), + dispose: jest.fn(), + })) const prompt = await SYSTEM_PROMPT( mockContext, @@ -365,6 +410,28 @@ describe("SYSTEM_PROMPT", () => { // Reset mock vscode.env = { language: "en" } + vscode.workspace = { + workspaceFolders: [ + { + uri: { + fsPath: "/test/path", + }, + }, + ], + getWorkspaceFolder: jest.fn().mockReturnValue({ + uri: { + fsPath: "/test/path", + }, + }), + } + vscode.window = { + activeTextEditor: undefined, + } + vscode.EventEmitter = jest.fn().mockImplementation(() => ({ + event: jest.fn(), + fire: jest.fn(), + dispose: jest.fn(), + })) }) it("should include custom mode role definition at top and instructions at bottom", async () => { diff --git a/src/core/prompts/sections/capabilities.ts b/src/core/prompts/sections/capabilities.ts index 0be797db4e..ef5da02b29 100644 --- a/src/core/prompts/sections/capabilities.ts +++ b/src/core/prompts/sections/capabilities.ts @@ -1,11 +1,13 @@ import { DiffStrategy } from "../../../shared/tools" import { McpHub } from "../../../services/mcp/McpHub" +import { CodeIndexManager } from "../../../services/code-index/manager" export function getCapabilitiesSection( cwd: string, supportsComputerUse: boolean, mcpHub?: McpHub, diffStrategy?: DiffStrategy, + codeIndexManager?: CodeIndexManager, ): string { return `==== @@ -15,7 +17,15 @@ CAPABILITIES supportsComputerUse ? ", use the browser" : "" }, read and write files, and ask follow-up questions. These tools help you effectively accomplish a wide range of tasks, such as writing code, making edits or improvements to existing files, understanding the current state of a project, performing system operations, and much more. - When the user initially gives you a task, a recursive list of all filepaths in the current workspace directory ('${cwd}') will be included in environment_details. This provides an overview of the project's file structure, offering key insights into the project from directory/file names (how developers conceptualize and organize their code) and file extensions (the language used). This can also guide decision-making on which files to explore further. If you need to further explore directories such as outside the current workspace directory, you can use the list_files tool. If you pass 'true' for the recursive parameter, it will list files recursively. Otherwise, it will list files at the top level, which is better suited for generic directories where you don't necessarily need the nested structure, like the Desktop. -- You can use search_files to perform regex searches across files in a specified directory, outputting context-rich results that include surrounding lines. This is particularly useful for understanding code patterns, finding specific implementations, or identifying areas that need refactoring. +- You can use search_files to perform regex searches across files in a specified directory, outputting context-rich results that include surrounding lines. This is particularly useful for understanding code patterns, finding specific implementations, or identifying areas that need refactoring.${ + codeIndexManager && + codeIndexManager.isFeatureEnabled && + codeIndexManager.isFeatureConfigured && + codeIndexManager.isInitialized + ? ` +- You can use the \`codebase_search\` tool to perform semantic searches across your entire codebase. This tool is powerful for finding functionally relevant code, even if you don't know the exact keywords or file names. It's particularly useful for understanding how features are implemented across multiple files, discovering usages of a particular API, or finding code examples related to a concept. This capability relies on a pre-built index of your code.` + : "" + } - You can use the list_code_definition_names tool to get an overview of source code definitions for all files at the top level of a specified directory. This can be particularly useful when you need to understand the broader context and relationships between certain parts of the code. You may need to call this tool multiple times to understand various parts of the codebase related to the task. - For example, when asked to make edits or improvements you might analyze the file structure in the initial environment_details to get an overview of the project, then use list_code_definition_names to get further insight using source code definitions for files located in relevant directories, then read_file to examine the contents of relevant files, analyze the code and suggest improvements or make necessary edits, then use ${diffStrategy ? "the apply_diff or write_to_file" : "the write_to_file"} tool to apply the changes. If you refactored code that could affect other parts of the codebase, you could use search_files to ensure you update other files as needed. - You can use the execute_command tool to run commands on the user's computer whenever you feel it can help accomplish the user's task. When you need to execute a CLI command, you must provide a clear explanation of what the command does. Prefer to execute complex CLI commands over creating executable scripts, since they are more flexible and easier to run. Interactive and long-running commands are allowed, since the commands are run in the user's VSCode terminal. The user may keep commands running in the background and you will be kept updated on their status along the way. Each command you execute is run in a new terminal instance.${ diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts index 0fce1765a9..96221ae91f 100644 --- a/src/core/prompts/system.ts +++ b/src/core/prompts/system.ts @@ -27,6 +27,7 @@ import { markdownFormattingSection, } from "./sections" import { formatLanguage } from "../../shared/language" +import { CodeIndexManager } from "../../services/code-index/manager" async function generatePrompt( context: vscode.ExtensionContext, @@ -63,6 +64,8 @@ async function generatePrompt( : Promise.resolve(""), ]) + const codeIndexManager = CodeIndexManager.getInstance(context) + const basePrompt = `${roleDefinition} ${markdownFormattingSection()} @@ -73,6 +76,7 @@ ${getToolDescriptionsForMode( mode, cwd, supportsComputerUse, + codeIndexManager, effectiveDiffStrategy, browserViewportSize, mcpHub, @@ -84,7 +88,7 @@ ${getToolUseGuidelinesSection()} ${mcpServersSection} -${getCapabilitiesSection(cwd, supportsComputerUse, mcpHub, effectiveDiffStrategy)} +${getCapabilitiesSection(cwd, supportsComputerUse, mcpHub, effectiveDiffStrategy, codeIndexManager)} ${modesSection} diff --git a/src/core/prompts/tools/codebase-search.ts b/src/core/prompts/tools/codebase-search.ts new file mode 100644 index 0000000000..81eaacae85 --- /dev/null +++ b/src/core/prompts/tools/codebase-search.ts @@ -0,0 +1,19 @@ +export function getCodebaseSearchDescription(): string { + return `## codebase_search +Description: Find files most relevant to the search query.\nThis is a semantic search tool, so the query should ask for something semantically matching what is needed.\nIf it makes sense to only search in a particular directory, please specify it in the path parameter.\nUnless there is a clear reason to use your own search query, please just reuse the user's exact query with their wording.\nTheir exact wording/phrasing can often be helpful for the semantic search query. Keeping the same exact question format can also be helpful. +Parameters: +- query: (required) The search query to find relevant code. You should reuse the user's exact query/most recent message with their wording unless there is a clear reason not to. +- path: (optional) The path to the directory to search in relative to the current working directory. This parameter should only be a directory path, file paths are not supported. Defaults to the current working directory. +Usage: + +Your natural language query here +Path to the directory to search in (optional) + + +Example: Searching for functions related to user authentication + +User login and password hashing +/path/to/directory + +` +} diff --git a/src/core/prompts/tools/index.ts b/src/core/prompts/tools/index.ts index d3e75d7b09..4b3f796919 100644 --- a/src/core/prompts/tools/index.ts +++ b/src/core/prompts/tools/index.ts @@ -20,6 +20,8 @@ import { getUseMcpToolDescription } from "./use-mcp-tool" import { getAccessMcpResourceDescription } from "./access-mcp-resource" import { getSwitchModeDescription } from "./switch-mode" import { getNewTaskDescription } from "./new-task" +import { getCodebaseSearchDescription } from "./codebase-search" +import { CodeIndexManager } from "../../../services/code-index/manager" // Map of tool names to their description functions const toolDescriptionMap: Record string | undefined> = { @@ -35,6 +37,7 @@ const toolDescriptionMap: Record string | undefined> attempt_completion: () => getAttemptCompletionDescription(), use_mcp_tool: (args) => getUseMcpToolDescription(args), access_mcp_resource: (args) => getAccessMcpResourceDescription(args), + codebase_search: () => getCodebaseSearchDescription(), switch_mode: () => getSwitchModeDescription(), new_task: (args) => getNewTaskDescription(args), insert_content: (args) => getInsertContentDescription(args), @@ -47,6 +50,7 @@ export function getToolDescriptionsForMode( mode: Mode, cwd: string, supportsComputerUse: boolean, + codeIndexManager?: CodeIndexManager, diffStrategy?: DiffStrategy, browserViewportSize?: string, mcpHub?: McpHub, @@ -89,6 +93,14 @@ export function getToolDescriptionsForMode( // Add always available tools ALWAYS_AVAILABLE_TOOLS.forEach((tool) => tools.add(tool)) + // Conditionally exclude codebase_search if feature is disabled or not configured + if ( + !codeIndexManager || + !(codeIndexManager.isFeatureEnabled && codeIndexManager.isFeatureConfigured && codeIndexManager.isInitialized) + ) { + tools.delete("codebase_search") + } + // Map tool descriptions for allowed tools const descriptions = Array.from(tools).map((toolName) => { const descriptionFn = toolDescriptionMap[toolName] @@ -122,4 +134,5 @@ export { getSwitchModeDescription, getInsertContentDescription, getSearchAndReplaceDescription, + getCodebaseSearchDescription, } diff --git a/src/core/tools/codebaseSearchTool.ts b/src/core/tools/codebaseSearchTool.ts new file mode 100644 index 0000000000..c6012f9f0f --- /dev/null +++ b/src/core/tools/codebaseSearchTool.ts @@ -0,0 +1,140 @@ +import * as vscode from "vscode" + +import { Task } from "../task/Task" +import { CodeIndexManager } from "../../services/code-index/manager" +import { getWorkspacePath } from "../../utils/path" +import { formatResponse } from "../prompts/responses" +import { VectorStoreSearchResult } from "../../services/code-index/interfaces" +import { AskApproval, HandleError, PushToolResult, RemoveClosingTag, ToolUse } from "../../shared/tools" +import path from "path" + +export async function codebaseSearchTool( + cline: Task, + block: ToolUse, + askApproval: AskApproval, + handleError: HandleError, + pushToolResult: PushToolResult, + removeClosingTag: RemoveClosingTag, +) { + const toolName = "codebase_search" + const workspacePath = getWorkspacePath() + + if (!workspacePath) { + // This case should ideally not happen if Cline is initialized correctly + await handleError(toolName, new Error("Could not determine workspace path.")) + return + } + + // --- Parameter Extraction and Validation --- + let query: string | undefined = block.params.query + let directoryPrefix: string | undefined = block.params.path + + if (!query) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError(toolName, "query")) + return + } + query = removeClosingTag("query", query) + + if (directoryPrefix) { + directoryPrefix = removeClosingTag("path", directoryPrefix) + directoryPrefix = path.normalize(directoryPrefix) + } + + // Extract optional sendResultsToUI parameter + + const approvalPayload = { + tool: "codebaseSearch", + query: query, + path: directoryPrefix, + isOutsideWorkspace: false, + } + + const didApprove = await askApproval("tool", JSON.stringify(approvalPayload)) + if (!didApprove) { + pushToolResult(formatResponse.toolDenied()) + return + } + + cline.consecutiveMistakeCount = 0 + + // --- Core Logic --- + try { + const context = cline.providerRef.deref()?.context + if (!context) { + throw new Error("Extension context is not available.") + } + + const manager = CodeIndexManager.getInstance(context) + + if (!manager) { + throw new Error("CodeIndexManager is not available.") + } + + if (!manager.isFeatureEnabled) { + throw new Error("Code Indexing is disabled in the settings.") + } + if (!manager.isFeatureConfigured) { + throw new Error("Code Indexing is not configured (Missing OpenAI Key or Qdrant URL).") + } + + const searchResults: VectorStoreSearchResult[] = await manager.searchIndex(query, directoryPrefix) + + // 3. Format and push results + if (!searchResults || searchResults.length === 0) { + pushToolResult(`No relevant code snippets found for the query: "${query}"`) // Use simple string for no results + return + } + + const jsonResult = { + query, + results: [], + } as { + query: string + results: Array<{ + filePath: string + score: number + startLine: number + endLine: number + codeChunk: string + }> + } + + searchResults.forEach((result) => { + if (!result.payload) return + if (!("filePath" in result.payload)) return + + const relativePath = vscode.workspace.asRelativePath(result.payload.filePath, false) + + jsonResult.results.push({ + filePath: relativePath, + score: result.score, + startLine: result.payload.startLine, + endLine: result.payload.endLine, + codeChunk: result.payload.codeChunk.trim(), + }) + }) + + // Send results to UI + const payload = { tool: "codebaseSearch", content: jsonResult } + await cline.say("codebase_search_result", JSON.stringify(payload)) + + // Push results to AI + const output = `Query: ${query} +Results: + +${jsonResult.results + .map( + (result) => `File path: ${result.filePath} +Score: ${result.score} +Lines: ${result.startLine}-${result.endLine} +Code Chunk: ${result.codeChunk} +`, + ) + .join("\n")}` + + pushToolResult(output) + } catch (error: any) { + await handleError(toolName, error) // Use the standard error handler + } +} diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index b4beed223e..7b04ee15c4 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -39,6 +39,8 @@ import WorkspaceTracker from "../../integrations/workspace/WorkspaceTracker" import { McpHub } from "../../services/mcp/McpHub" import { McpServerManager } from "../../services/mcp/McpServerManager" import { ShadowCheckpointService } from "../../services/checkpoints/ShadowCheckpointService" +import { CodeIndexManager } from "../../services/code-index/manager" +import type { IndexProgressUpdate } from "../../services/code-index/interfaces/manager" import { fileExistsAtPath } from "../../utils/fs" import { setTtsEnabled, setTtsSpeed } from "../../utils/tts" import { ContextProxy } from "../config/ContextProxy" @@ -53,6 +55,7 @@ import { telemetryService } from "../../services/telemetry/TelemetryService" import { getWorkspacePath } from "../../utils/path" import { webviewMessageHandler } from "./webviewMessageHandler" import { WebviewMessage } from "../../shared/WebviewMessage" +import { EMBEDDING_MODEL_PROFILES } from "../../shared/embeddingModels" /** * https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts @@ -73,6 +76,7 @@ export class ClineProvider extends EventEmitter implements private disposables: vscode.Disposable[] = [] private view?: vscode.WebviewView | vscode.WebviewPanel private clineStack: Task[] = [] + private codeIndexStatusSubscription?: vscode.Disposable private _workspaceTracker?: WorkspaceTracker // workSpaceTracker read-only for access outside this class public get workspaceTracker(): WorkspaceTracker | undefined { return this._workspaceTracker @@ -90,12 +94,19 @@ export class ClineProvider extends EventEmitter implements private readonly outputChannel: vscode.OutputChannel, private readonly renderContext: "sidebar" | "editor" = "sidebar", public readonly contextProxy: ContextProxy, + public readonly codeIndexManager?: CodeIndexManager, ) { super() this.log("ClineProvider instantiated") ClineProvider.activeInstances.add(this) + this.codeIndexManager = codeIndexManager + this.updateGlobalState("codebaseIndexModels", EMBEDDING_MODEL_PROFILES) + + // Start configuration loading (which might trigger indexing) in the background. + // Don't await, allowing activation to continue immediately. + // Register this provider with the telemetry service to enable it to add // properties like mode and provider. telemetryService.setProvider(this) @@ -320,10 +331,6 @@ export class ClineProvider extends EventEmitter implements async resolveWebviewView(webviewView: vscode.WebviewView | vscode.WebviewPanel) { this.log("Resolving webview view") - if (!this.contextProxy.isInitialized) { - await this.contextProxy.initialize() - } - this.view = webviewView // Set panel reference according to webview type @@ -383,6 +390,18 @@ export class ClineProvider extends EventEmitter implements // and executes code based on the message that is recieved this.setWebviewMessageListener(webviewView.webview) + // Subscribe to code index status updates if the manager exists + if (this.codeIndexManager) { + this.codeIndexStatusSubscription = this.codeIndexManager.onProgressUpdate((update: IndexProgressUpdate) => { + this.postMessageToWebview({ + type: "indexingStatusUpdate", + values: update, + }) + }) + // Add the subscription to the main disposables array + this.disposables.push(this.codeIndexStatusSubscription) + } + // Logs show up in bottom panel > Debug Console //console.log("registering listener") @@ -1239,6 +1258,8 @@ export class ClineProvider extends EventEmitter implements historyPreviewCollapsed, condensingApiConfigId, customCondensingPrompt, + codebaseIndexConfig, + codebaseIndexModels, } = await this.getState() const telemetryKey = process.env.POSTHOG_API_KEY @@ -1330,6 +1351,17 @@ export class ClineProvider extends EventEmitter implements historyPreviewCollapsed: historyPreviewCollapsed ?? false, condensingApiConfigId, customCondensingPrompt, + codebaseIndexModels: codebaseIndexModels ?? { + openai: {}, + ollama: {}, + }, + codebaseIndexConfig: codebaseIndexConfig ?? { + codebaseIndexEnabled: false, + codebaseIndexQdrantUrl: "http://localhost:6333", + codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderBaseUrl: "", + codebaseIndexEmbedderModelId: "", + }, } } @@ -1424,6 +1456,17 @@ export class ClineProvider extends EventEmitter implements // Explicitly add condensing settings condensingApiConfigId: stateValues.condensingApiConfigId, customCondensingPrompt: stateValues.customCondensingPrompt, + codebaseIndexModels: stateValues.codebaseIndexModels ?? { + openai: {}, + ollama: {}, + }, + codebaseIndexConfig: stateValues.codebaseIndexConfig ?? { + codebaseIndexEnabled: false, + codebaseIndexQdrantUrl: "http://localhost:6333", + codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderBaseUrl: "", + codebaseIndexEmbedderModelId: "", + }, } } diff --git a/src/core/webview/__tests__/ClineProvider.test.ts b/src/core/webview/__tests__/ClineProvider.test.ts index 5af4a476b7..f058dcae14 100644 --- a/src/core/webview/__tests__/ClineProvider.test.ts +++ b/src/core/webview/__tests__/ClineProvider.test.ts @@ -387,6 +387,13 @@ describe("ClineProvider", () => { alwaysAllowReadOnly: false, alwaysAllowReadOnlyOutsideWorkspace: false, alwaysAllowWrite: false, + codebaseIndexConfig: { + codebaseIndexEnabled: false, + codebaseIndexQdrantUrl: "", + codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderBaseUrl: "", + codebaseIndexEmbedderModelId: "", + }, alwaysAllowWriteOutsideWorkspace: false, alwaysAllowExecute: false, alwaysAllowBrowser: false, @@ -1554,8 +1561,10 @@ describe("ClineProvider", () => { setModeConfig: jest.fn(), } as any - // Mock current config name - mockContext.globalState.get = jest.fn((key: string) => { + // Mock the ContextProxy's getValue method to return the current config name + const contextProxy = (provider as any).contextProxy + const getValueSpy = jest.spyOn(contextProxy, "getValue") + getValueSpy.mockImplementation((key: any) => { if (key === "currentApiConfigName") return "current-config" return undefined }) diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index 7c4e906849..09c472cc0d 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -1320,5 +1320,66 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We await provider.postStateToWebview() break } + case "codebaseIndexConfig": { + const codebaseIndexConfig = message.values ?? { + codebaseIndexEnabled: false, + codebaseIndexQdrantUrl: "http://localhost:6333", + codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderBaseUrl: "", + codebaseIndexEmbedderModelId: "", + } + await updateGlobalState("codebaseIndexConfig", codebaseIndexConfig) + + try { + await provider.codeIndexManager?.initialize(provider.contextProxy) + } catch (error) { + provider.log( + `[CodeIndexManager] Error during background CodeIndexManager configuration/indexing: ${error.message || error}`, + ) + } + + await provider.postStateToWebview() + break + } + case "requestIndexingStatus": { + const status = provider.codeIndexManager!.getCurrentStatus() + provider.postMessageToWebview({ + type: "indexingStatusUpdate", + values: status, + }) + break + } + case "startIndexing": { + try { + const manager = provider.codeIndexManager! + if (manager.isFeatureEnabled && manager.isFeatureConfigured) { + if (!manager.isInitialized) { + await manager.initialize(provider.contextProxy) + } + + manager.startIndexing() + } + } catch (error) { + provider.log(`Error starting indexing: ${error instanceof Error ? error.message : String(error)}`) + } + break + } + case "clearIndexData": { + try { + const manager = provider.codeIndexManager! + await manager.clearIndexData() + provider.postMessageToWebview({ type: "indexCleared", values: { success: true } }) + } catch (error) { + provider.log(`Error clearing index data: ${error instanceof Error ? error.message : String(error)}`) + provider.postMessageToWebview({ + type: "indexCleared", + values: { + success: false, + error: error instanceof Error ? error.message : String(error), + }, + }) + } + break + } } } diff --git a/src/exports/roo-code.d.ts b/src/exports/roo-code.d.ts index 9b2cb8469c..904eba8530 100644 --- a/src/exports/roo-code.d.ts +++ b/src/exports/roo-code.d.ts @@ -61,6 +61,33 @@ type GlobalSettings = { autoApprovalEnabled?: boolean | undefined alwaysAllowReadOnly?: boolean | undefined alwaysAllowReadOnlyOutsideWorkspace?: boolean | undefined + codebaseIndexModels?: + | { + openai?: + | { + [x: string]: { + dimension: number + } + } + | undefined + ollama?: + | { + [x: string]: { + dimension: number + } + } + | undefined + } + | undefined + codebaseIndexConfig?: + | { + codebaseIndexEnabled?: boolean | undefined + codebaseIndexQdrantUrl?: string | undefined + codebaseIndexEmbedderProvider?: ("openai" | "ollama") | undefined + codebaseIndexEmbedderBaseUrl?: string | undefined + codebaseIndexEmbedderModelId?: string | undefined + } + | undefined alwaysAllowWrite?: boolean | undefined alwaysAllowWriteOutsideWorkspace?: boolean | undefined writeDelayMs?: number | undefined @@ -339,6 +366,8 @@ type ProviderSettings = { litellmBaseUrl?: string | undefined litellmApiKey?: string | undefined litellmModelId?: string | undefined + codeIndexOpenAiKey?: string | undefined + codeIndexQdrantApiKey?: string | undefined } type ProviderSettingsEntry = { @@ -414,6 +443,7 @@ type ClineMessage = { | "rooignore_error" | "diff_error" | "condense_context" + | "codebase_search_result" ) | undefined text?: string | undefined @@ -499,6 +529,7 @@ type RooCodeEvents = { | "rooignore_error" | "diff_error" | "condense_context" + | "codebase_search_result" ) | undefined text?: string | undefined @@ -584,6 +615,7 @@ type RooCodeEvents = { | "switch_mode" | "new_task" | "fetch_instructions" + | "codebase_search" ), string, ] @@ -747,6 +779,8 @@ type IpcMessage = litellmBaseUrl?: string | undefined litellmApiKey?: string | undefined litellmModelId?: string | undefined + codeIndexOpenAiKey?: string | undefined + codeIndexQdrantApiKey?: string | undefined currentApiConfigName?: string | undefined listApiConfigMeta?: | { @@ -806,6 +840,33 @@ type IpcMessage = autoApprovalEnabled?: boolean | undefined alwaysAllowReadOnly?: boolean | undefined alwaysAllowReadOnlyOutsideWorkspace?: boolean | undefined + codebaseIndexModels?: + | { + openai?: + | { + [x: string]: { + dimension: number + } + } + | undefined + ollama?: + | { + [x: string]: { + dimension: number + } + } + | undefined + } + | undefined + codebaseIndexConfig?: + | { + codebaseIndexEnabled?: boolean | undefined + codebaseIndexQdrantUrl?: string | undefined + codebaseIndexEmbedderProvider?: ("openai" | "ollama") | undefined + codebaseIndexEmbedderBaseUrl?: string | undefined + codebaseIndexEmbedderModelId?: string | undefined + } + | undefined alwaysAllowWrite?: boolean | undefined alwaysAllowWriteOutsideWorkspace?: boolean | undefined writeDelayMs?: number | undefined @@ -987,6 +1048,7 @@ type IpcMessage = | "rooignore_error" | "diff_error" | "condense_context" + | "codebase_search_result" ) | undefined text?: string | undefined @@ -1229,6 +1291,8 @@ type TaskCommand = litellmBaseUrl?: string | undefined litellmApiKey?: string | undefined litellmModelId?: string | undefined + codeIndexOpenAiKey?: string | undefined + codeIndexQdrantApiKey?: string | undefined currentApiConfigName?: string | undefined listApiConfigMeta?: | { @@ -1288,6 +1352,33 @@ type TaskCommand = autoApprovalEnabled?: boolean | undefined alwaysAllowReadOnly?: boolean | undefined alwaysAllowReadOnlyOutsideWorkspace?: boolean | undefined + codebaseIndexModels?: + | { + openai?: + | { + [x: string]: { + dimension: number + } + } + | undefined + ollama?: + | { + [x: string]: { + dimension: number + } + } + | undefined + } + | undefined + codebaseIndexConfig?: + | { + codebaseIndexEnabled?: boolean | undefined + codebaseIndexQdrantUrl?: string | undefined + codebaseIndexEmbedderProvider?: ("openai" | "ollama") | undefined + codebaseIndexEmbedderBaseUrl?: string | undefined + codebaseIndexEmbedderModelId?: string | undefined + } + | undefined alwaysAllowWrite?: boolean | undefined alwaysAllowWriteOutsideWorkspace?: boolean | undefined writeDelayMs?: number | undefined @@ -1465,6 +1556,7 @@ type TaskEvent = | "rooignore_error" | "diff_error" | "condense_context" + | "codebase_search_result" ) | undefined text?: string | undefined diff --git a/src/exports/types.ts b/src/exports/types.ts index 52bdabc331..6f4989df62 100644 --- a/src/exports/types.ts +++ b/src/exports/types.ts @@ -61,6 +61,33 @@ type GlobalSettings = { autoApprovalEnabled?: boolean | undefined alwaysAllowReadOnly?: boolean | undefined alwaysAllowReadOnlyOutsideWorkspace?: boolean | undefined + codebaseIndexModels?: + | { + openai?: + | { + [x: string]: { + dimension: number + } + } + | undefined + ollama?: + | { + [x: string]: { + dimension: number + } + } + | undefined + } + | undefined + codebaseIndexConfig?: + | { + codebaseIndexEnabled?: boolean | undefined + codebaseIndexQdrantUrl?: string | undefined + codebaseIndexEmbedderProvider?: ("openai" | "ollama") | undefined + codebaseIndexEmbedderBaseUrl?: string | undefined + codebaseIndexEmbedderModelId?: string | undefined + } + | undefined alwaysAllowWrite?: boolean | undefined alwaysAllowWriteOutsideWorkspace?: boolean | undefined writeDelayMs?: number | undefined @@ -343,6 +370,8 @@ type ProviderSettings = { litellmBaseUrl?: string | undefined litellmApiKey?: string | undefined litellmModelId?: string | undefined + codeIndexOpenAiKey?: string | undefined + codeIndexQdrantApiKey?: string | undefined } export type { ProviderSettings } @@ -422,6 +451,7 @@ type ClineMessage = { | "rooignore_error" | "diff_error" | "condense_context" + | "codebase_search_result" ) | undefined text?: string | undefined @@ -511,6 +541,7 @@ type RooCodeEvents = { | "rooignore_error" | "diff_error" | "condense_context" + | "codebase_search_result" ) | undefined text?: string | undefined @@ -596,6 +627,7 @@ type RooCodeEvents = { | "switch_mode" | "new_task" | "fetch_instructions" + | "codebase_search" ), string, ] @@ -761,6 +793,8 @@ type IpcMessage = litellmBaseUrl?: string | undefined litellmApiKey?: string | undefined litellmModelId?: string | undefined + codeIndexOpenAiKey?: string | undefined + codeIndexQdrantApiKey?: string | undefined currentApiConfigName?: string | undefined listApiConfigMeta?: | { @@ -820,6 +854,33 @@ type IpcMessage = autoApprovalEnabled?: boolean | undefined alwaysAllowReadOnly?: boolean | undefined alwaysAllowReadOnlyOutsideWorkspace?: boolean | undefined + codebaseIndexModels?: + | { + openai?: + | { + [x: string]: { + dimension: number + } + } + | undefined + ollama?: + | { + [x: string]: { + dimension: number + } + } + | undefined + } + | undefined + codebaseIndexConfig?: + | { + codebaseIndexEnabled?: boolean | undefined + codebaseIndexQdrantUrl?: string | undefined + codebaseIndexEmbedderProvider?: ("openai" | "ollama") | undefined + codebaseIndexEmbedderBaseUrl?: string | undefined + codebaseIndexEmbedderModelId?: string | undefined + } + | undefined alwaysAllowWrite?: boolean | undefined alwaysAllowWriteOutsideWorkspace?: boolean | undefined writeDelayMs?: number | undefined @@ -1001,6 +1062,7 @@ type IpcMessage = | "rooignore_error" | "diff_error" | "condense_context" + | "codebase_search_result" ) | undefined text?: string | undefined @@ -1245,6 +1307,8 @@ type TaskCommand = litellmBaseUrl?: string | undefined litellmApiKey?: string | undefined litellmModelId?: string | undefined + codeIndexOpenAiKey?: string | undefined + codeIndexQdrantApiKey?: string | undefined currentApiConfigName?: string | undefined listApiConfigMeta?: | { @@ -1304,6 +1368,33 @@ type TaskCommand = autoApprovalEnabled?: boolean | undefined alwaysAllowReadOnly?: boolean | undefined alwaysAllowReadOnlyOutsideWorkspace?: boolean | undefined + codebaseIndexModels?: + | { + openai?: + | { + [x: string]: { + dimension: number + } + } + | undefined + ollama?: + | { + [x: string]: { + dimension: number + } + } + | undefined + } + | undefined + codebaseIndexConfig?: + | { + codebaseIndexEnabled?: boolean | undefined + codebaseIndexQdrantUrl?: string | undefined + codebaseIndexEmbedderProvider?: ("openai" | "ollama") | undefined + codebaseIndexEmbedderBaseUrl?: string | undefined + codebaseIndexEmbedderModelId?: string | undefined + } + | undefined alwaysAllowWrite?: boolean | undefined alwaysAllowWriteOutsideWorkspace?: boolean | undefined writeDelayMs?: number | undefined @@ -1483,6 +1574,7 @@ type TaskEvent = | "rooignore_error" | "diff_error" | "condense_context" + | "codebase_search_result" ) | undefined text?: string | undefined diff --git a/src/extension.ts b/src/extension.ts index cb71016f75..5037027424 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -24,6 +24,7 @@ import { telemetryService } from "./services/telemetry/TelemetryService" import { API } from "./exports/api" import { migrateSettings } from "./utils/migrateSettings" import { formatLanguage } from "./shared/language" +import { CodeIndexManager } from "./services/code-index/manager" import { handleUri, @@ -74,9 +75,23 @@ export async function activate(context: vscode.ExtensionContext) { } const contextProxy = await ContextProxy.getInstance(context) - const provider = new ClineProvider(context, outputChannel, "sidebar", contextProxy) + const codeIndexManager = CodeIndexManager.getInstance(context) + + try { + await codeIndexManager?.initialize(contextProxy) + } catch (error) { + outputChannel.appendLine( + `[CodeIndexManager] Error during background CodeIndexManager configuration/indexing: ${error.message || error}`, + ) + } + + const provider = new ClineProvider(context, outputChannel, "sidebar", contextProxy, codeIndexManager) telemetryService.setProvider(provider) + if (codeIndexManager) { + context.subscriptions.push(codeIndexManager) + } + context.subscriptions.push( vscode.window.registerWebviewViewProvider(ClineProvider.sideBarId, provider, { webviewOptions: { retainContextWhenHidden: true }, diff --git a/src/i18n/locales/ca/tools.json b/src/i18n/locales/ca/tools.json index c0d8ac20af..29b89db0eb 100644 --- a/src/i18n/locales/ca/tools.json +++ b/src/i18n/locales/ca/tools.json @@ -6,5 +6,8 @@ "definitionsOnly": " (només definicions)", "maxLines": " (màxim {{max}} línies)" }, - "toolRepetitionLimitReached": "Roo sembla estar atrapat en un bucle, intentant la mateixa acció ({{toolName}}) repetidament. Això podria indicar un problema amb la seva estratègia actual. Considera reformular la tasca, proporcionar instruccions més específiques o guiar-lo cap a un enfocament diferent." + "toolRepetitionLimitReached": "Roo sembla estar atrapat en un bucle, intentant la mateixa acció ({{toolName}}) repetidament. Això podria indicar un problema amb la seva estratègia actual. Considera reformular la tasca, proporcionar instruccions més específiques o guiar-lo cap a un enfocament diferent.", + "codebaseSearch": { + "approval": "Cercant '{{query}}' a la base de codi..." + } } diff --git a/src/i18n/locales/de/tools.json b/src/i18n/locales/de/tools.json index e4cb085141..9b1d20006e 100644 --- a/src/i18n/locales/de/tools.json +++ b/src/i18n/locales/de/tools.json @@ -6,5 +6,8 @@ "definitionsOnly": " (nur Definitionen)", "maxLines": " (maximal {{max}} Zeilen)" }, - "toolRepetitionLimitReached": "Roo scheint in einer Schleife festzustecken und versucht wiederholt dieselbe Aktion ({{toolName}}). Dies könnte auf ein Problem mit der aktuellen Strategie hindeuten. Überlege dir, die Aufgabe umzuformulieren, genauere Anweisungen zu geben oder Roo zu einem anderen Ansatz zu führen." + "toolRepetitionLimitReached": "Roo scheint in einer Schleife festzustecken und versucht wiederholt dieselbe Aktion ({{toolName}}). Dies könnte auf ein Problem mit der aktuellen Strategie hindeuten. Überlege dir, die Aufgabe umzuformulieren, genauere Anweisungen zu geben oder Roo zu einem anderen Ansatz zu führen.", + "codebaseSearch": { + "approval": "Suche nach '{{query}}' im Codebase..." + } } diff --git a/src/i18n/locales/en/tools.json b/src/i18n/locales/en/tools.json index 70b0e8d964..4c7901b80d 100644 --- a/src/i18n/locales/en/tools.json +++ b/src/i18n/locales/en/tools.json @@ -6,5 +6,8 @@ "definitionsOnly": " (definitions only)", "maxLines": " (max {{max}} lines)" }, - "toolRepetitionLimitReached": "Roo appears to be stuck in a loop, attempting the same action ({{toolName}}) repeatedly. This might indicate a problem with its current strategy. Consider rephrasing the task, providing more specific instructions, or guiding it towards a different approach." + "toolRepetitionLimitReached": "Roo appears to be stuck in a loop, attempting the same action ({{toolName}}) repeatedly. This might indicate a problem with its current strategy. Consider rephrasing the task, providing more specific instructions, or guiding it towards a different approach.", + "codebaseSearch": { + "approval": "Searching for '{{query}}' in codebase..." + } } diff --git a/src/i18n/locales/es/tools.json b/src/i18n/locales/es/tools.json index e5e7b86627..97cbaf7e02 100644 --- a/src/i18n/locales/es/tools.json +++ b/src/i18n/locales/es/tools.json @@ -6,5 +6,8 @@ "definitionsOnly": " (solo definiciones)", "maxLines": " (máximo {{max}} líneas)" }, - "toolRepetitionLimitReached": "Roo parece estar atrapado en un bucle, intentando la misma acción ({{toolName}}) repetidamente. Esto podría indicar un problema con su estrategia actual. Considera reformular la tarea, proporcionar instrucciones más específicas o guiarlo hacia un enfoque diferente." + "toolRepetitionLimitReached": "Roo parece estar atrapado en un bucle, intentando la misma acción ({{toolName}}) repetidamente. Esto podría indicar un problema con su estrategia actual. Considera reformular la tarea, proporcionar instrucciones más específicas o guiarlo hacia un enfoque diferente.", + "codebaseSearch": { + "approval": "Buscando '{{query}}' en la base de código..." + } } diff --git a/src/i18n/locales/fr/tools.json b/src/i18n/locales/fr/tools.json index 777727c34e..1091ae657a 100644 --- a/src/i18n/locales/fr/tools.json +++ b/src/i18n/locales/fr/tools.json @@ -6,5 +6,8 @@ "definitionsOnly": " (définitions uniquement)", "maxLines": " (max {{max}} lignes)" }, - "toolRepetitionLimitReached": "Roo semble être bloqué dans une boucle, tentant la même action ({{toolName}}) de façon répétée. Cela pourrait indiquer un problème avec sa stratégie actuelle. Envisage de reformuler la tâche, de fournir des instructions plus spécifiques ou de le guider vers une approche différente." + "toolRepetitionLimitReached": "Roo semble être bloqué dans une boucle, tentant la même action ({{toolName}}) de façon répétée. Cela pourrait indiquer un problème avec sa stratégie actuelle. Envisage de reformuler la tâche, de fournir des instructions plus spécifiques ou de le guider vers une approche différente.", + "codebaseSearch": { + "approval": "Recherche de '{{query}}' dans la base de code..." + } } diff --git a/src/i18n/locales/hi/tools.json b/src/i18n/locales/hi/tools.json index 7784f70480..92a756ab9e 100644 --- a/src/i18n/locales/hi/tools.json +++ b/src/i18n/locales/hi/tools.json @@ -6,5 +6,8 @@ "definitionsOnly": " (केवल परिभाषाएँ)", "maxLines": " (अधिकतम {{max}} पंक्तियाँ)" }, - "toolRepetitionLimitReached": "Roo एक लूप में फंसा हुआ लगता है, बार-बार एक ही क्रिया ({{toolName}}) को दोहरा रहा है। यह उसकी वर्तमान रणनीति में किसी समस्या का संकेत हो सकता है। कार्य को पुनः परिभाषित करने, अधिक विशिष्ट निर्देश देने, या उसे एक अलग दृष्टिकोण की ओर मार्गदर्शित करने पर विचार करें।" + "toolRepetitionLimitReached": "Roo एक लूप में फंसा हुआ लगता है, बार-बार एक ही क्रिया ({{toolName}}) को दोहरा रहा है। यह उसकी वर्तमान रणनीति में किसी समस्या का संकेत हो सकता है। कार्य को पुनः परिभाषित करने, अधिक विशिष्ट निर्देश देने, या उसे एक अलग दृष्टिकोण की ओर मार्गदर्शित करने पर विचार करें।", + "codebaseSearch": { + "approval": "कोडबेस में '{{query}}' खोज रहा है..." + } } diff --git a/src/i18n/locales/it/tools.json b/src/i18n/locales/it/tools.json index a639137eb8..2b1959d430 100644 --- a/src/i18n/locales/it/tools.json +++ b/src/i18n/locales/it/tools.json @@ -6,5 +6,8 @@ "definitionsOnly": " (solo definizioni)", "maxLines": " (max {{max}} righe)" }, - "toolRepetitionLimitReached": "Roo sembra essere bloccato in un ciclo, tentando ripetutamente la stessa azione ({{toolName}}). Questo potrebbe indicare un problema con la sua strategia attuale. Considera di riformulare l'attività, fornire istruzioni più specifiche o guidarlo verso un approccio diverso." + "toolRepetitionLimitReached": "Roo sembra essere bloccato in un ciclo, tentando ripetutamente la stessa azione ({{toolName}}). Questo potrebbe indicare un problema con la sua strategia attuale. Considera di riformulare l'attività, fornire istruzioni più specifiche o guidarlo verso un approccio diverso.", + "codebaseSearch": { + "approval": "Ricerca di '{{query}}' nella base di codice..." + } } diff --git a/src/i18n/locales/ja/tools.json b/src/i18n/locales/ja/tools.json index fda79adb24..57f6cb04aa 100644 --- a/src/i18n/locales/ja/tools.json +++ b/src/i18n/locales/ja/tools.json @@ -6,5 +6,8 @@ "definitionsOnly": " (定義のみ)", "maxLines": " (最大{{max}}行)" }, - "toolRepetitionLimitReached": "Rooが同じ操作({{toolName}})を繰り返し試みるループに陥っているようです。これは現在の方法に問題がある可能性を示しています。タスクの言い換え、より具体的な指示の提供、または別のアプローチへの誘導を検討してください。" + "toolRepetitionLimitReached": "Rooが同じ操作({{toolName}})を繰り返し試みるループに陥っているようです。これは現在の方法に問題がある可能性を示しています。タスクの言い換え、より具体的な指示の提供、または別のアプローチへの誘導を検討してください。", + "codebaseSearch": { + "approval": "コードベースで '{{query}}' を検索中..." + } } diff --git a/src/i18n/locales/ko/tools.json b/src/i18n/locales/ko/tools.json index cd4679b2eb..1a654d6652 100644 --- a/src/i18n/locales/ko/tools.json +++ b/src/i18n/locales/ko/tools.json @@ -6,5 +6,8 @@ "definitionsOnly": " (정의만)", "maxLines": " (최대 {{max}}행)" }, - "toolRepetitionLimitReached": "Roo가 같은 동작({{toolName}})을 반복적으로 시도하면서 루프에 갇힌 것 같습니다. 이는 현재 전략에 문제가 있을 수 있음을 나타냅니다. 작업을 다시 표현하거나, 더 구체적인 지침을 제공하거나, 다른 접근 방식으로 안내해 보세요." + "toolRepetitionLimitReached": "Roo가 같은 동작({{toolName}})을 반복적으로 시도하면서 루프에 갇힌 것 같습니다. 이는 현재 전략에 문제가 있을 수 있음을 나타냅니다. 작업을 다시 표현하거나, 더 구체적인 지침을 제공하거나, 다른 접근 방식으로 안내해 보세요.", + "codebaseSearch": { + "approval": "코드베이스에서 '{{query}}' 검색 중..." + } } diff --git a/src/i18n/locales/nl/tools.json b/src/i18n/locales/nl/tools.json index a3f85a911e..472e7fa61b 100644 --- a/src/i18n/locales/nl/tools.json +++ b/src/i18n/locales/nl/tools.json @@ -6,5 +6,8 @@ "definitionsOnly": " (alleen definities)", "maxLines": " (max {{max}} regels)" }, - "toolRepetitionLimitReached": "Roo lijkt vast te zitten in een lus, waarbij hij herhaaldelijk dezelfde actie ({{toolName}}) probeert. Dit kan duiden op een probleem met de huidige strategie. Overweeg de taak te herformuleren, specifiekere instructies te geven of Roo naar een andere aanpak te leiden." + "toolRepetitionLimitReached": "Roo lijkt vast te zitten in een lus, waarbij hij herhaaldelijk dezelfde actie ({{toolName}}) probeert. Dit kan duiden op een probleem met de huidige strategie. Overweeg de taak te herformuleren, specifiekere instructies te geven of Roo naar een andere aanpak te leiden.", + "codebaseSearch": { + "approval": "Zoeken naar '{{query}}' in codebase..." + } } diff --git a/src/i18n/locales/pl/tools.json b/src/i18n/locales/pl/tools.json index 6de5cc8f78..a020dec952 100644 --- a/src/i18n/locales/pl/tools.json +++ b/src/i18n/locales/pl/tools.json @@ -6,5 +6,8 @@ "definitionsOnly": " (tylko definicje)", "maxLines": " (maks. {{max}} linii)" }, - "toolRepetitionLimitReached": "Wygląda na to, że Roo utknął w pętli, wielokrotnie próbując wykonać tę samą akcję ({{toolName}}). Może to wskazywać na problem z jego obecną strategią. Rozważ przeformułowanie zadania, podanie bardziej szczegółowych instrukcji lub nakierowanie go na inne podejście." + "toolRepetitionLimitReached": "Wygląda na to, że Roo utknął w pętli, wielokrotnie próbując wykonać tę samą akcję ({{toolName}}). Może to wskazywać na problem z jego obecną strategią. Rozważ przeformułowanie zadania, podanie bardziej szczegółowych instrukcji lub nakierowanie go na inne podejście.", + "codebaseSearch": { + "approval": "Wyszukiwanie '{{query}}' w bazie kodu..." + } } diff --git a/src/i18n/locales/pt-BR/tools.json b/src/i18n/locales/pt-BR/tools.json index 096aadad20..d1c99f9e2b 100644 --- a/src/i18n/locales/pt-BR/tools.json +++ b/src/i18n/locales/pt-BR/tools.json @@ -6,5 +6,8 @@ "definitionsOnly": " (apenas definições)", "maxLines": " (máx. {{max}} linhas)" }, - "toolRepetitionLimitReached": "Roo parece estar preso em um loop, tentando a mesma ação ({{toolName}}) repetidamente. Isso pode indicar um problema com sua estratégia atual. Considere reformular a tarefa, fornecer instruções mais específicas ou guiá-lo para uma abordagem diferente." + "toolRepetitionLimitReached": "Roo parece estar preso em um loop, tentando a mesma ação ({{toolName}}) repetidamente. Isso pode indicar um problema com sua estratégia atual. Considere reformular a tarefa, fornecer instruções mais específicas ou guiá-lo para uma abordagem diferente.", + "codebaseSearch": { + "approval": "Pesquisando '{{query}}' na base de código..." + } } diff --git a/src/i18n/locales/ru/tools.json b/src/i18n/locales/ru/tools.json index 2968c08935..74abd00f83 100644 --- a/src/i18n/locales/ru/tools.json +++ b/src/i18n/locales/ru/tools.json @@ -6,5 +6,8 @@ "definitionsOnly": " (только определения)", "maxLines": " (макс. {{max}} строк)" }, - "toolRepetitionLimitReached": "Похоже, что Roo застрял в цикле, многократно пытаясь выполнить одно и то же действие ({{toolName}}). Это может указывать на проблему с его текущей стратегией. Попробуйте переформулировать задачу, предоставить более конкретные инструкции или направить его к другому подходу." + "toolRepetitionLimitReached": "Похоже, что Roo застрял в цикле, многократно пытаясь выполнить одно и то же действие ({{toolName}}). Это может указывать на проблему с его текущей стратегией. Попробуйте переформулировать задачу, предоставить более конкретные инструкции или направить его к другому подходу.", + "codebaseSearch": { + "approval": "Поиск '{{query}}' в кодовой базе..." + } } diff --git a/src/i18n/locales/tr/tools.json b/src/i18n/locales/tr/tools.json index 86fcada627..1ac103f0b6 100644 --- a/src/i18n/locales/tr/tools.json +++ b/src/i18n/locales/tr/tools.json @@ -6,5 +6,8 @@ "definitionsOnly": " (sadece tanımlar)", "maxLines": " (maks. {{max}} satır)" }, - "toolRepetitionLimitReached": "Roo bir döngüye takılmış gibi görünüyor, aynı eylemi ({{toolName}}) tekrar tekrar deniyor. Bu, mevcut stratejisinde bir sorun olduğunu gösterebilir. Görevi yeniden ifade etmeyi, daha spesifik talimatlar vermeyi veya onu farklı bir yaklaşıma yönlendirmeyi düşünün." + "toolRepetitionLimitReached": "Roo bir döngüye takılmış gibi görünüyor, aynı eylemi ({{toolName}}) tekrar tekrar deniyor. Bu, mevcut stratejisinde bir sorun olduğunu gösterebilir. Görevi yeniden ifade etmeyi, daha spesifik talimatlar vermeyi veya onu farklı bir yaklaşıma yönlendirmeyi düşünün.", + "codebaseSearch": { + "approval": "Kod tabanında '{{query}}' aranıyor..." + } } diff --git a/src/i18n/locales/vi/tools.json b/src/i18n/locales/vi/tools.json index d77fd34f1d..704cd21aea 100644 --- a/src/i18n/locales/vi/tools.json +++ b/src/i18n/locales/vi/tools.json @@ -6,5 +6,8 @@ "definitionsOnly": " (chỉ định nghĩa)", "maxLines": " (tối đa {{max}} dòng)" }, - "toolRepetitionLimitReached": "Roo dường như đang bị mắc kẹt trong một vòng lặp, liên tục cố gắng thực hiện cùng một hành động ({{toolName}}). Điều này có thể cho thấy vấn đề với chiến lược hiện tại. Hãy cân nhắc việc diễn đạt lại nhiệm vụ, cung cấp hướng dẫn cụ thể hơn, hoặc hướng Roo theo một cách tiếp cận khác." + "toolRepetitionLimitReached": "Roo dường như đang bị mắc kẹt trong một vòng lặp, liên tục cố gắng thực hiện cùng một hành động ({{toolName}}). Điều này có thể cho thấy vấn đề với chiến lược hiện tại. Hãy cân nhắc việc diễn đạt lại nhiệm vụ, cung cấp hướng dẫn cụ thể hơn, hoặc hướng Roo theo một cách tiếp cận khác.", + "codebaseSearch": { + "approval": "Đang tìm kiếm '{{query}}' trong cơ sở mã..." + } } diff --git a/src/i18n/locales/zh-CN/tools.json b/src/i18n/locales/zh-CN/tools.json index 30ca1f7ab1..38e8d6f0e1 100644 --- a/src/i18n/locales/zh-CN/tools.json +++ b/src/i18n/locales/zh-CN/tools.json @@ -6,5 +6,8 @@ "definitionsOnly": " (仅定义)", "maxLines": " (最多 {{max}} 行)" }, - "toolRepetitionLimitReached": "Roo 似乎陷入循环,反复尝试同一操作 ({{toolName}})。这可能表明当前策略存在问题。请考虑重新描述任务、提供更具体的指示或引导其尝试不同的方法。" + "toolRepetitionLimitReached": "Roo 似乎陷入循环,反复尝试同一操作 ({{toolName}})。这可能表明当前策略存在问题。请考虑重新描述任务、提供更具体的指示或引导其尝试不同的方法。", + "codebaseSearch": { + "approval": "正在搜索代码库中的 '{{query}}'..." + } } diff --git a/src/i18n/locales/zh-TW/tools.json b/src/i18n/locales/zh-TW/tools.json index 2416fe57f6..01289d6c96 100644 --- a/src/i18n/locales/zh-TW/tools.json +++ b/src/i18n/locales/zh-TW/tools.json @@ -6,5 +6,8 @@ "definitionsOnly": " (僅定義)", "maxLines": " (最多 {{max}} 行)" }, - "toolRepetitionLimitReached": "Roo 似乎陷入循環,反覆嘗試同一操作 ({{toolName}})。這可能表明目前策略存在問題。請考慮重新描述工作、提供更具體的指示或引導其嘗試不同的方法。" + "toolRepetitionLimitReached": "Roo 似乎陷入循環,反覆嘗試同一操作 ({{toolName}})。這可能表明目前策略存在問題。請考慮重新描述工作、提供更具體的指示或引導其嘗試不同的方法。", + "codebaseSearch": { + "approval": "正在搜尋程式碼庫中的「{{query}}」..." + } } diff --git a/src/jest.config.mjs b/src/jest.config.mjs index 469988287a..f285c67c11 100644 --- a/src/jest.config.mjs +++ b/src/jest.config.mjs @@ -32,6 +32,7 @@ export default { "@modelcontextprotocol/sdk/(.*)": "/__mocks__/@modelcontextprotocol/sdk/$1", "^delay$": "/__mocks__/delay.js", "^p-wait-for$": "/__mocks__/p-wait-for.js", + "^p-limit$": "/__mocks__/p-limit.js", "^serialize-error$": "/__mocks__/serialize-error.js", "^strip-ansi$": "/__mocks__/strip-ansi.js", "^default-shell$": "/__mocks__/default-shell.js", diff --git a/src/package.json b/src/package.json index ce108f91f7..f89e354f81 100644 --- a/src/package.json +++ b/src/package.json @@ -343,7 +343,10 @@ "@google/genai": "^0.13.0", "@mistralai/mistralai": "^1.3.6", "@modelcontextprotocol/sdk": "^1.9.0", + "@qdrant/js-client-rest": "^1.14.0", + "@types/lodash.debounce": "^4.0.9", "@vscode/codicons": "^0.0.36", + "async-mutex": "^0.5.0", "axios": "^1.7.4", "cheerio": "^1.0.0", "chokidar": "^4.0.1", @@ -361,12 +364,14 @@ "i18next": "^24.2.2", "ignore": "^7.0.3", "isbinaryfile": "^5.0.2", + "lodash.debounce": "^4.0.8", "mammoth": "^1.8.0", "monaco-vscode-textmate-theme-converter": "^0.1.7", "node-cache": "^5.1.2", "node-ipc": "^12.0.0", "openai": "^4.78.1", "os-name": "^6.0.0", + "p-limit": "^6.2.0", "p-wait-for": "^5.0.2", "pdf-parse": "^1.1.1", "pkce-challenge": "^4.1.0", @@ -388,6 +393,7 @@ "tmp": "^0.2.3", "tree-sitter-wasms": "^0.1.11", "turndown": "^7.2.0", + "uuid": "^11.1.0", "vscode-material-icons": "^0.1.1", "web-tree-sitter": "^0.22.6", "workerpool": "^9.2.0", @@ -396,9 +402,9 @@ }, "devDependencies": { "@jest/globals": "^29.7.0", + "@roo-code/build": "workspace:^", "@roo-code/config-eslint": "workspace:^", "@roo-code/config-typescript": "workspace:^", - "@roo-code/build": "workspace:^", "@types/clone-deep": "^4.0.4", "@types/debug": "^4.1.12", "@types/diff": "^5.2.1", diff --git a/src/schemas/index.ts b/src/schemas/index.ts index 1233745e53..4fb893ae1f 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -223,6 +223,31 @@ export const modelInfoSchema = z.object({ export type ModelInfo = z.infer +/** + * Codebase Index Config + */ +export const codebaseIndexConfigSchema = z.object({ + codebaseIndexEnabled: z.boolean().optional(), + codebaseIndexQdrantUrl: z.string().optional(), + codebaseIndexEmbedderProvider: z.enum(["openai", "ollama"]).optional(), + codebaseIndexEmbedderBaseUrl: z.string().optional(), + codebaseIndexEmbedderModelId: z.string().optional(), +}) + +export type CodebaseIndexConfig = z.infer + +export const codebaseIndexModelsSchema = z.object({ + openai: z.record(z.string(), z.object({ dimension: z.number() })).optional(), + ollama: z.record(z.string(), z.object({ dimension: z.number() })).optional(), +}) + +export type CodebaseIndexModels = z.infer + +export const codebaseIndexProviderSchema = z.object({ + codeIndexOpenAiKey: z.string().optional(), + codeIndexQdrantApiKey: z.string().optional(), +}) + /** * HistoryItem */ @@ -636,6 +661,7 @@ export const providerSettingsSchema = z.object({ ...groqSchema.shape, ...chutesSchema.shape, ...litellmSchema.shape, + ...codebaseIndexProviderSchema.shape }) export type ProviderSettings = z.infer @@ -712,6 +738,9 @@ const providerSettingsRecord: ProviderSettingsRecord = { // Requesty requestyApiKey: undefined, requestyModelId: undefined, + // Code Index + codeIndexOpenAiKey: undefined, + codeIndexQdrantApiKey: undefined, // Reasoning enableReasoningEffort: undefined, reasoningEffort: undefined, @@ -758,6 +787,8 @@ export const globalSettingsSchema = z.object({ autoApprovalEnabled: z.boolean().optional(), alwaysAllowReadOnly: z.boolean().optional(), alwaysAllowReadOnlyOutsideWorkspace: z.boolean().optional(), + codebaseIndexModels: codebaseIndexModelsSchema.optional(), + codebaseIndexConfig: codebaseIndexConfigSchema.optional(), alwaysAllowWrite: z.boolean().optional(), alwaysAllowWriteOutsideWorkspace: z.boolean().optional(), writeDelayMs: z.number().optional(), @@ -828,6 +859,8 @@ export type GlobalSettings = z.infer type GlobalSettingsRecord = Record, undefined> const globalSettingsRecord: GlobalSettingsRecord = { + codebaseIndexModels: undefined, + codebaseIndexConfig: undefined, currentApiConfigName: undefined, listApiConfigMeta: undefined, pinnedApiConfigs: undefined, @@ -940,8 +973,12 @@ export type SecretState = Pick< | "groqApiKey" | "chutesApiKey" | "litellmApiKey" + | "codeIndexOpenAiKey" + | "codeIndexQdrantApiKey" > +export type CodeIndexSecrets = "codeIndexOpenAiKey" | "codeIndexQdrantApiKey" + type SecretStateRecord = Record, undefined> const secretStateRecord: SecretStateRecord = { @@ -962,6 +999,8 @@ const secretStateRecord: SecretStateRecord = { groqApiKey: undefined, chutesApiKey: undefined, litellmApiKey: undefined, + codeIndexOpenAiKey: undefined, + codeIndexQdrantApiKey: undefined, } export const SECRET_STATE_KEYS = Object.keys(secretStateRecord) as Keys[] @@ -1030,6 +1069,7 @@ export const clineSays = [ "rooignore_error", "diff_error", "condense_context", + "codebase_search_result", ] as const export const clineSaySchema = z.enum(clineSays) @@ -1118,6 +1158,7 @@ export const toolNames = [ "switch_mode", "new_task", "fetch_instructions", + "codebase_search", ] as const export const toolNamesSchema = z.enum(toolNames) diff --git a/src/services/code-index/__tests__/cache-manager.test.ts b/src/services/code-index/__tests__/cache-manager.test.ts new file mode 100644 index 0000000000..3746d949d3 --- /dev/null +++ b/src/services/code-index/__tests__/cache-manager.test.ts @@ -0,0 +1,180 @@ +import * as vscode from "vscode" +import { createHash } from "crypto" +import debounce from "lodash.debounce" +import { CacheManager } from "../cache-manager" + +// Mock vscode +jest.mock("vscode", () => ({ + Uri: { + joinPath: jest.fn(), + }, + workspace: { + fs: { + readFile: jest.fn(), + writeFile: jest.fn(), + delete: jest.fn(), + }, + }, +})) + +// Mock debounce to execute immediately +jest.mock("lodash.debounce", () => jest.fn((fn) => fn)) + +describe("CacheManager", () => { + let mockContext: vscode.ExtensionContext + let mockWorkspacePath: string + let mockCachePath: vscode.Uri + let cacheManager: CacheManager + + beforeEach(() => { + // Reset all mocks + jest.clearAllMocks() + + // Mock context + mockWorkspacePath = "/mock/workspace" + mockCachePath = { fsPath: "/mock/storage/cache.json" } as vscode.Uri + mockContext = { + globalStorageUri: { fsPath: "/mock/storage" } as vscode.Uri, + } as vscode.ExtensionContext + + // Mock Uri.joinPath + ;(vscode.Uri.joinPath as jest.Mock).mockReturnValue(mockCachePath) + + // Create cache manager instance + cacheManager = new CacheManager(mockContext, mockWorkspacePath) + }) + + describe("constructor", () => { + it("should correctly set up cachePath using Uri.joinPath and crypto.createHash", () => { + const expectedHash = createHash("sha256").update(mockWorkspacePath).digest("hex") + + expect(vscode.Uri.joinPath).toHaveBeenCalledWith( + mockContext.globalStorageUri, + `roo-index-cache-${expectedHash}.json`, + ) + }) + + it("should set up debounced save function", () => { + expect(debounce).toHaveBeenCalledWith(expect.any(Function), 1500) + }) + }) + + describe("initialize", () => { + it("should load existing cache file successfully", async () => { + const mockCache = { "file1.ts": "hash1", "file2.ts": "hash2" } + const mockBuffer = Buffer.from(JSON.stringify(mockCache)) + ;(vscode.workspace.fs.readFile as jest.Mock).mockResolvedValue(mockBuffer) + + await cacheManager.initialize() + + expect(vscode.workspace.fs.readFile).toHaveBeenCalledWith(mockCachePath) + expect(cacheManager.getAllHashes()).toEqual(mockCache) + }) + + it("should handle missing cache file by creating empty cache", async () => { + ;(vscode.workspace.fs.readFile as jest.Mock).mockRejectedValue(new Error("File not found")) + + await cacheManager.initialize() + + expect(cacheManager.getAllHashes()).toEqual({}) + }) + }) + + describe("hash management", () => { + it("should update hash and trigger save", () => { + const filePath = "test.ts" + const hash = "testhash" + + cacheManager.updateHash(filePath, hash) + + expect(cacheManager.getHash(filePath)).toBe(hash) + expect(vscode.workspace.fs.writeFile).toHaveBeenCalled() + }) + + it("should delete hash and trigger save", () => { + const filePath = "test.ts" + const hash = "testhash" + + cacheManager.updateHash(filePath, hash) + cacheManager.deleteHash(filePath) + + expect(cacheManager.getHash(filePath)).toBeUndefined() + expect(vscode.workspace.fs.writeFile).toHaveBeenCalled() + }) + + it("should return shallow copy of hashes", () => { + const filePath = "test.ts" + const hash = "testhash" + + cacheManager.updateHash(filePath, hash) + const hashes = cacheManager.getAllHashes() + + // Modify the returned object + hashes[filePath] = "modified" + + // Original should remain unchanged + expect(cacheManager.getHash(filePath)).toBe(hash) + }) + }) + + describe("saving", () => { + it("should save cache to disk with correct data", async () => { + const filePath = "test.ts" + const hash = "testhash" + + cacheManager.updateHash(filePath, hash) + + expect(vscode.workspace.fs.writeFile).toHaveBeenCalledWith(mockCachePath, expect.any(Uint8Array)) + + // Verify the saved data + const savedData = JSON.parse( + Buffer.from((vscode.workspace.fs.writeFile as jest.Mock).mock.calls[0][1]).toString(), + ) + expect(savedData).toEqual({ [filePath]: hash }) + }) + + it("should handle save errors gracefully", async () => { + const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation() + ;(vscode.workspace.fs.writeFile as jest.Mock).mockRejectedValue(new Error("Save failed")) + + cacheManager.updateHash("test.ts", "hash") + + // Wait for any pending promises + await new Promise((resolve) => setTimeout(resolve, 0)) + + expect(consoleErrorSpy).toHaveBeenCalledWith("Failed to save cache:", expect.any(Error)) + + consoleErrorSpy.mockRestore() + }) + }) + + describe("clearCacheFile", () => { + it("should clear cache file and reset state", async () => { + cacheManager.updateHash("test.ts", "hash") + + // Reset the mock to ensure writeFile succeeds for clearCacheFile + ;(vscode.workspace.fs.writeFile as jest.Mock).mockClear() + ;(vscode.workspace.fs.writeFile as jest.Mock).mockResolvedValue(undefined) + + await cacheManager.clearCacheFile() + + expect(vscode.workspace.fs.writeFile).toHaveBeenCalledWith(mockCachePath, Buffer.from("{}")) + expect(cacheManager.getAllHashes()).toEqual({}) + }) + + it("should handle clear errors gracefully", async () => { + const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation() + ;(vscode.workspace.fs.writeFile as jest.Mock).mockRejectedValue(new Error("Save failed")) + + await cacheManager.clearCacheFile() + + expect(consoleErrorSpy).toHaveBeenCalledWith( + "Failed to clear cache file:", + expect.any(Error), + mockCachePath, + ) + + consoleErrorSpy.mockRestore() + }) + }) +}) diff --git a/src/services/code-index/__tests__/config-manager.test.ts b/src/services/code-index/__tests__/config-manager.test.ts new file mode 100644 index 0000000000..87fc958003 --- /dev/null +++ b/src/services/code-index/__tests__/config-manager.test.ts @@ -0,0 +1,216 @@ +import { ContextProxy } from "../../../core/config/ContextProxy" +import { CodeIndexConfigManager } from "../config-manager" + +describe("CodeIndexConfigManager", () => { + let mockContextProxy: jest.Mocked + let configManager: CodeIndexConfigManager + + beforeEach(() => { + // Setup mock ContextProxy + mockContextProxy = { + getGlobalState: jest.fn(), + getSecret: jest.fn().mockReturnValue(undefined), + } as unknown as jest.Mocked + + configManager = new CodeIndexConfigManager(mockContextProxy) + }) + + describe("constructor", () => { + it("should initialize with ContextProxy", () => { + expect(configManager).toBeDefined() + expect(configManager.isFeatureEnabled).toBe(false) + expect(configManager.currentEmbedderProvider).toBe("openai") + }) + }) + + describe("loadConfiguration", () => { + it("should load default configuration when no state exists", async () => { + mockContextProxy.getGlobalState.mockReturnValue(undefined) + mockContextProxy.getSecret.mockReturnValue(undefined) + + const result = await configManager.loadConfiguration() + + expect(result.currentConfig).toEqual({ + isEnabled: false, + isConfigured: false, + embedderProvider: "openai", + modelId: undefined, + openAiOptions: { openAiNativeApiKey: "" }, + ollamaOptions: { ollamaBaseUrl: "" }, + qdrantUrl: "http://localhost:6333", + qdrantApiKey: "", + searchMinScore: 0.4, + }) + expect(result.requiresRestart).toBe(false) + expect(result.requiresClear).toBe(false) + }) + + it("should load configuration from globalState and secrets", async () => { + const mockGlobalState = { + codebaseIndexEnabled: true, + codebaseIndexQdrantUrl: "http://qdrant.local", + codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderBaseUrl: "", + codebaseIndexEmbedderModelId: "text-embedding-3-large", + } + mockContextProxy.getGlobalState.mockReturnValue(mockGlobalState) + mockContextProxy.getSecret.mockImplementation((key: string) => { + if (key === "codeIndexOpenAiKey") return "test-openai-key" + if (key === "codeIndexQdrantApiKey") return "test-qdrant-key" + return undefined + }) + + const result = await configManager.loadConfiguration() + + expect(result.currentConfig).toEqual({ + isEnabled: true, + isConfigured: true, + embedderProvider: "openai", + modelId: "text-embedding-3-large", + openAiOptions: { openAiNativeApiKey: "test-openai-key" }, + ollamaOptions: { ollamaBaseUrl: "" }, + qdrantUrl: "http://qdrant.local", + qdrantApiKey: "test-qdrant-key", + searchMinScore: 0.4, + }) + }) + + it("should detect restart requirement when provider changes", async () => { + // Initial state + mockContextProxy.getGlobalState.mockReturnValue({ + codebaseIndexEnabled: true, + codebaseIndexQdrantUrl: "http://qdrant.local", + codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderModelId: "text-embedding-3-large", + }) + + await configManager.loadConfiguration() + + // Change provider + mockContextProxy.getGlobalState.mockReturnValue({ + codebaseIndexEnabled: true, + codebaseIndexQdrantUrl: "http://qdrant.local", + codebaseIndexEmbedderProvider: "ollama", + codebaseIndexEmbedderBaseUrl: "http://ollama.local", + codebaseIndexEmbedderModelId: "llama2", + }) + + const result = await configManager.loadConfiguration() + expect(result.requiresRestart).toBe(true) + }) + + it("should detect clear requirement when model dimensions change", async () => { + // Initial state with a model + mockContextProxy.getGlobalState.mockReturnValue({ + codebaseIndexEnabled: true, + codebaseIndexQdrantUrl: "http://qdrant.local", + codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderModelId: "text-embedding-3-small", + }) + + await configManager.loadConfiguration() + + // Change to a model with different dimensions + mockContextProxy.getGlobalState.mockReturnValue({ + codebaseIndexEnabled: true, + codebaseIndexQdrantUrl: "http://qdrant.local", + codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderModelId: "text-embedding-3-large", + }) + + const result = await configManager.loadConfiguration() + expect(result.requiresClear).toBe(true) + }) + }) + + describe("isConfigured", () => { + it("should validate OpenAI configuration correctly", async () => { + mockContextProxy.getGlobalState.mockReturnValue({ + codebaseIndexEnabled: true, + codebaseIndexQdrantUrl: "http://qdrant.local", + codebaseIndexEmbedderProvider: "openai", + }) + mockContextProxy.getSecret.mockImplementation((key: string) => { + if (key === "codeIndexOpenAiKey") return "test-key" + return undefined + }) + + await configManager.loadConfiguration() + expect(configManager.isFeatureConfigured).toBe(true) + }) + + it("should validate Ollama configuration correctly", async () => { + mockContextProxy.getGlobalState.mockReturnValue({ + codebaseIndexEnabled: true, + codebaseIndexQdrantUrl: "http://qdrant.local", + codebaseIndexEmbedderProvider: "ollama", + codebaseIndexEmbedderBaseUrl: "http://ollama.local", + }) + + await configManager.loadConfiguration() + expect(configManager.isFeatureConfigured).toBe(true) + }) + + it("should return false when required values are missing", async () => { + mockContextProxy.getGlobalState.mockReturnValue({ + codebaseIndexEnabled: true, + codebaseIndexEmbedderProvider: "openai", + }) + + await configManager.loadConfiguration() + expect(configManager.isFeatureConfigured).toBe(false) + }) + }) + + describe("getter properties", () => { + beforeEach(async () => { + mockContextProxy.getGlobalState.mockReturnValue({ + codebaseIndexEnabled: true, + codebaseIndexQdrantUrl: "http://qdrant.local", + codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderModelId: "text-embedding-3-large", + }) + mockContextProxy.getSecret.mockImplementation((key: string) => { + if (key === "codeIndexOpenAiKey") return "test-openai-key" + if (key === "codeIndexQdrantApiKey") return "test-qdrant-key" + return undefined + }) + + await configManager.loadConfiguration() + }) + + it("should return correct configuration via getConfig", () => { + const config = configManager.getConfig() + expect(config).toEqual({ + isEnabled: true, + isConfigured: true, + embedderProvider: "openai", + modelId: "text-embedding-3-large", + openAiOptions: { openAiNativeApiKey: "test-openai-key" }, + ollamaOptions: { ollamaBaseUrl: undefined }, + qdrantUrl: "http://qdrant.local", + qdrantApiKey: "test-qdrant-key", + searchMinScore: 0.4, + }) + }) + + it("should return correct feature enabled state", () => { + expect(configManager.isFeatureEnabled).toBe(true) + }) + + it("should return correct embedder provider", () => { + expect(configManager.currentEmbedderProvider).toBe("openai") + }) + + it("should return correct Qdrant configuration", () => { + expect(configManager.qdrantConfig).toEqual({ + url: "http://qdrant.local", + apiKey: "test-qdrant-key", + }) + }) + + it("should return correct model ID", () => { + expect(configManager.currentModelId).toBe("text-embedding-3-large") + }) + }) +}) diff --git a/src/services/code-index/cache-manager.ts b/src/services/code-index/cache-manager.ts new file mode 100644 index 0000000000..f66f933a0b --- /dev/null +++ b/src/services/code-index/cache-manager.ts @@ -0,0 +1,102 @@ +import * as vscode from "vscode" +import { createHash } from "crypto" +import { ICacheManager } from "./interfaces/cache" +import debounce from "lodash.debounce" + +/** + * Manages the cache for code indexing + */ +export class CacheManager implements ICacheManager { + private cachePath: vscode.Uri + private fileHashes: Record = {} + private _debouncedSaveCache: () => void + + /** + * Creates a new cache manager + * @param context VS Code extension context + * @param workspacePath Path to the workspace + */ + constructor( + private context: vscode.ExtensionContext, + private workspacePath: string, + ) { + this.cachePath = vscode.Uri.joinPath( + context.globalStorageUri, + `roo-index-cache-${createHash("sha256").update(workspacePath).digest("hex")}.json`, + ) + this._debouncedSaveCache = debounce(async () => { + await this._performSave() + }, 1500) + } + + /** + * Initializes the cache manager by loading the cache file + */ + async initialize(): Promise { + try { + const cacheData = await vscode.workspace.fs.readFile(this.cachePath) + this.fileHashes = JSON.parse(cacheData.toString()) + } catch (error) { + this.fileHashes = {} + } + } + + /** + * Saves the cache to disk + */ + private async _performSave(): Promise { + try { + await vscode.workspace.fs.writeFile(this.cachePath, Buffer.from(JSON.stringify(this.fileHashes, null, 2))) + } catch (error) { + console.error("Failed to save cache:", error) + } + } + + /** + * Clears the cache file by writing an empty object to it + */ + async clearCacheFile(): Promise { + try { + await vscode.workspace.fs.writeFile(this.cachePath, Buffer.from("{}")) + this.fileHashes = {} + } catch (error) { + console.error("Failed to clear cache file:", error, this.cachePath) + } + } + + /** + * Gets the hash for a file path + * @param filePath Path to the file + * @returns The hash for the file or undefined if not found + */ + getHash(filePath: string): string | undefined { + return this.fileHashes[filePath] + } + + /** + * Updates the hash for a file path + * @param filePath Path to the file + * @param hash New hash value + */ + updateHash(filePath: string, hash: string): void { + this.fileHashes[filePath] = hash + this._debouncedSaveCache() + } + + /** + * Deletes the hash for a file path + * @param filePath Path to the file + */ + deleteHash(filePath: string): void { + delete this.fileHashes[filePath] + this._debouncedSaveCache() + } + + /** + * Gets a copy of all file hashes + * @returns A copy of the file hashes record + */ + getAllHashes(): Record { + return { ...this.fileHashes } + } +} diff --git a/src/services/code-index/config-manager.ts b/src/services/code-index/config-manager.ts new file mode 100644 index 0000000000..e866c4c7a1 --- /dev/null +++ b/src/services/code-index/config-manager.ts @@ -0,0 +1,235 @@ +import { ApiHandlerOptions } from "../../shared/api" +import { ContextProxy } from "../../core/config/ContextProxy" +import { EmbedderProvider } from "./interfaces/manager" +import { getModelDimension, getDefaultModelId } from "../../shared/embeddingModels" +import { CodeIndexConfig, PreviousConfigSnapshot } from "./interfaces/config" +import { SEARCH_MIN_SCORE } from "./constants" + +/** + * Manages configuration state and validation for the code indexing feature. + * Handles loading, validating, and providing access to configuration values. + */ +export class CodeIndexConfigManager { + private isEnabled: boolean = false + private embedderProvider: EmbedderProvider = "openai" + private modelId?: string + private openAiOptions?: ApiHandlerOptions + private ollamaOptions?: ApiHandlerOptions + private qdrantUrl?: string + private qdrantApiKey?: string + private searchMinScore?: number + + constructor(private readonly contextProxy: ContextProxy) {} + + /** + * Loads persisted configuration from globalState. + */ + public async loadConfiguration(): Promise<{ + configSnapshot: PreviousConfigSnapshot + currentConfig: { + isEnabled: boolean + isConfigured: boolean + embedderProvider: EmbedderProvider + modelId?: string + openAiOptions?: ApiHandlerOptions + ollamaOptions?: ApiHandlerOptions + qdrantUrl?: string + qdrantApiKey?: string + searchMinScore?: number + } + requiresRestart: boolean + requiresClear: boolean + }> { + const previousConfigSnapshot: PreviousConfigSnapshot = { + enabled: this.isEnabled, + configured: this.isConfigured(), + embedderProvider: this.embedderProvider, + modelId: this.modelId, + openAiKey: this.openAiOptions?.openAiNativeApiKey, + ollamaBaseUrl: this.ollamaOptions?.ollamaBaseUrl, + qdrantUrl: this.qdrantUrl, + qdrantApiKey: this.qdrantApiKey, + } + + let codebaseIndexConfig = this.contextProxy?.getGlobalState("codebaseIndexConfig") ?? { + codebaseIndexEnabled: false, + codebaseIndexQdrantUrl: "http://localhost:6333", + codebaseIndexSearchMinScore: 0.4, + codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderBaseUrl: "", + codebaseIndexEmbedderModelId: "", + } + + const { + codebaseIndexEnabled, + codebaseIndexQdrantUrl, + codebaseIndexEmbedderProvider, + codebaseIndexEmbedderBaseUrl, + codebaseIndexEmbedderModelId, + } = codebaseIndexConfig + + const openAiKey = this.contextProxy?.getSecret("codeIndexOpenAiKey") ?? "" + const qdrantApiKey = this.contextProxy?.getSecret("codeIndexQdrantApiKey") ?? "" + + this.isEnabled = codebaseIndexEnabled || false + this.qdrantUrl = codebaseIndexQdrantUrl + this.qdrantApiKey = qdrantApiKey ?? "" + this.openAiOptions = { openAiNativeApiKey: openAiKey } + this.searchMinScore = SEARCH_MIN_SCORE + + this.embedderProvider = codebaseIndexEmbedderProvider === "ollama" ? "ollama" : "openai" + this.modelId = codebaseIndexEmbedderModelId || undefined + + this.ollamaOptions = { + ollamaBaseUrl: codebaseIndexEmbedderBaseUrl, + } + + const previousModelId = + previousConfigSnapshot.modelId ?? getDefaultModelId(previousConfigSnapshot.embedderProvider) + const currentModelId = this.modelId ?? getDefaultModelId(this.embedderProvider) + const previousDimension = previousModelId + ? getModelDimension(previousConfigSnapshot.embedderProvider, previousModelId) + : undefined + const currentDimension = currentModelId ? getModelDimension(this.embedderProvider, currentModelId) : undefined + const requiresClear = + previousDimension !== undefined && currentDimension !== undefined && previousDimension !== currentDimension + + return { + configSnapshot: previousConfigSnapshot, + currentConfig: { + isEnabled: this.isEnabled, + isConfigured: this.isConfigured(), + embedderProvider: this.embedderProvider, + modelId: this.modelId, + openAiOptions: this.openAiOptions, + ollamaOptions: this.ollamaOptions, + qdrantUrl: this.qdrantUrl, + qdrantApiKey: this.qdrantApiKey, + searchMinScore: this.searchMinScore, + }, + requiresRestart: this._didConfigChangeRequireRestart(previousConfigSnapshot), + requiresClear, + } + } + + /** + * Checks if the service is properly configured based on the embedder type. + */ + public isConfigured(): boolean { + if (this.embedderProvider === "openai") { + return !!(this.openAiOptions?.openAiNativeApiKey && this.qdrantUrl) + } else if (this.embedderProvider === "ollama") { + // Ollama model ID has a default, so only base URL is strictly required for config + return !!(this.ollamaOptions?.ollamaBaseUrl && this.qdrantUrl) + } + return false // Should not happen if embedderProvider is always set correctly + } + + /** + * Determines if a configuration change requires restarting the indexing process. + * @param prev The previous configuration snapshot + * @returns boolean indicating whether a restart is needed + */ + private _didConfigChangeRequireRestart(prev: PreviousConfigSnapshot): boolean { + const nowConfigured = this.isConfigured() // Recalculate based on current state + + // Check for transition from disabled/unconfigured to enabled+configured + const transitionedToReady = (!prev.enabled || !prev.configured) && this.isEnabled && nowConfigured + if (transitionedToReady) return true + + // If wasn't ready before and isn't ready now, no restart needed for config change itself + if (!prev.configured && !nowConfigured) return false + // If was disabled and still is, no restart needed + if (!prev.enabled && !this.isEnabled) return false + + // Check for changes in relevant settings if the feature is enabled (or was enabled) + if (this.isEnabled || prev.enabled) { + // Check for embedder type change + if (prev.embedderProvider !== this.embedderProvider) return true + if (prev.modelId !== this.modelId) return true // Any model change requires restart + + // Check OpenAI settings change if using OpenAI + if (this.embedderProvider === "openai") { + if (prev.openAiKey !== this.openAiOptions?.openAiNativeApiKey) return true + // Model ID check moved above + } + + // Check Ollama settings change if using Ollama + if (this.embedderProvider === "ollama") { + if (prev.ollamaBaseUrl !== this.ollamaOptions?.ollamaBaseUrl) { + return true + } + // Model ID check moved above + } + + // Check Qdrant settings changes + if (prev.qdrantUrl !== this.qdrantUrl || prev.qdrantApiKey !== this.qdrantApiKey) { + return true + } + } + + return false + } + + /** + * Gets the current configuration state. + */ + public getConfig(): CodeIndexConfig { + return { + isEnabled: this.isEnabled, + isConfigured: this.isConfigured(), + embedderProvider: this.embedderProvider, + modelId: this.modelId, + openAiOptions: this.openAiOptions, + ollamaOptions: this.ollamaOptions, + qdrantUrl: this.qdrantUrl, + qdrantApiKey: this.qdrantApiKey, + searchMinScore: this.searchMinScore, + } + } + + /** + * Gets whether the code indexing feature is enabled + */ + public get isFeatureEnabled(): boolean { + return this.isEnabled + } + + /** + * Gets whether the code indexing feature is properly configured + */ + public get isFeatureConfigured(): boolean { + return this.isConfigured() + } + + /** + * Gets the current embedder type (openai or ollama) + */ + public get currentEmbedderProvider(): EmbedderProvider { + return this.embedderProvider + } + + /** + * Gets the current Qdrant configuration + */ + public get qdrantConfig(): { url?: string; apiKey?: string } { + return { + url: this.qdrantUrl, + apiKey: this.qdrantApiKey, + } + } + + /** + * Gets the current model ID being used for embeddings. + */ + public get currentModelId(): string | undefined { + return this.modelId + } + + /** + * Gets the configured minimum search score. + */ + public get currentSearchMinScore(): number | undefined { + return this.searchMinScore + } +} diff --git a/src/services/code-index/constants/index.ts b/src/services/code-index/constants/index.ts new file mode 100644 index 0000000000..cbf6941817 --- /dev/null +++ b/src/services/code-index/constants/index.ts @@ -0,0 +1,25 @@ +/**Parser */ +export const MAX_BLOCK_CHARS = 1000 +export const MIN_BLOCK_CHARS = 100 +export const MIN_CHUNK_REMAINDER_CHARS = 200 // Minimum characters for the *next* chunk after a split +export const MAX_CHARS_TOLERANCE_FACTOR = 1.15 // 15% tolerance for max chars + +/**Search */ +export const SEARCH_MIN_SCORE = 0.4 +export const MAX_SEARCH_RESULTS = 50 // Maximum number of search results to return + +/**File Watcher */ +export const QDRANT_CODE_BLOCK_NAMESPACE = "f47ac10b-58cc-4372-a567-0e02b2c3d479" +export const MAX_FILE_SIZE_BYTES = 1 * 1024 * 1024 // 1MB + +/**Directory Scanner */ +export const MAX_LIST_FILES_LIMIT = 3_000 +export const BATCH_SEGMENT_THRESHOLD = 60 // Number of code segments to batch for embeddings/upserts +export const MAX_BATCH_RETRIES = 3 +export const INITIAL_RETRY_DELAY_MS = 500 +export const PARSING_CONCURRENCY = 10 + +/**OpenAI Embedder */ +export const MAX_BATCH_TOKENS = 100000 +export const MAX_ITEM_TOKENS = 8191 +export const BATCH_PROCESSING_CONCURRENCY = 10 diff --git a/src/services/code-index/embedders/ollama.ts b/src/services/code-index/embedders/ollama.ts new file mode 100644 index 0000000000..8601f0c606 --- /dev/null +++ b/src/services/code-index/embedders/ollama.ts @@ -0,0 +1,79 @@ +import { ApiHandlerOptions } from "../../../shared/api" +import { EmbedderInfo, EmbeddingResponse, IEmbedder } from "../interfaces" + +/** + * Implements the IEmbedder interface using a local Ollama instance. + */ +export class CodeIndexOllamaEmbedder implements IEmbedder { + private readonly baseUrl: string + private readonly defaultModelId: string + + constructor(options: ApiHandlerOptions) { + // Ensure ollamaBaseUrl and ollamaModelId exist on ApiHandlerOptions or add defaults + this.baseUrl = options.ollamaBaseUrl || "http://localhost:11434" + this.defaultModelId = options.ollamaModelId || "nomic-embed-text:latest" + } + + /** + * Creates embeddings for the given texts using the specified Ollama model. + * @param texts - An array of strings to embed. + * @param model - Optional model ID to override the default. + * @returns A promise that resolves to an EmbeddingResponse containing the embeddings and usage data. + */ + async createEmbeddings(texts: string[], model?: string): Promise { + const modelToUse = model || this.defaultModelId + const url = `${this.baseUrl}/api/embed` // Endpoint as specified + + try { + // Note: Standard Ollama API uses 'prompt' for single text, not 'input' for array. + // Implementing based on user's specific request structure. + const response = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + model: modelToUse, + input: texts, // Using 'input' as requested + }), + }) + + if (!response.ok) { + let errorBody = "Could not read error body" + try { + errorBody = await response.text() + } catch (e) { + // Ignore error reading body + } + throw new Error( + `Ollama API request failed with status ${response.status} ${response.statusText}: ${errorBody}`, + ) + } + + const data = await response.json() + + // Extract embeddings using 'embeddings' key as requested + const embeddings = data.embeddings + if (!embeddings || !Array.isArray(embeddings)) { + throw new Error( + 'Invalid response structure from Ollama API: "embeddings" array not found or not an array.', + ) + } + + return { + embeddings: embeddings, + } + } catch (error: any) { + // Log the original error for debugging purposes + console.error("Ollama embedding failed:", error) + // Re-throw a more specific error for the caller + throw new Error(`Ollama embedding failed: ${error.message}`) + } + } + + get embedderInfo(): EmbedderInfo { + return { + name: "ollama", + } + } +} diff --git a/src/services/code-index/embedders/openai.ts b/src/services/code-index/embedders/openai.ts new file mode 100644 index 0000000000..907c9e1283 --- /dev/null +++ b/src/services/code-index/embedders/openai.ts @@ -0,0 +1,135 @@ +import { OpenAI } from "openai" +import { OpenAiNativeHandler } from "../../../api/providers/openai-native" +import { ApiHandlerOptions } from "../../../shared/api" +import { IEmbedder, EmbeddingResponse, EmbedderInfo } from "../interfaces" +import { + MAX_BATCH_TOKENS, + MAX_ITEM_TOKENS, + MAX_BATCH_RETRIES as MAX_RETRIES, + INITIAL_RETRY_DELAY_MS as INITIAL_DELAY_MS, +} from "../constants" + +/** + * OpenAI implementation of the embedder interface with batching and rate limiting + */ +export class OpenAiEmbedder extends OpenAiNativeHandler implements IEmbedder { + private embeddingsClient: OpenAI + private readonly defaultModelId: string + + /** + * Creates a new OpenAI embedder + * @param options API handler options + */ + constructor(options: ApiHandlerOptions & { openAiEmbeddingModelId?: string }) { + super(options) + const apiKey = this.options.openAiNativeApiKey ?? "not-provided" + this.embeddingsClient = new OpenAI({ apiKey }) + this.defaultModelId = options.openAiEmbeddingModelId || "text-embedding-3-small" + } + + /** + * Creates embeddings for the given texts with batching and rate limiting + * @param texts Array of text strings to embed + * @param model Optional model identifier + * @returns Promise resolving to embedding response + */ + async createEmbeddings(texts: string[], model?: string): Promise { + const modelToUse = model || this.defaultModelId + const allEmbeddings: number[][] = [] + const usage = { promptTokens: 0, totalTokens: 0 } + const remainingTexts = [...texts] + + while (remainingTexts.length > 0) { + const currentBatch: string[] = [] + let currentBatchTokens = 0 + const processedIndices: number[] = [] + + for (let i = 0; i < remainingTexts.length; i++) { + const text = remainingTexts[i] + const itemTokens = Math.ceil(text.length / 4) + + if (itemTokens > MAX_ITEM_TOKENS) { + console.warn( + `Text at index ${i} exceeds maximum token limit (${itemTokens} > ${MAX_ITEM_TOKENS}). Skipping.`, + ) + processedIndices.push(i) + continue + } + + if (currentBatchTokens + itemTokens <= MAX_BATCH_TOKENS) { + currentBatch.push(text) + currentBatchTokens += itemTokens + processedIndices.push(i) + } else { + break + } + } + + // Remove processed items from remainingTexts (in reverse order to maintain correct indices) + for (let i = processedIndices.length - 1; i >= 0; i--) { + remainingTexts.splice(processedIndices[i], 1) + } + + if (currentBatch.length > 0) { + try { + const batchResult = await this._embedBatchWithRetries(currentBatch, modelToUse) + allEmbeddings.push(...batchResult.embeddings) + usage.promptTokens += batchResult.usage.promptTokens + usage.totalTokens += batchResult.usage.totalTokens + } catch (error) { + console.error("Failed to process batch:", error) + throw new Error("Failed to create embeddings: batch processing error") + } + } + } + + return { embeddings: allEmbeddings, usage } + } + + /** + * Helper method to handle batch embedding with retries and exponential backoff + * @param batchTexts Array of texts to embed in this batch + * @param model Model identifier to use + * @returns Promise resolving to embeddings and usage statistics + */ + private async _embedBatchWithRetries( + batchTexts: string[], + model: string, + ): Promise<{ embeddings: number[][]; usage: { promptTokens: number; totalTokens: number } }> { + for (let attempts = 0; attempts < MAX_RETRIES; attempts++) { + try { + const response = await this.embeddingsClient.embeddings.create({ + input: batchTexts, + model: model, + }) + + return { + embeddings: response.data.map((item) => item.embedding), + usage: { + promptTokens: response.usage?.prompt_tokens || 0, + totalTokens: response.usage?.total_tokens || 0, + }, + } + } catch (error: any) { + const isRateLimitError = error?.status === 429 + const hasMoreAttempts = attempts < MAX_RETRIES - 1 + + if (isRateLimitError && hasMoreAttempts) { + const delayMs = INITIAL_DELAY_MS * Math.pow(2, attempts) + await new Promise((resolve) => setTimeout(resolve, delayMs)) + continue + } + + throw error + } + } + + throw new Error(`Failed to create embeddings after ${MAX_RETRIES} attempts`) + } + + get embedderInfo(): EmbedderInfo { + return { + name: "openai", + } + } +} diff --git a/src/services/code-index/interfaces/cache.ts b/src/services/code-index/interfaces/cache.ts new file mode 100644 index 0000000000..a2e62bcac1 --- /dev/null +++ b/src/services/code-index/interfaces/cache.ts @@ -0,0 +1,6 @@ +export interface ICacheManager { + getHash(filePath: string): string | undefined + updateHash(filePath: string, hash: string): void + deleteHash(filePath: string): void + getAllHashes(): Record +} diff --git a/src/services/code-index/interfaces/config.ts b/src/services/code-index/interfaces/config.ts new file mode 100644 index 0000000000..2d07911783 --- /dev/null +++ b/src/services/code-index/interfaces/config.ts @@ -0,0 +1,31 @@ +import { ApiHandlerOptions } from "../../../shared/api" // Adjust path if needed +import { EmbedderProvider } from "./manager" + +/** + * Configuration state for the code indexing feature + */ +export interface CodeIndexConfig { + isEnabled: boolean + isConfigured: boolean + embedderProvider: EmbedderProvider + modelId?: string + openAiOptions?: ApiHandlerOptions + ollamaOptions?: ApiHandlerOptions + qdrantUrl?: string + qdrantApiKey?: string + searchMinScore?: number +} + +/** + * Snapshot of previous configuration used to determine if a restart is required + */ +export type PreviousConfigSnapshot = { + enabled: boolean + configured: boolean + embedderProvider: EmbedderProvider + modelId?: string + openAiKey?: string + ollamaBaseUrl?: string + qdrantUrl?: string + qdrantApiKey?: string +} diff --git a/src/services/code-index/interfaces/embedder.ts b/src/services/code-index/interfaces/embedder.ts new file mode 100644 index 0000000000..de43255a65 --- /dev/null +++ b/src/services/code-index/interfaces/embedder.ts @@ -0,0 +1,28 @@ +/** + * Interface for code index embedders. + * This interface is implemented by both OpenAI and Ollama embedders. + */ +export interface IEmbedder { + /** + * Creates embeddings for the given texts. + * @param texts Array of text strings to create embeddings for + * @param model Optional model ID to use for embeddings + * @returns Promise resolving to an EmbeddingResponse + */ + createEmbeddings(texts: string[], model?: string): Promise + get embedderInfo(): EmbedderInfo +} + +export interface EmbeddingResponse { + embeddings: number[][] + usage?: { + promptTokens: number + totalTokens: number + } +} + +export type AvailableEmbedders = "openai" | "ollama" + +export interface EmbedderInfo { + name: AvailableEmbedders +} diff --git a/src/services/code-index/interfaces/file-processor.ts b/src/services/code-index/interfaces/file-processor.ts new file mode 100644 index 0000000000..f00c19c619 --- /dev/null +++ b/src/services/code-index/interfaces/file-processor.ts @@ -0,0 +1,117 @@ +import * as vscode from "vscode" +import { PointStruct } from "./vector-store" + +/** + * Interface for code file parser + */ +export interface ICodeParser { + /** + * Parses a code file into code blocks + * @param filePath Path to the file to parse + * @param options Optional parsing options + * @returns Promise resolving to array of code blocks + */ + parseFile( + filePath: string, + options?: { + minBlockLines?: number + maxBlockLines?: number + content?: string + fileHash?: string + }, + ): Promise +} + +/** + * Interface for directory scanner + */ +export interface IDirectoryScanner { + /** + * Scans a directory for code blocks + * @param directoryPath Path to the directory to scan + * @param options Optional scanning options + * @returns Promise resolving to scan results + */ + scanDirectory( + directory: string, + onError?: (error: Error) => void, + onBlocksIndexed?: (indexedCount: number) => void, + onFileParsed?: (fileBlockCount: number) => void, + ): Promise<{ + codeBlocks: CodeBlock[] + stats: { + processed: number + skipped: number + } + totalBlockCount: number + }> +} + +/** + * Interface for file watcher + */ +export interface IFileWatcher extends vscode.Disposable { + /** + * Initializes the file watcher + */ + initialize(): Promise + + /** + * Event emitted when a batch of files begins processing. + * The event payload is an array of file paths included in the batch. + */ + readonly onDidStartBatchProcessing: vscode.Event + + /** + * Event emitted to report progress during batch processing. + */ + readonly onBatchProgressUpdate: vscode.Event<{ + processedInBatch: number + totalInBatch: number + currentFile?: string + }> + + /** + * Event emitted when a batch of files has finished processing. + * The event payload contains a summary of the batch operation. + */ + readonly onDidFinishBatchProcessing: vscode.Event + + /** + * Processes a file + * @param filePath Path to the file to process + * @returns Promise resolving to processing result + */ + processFile(filePath: string): Promise +} + +export interface BatchProcessingSummary { + /** All files attempted in the batch, including their final status. */ + processedFiles: FileProcessingResult[] + /** Optional error if the entire batch operation failed (e.g., database connection issue). */ + batchError?: Error +} + +export interface FileProcessingResult { + path: string + status: "success" | "skipped" | "error" | "processed_for_batching" | "local_error" + error?: Error + reason?: string + newHash?: string + pointsToUpsert?: PointStruct[] +} + +/** + * Common types used across the code-index service + */ + +export interface CodeBlock { + file_path: string + identifier: string | null + type: string + start_line: number + end_line: number + content: string + fileHash: string + segmentHash: string +} diff --git a/src/services/code-index/interfaces/index.ts b/src/services/code-index/interfaces/index.ts new file mode 100644 index 0000000000..20dd55ad89 --- /dev/null +++ b/src/services/code-index/interfaces/index.ts @@ -0,0 +1,4 @@ +export * from "./embedder" +export * from "./vector-store" +export * from "./file-processor" +export * from "./manager" diff --git a/src/services/code-index/interfaces/manager.ts b/src/services/code-index/interfaces/manager.ts new file mode 100644 index 0000000000..1d3ce92c95 --- /dev/null +++ b/src/services/code-index/interfaces/manager.ts @@ -0,0 +1,80 @@ +import { VectorStoreSearchResult } from "./vector-store" +import * as vscode from "vscode" + +/** + * Interface for the code index manager + */ +export interface ICodeIndexManager { + /** + * Event emitted when progress is updated + */ + onProgressUpdate: vscode.Event<{ + systemStatus: IndexingState + fileStatuses: Record + message?: string + }> + + /** + * Current state of the indexing process + */ + readonly state: IndexingState + + /** + * Whether the code indexing feature is enabled + */ + readonly isFeatureEnabled: boolean + + /** + * Whether the code indexing feature is configured + */ + readonly isFeatureConfigured: boolean + + /** + * Loads configuration from storage + */ + loadConfiguration(): Promise + + /** + * Starts the indexing process + */ + startIndexing(): Promise + + /** + * Stops the file watcher + */ + stopWatcher(): void + + /** + * Clears the index data + */ + clearIndexData(): Promise + + /** + * Searches the index + * @param query Query string + * @param limit Maximum number of results to return + * @returns Promise resolving to search results + */ + searchIndex(query: string, limit: number): Promise + + /** + * Gets the current status of the indexing system + * @returns Current status information + */ + getCurrentStatus(): { systemStatus: IndexingState; fileStatuses: Record; message?: string } + + /** + * Disposes of resources used by the manager + */ + dispose(): void +} + +export type IndexingState = "Standby" | "Indexing" | "Indexed" | "Error" +export type EmbedderProvider = "openai" | "ollama" + +export interface IndexProgressUpdate { + systemStatus: IndexingState + message?: string + processedBlockCount?: number + totalBlockCount?: number +} diff --git a/src/services/code-index/interfaces/vector-store.ts b/src/services/code-index/interfaces/vector-store.ts new file mode 100644 index 0000000000..1896942a01 --- /dev/null +++ b/src/services/code-index/interfaces/vector-store.ts @@ -0,0 +1,72 @@ +/** + * Interface for vector database clients + */ +export type PointStruct = { + id: string + vector: number[] + payload: Record +} + +export interface IVectorStore { + /** + * Initializes the vector store + * @returns Promise resolving to boolean indicating if a new collection was created + */ + initialize(): Promise + + /** + * Upserts points into the vector store + * @param points Array of points to upsert + */ + upsertPoints(points: PointStruct[]): Promise + + /** + * Searches for similar vectors + * @param queryVector Vector to search for + * @param limit Maximum number of results to return + * @returns Promise resolving to search results + */ + search(queryVector: number[], directoryPrefix?: string, minScore?: number): Promise + + /** + * Deletes points by file path + * @param filePath Path of the file to delete points for + */ + deletePointsByFilePath(filePath: string): Promise + + /** + * Deletes points by multiple file paths + * @param filePaths Array of file paths to delete points for + */ + deletePointsByMultipleFilePaths(filePaths: string[]): Promise + + /** + * Clears all points from the collection + */ + clearCollection(): Promise + + /** + * Deletes the entire collection. + */ + deleteCollection(): Promise + + /** + * Checks if the collection exists + * @returns Promise resolving to boolean indicating if the collection exists + */ + collectionExists(): Promise +} + +export interface VectorStoreSearchResult { + id: string | number + score: number + payload?: Payload | null +} + +export interface Payload { + filePath: string + codeChunk: string + startLine: number + endLine: number + [key: string]: any +} diff --git a/src/services/code-index/manager.ts b/src/services/code-index/manager.ts new file mode 100644 index 0000000000..6af05f1e18 --- /dev/null +++ b/src/services/code-index/manager.ts @@ -0,0 +1,256 @@ +import * as vscode from "vscode" +import { getWorkspacePath } from "../../utils/path" +import { ContextProxy } from "../../core/config/ContextProxy" +import { VectorStoreSearchResult } from "./interfaces" +import { IndexingState } from "./interfaces/manager" +import { CodeIndexConfigManager } from "./config-manager" +import { CodeIndexStateManager } from "./state-manager" +import { CodeIndexServiceFactory } from "./service-factory" +import { CodeIndexSearchService } from "./search-service" +import { CodeIndexOrchestrator } from "./orchestrator" +import { CacheManager } from "./cache-manager" +import fs from "fs/promises" +import ignore from "ignore" +import path from "path" + +export class CodeIndexManager { + // --- Singleton Implementation --- + private static instances = new Map() // Map workspace path to instance + + // Specialized class instances + private _configManager: CodeIndexConfigManager | undefined + private readonly _stateManager: CodeIndexStateManager + private _serviceFactory: CodeIndexServiceFactory | undefined + private _orchestrator: CodeIndexOrchestrator | undefined + private _searchService: CodeIndexSearchService | undefined + private _cacheManager: CacheManager | undefined + + public static getInstance(context: vscode.ExtensionContext): CodeIndexManager | undefined { + const workspacePath = getWorkspacePath() // Assumes single workspace for now + + if (!workspacePath) { + return undefined + } + + if (!CodeIndexManager.instances.has(workspacePath)) { + CodeIndexManager.instances.set(workspacePath, new CodeIndexManager(workspacePath, context)) + } + return CodeIndexManager.instances.get(workspacePath)! + } + + public static disposeAll(): void { + for (const instance of CodeIndexManager.instances.values()) { + instance.dispose() + } + CodeIndexManager.instances.clear() + } + + private readonly workspacePath: string + private readonly context: vscode.ExtensionContext + + // Private constructor for singleton pattern + private constructor(workspacePath: string, context: vscode.ExtensionContext) { + this.workspacePath = workspacePath + this.context = context + this._stateManager = new CodeIndexStateManager() + } + + // --- Public API --- + + public get onProgressUpdate() { + return this._stateManager.onProgressUpdate + } + + private assertInitialized() { + if (!this._configManager || !this._orchestrator || !this._searchService || !this._cacheManager) { + throw new Error("CodeIndexManager not initialized. Call initialize() first.") + } + } + + public get state(): IndexingState { + if (!this.isFeatureEnabled) { + return "Standby" + } + this.assertInitialized() + return this._orchestrator!.state + } + + public get isFeatureEnabled(): boolean { + return this._configManager?.isFeatureEnabled ?? false + } + + public get isFeatureConfigured(): boolean { + return this._configManager?.isFeatureConfigured ?? false + } + + public get isInitialized(): boolean { + try { + this.assertInitialized() + return true + } catch (error) { + return false + } + } + + /** + * Initializes the manager with configuration and dependent services. + * Must be called before using any other methods. + * @returns Object indicating if a restart is needed + */ + public async initialize(contextProxy: ContextProxy): Promise<{ requiresRestart: boolean }> { + // 1. ConfigManager Initialization and Configuration Loading + this._configManager = new CodeIndexConfigManager(contextProxy) + const { requiresRestart, requiresClear } = await this._configManager.loadConfiguration() + + // 2. Check if feature is enabled + if (!this.isFeatureEnabled) { + if (this._orchestrator) { + this._orchestrator.stopWatcher() + } + return { requiresRestart } + } + + // 3. CacheManager Initialization + if (!this._cacheManager) { + this._cacheManager = new CacheManager(this.context, this.workspacePath) + await this._cacheManager.initialize() + } + + // 4. Determine if Core Services Need Recreation + const needsServiceRecreation = !this._serviceFactory || requiresRestart + + if (needsServiceRecreation) { + // Stop watcher if it exists + if (this._orchestrator) { + this.stopWatcher() + } + + // (Re)Initialize service factory + this._serviceFactory = new CodeIndexServiceFactory( + this._configManager, + this.workspacePath, + this._cacheManager, + ) + + const ignoreInstance = ignore() + const ignorePath = path.join(getWorkspacePath(), ".gitignore") + try { + const content = await fs.readFile(ignorePath, "utf8") + ignoreInstance.add(content) + ignoreInstance.add(".gitignore") + } catch (error) { + // Should never happen: reading file failed even though it exists + console.error("Unexpected error loading .gitignore:", error) + } + + // (Re)Create shared service instances + const { embedder, vectorStore, scanner, fileWatcher } = this._serviceFactory.createServices( + this.context, + this._cacheManager, + ignoreInstance, + ) + + // (Re)Initialize orchestrator + this._orchestrator = new CodeIndexOrchestrator( + this._configManager, + this._stateManager, + this.workspacePath, + this._cacheManager, + vectorStore, + scanner, + fileWatcher, + ) + + // (Re)Initialize search service + this._searchService = new CodeIndexSearchService( + this._configManager, + this._stateManager, + embedder, + vectorStore, + ) + } + + // 5. Handle Data Clearing + if (requiresClear) { + if (this._orchestrator) { + await this._orchestrator.clearIndexData() + } + if (this._cacheManager) { + await this._cacheManager.clearCacheFile() + } + } + + // Handle Indexing Start/Restart + const shouldStartOrRestartIndexing = + requiresRestart || + requiresClear || + (needsServiceRecreation && (!this._orchestrator || this._orchestrator.state !== "Indexing")) + + if (shouldStartOrRestartIndexing) { + this._orchestrator?.startIndexing() // This method is async, but we don't await it here + } + + return { requiresRestart } + } + + /** + * Initiates the indexing process (initial scan and starts watcher). + */ + + public async startIndexing(): Promise { + if (!this.isFeatureEnabled) { + return + } + this.assertInitialized() + await this._orchestrator!.startIndexing() + } + + /** + * Stops the file watcher and potentially cleans up resources. + */ + public stopWatcher(): void { + if (!this.isFeatureEnabled) { + return + } + if (this._orchestrator) { + this._orchestrator.stopWatcher() + } + } + + /** + * Cleans up the manager instance. + */ + public dispose(): void { + if (this._orchestrator) { + this.stopWatcher() + } + this._stateManager.dispose() + } + + /** + * Clears all index data by stopping the watcher, clearing the Qdrant collection, + * and deleting the cache file. + */ + public async clearIndexData(): Promise { + if (!this.isFeatureEnabled) { + return + } + this.assertInitialized() + await this._orchestrator!.clearIndexData() + await this._cacheManager!.clearCacheFile() + } + + // --- Private Helpers --- + + public getCurrentStatus() { + return this._stateManager.getCurrentStatus() + } + + public async searchIndex(query: string, directoryPrefix?: string): Promise { + if (!this.isFeatureEnabled) { + return [] + } + this.assertInitialized() + return this._searchService!.searchIndex(query, directoryPrefix) + } +} diff --git a/src/services/code-index/orchestrator.ts b/src/services/code-index/orchestrator.ts new file mode 100644 index 0000000000..5784f0dcfc --- /dev/null +++ b/src/services/code-index/orchestrator.ts @@ -0,0 +1,222 @@ +import * as vscode from "vscode" +import * as path from "path" +import { CodeIndexConfigManager } from "./config-manager" +import { CodeIndexStateManager, IndexingState } from "./state-manager" +import { IFileWatcher, IVectorStore, BatchProcessingSummary } from "./interfaces" +import { DirectoryScanner } from "./processors" +import { CacheManager } from "./cache-manager" + +/** + * Manages the code indexing workflow, coordinating between different services and managers. + */ +export class CodeIndexOrchestrator { + private _fileWatcherSubscriptions: vscode.Disposable[] = [] + private _isProcessing: boolean = false + + constructor( + private readonly configManager: CodeIndexConfigManager, + private readonly stateManager: CodeIndexStateManager, + private readonly workspacePath: string, + private readonly cacheManager: CacheManager, + private readonly vectorStore: IVectorStore, + private readonly scanner: DirectoryScanner, + private readonly fileWatcher: IFileWatcher, + ) {} + + /** + * Starts the file watcher if not already running. + */ + private async _startWatcher(): Promise { + if (!this.configManager.isFeatureConfigured) { + throw new Error("Cannot start watcher: Service not configured.") + } + + this.stateManager.setSystemState("Indexing", "Initializing file watcher...") + + try { + await this.fileWatcher.initialize() + + this._fileWatcherSubscriptions = [ + this.fileWatcher.onDidStartBatchProcessing((filePaths: string[]) => {}), + this.fileWatcher.onBatchProgressUpdate(({ processedInBatch, totalInBatch, currentFile }) => { + if (totalInBatch > 0 && this.stateManager.state !== "Indexing") { + this.stateManager.setSystemState("Indexing", "Processing file changes...") + } + this.stateManager.reportFileQueueProgress( + processedInBatch, + totalInBatch, + currentFile ? path.basename(currentFile) : undefined, + ) + if (processedInBatch === totalInBatch) { + // Covers (N/N) and (0/0) + if (totalInBatch > 0) { + // Batch with items completed + this.stateManager.setSystemState("Indexed", "File changes processed. Index up-to-date.") + } else { + if (this.stateManager.state === "Indexing") { + // Only transition if it was "Indexing" + this.stateManager.setSystemState("Indexed", "Index up-to-date. File queue empty.") + } + } + } + }), + this.fileWatcher.onDidFinishBatchProcessing((summary: BatchProcessingSummary) => { + if (summary.batchError) { + console.error(`[CodeIndexOrchestrator] Batch processing failed:`, summary.batchError) + } else { + const successCount = summary.processedFiles.filter( + (f: { status: string }) => f.status === "success", + ).length + const errorCount = summary.processedFiles.filter( + (f: { status: string }) => f.status === "error" || f.status === "local_error", + ).length + } + }), + ] + } catch (error) { + console.error("[CodeIndexOrchestrator] Failed to start file watcher:", error) + throw error + } + } + + /** + * Updates the status of a file in the state manager. + */ + + /** + * Initiates the indexing process (initial scan and starts watcher). + */ + public async startIndexing(): Promise { + if (!this.configManager.isFeatureConfigured) { + this.stateManager.setSystemState("Standby", "Missing configuration. Save your settings to start indexing.") + console.warn("[CodeIndexOrchestrator] Start rejected: Missing configuration.") + return + } + + if ( + this._isProcessing || + (this.stateManager.state !== "Standby" && + this.stateManager.state !== "Error" && + this.stateManager.state !== "Indexed") + ) { + console.warn( + `[CodeIndexOrchestrator] Start rejected: Already processing or in state ${this.stateManager.state}.`, + ) + return + } + + this._isProcessing = true + this.stateManager.setSystemState("Indexing", "Initializing services...") + + try { + const collectionCreated = await this.vectorStore.initialize() + + if (collectionCreated) { + await this.cacheManager.clearCacheFile() + } + + this.stateManager.setSystemState("Indexing", "Services ready. Starting workspace scan...") + + let cumulativeBlocksIndexed = 0 + let cumulativeBlocksFoundSoFar = 0 + + const handleFileParsed = (fileBlockCount: number) => { + cumulativeBlocksFoundSoFar += fileBlockCount + this.stateManager.reportBlockIndexingProgress(cumulativeBlocksIndexed, cumulativeBlocksFoundSoFar) + } + + const handleBlocksIndexed = (indexedCount: number) => { + cumulativeBlocksIndexed += indexedCount + this.stateManager.reportBlockIndexingProgress(cumulativeBlocksIndexed, cumulativeBlocksFoundSoFar) + } + + const result = await this.scanner.scanDirectory( + this.workspacePath, + (batchError: Error) => { + console.error( + `[CodeIndexOrchestrator] Error during initial scan batch: ${batchError.message}`, + batchError, + ) + }, + handleBlocksIndexed, + handleFileParsed, + ) + + if (!result) { + throw new Error("Scan failed, is scanner initialized?") + } + + const { stats } = result + + await this._startWatcher() + + this.stateManager.setSystemState("Indexed", "File watcher started.") + } catch (error: any) { + console.error("[CodeIndexOrchestrator] Error during indexing:", error) + try { + await this.vectorStore.clearCollection() + } catch (cleanupError) { + console.error("[CodeIndexOrchestrator] Failed to clean up after error:", cleanupError) + } + + await this.cacheManager.clearCacheFile() + + this.stateManager.setSystemState("Error", `Failed during initial scan: ${error.message || "Unknown error"}`) + this.stopWatcher() + } finally { + this._isProcessing = false + } + } + + /** + * Stops the file watcher and cleans up resources. + */ + public stopWatcher(): void { + this.fileWatcher.dispose() + this._fileWatcherSubscriptions.forEach((sub) => sub.dispose()) + this._fileWatcherSubscriptions = [] + + if (this.stateManager.state !== "Error") { + this.stateManager.setSystemState("Standby", "File watcher stopped.") + } + this._isProcessing = false + } + + /** + * Clears all index data by stopping the watcher, clearing the vector store, + * and resetting the cache file. + */ + public async clearIndexData(): Promise { + this._isProcessing = true + + try { + await this.stopWatcher() + + try { + if (this.configManager.isFeatureConfigured) { + await this.vectorStore.deleteCollection() + } else { + console.warn("[CodeIndexOrchestrator] Service not configured, skipping vector collection clear.") + } + } catch (error: any) { + console.error("[CodeIndexOrchestrator] Failed to clear vector collection:", error) + this.stateManager.setSystemState("Error", `Failed to clear vector collection: ${error.message}`) + } + + await this.cacheManager.clearCacheFile() + + if (this.stateManager.state !== "Error") { + this.stateManager.setSystemState("Standby", "Index data cleared successfully.") + } + } finally { + this._isProcessing = false + } + } + + /** + * Gets the current state of the indexing system. + */ + public get state(): IndexingState { + return this.stateManager.state + } +} diff --git a/src/services/code-index/processors/__tests__/file-watcher.test.ts b/src/services/code-index/processors/__tests__/file-watcher.test.ts new file mode 100644 index 0000000000..21487a9a29 --- /dev/null +++ b/src/services/code-index/processors/__tests__/file-watcher.test.ts @@ -0,0 +1,908 @@ +import { IEmbedder } from "../../interfaces/embedder" +import { IVectorStore } from "../../interfaces/vector-store" +import { FileProcessingResult } from "../../interfaces/file-processor" +import { FileWatcher } from "../file-watcher" + +import { createHash } from "crypto" + +jest.mock("vscode", () => { + type Disposable = { dispose: () => void } + + type _Event = (listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]) => Disposable + + const MOCK_EMITTER_REGISTRY = new Map any>>() + + return { + EventEmitter: jest.fn().mockImplementation(() => { + const emitterInstanceKey = {} + MOCK_EMITTER_REGISTRY.set(emitterInstanceKey, new Set()) + + return { + event: function (listener: (e: T) => any): Disposable { + const listeners = MOCK_EMITTER_REGISTRY.get(emitterInstanceKey) + listeners!.add(listener as any) + return { + dispose: () => { + listeners!.delete(listener as any) + }, + } + }, + + fire: function (data: T): void { + const listeners = MOCK_EMITTER_REGISTRY.get(emitterInstanceKey) + listeners!.forEach((fn) => fn(data)) + }, + + dispose: () => { + MOCK_EMITTER_REGISTRY.get(emitterInstanceKey)!.clear() + MOCK_EMITTER_REGISTRY.delete(emitterInstanceKey) + }, + } + }), + RelativePattern: jest.fn().mockImplementation((base, pattern) => ({ + base, + pattern, + })), + Uri: { + file: jest.fn().mockImplementation((path) => ({ fsPath: path })), + }, + window: { + activeTextEditor: undefined, + }, + workspace: { + createFileSystemWatcher: jest.fn().mockReturnValue({ + onDidCreate: jest.fn(), + onDidChange: jest.fn(), + onDidDelete: jest.fn(), + dispose: jest.fn(), + }), + fs: { + stat: jest.fn(), + readFile: jest.fn(), + }, + workspaceFolders: [{ uri: { fsPath: "/mock/workspace" } }], + getWorkspaceFolder: jest.fn((uri) => { + if (uri && uri.fsPath && uri.fsPath.startsWith("/mock/workspace")) { + return { uri: { fsPath: "/mock/workspace" } } + } + return undefined + }), + }, + } +}) + +const vscode = require("vscode") +jest.mock("crypto") +jest.mock("uuid", () => ({ + ...jest.requireActual("uuid"), + v5: jest.fn().mockReturnValue("mocked-uuid-v5-for-testing"), +})) +jest.mock("../../../../core/ignore/RooIgnoreController", () => ({ + RooIgnoreController: jest.fn().mockImplementation(() => ({ + validateAccess: jest.fn(), + })), + mockValidateAccess: jest.fn(), +})) +jest.mock("../../cache-manager") +jest.mock("../parser", () => ({ codeParser: { parseFile: jest.fn() } })) + +describe("FileWatcher", () => { + let fileWatcher: FileWatcher + let mockEmbedder: IEmbedder + let mockVectorStore: IVectorStore + let mockCacheManager: any + let mockContext: any + let mockRooIgnoreController: any + + beforeEach(() => { + mockEmbedder = { + createEmbeddings: jest.fn().mockResolvedValue({ embeddings: [[0.1, 0.2, 0.3]] }), + embedderInfo: { name: "openai" }, + } + mockVectorStore = { + upsertPoints: jest.fn().mockResolvedValue(undefined), + deletePointsByFilePath: jest.fn().mockResolvedValue(undefined), + deletePointsByMultipleFilePaths: jest.fn().mockResolvedValue(undefined), + initialize: jest.fn().mockResolvedValue(true), + search: jest.fn().mockResolvedValue([]), + clearCollection: jest.fn().mockResolvedValue(undefined), + deleteCollection: jest.fn().mockResolvedValue(undefined), + collectionExists: jest.fn().mockResolvedValue(true), + } + mockCacheManager = { + getHash: jest.fn(), + updateHash: jest.fn(), + deleteHash: jest.fn(), + } + mockContext = { + subscriptions: [], + } + + const { RooIgnoreController, mockValidateAccess } = require("../../../../core/ignore/RooIgnoreController") + mockRooIgnoreController = new RooIgnoreController() + mockRooIgnoreController.validateAccess = mockValidateAccess.mockReturnValue(true) + + fileWatcher = new FileWatcher( + "/mock/workspace", + mockContext, + mockCacheManager, + mockEmbedder, + mockVectorStore, + undefined, + mockRooIgnoreController, + ) + }) + + describe("constructor", () => { + it("should initialize with correct properties", () => { + expect(fileWatcher).toBeDefined() + + mockContext.subscriptions.push({ dispose: jest.fn() }, { dispose: jest.fn() }) + expect(mockContext.subscriptions).toHaveLength(2) + }) + }) + + describe("initialize", () => { + it("should create file watcher with correct pattern", async () => { + await fileWatcher.initialize() + expect(vscode.workspace.createFileSystemWatcher).toHaveBeenCalled() + expect(vscode.workspace.createFileSystemWatcher.mock.calls[0][0].pattern).toMatch( + /\{tla,js,jsx,ts,vue,tsx,py,rs,go,c,h,cpp,hpp,cs,rb,java,php,swift,sol,kt,kts,ex,exs,el,html,htm,json,css,rdl,ml,mli,lua,scala,toml,zig,elm,ejs,erb\}/, + ) + }) + + it("should register event handlers", async () => { + await fileWatcher.initialize() + const watcher = vscode.workspace.createFileSystemWatcher.mock.results[0].value + expect(watcher.onDidCreate).toHaveBeenCalled() + expect(watcher.onDidChange).toHaveBeenCalled() + expect(watcher.onDidDelete).toHaveBeenCalled() + }) + }) + + describe("dispose", () => { + it("should dispose all resources", async () => { + await fileWatcher.initialize() + fileWatcher.dispose() + const watcher = vscode.workspace.createFileSystemWatcher.mock.results[0].value + expect(watcher.dispose).toHaveBeenCalled() + }) + }) + + describe("handleFileCreated", () => { + beforeEach(() => { + jest.useFakeTimers() + }) + + afterEach(() => { + jest.useRealTimers() + }) + + it("should call processFile with correct path", async () => { + const mockUri = { fsPath: "/mock/workspace/test.js" } + const processFileSpy = jest.spyOn(fileWatcher, "processFile").mockResolvedValue({ + path: mockUri.fsPath, + status: "processed_for_batching", + newHash: "mock-hash", + pointsToUpsert: [{ id: "mock-point-id", vector: [0.1], payload: { filePath: mockUri.fsPath } }], + reason: undefined, + error: undefined, + } as FileProcessingResult) + + // Setup a spy for the _onDidFinishBatchProcessing event + let batchProcessingFinished = false + const batchFinishedSpy = jest.fn(() => { + batchProcessingFinished = true + }) + fileWatcher.onDidFinishBatchProcessing(batchFinishedSpy) + + // Directly accumulate the event and trigger batch processing + ;(fileWatcher as any).accumulatedEvents.set(mockUri.fsPath, { uri: mockUri, type: "create" }) + ;(fileWatcher as any).scheduleBatchProcessing() + + // Advance timers to trigger debounced processing + await jest.advanceTimersByTimeAsync(1000) + await jest.runAllTicks() + + // Wait for batch processing to complete + while (!batchProcessingFinished) { + await jest.runAllTicks() + await new Promise((resolve) => setImmediate(resolve)) + } + + expect(processFileSpy).toHaveBeenCalledWith(mockUri.fsPath) + }) + }) + + describe("handleFileChanged", () => { + beforeEach(() => { + jest.useFakeTimers() + }) + + afterEach(() => { + jest.useRealTimers() + }) + + it("should call processFile with correct path", async () => { + const mockUri = { fsPath: "/mock/workspace/test.js" } + const processFileSpy = jest.spyOn(fileWatcher, "processFile").mockResolvedValue({ + path: mockUri.fsPath, + status: "processed_for_batching", + newHash: "mock-hash", + pointsToUpsert: [{ id: "mock-point-id", vector: [0.1], payload: { filePath: mockUri.fsPath } }], + reason: undefined, + error: undefined, + } as FileProcessingResult) + + // Setup a spy for the _onDidFinishBatchProcessing event + let batchProcessingFinished = false + const batchFinishedSpy = jest.fn(() => { + batchProcessingFinished = true + }) + fileWatcher.onDidFinishBatchProcessing(batchFinishedSpy) + + // Directly accumulate the event and trigger batch processing + ;(fileWatcher as any).accumulatedEvents.set(mockUri.fsPath, { uri: mockUri, type: "change" }) + ;(fileWatcher as any).scheduleBatchProcessing() + + // Advance timers to trigger debounced processing + await jest.advanceTimersByTimeAsync(1000) + await jest.runAllTicks() + + // Wait for batch processing to complete + while (!batchProcessingFinished) { + await jest.runAllTicks() + await new Promise((resolve) => setImmediate(resolve)) + } + + expect(processFileSpy).toHaveBeenCalledWith(mockUri.fsPath) + }) + }) + + describe("handleFileDeleted", () => { + beforeEach(() => { + jest.useFakeTimers() + }) + + afterEach(() => { + jest.useRealTimers() + }) + + it("should delete from cache and process deletion in batch", async () => { + const mockUri = { fsPath: "/mock/workspace/test.js" } + + // Setup a spy for the _onDidFinishBatchProcessing event + let batchProcessingFinished = false + const batchFinishedSpy = jest.fn(() => { + batchProcessingFinished = true + }) + fileWatcher.onDidFinishBatchProcessing(batchFinishedSpy) + + // Directly accumulate the event and trigger batch processing + ;(fileWatcher as any).accumulatedEvents.set(mockUri.fsPath, { uri: mockUri, type: "delete" }) + ;(fileWatcher as any).scheduleBatchProcessing() + + // Advance timers to trigger debounced processing + await jest.advanceTimersByTimeAsync(1000) + await jest.runAllTicks() + + // Wait for batch processing to complete + while (!batchProcessingFinished) { + await jest.runAllTicks() + await new Promise((resolve) => setImmediate(resolve)) + } + + expect(mockCacheManager.deleteHash).toHaveBeenCalledWith(mockUri.fsPath) + expect(mockVectorStore.deletePointsByMultipleFilePaths).toHaveBeenCalledWith( + expect.arrayContaining([mockUri.fsPath]), + ) + expect(mockVectorStore.deletePointsByMultipleFilePaths).toHaveBeenCalledTimes(1) + }) + + it("should handle errors during deletePointsByMultipleFilePaths", async () => { + // Setup mock error + const mockError = new Error("Failed to delete points from vector store") as Error + ;(mockVectorStore.deletePointsByMultipleFilePaths as jest.Mock).mockRejectedValueOnce(mockError) + + // Create a spy for the _onDidFinishBatchProcessing event + let capturedBatchSummary: any = null + let batchProcessingFinished = false + const batchFinishedSpy = jest.fn((summary) => { + capturedBatchSummary = summary + batchProcessingFinished = true + }) + fileWatcher.onDidFinishBatchProcessing(batchFinishedSpy) + + // Trigger delete event + const mockUri = { fsPath: "/mock/workspace/test-error.js" } + + // Directly accumulate the event and trigger batch processing + ;(fileWatcher as any).accumulatedEvents.set(mockUri.fsPath, { uri: mockUri, type: "delete" }) + ;(fileWatcher as any).scheduleBatchProcessing() + + // Advance timers to trigger debounced processing + await jest.advanceTimersByTimeAsync(1000) + await jest.runAllTicks() + + // Wait for batch processing to complete + while (!batchProcessingFinished) { + await jest.runAllTicks() + await new Promise((resolve) => setImmediate(resolve)) + } + + // Verify that deletePointsByMultipleFilePaths was called + expect(mockVectorStore.deletePointsByMultipleFilePaths).toHaveBeenCalledWith( + expect.arrayContaining([mockUri.fsPath]), + ) + + // Verify that cacheManager.deleteHash is not called when vectorStore.deletePointsByMultipleFilePaths fails + expect(mockCacheManager.deleteHash).not.toHaveBeenCalledWith(mockUri.fsPath) + }) + }) + + describe("processFile", () => { + it("should skip ignored files", async () => { + mockRooIgnoreController.validateAccess.mockImplementation((path: string) => { + if (path === "/mock/workspace/ignored.js") return false + return true + }) + const filePath = "/mock/workspace/ignored.js" + vscode.Uri.file.mockImplementation((path: string) => ({ fsPath: path })) + const result = await fileWatcher.processFile(filePath) + + expect(result.status).toBe("skipped") + expect(result.reason).toBe("File is ignored by .rooignore or .gitignore") + expect(mockCacheManager.updateHash).not.toHaveBeenCalled() + expect(vscode.workspace.fs.stat).not.toHaveBeenCalled() + expect(vscode.workspace.fs.readFile).not.toHaveBeenCalled() + }) + + it("should skip files larger than MAX_FILE_SIZE_BYTES", async () => { + vscode.workspace.fs.stat.mockResolvedValue({ size: 2 * 1024 * 1024 }) + vscode.workspace.fs.readFile.mockResolvedValue(Buffer.from("large file content")) + mockRooIgnoreController.validateAccess.mockReturnValue(true) + const result = await fileWatcher.processFile("/mock/workspace/large.js") + expect(vscode.Uri.file).toHaveBeenCalledWith("/mock/workspace/large.js") + + expect(result.status).toBe("skipped") + expect(result.reason).toBe("File is too large") + expect(mockCacheManager.updateHash).not.toHaveBeenCalled() + }) + + it("should skip unchanged files", async () => { + vscode.workspace.fs.stat.mockResolvedValue({ size: 1024, mtime: Date.now() }) + vscode.workspace.fs.readFile.mockResolvedValue(Buffer.from("test content")) + mockCacheManager.getHash.mockReturnValue("hash") + mockRooIgnoreController.validateAccess.mockReturnValue(true) + ;(createHash as jest.Mock).mockReturnValue({ + update: jest.fn().mockReturnThis(), + digest: jest.fn().mockReturnValue("hash"), + }) + + const result = await fileWatcher.processFile("/mock/workspace/unchanged.js") + + expect(result.status).toBe("skipped") + expect(result.reason).toBe("File has not changed") + expect(mockCacheManager.updateHash).not.toHaveBeenCalled() + }) + + it("should process changed files", async () => { + vscode.Uri.file.mockImplementation((path: string) => ({ fsPath: path })) + vscode.workspace.fs.stat.mockResolvedValue({ size: 1024, mtime: Date.now() }) + vscode.workspace.fs.readFile.mockResolvedValue(Buffer.from("test content")) + mockCacheManager.getHash.mockReturnValue("old-hash") + mockRooIgnoreController.validateAccess.mockReturnValue(true) + ;(createHash as jest.Mock).mockReturnValue({ + update: jest.fn().mockReturnThis(), + digest: jest.fn().mockReturnValue("new-hash"), + }) + + const { codeParser: mockCodeParser } = require("../parser") + mockCodeParser.parseFile.mockResolvedValue([ + { + file_path: "/mock/workspace/test.js", + content: "test content", + start_line: 1, + end_line: 5, + identifier: "test", + type: "function", + fileHash: "new-hash", + segmentHash: "segment-hash", + }, + ]) + + const result = await fileWatcher.processFile("/mock/workspace/test.js") + + expect(result.status).toBe("processed_for_batching") + expect(result.newHash).toBe("new-hash") + expect(result.pointsToUpsert).toEqual([ + expect.objectContaining({ + id: "mocked-uuid-v5-for-testing", + vector: [0.1, 0.2, 0.3], + payload: { + filePath: "test.js", + codeChunk: "test content", + startLine: 1, + endLine: 5, + }, + }), + ]) + expect(mockCodeParser.parseFile).toHaveBeenCalled() + expect(mockEmbedder.createEmbeddings).toHaveBeenCalled() + }) + + it("should handle processing errors", async () => { + vscode.workspace.fs.stat.mockResolvedValue({ size: 1024 }) + vscode.workspace.fs.readFile.mockRejectedValue(new Error("Read error")) + + const result = await fileWatcher.processFile("/mock/workspace/error.js") + + expect(result.status).toBe("local_error") + expect(result.error).toBeDefined() + }) + }) + + describe("Batch processing of rapid delete-then-create/change events", () => { + let onDidDeleteCallback: (uri: any) => void + let onDidCreateCallback: (uri: any) => void + let mockUri: { fsPath: string } + + beforeEach(() => { + jest.useFakeTimers() + + // Clear all relevant mocks + mockCacheManager.deleteHash.mockClear() + mockCacheManager.getHash.mockClear() + mockCacheManager.updateHash.mockClear() + ;(mockVectorStore.deletePointsByFilePath as jest.Mock).mockClear() + ;(mockVectorStore.upsertPoints as jest.Mock).mockClear() + ;(mockVectorStore.deletePointsByMultipleFilePaths as jest.Mock).mockClear() + + // Setup file watcher mocks + vscode.workspace.createFileSystemWatcher.mockReturnValue({ + onDidCreate: jest.fn((callback) => { + onDidCreateCallback = callback + return { dispose: jest.fn() } + }), + onDidChange: jest.fn().mockReturnValue({ dispose: jest.fn() }), + onDidDelete: jest.fn((callback) => { + onDidDeleteCallback = callback + return { dispose: jest.fn() } + }), + dispose: jest.fn(), + }) + + fileWatcher.initialize() + mockUri = { fsPath: "/mock/workspace/test-race.js" } + + // Ensure file access is allowed + mockRooIgnoreController.validateAccess.mockReturnValue(true) + }) + + afterEach(() => { + jest.useRealTimers() + }) + + it("should correctly process a file that is deleted and then quickly re-created/changed", async () => { + // Setup initial file state mocks + vscode.workspace.fs.stat.mockResolvedValue({ size: 100 }) + vscode.workspace.fs.readFile.mockResolvedValue(Buffer.from("new content")) + mockCacheManager.getHash.mockReturnValue("old-hash") + ;(createHash as jest.Mock).mockReturnValue({ + update: jest.fn().mockReturnThis(), + digest: jest.fn().mockReturnValue("new-hash-for-recreated-file"), + }) + + // Setup code parser mock for the re-created file + const { codeParser: mockCodeParser } = require("../parser") + mockCodeParser.parseFile.mockResolvedValue([ + { + file_path: mockUri.fsPath, + content: "new content", + start_line: 1, + end_line: 5, + identifier: "test", + type: "function", + fileHash: "new-hash-for-recreated-file", + segmentHash: "segment-hash", + }, + ]) + + // Setup a spy for the _onDidFinishBatchProcessing event + let batchProcessingFinished = false + const batchFinishedSpy = jest.fn(() => { + batchProcessingFinished = true + }) + fileWatcher.onDidFinishBatchProcessing(batchFinishedSpy) + + // Simulate delete event by directly calling the private method that accumulates events + ;(fileWatcher as any).accumulatedEvents.set(mockUri.fsPath, { uri: mockUri, type: "delete" }) + ;(fileWatcher as any).scheduleBatchProcessing() + await jest.runAllTicks() + + // For a delete-then-create in same batch, deleteHash should not be called + expect(mockCacheManager.deleteHash).not.toHaveBeenCalledWith(mockUri.fsPath) + + // Simulate quick re-creation by overriding the delete event with create + ;(fileWatcher as any).accumulatedEvents.set(mockUri.fsPath, { uri: mockUri, type: "create" }) + await jest.runAllTicks() + + // Advance timers to trigger batch processing and wait for completion + await jest.advanceTimersByTimeAsync(1000) + await jest.runAllTicks() + + // Wait for batch processing to complete + while (!batchProcessingFinished) { + await jest.runAllTicks() + await new Promise((resolve) => setImmediate(resolve)) + } + + // Verify the deletion operations + expect(mockVectorStore.deletePointsByMultipleFilePaths).not.toHaveBeenCalledWith( + expect.arrayContaining([mockUri.fsPath]), + ) + + // Verify the re-creation operations + expect(mockVectorStore.upsertPoints).toHaveBeenCalledWith( + expect.arrayContaining([ + expect.objectContaining({ + id: "mocked-uuid-v5-for-testing", + payload: expect.objectContaining({ + filePath: expect.stringContaining("test-race.js"), + codeChunk: "new content", + startLine: 1, + endLine: 5, + }), + }), + ]), + ) + + // Verify final state + expect(mockCacheManager.updateHash).toHaveBeenCalledWith(mockUri.fsPath, "new-hash-for-recreated-file") + }, 15000) + }) + + describe("Batch upsert retry logic", () => { + beforeEach(() => { + jest.useFakeTimers() + + // Clear all relevant mocks + mockCacheManager.deleteHash.mockClear() + mockCacheManager.getHash.mockClear() + mockCacheManager.updateHash.mockClear() + ;(mockVectorStore.upsertPoints as jest.Mock).mockClear() + ;(mockVectorStore.deletePointsByFilePath as jest.Mock).mockClear() + ;(mockVectorStore.deletePointsByMultipleFilePaths as jest.Mock).mockClear() + + // Ensure file access is allowed + mockRooIgnoreController.validateAccess.mockReturnValue(true) + }) + + afterEach(() => { + jest.useRealTimers() + }) + + it("should retry upsert operation when it fails initially and succeed on retry", async () => { + // Import constants for correct timing + const { INITIAL_RETRY_DELAY_MS } = require("../../constants/index") + + // Setup file state mocks + vscode.workspace.fs.stat.mockResolvedValue({ size: 100 }) + vscode.workspace.fs.readFile.mockResolvedValue(Buffer.from("test content for retry")) + mockCacheManager.getHash.mockReturnValue("old-hash") + ;(createHash as jest.Mock).mockReturnValue({ + update: jest.fn().mockReturnThis(), + digest: jest.fn().mockReturnValue("new-hash-for-retry-test"), + }) + + // Setup code parser mock + const { codeParser: mockCodeParser } = require("../parser") + mockCodeParser.parseFile.mockResolvedValue([ + { + file_path: "/mock/workspace/retry-test.js", + content: "test content for retry", + start_line: 1, + end_line: 5, + identifier: "test", + type: "function", + fileHash: "new-hash-for-retry-test", + segmentHash: "segment-hash", + }, + ]) + + // Setup a spy for the _onDidFinishBatchProcessing event + let capturedBatchSummary: any = null + let batchProcessingFinished = false + const batchFinishedSpy = jest.fn((summary) => { + capturedBatchSummary = summary + batchProcessingFinished = true + }) + fileWatcher.onDidFinishBatchProcessing(batchFinishedSpy) + + // Mock vectorStore.upsertPoints to fail on first call and succeed on second call + const mockError = new Error("Failed to upsert points to vector store") + ;(mockVectorStore.upsertPoints as jest.Mock) + .mockRejectedValueOnce(mockError) // First call fails + .mockResolvedValueOnce(undefined) // Second call succeeds + + // Trigger file change event + const mockUri = { fsPath: "/mock/workspace/retry-test.js" } + + // Directly accumulate the event and trigger batch processing + ;(fileWatcher as any).accumulatedEvents.set(mockUri.fsPath, { uri: mockUri, type: "change" }) + ;(fileWatcher as any).scheduleBatchProcessing() + + // Wait for processing to start + await jest.runAllTicks() + + // Advance timers to trigger batch processing + await jest.advanceTimersByTimeAsync(1000) // Advance past debounce delay + await jest.runAllTicks() + + // Advance timers to trigger retry after initial failure + // Use correct exponential backoff: INITIAL_RETRY_DELAY_MS * Math.pow(2, retryCount - 1) + // For first retry (retryCount = 1): 500 * Math.pow(2, 0) = 500ms + const firstRetryDelay = INITIAL_RETRY_DELAY_MS * Math.pow(2, 1 - 1) + await jest.advanceTimersByTimeAsync(firstRetryDelay) + await jest.runAllTicks() + + // Wait for batch processing to complete + while (!batchProcessingFinished) { + await jest.runAllTicks() + await new Promise((resolve) => setImmediate(resolve)) + } + + // Verify that upsertPoints was called twice (initial failure + successful retry) + expect(mockVectorStore.upsertPoints).toHaveBeenCalledTimes(2) + + // Verify that the cache was updated after successful retry + expect(mockCacheManager.updateHash).toHaveBeenCalledWith(mockUri.fsPath, "new-hash-for-retry-test") + + // Verify the batch summary + expect(capturedBatchSummary).not.toBeNull() + expect(capturedBatchSummary.batchError).toBeUndefined() + + // Verify that the processedFiles array includes the file with success status + const processedFile = capturedBatchSummary.processedFiles.find((file: any) => file.path === mockUri.fsPath) + expect(processedFile).toBeDefined() + expect(processedFile.status).toBe("success") + expect(processedFile.error).toBeUndefined() + }, 15000) + + it("should handle the case where upsert fails all retries", async () => { + // Import constants directly for test + const { MAX_BATCH_RETRIES, INITIAL_RETRY_DELAY_MS } = require("../../constants/index") + + // Setup file state mocks + vscode.workspace.fs.stat.mockResolvedValue({ size: 100 }) + vscode.workspace.fs.readFile.mockResolvedValue(Buffer.from("test content for failed retries")) + mockCacheManager.getHash.mockReturnValue("old-hash") + ;(createHash as jest.Mock).mockReturnValue({ + update: jest.fn().mockReturnThis(), + digest: jest.fn().mockReturnValue("new-hash-for-failed-retries-test"), + }) + + // Setup code parser mock + const { codeParser: mockCodeParser } = require("../parser") + mockCodeParser.parseFile.mockResolvedValue([ + { + file_path: "/mock/workspace/failed-retries-test.js", + content: "test content for failed retries", + start_line: 1, + end_line: 5, + identifier: "test", + type: "function", + fileHash: "new-hash-for-failed-retries-test", + segmentHash: "segment-hash", + }, + ]) + + // Setup a spy for the _onDidFinishBatchProcessing event + let capturedBatchSummary: any = null + let batchProcessingFinished = false + const batchFinishedSpy = jest.fn((summary) => { + capturedBatchSummary = summary + batchProcessingFinished = true + }) + fileWatcher.onDidFinishBatchProcessing(batchFinishedSpy) + + // Mock vectorStore.upsertPoints to fail consistently for all retry attempts + const mockError = new Error("Persistent upsert failure") + ;(mockVectorStore.upsertPoints as jest.Mock).mockRejectedValue(mockError) + + // Trigger file change event + const mockUri = { fsPath: "/mock/workspace/failed-retries-test.js" } + + // Directly accumulate the event and trigger batch processing + ;(fileWatcher as any).accumulatedEvents.set(mockUri.fsPath, { uri: mockUri, type: "change" }) + ;(fileWatcher as any).scheduleBatchProcessing() + + // Wait for processing to start + await jest.runAllTicks() + + // Advance timers to trigger batch processing + await jest.advanceTimersByTimeAsync(1000) // Advance past debounce delay + await jest.runAllTicks() + + // Advance timers for each retry attempt using correct exponential backoff + for (let i = 1; i <= MAX_BATCH_RETRIES; i++) { + // Use correct exponential backoff: INITIAL_RETRY_DELAY_MS * Math.pow(2, retryCount - 1) + const delay = INITIAL_RETRY_DELAY_MS * Math.pow(2, i - 1) + await jest.advanceTimersByTimeAsync(delay) + await jest.runAllTicks() + } + + // Wait for batch processing to complete + while (!batchProcessingFinished) { + await jest.runAllTicks() + await new Promise((resolve) => setImmediate(resolve)) + } + + // Verify that upsertPoints was called exactly MAX_BATCH_RETRIES times + expect(mockVectorStore.upsertPoints).toHaveBeenCalledTimes(MAX_BATCH_RETRIES) + + // Verify that the cache was NOT updated after failed retries + expect(mockCacheManager.updateHash).not.toHaveBeenCalledWith( + mockUri.fsPath, + "new-hash-for-failed-retries-test", + ) + + // Verify the batch summary + expect(capturedBatchSummary).not.toBeNull() + expect(capturedBatchSummary.batchError).toBeDefined() + expect(capturedBatchSummary.batchError.message).toContain( + `Failed to upsert batch after ${MAX_BATCH_RETRIES} retries`, + ) + + // Verify that the processedFiles array includes the file with error status + const processedFile = capturedBatchSummary.processedFiles.find((file: any) => file.path === mockUri.fsPath) + expect(processedFile).toBeDefined() + expect(processedFile.status).toBe("error") + expect(processedFile.error).toBeDefined() + expect(processedFile.error.message).toContain(`Failed to upsert batch after ${MAX_BATCH_RETRIES} retries`) + }, 15000) + }) + + describe("Pre-existing batch error propagation", () => { + let onDidDeleteCallback: (uri: any) => void + let onDidCreateCallback: (uri: any) => void + let onDidChangeCallback: (uri: any) => void + let deleteUri: { fsPath: string } + let createUri: { fsPath: string } + let changeUri: { fsPath: string } + + beforeEach(() => { + jest.useFakeTimers() + + // Clear all relevant mocks + mockCacheManager.deleteHash.mockClear() + mockCacheManager.getHash.mockClear() + mockCacheManager.updateHash.mockClear() + ;(mockVectorStore.upsertPoints as jest.Mock).mockClear() + ;(mockVectorStore.deletePointsByFilePath as jest.Mock).mockClear() + ;(mockVectorStore.deletePointsByMultipleFilePaths as jest.Mock).mockClear() + + // Setup file watcher mocks + vscode.workspace.createFileSystemWatcher.mockReturnValue({ + onDidCreate: jest.fn((callback) => { + onDidCreateCallback = callback + return { dispose: jest.fn() } + }), + onDidChange: jest.fn((callback) => { + onDidChangeCallback = callback + return { dispose: jest.fn() } + }), + onDidDelete: jest.fn((callback) => { + onDidDeleteCallback = callback + return { dispose: jest.fn() } + }), + dispose: jest.fn(), + }) + + fileWatcher.initialize() + deleteUri = { fsPath: "/mock/workspace/to-be-deleted.js" } + createUri = { fsPath: "/mock/workspace/to-be-created.js" } + changeUri = { fsPath: "/mock/workspace/to-be-changed.js" } + + // Ensure file access is allowed + mockRooIgnoreController.validateAccess.mockReturnValue(true) + }) + + afterEach(() => { + jest.useRealTimers() + }) + + it("should not execute upsert operations when an overallBatchError pre-exists from deletion phase", async () => { + // Setup file state mocks for the files to be processed + vscode.workspace.fs.stat.mockResolvedValue({ size: 100 }) + vscode.workspace.fs.readFile.mockResolvedValue(Buffer.from("test content")) + mockCacheManager.getHash.mockReturnValue("old-hash") + ;(createHash as jest.Mock).mockReturnValue({ + update: jest.fn().mockReturnThis(), + digest: jest.fn().mockReturnValue("new-hash"), + }) + + // Setup code parser mock for the files to be processed + const { codeParser: mockCodeParser } = require("../parser") + mockCodeParser.parseFile.mockResolvedValue([ + { + file_path: createUri.fsPath, + content: "test content", + start_line: 1, + end_line: 5, + identifier: "test", + type: "function", + fileHash: "new-hash", + segmentHash: "segment-hash", + }, + ]) + + // Setup a spy for the _onDidFinishBatchProcessing event + let capturedBatchSummary: any = null + let batchProcessingFinished = false + const batchFinishedSpy = jest.fn((summary) => { + capturedBatchSummary = summary + batchProcessingFinished = true + }) + fileWatcher.onDidFinishBatchProcessing(batchFinishedSpy) + + // Mock deletePointsByMultipleFilePaths to throw an error + const mockDeletionError = new Error("Failed to delete points from vector store") + ;(mockVectorStore.deletePointsByMultipleFilePaths as jest.Mock).mockRejectedValueOnce(mockDeletionError) + + // Simulate delete event by directly adding to accumulated events + ;(fileWatcher as any).accumulatedEvents.set(deleteUri.fsPath, { uri: deleteUri, type: "delete" }) + ;(fileWatcher as any).scheduleBatchProcessing() + await jest.runAllTicks() + + // Simulate create event in the same batch + ;(fileWatcher as any).accumulatedEvents.set(createUri.fsPath, { uri: createUri, type: "create" }) + await jest.runAllTicks() + + // Simulate change event in the same batch + ;(fileWatcher as any).accumulatedEvents.set(changeUri.fsPath, { uri: changeUri, type: "change" }) + await jest.runAllTicks() + + // Advance timers to trigger batch processing + await jest.advanceTimersByTimeAsync(1000) // Advance past debounce delay + await jest.runAllTicks() + + // Wait for batch processing to complete + while (!batchProcessingFinished) { + await jest.runAllTicks() + await new Promise((resolve) => setImmediate(resolve)) + } + + // Verify that deletePointsByMultipleFilePaths was called + expect(mockVectorStore.deletePointsByMultipleFilePaths).toHaveBeenCalled() + + // Verify that upsertPoints was NOT called due to pre-existing error + expect(mockVectorStore.upsertPoints).not.toHaveBeenCalled() + + // Verify that the cache was NOT updated for the created/changed files + expect(mockCacheManager.updateHash).not.toHaveBeenCalledWith(createUri.fsPath, expect.any(String)) + expect(mockCacheManager.updateHash).not.toHaveBeenCalledWith(changeUri.fsPath, expect.any(String)) + + // Verify the batch summary + expect(capturedBatchSummary).not.toBeNull() + expect(capturedBatchSummary.batchError).toBe(mockDeletionError) + + // Verify that the processedFiles array includes all files with appropriate status + const deletedFile = capturedBatchSummary.processedFiles.find((file: any) => file.path === deleteUri.fsPath) + expect(deletedFile).toBeDefined() + expect(deletedFile.status).toBe("error") + expect(deletedFile.error).toBe(mockDeletionError) + + // Verify that the create/change files also have error status with the same error + const createdFile = capturedBatchSummary.processedFiles.find((file: any) => file.path === createUri.fsPath) + expect(createdFile).toBeDefined() + expect(createdFile.status).toBe("error") + expect(createdFile.error).toBe(mockDeletionError) + + const changedFile = capturedBatchSummary.processedFiles.find((file: any) => file.path === changeUri.fsPath) + expect(changedFile).toBeDefined() + expect(changedFile.status).toBe("error") + expect(changedFile.error).toBe(mockDeletionError) + }, 15000) + }) +}) diff --git a/src/services/code-index/processors/__tests__/parser.test.ts b/src/services/code-index/processors/__tests__/parser.test.ts new file mode 100644 index 0000000000..a24c24593f --- /dev/null +++ b/src/services/code-index/processors/__tests__/parser.test.ts @@ -0,0 +1,226 @@ +import { jest } from "@jest/globals" +import { CodeParser, codeParser } from "../parser" +import { mockedFs } from "../../../tree-sitter/__tests__/helpers" +import Parser from "web-tree-sitter" +import { loadRequiredLanguageParsers } from "../../../tree-sitter/languageParser" + +jest.mock("fs/promises") +jest.mock("../../../tree-sitter/languageParser") + +const mockLanguageParser = { + js: { + parser: { + parse: jest.fn((content: string) => ({ + rootNode: { + text: content, + startPosition: { row: 0 }, + endPosition: { row: content.split("\n").length - 1 }, + children: [], + type: "program", + }, + })), + }, + query: { + captures: jest.fn().mockReturnValue([]), + }, + }, +} + +describe("CodeParser", () => { + let parser: CodeParser + + beforeEach(() => { + jest.clearAllMocks() + parser = new CodeParser() + ;(loadRequiredLanguageParsers as jest.MockedFunction).mockResolvedValue( + mockLanguageParser as any, + ) + }) + + describe("parseFile", () => { + it("should return empty array for unsupported extensions", async () => { + const result = await parser.parseFile("test.unsupported") + expect(result).toEqual([]) + }) + + it("should use provided content instead of reading file when options.content is provided", async () => { + const content = `/* This is a long test content string that exceeds 100 characters to properly test the parser's behavior with large inputs. + It includes multiple lines and various JavaScript constructs to simulate real-world code. + const a = 1; + const b = 2; + function test() { return a + b; } + class Example { constructor() { this.value = 42; } } + // More comments to pad the length to ensure we hit the minimum character requirement */` + const result = await parser.parseFile("test.js", { content }) + expect(mockedFs.readFile).not.toHaveBeenCalled() + expect(result.length).toBeGreaterThan(0) + }) + + it("should read file when no content is provided", async () => { + mockedFs.readFile + .mockResolvedValue(`/* This is a long test content string that exceeds 100 characters to properly test file reading behavior. + It includes multiple lines and various JavaScript constructs to simulate real-world code. + const x = 10; + const y = 20; + function calculate() { return x * y; } + class Calculator { + constructor() { this.history = []; } + add(a, b) { return a + b; } + } + // More comments to pad the length to ensure we hit the minimum character requirement */`) + const result = await parser.parseFile("test.js") + expect(mockedFs.readFile).toHaveBeenCalledWith("test.js", "utf8") + expect(result.length).toBeGreaterThan(0) + }) + + it("should handle file read errors gracefully", async () => { + mockedFs.readFile.mockRejectedValue(new Error("File not found")) + const result = await parser.parseFile("test.js") + expect(result).toEqual([]) + }) + + it("should use provided fileHash when available", async () => { + const content = `/* This is a long test content string that exceeds 100 characters to test fileHash behavior. + It includes multiple lines and various JavaScript constructs to simulate real-world code. + const items = [1, 2, 3]; + const sum = items.reduce((a, b) => a + b, 0); + function processItems(items) { + return items.map(item => item * 2); + } + // More comments to pad the length to ensure we hit the minimum character requirement */` + const fileHash = "test-hash" + const result = await parser.parseFile("test.js", { content, fileHash }) + expect(result[0].fileHash).toBe(fileHash) + }) + }) + + describe("isSupportedLanguage", () => { + it("should return true for supported extensions", () => { + expect(parser["isSupportedLanguage"](".js")).toBe(true) + }) + + it("should return false for unsupported extensions", () => { + expect(parser["isSupportedLanguage"](".unsupported")).toBe(false) + }) + }) + + describe("createFileHash", () => { + it("should generate consistent hashes for same content", () => { + const content = "test content" + const hash1 = parser["createFileHash"](content) + const hash2 = parser["createFileHash"](content) + expect(hash1).toBe(hash2) + expect(hash1).toMatch(/^[a-f0-9]{64}$/) // SHA-256 hex format + }) + + it("should generate different hashes for different content", () => { + const hash1 = parser["createFileHash"]("content1") + const hash2 = parser["createFileHash"]("content2") + expect(hash1).not.toBe(hash2) + }) + }) + + describe("parseContent", () => { + it("should wait for pending parser loads", async () => { + const pendingLoad = new Promise((resolve) => setTimeout(() => resolve(mockLanguageParser), 100)) + parser["pendingLoads"].set(".js", pendingLoad as Promise) + + const result = await parser["parseContent"]("test.js", "const test = 123", "hash") + expect(result).toBeDefined() + }) + + it("should handle parser load errors", async () => { + ;(loadRequiredLanguageParsers as jest.MockedFunction).mockRejectedValue( + new Error("Load failed"), + ) + const result = await parser["parseContent"]("test.js", "const test = 123", "hash") + expect(result).toEqual([]) + }) + + it("should return empty array when no parser is available", async () => { + ;(loadRequiredLanguageParsers as jest.MockedFunction).mockResolvedValue( + {} as any, + ) + const result = await parser["parseContent"]("test.js", "const test = 123", "hash") + expect(result).toEqual([]) + }) + }) + + describe("_performFallbackChunking", () => { + it("should chunk content when no captures are found", async () => { + const content = `/* This is a long test content string that exceeds 100 characters to test fallback chunking behavior. + It includes multiple lines and various JavaScript constructs to simulate real-world code. + line1: const a = 1; + line2: const b = 2; + line3: function sum() { return a + b; } + line4: class Adder { constructor(x, y) { this.x = x; this.y = y; } } + line5: const instance = new Adder(1, 2); + line6: console.log(instance.x + instance.y); + line7: // More comments to pad the length to ensure we hit the minimum character requirement */` + const result = await parser["_performFallbackChunking"]("test.js", content, "hash", new Set()) + expect(result.length).toBeGreaterThan(0) + expect(result[0].type).toBe("fallback_chunk") + }) + + it("should respect MIN_BLOCK_CHARS for fallback chunks", async () => { + const shortContent = "short" + const result = await parser["_performFallbackChunking"]("test.js", shortContent, "hash", new Set()) + expect(result).toEqual([]) + }) + }) + + describe("_chunkLeafNodeByLines", () => { + it("should chunk leaf nodes by lines", async () => { + const mockNode = { + text: `/* This is a long test content string that exceeds 100 characters to test line chunking behavior. + line1: const a = 1; + line2: const b = 2; + line3: function sum() { return a + b; } + line4: class Multiplier { constructor(x, y) { this.x = x; this.y = y; } } + line5: const instance = new Multiplier(3, 4); + line6: console.log(instance.x * instance.y); + line7: // More comments to pad the length to ensure we hit the minimum character requirement */`, + startPosition: { row: 10 }, + endPosition: { row: 12 }, + type: "function", + } as unknown as Parser.SyntaxNode + + const result = await parser["_chunkLeafNodeByLines"](mockNode, "test.js", "hash", new Set()) + expect(result.length).toBeGreaterThan(0) + expect(result[0].type).toBe("function") + expect(result[0].start_line).toBe(11) // 1-based + }) + }) + + describe("_chunkTextByLines", () => { + it("should handle oversized lines by splitting them", async () => { + const longLine = "a".repeat(2000) + const lines = ["normal", longLine, "normal"] + const result = await parser["_chunkTextByLines"](lines, "test.js", "hash", "test_type", new Set()) + + const segments = result.filter((r) => r.type === "test_type_segment") + expect(segments.length).toBeGreaterThan(1) + }) + + it("should re-balance chunks when remainder is too small", async () => { + const lines = Array(100) + .fill("line with 10 chars") + .map((_, i) => `${i}: line`) + const result = await parser["_chunkTextByLines"](lines, "test.js", "hash", "test_type", new Set()) + + result.forEach((chunk) => { + expect(chunk.content.length).toBeGreaterThanOrEqual(100) + expect(chunk.content.length).toBeLessThanOrEqual(1150) + }) + }) + }) + + describe("singleton instance", () => { + it("should maintain parser state across calls", async () => { + const result1 = await codeParser.parseFile("test.js", { content: "const a = 1" }) + const result2 = await codeParser.parseFile("test.js", { content: "const b = 2" }) + expect(result1).toBeDefined() + expect(result2).toBeDefined() + }) + }) +}) diff --git a/src/services/code-index/processors/__tests__/scanner.test.ts b/src/services/code-index/processors/__tests__/scanner.test.ts new file mode 100644 index 0000000000..bcfb230980 --- /dev/null +++ b/src/services/code-index/processors/__tests__/scanner.test.ts @@ -0,0 +1,157 @@ +// @ts-nocheck +import { DirectoryScanner } from "../scanner" +import { stat } from "fs/promises" +import { IEmbedder, IVectorStore, CodeBlock } from "../../../../core/interfaces" +jest.mock("fs/promises", () => ({ + stat: jest.fn(), +})) + +// Create a simple mock for vscode since we can't access the real one +jest.mock("vscode", () => ({ + workspace: { + workspaceFolders: [ + { + uri: { + fsPath: "/mock/workspace", + }, + }, + ], + getWorkspaceFolder: jest.fn().mockReturnValue({ + uri: { + fsPath: "/mock/workspace", + }, + }), + fs: { + readFile: jest.fn().mockResolvedValue(Buffer.from("test content")), + }, + }, + Uri: { + file: jest.fn().mockImplementation((path) => path), + }, + window: { + activeTextEditor: { + document: { + uri: { + fsPath: "/mock/workspace", + }, + }, + }, + }, +})) + +jest.mock("fs/promises") +jest.mock("../../../glob/list-files") +jest.mock("../../../../core/ignore/RooIgnoreController") +jest.mock("ignore") + +describe("DirectoryScanner", () => { + let scanner: DirectoryScanner + let mockEmbedder: IEmbedder + let mockVectorStore: IVectorStore + let mockCodeParser: ICodeParser + let mockCacheManager: CacheManager + let mockIgnoreInstance: any + + beforeEach(() => { + mockEmbedder = { + createEmbeddings: jest.fn().mockResolvedValue({ embeddings: [[0.1, 0.2, 0.3]] }), + embedderInfo: { name: "mock-embedder", dimensions: 384 }, + } + mockVectorStore = { + upsertPoints: jest.fn().mockResolvedValue(undefined), + deletePointsByFilePath: jest.fn().mockResolvedValue(undefined), + deletePointsByMultipleFilePaths: jest.fn().mockResolvedValue(undefined), + initialize: jest.fn().mockResolvedValue(true), + search: jest.fn().mockResolvedValue([]), + clearCollection: jest.fn().mockResolvedValue(undefined), + deleteCollection: jest.fn().mockResolvedValue(undefined), + collectionExists: jest.fn().mockResolvedValue(true), + } + mockCodeParser = { + parseFile: jest.fn().mockResolvedValue([]), + } + mockCacheManager = { + getHash: jest.fn().mockReturnValue(undefined), + getAllHashes: jest.fn().mockReturnValue({}), + updateHash: jest.fn().mockResolvedValue(undefined), + deleteHash: jest.fn().mockResolvedValue(undefined), + initialize: jest.fn().mockResolvedValue(undefined), + clearCacheFile: jest.fn().mockResolvedValue(undefined), + } + mockIgnoreInstance = { + ignores: jest.fn().mockReturnValue(false), + } + + scanner = new DirectoryScanner( + mockEmbedder, + mockVectorStore, + mockCodeParser, + mockCacheManager, + mockIgnoreInstance, + ) + + // Mock default implementations + ;(stat as unknown as jest.Mock).mockResolvedValue({ size: 1024 }) + require("../../../glob/list-files").listFiles.mockResolvedValue([["test/file1.js", "test/file2.js"], []]) + }) + + describe("scanDirectory", () => { + it("should skip files larger than MAX_FILE_SIZE_BYTES", async () => { + require("../../../glob/list-files").listFiles.mockResolvedValue([["test/file1.js"], []]) + ;(stat as jest.Mock).mockResolvedValueOnce({ size: 2 * 1024 * 1024 }) // 2MB > 1MB limit + + const result = await scanner.scanDirectory("/test") + expect(result.stats.skipped).toBe(1) + expect(mockCodeParser.parseFile).not.toHaveBeenCalled() + }) + + it("should parse changed files and return code blocks", async () => { + require("../../../glob/list-files").listFiles.mockResolvedValue([["test/file1.js"], []]) + const mockBlocks: CodeBlock[] = [ + { + file_path: "test/file1.js", + content: "test content", + start_line: 1, + end_line: 5, + identifier: "test", + type: "function", + fileHash: "hash", + segmentHash: "segment-hash", + }, + ] + ;(mockCodeParser.parseFile as jest.Mock).mockResolvedValue(mockBlocks) + + const result = await scanner.scanDirectory("/test") + expect(result.codeBlocks).toEqual(mockBlocks) + expect(result.stats.processed).toBe(1) + }) + + it("should process embeddings for new/changed files", async () => { + const mockBlocks: CodeBlock[] = [ + { + file_path: "test/file1.js", + content: "test content", + start_line: 1, + end_line: 5, + identifier: "test", + type: "function", + fileHash: "hash", + segmentHash: "segment-hash", + }, + ] + ;(mockCodeParser.parseFile as jest.Mock).mockResolvedValue(mockBlocks) + + await scanner.scanDirectory("/test") + expect(mockEmbedder.createEmbeddings).toHaveBeenCalled() + expect(mockVectorStore.upsertPoints).toHaveBeenCalled() + }) + + it("should delete points for removed files", async () => { + ;(mockCacheManager.getAllHashes as jest.Mock).mockReturnValue({ "old/file.js": "old-hash" }) + + await scanner.scanDirectory("/test") + expect(mockVectorStore.deletePointsByFilePath).toHaveBeenCalledWith("old/file.js") + expect(mockCacheManager.deleteHash).toHaveBeenCalledWith("old/file.js") + }) + }) +}) diff --git a/src/services/code-index/processors/file-watcher.ts b/src/services/code-index/processors/file-watcher.ts new file mode 100644 index 0000000000..dfbf0169e3 --- /dev/null +++ b/src/services/code-index/processors/file-watcher.ts @@ -0,0 +1,536 @@ +import * as vscode from "vscode" +import { + QDRANT_CODE_BLOCK_NAMESPACE, + MAX_FILE_SIZE_BYTES, + BATCH_SEGMENT_THRESHOLD, + MAX_BATCH_RETRIES, + INITIAL_RETRY_DELAY_MS, +} from "../constants" +import { createHash } from "crypto" +import { RooIgnoreController } from "../../../core/ignore/RooIgnoreController" +import { v5 as uuidv5 } from "uuid" +import { Ignore } from "ignore" +import { scannerExtensions } from "../shared/supported-extensions" +import { + IFileWatcher, + FileProcessingResult, + IEmbedder, + IVectorStore, + PointStruct, + BatchProcessingSummary, +} from "../interfaces" +import { codeParser } from "./parser" +import { CacheManager } from "../cache-manager" +import { generateNormalizedAbsolutePath, generateRelativeFilePath } from "../shared/get-relative-path" + +/** + * Implementation of the file watcher interface + */ +export class FileWatcher implements IFileWatcher { + private ignoreInstance?: Ignore + private fileWatcher?: vscode.FileSystemWatcher + private ignoreController: RooIgnoreController + private accumulatedEvents: Map = new Map() + private batchProcessDebounceTimer?: NodeJS.Timeout + private readonly BATCH_DEBOUNCE_DELAY_MS = 500 + private readonly FILE_PROCESSING_CONCURRENCY_LIMIT = 10 + + private readonly _onDidStartBatchProcessing = new vscode.EventEmitter() + private readonly _onBatchProgressUpdate = new vscode.EventEmitter<{ + processedInBatch: number + totalInBatch: number + currentFile?: string + }>() + private readonly _onDidFinishBatchProcessing = new vscode.EventEmitter() + + /** + * Event emitted when a batch of files begins processing + */ + public readonly onDidStartBatchProcessing = this._onDidStartBatchProcessing.event + + /** + * Event emitted to report progress during batch processing + */ + public readonly onBatchProgressUpdate = this._onBatchProgressUpdate.event + + /** + * Event emitted when a batch of files has finished processing + */ + public readonly onDidFinishBatchProcessing = this._onDidFinishBatchProcessing.event + + /** + * Creates a new file watcher + * @param workspacePath Path to the workspace + * @param context VS Code extension context + * @param embedder Optional embedder + * @param vectorStore Optional vector store + * @param cacheManager Cache manager + */ + constructor( + private workspacePath: string, + private context: vscode.ExtensionContext, + private readonly cacheManager: CacheManager, + private embedder?: IEmbedder, + private vectorStore?: IVectorStore, + ignoreInstance?: Ignore, + ignoreController?: RooIgnoreController, + ) { + this.ignoreController = ignoreController || new RooIgnoreController(workspacePath) + if (ignoreInstance) { + this.ignoreInstance = ignoreInstance + } + } + + /** + * Initializes the file watcher + */ + async initialize(): Promise { + // Create file watcher + const filePattern = new vscode.RelativePattern( + this.workspacePath, + `**/*{${scannerExtensions.map((e) => e.substring(1)).join(",")}}`, + ) + this.fileWatcher = vscode.workspace.createFileSystemWatcher(filePattern) + + // Register event handlers + this.fileWatcher.onDidCreate(this.handleFileCreated.bind(this)) + this.fileWatcher.onDidChange(this.handleFileChanged.bind(this)) + this.fileWatcher.onDidDelete(this.handleFileDeleted.bind(this)) + } + + /** + * Disposes the file watcher + */ + dispose(): void { + this.fileWatcher?.dispose() + if (this.batchProcessDebounceTimer) { + clearTimeout(this.batchProcessDebounceTimer) + } + this._onDidStartBatchProcessing.dispose() + this._onBatchProgressUpdate.dispose() + this._onDidFinishBatchProcessing.dispose() + this.accumulatedEvents.clear() + } + + /** + * Handles file creation events + * @param uri URI of the created file + */ + private async handleFileCreated(uri: vscode.Uri): Promise { + this.accumulatedEvents.set(uri.fsPath, { uri, type: "create" }) + this.scheduleBatchProcessing() + } + + /** + * Handles file change events + * @param uri URI of the changed file + */ + private async handleFileChanged(uri: vscode.Uri): Promise { + this.accumulatedEvents.set(uri.fsPath, { uri, type: "change" }) + this.scheduleBatchProcessing() + } + + /** + * Handles file deletion events + * @param uri URI of the deleted file + */ + private async handleFileDeleted(uri: vscode.Uri): Promise { + this.accumulatedEvents.set(uri.fsPath, { uri, type: "delete" }) + this.scheduleBatchProcessing() + } + + /** + * Schedules batch processing with debounce + */ + private scheduleBatchProcessing(): void { + if (this.batchProcessDebounceTimer) { + clearTimeout(this.batchProcessDebounceTimer) + } + this.batchProcessDebounceTimer = setTimeout(() => this.triggerBatchProcessing(), this.BATCH_DEBOUNCE_DELAY_MS) + } + + /** + * Triggers processing of accumulated events + */ + private async triggerBatchProcessing(): Promise { + if (this.accumulatedEvents.size === 0) { + return + } + + const eventsToProcess = new Map(this.accumulatedEvents) + this.accumulatedEvents.clear() + + const filePathsInBatch = Array.from(eventsToProcess.keys()) + this._onDidStartBatchProcessing.fire(filePathsInBatch) + + await this.processBatch(eventsToProcess) + } + + /** + * Processes a batch of accumulated events + * @param eventsToProcess Map of events to process + */ + private async _handleBatchDeletions( + batchResults: FileProcessingResult[], + processedCountInBatch: number, + totalFilesInBatch: number, + pathsToExplicitlyDelete: string[], + filesToUpsertDetails: Array<{ path: string; uri: vscode.Uri; originalType: "create" | "change" }>, + ): Promise<{ overallBatchError?: Error; clearedPaths: Set; processedCount: number }> { + let overallBatchError: Error | undefined + const allPathsToClearFromDB = new Set(pathsToExplicitlyDelete) + + for (const fileDetail of filesToUpsertDetails) { + if (fileDetail.originalType === "change") { + allPathsToClearFromDB.add(fileDetail.path) + } + } + + if (allPathsToClearFromDB.size > 0 && this.vectorStore) { + try { + await this.vectorStore.deletePointsByMultipleFilePaths(Array.from(allPathsToClearFromDB)) + + for (const path of pathsToExplicitlyDelete) { + this.cacheManager.deleteHash(path) + batchResults.push({ path, status: "success" }) + processedCountInBatch++ + this._onBatchProgressUpdate.fire({ + processedInBatch: processedCountInBatch, + totalInBatch: totalFilesInBatch, + currentFile: path, + }) + } + } catch (error) { + overallBatchError = error as Error + for (const path of pathsToExplicitlyDelete) { + batchResults.push({ path, status: "error", error: error as Error }) + processedCountInBatch++ + this._onBatchProgressUpdate.fire({ + processedInBatch: processedCountInBatch, + totalInBatch: totalFilesInBatch, + currentFile: path, + }) + } + } + } + + return { overallBatchError, clearedPaths: allPathsToClearFromDB, processedCount: processedCountInBatch } + } + + private async _processFilesAndPrepareUpserts( + filesToUpsertDetails: Array<{ path: string; uri: vscode.Uri; originalType: "create" | "change" }>, + batchResults: FileProcessingResult[], + processedCountInBatch: number, + totalFilesInBatch: number, + pathsToExplicitlyDelete: string[], + ): Promise<{ + pointsForBatchUpsert: PointStruct[] + successfullyProcessedForUpsert: Array<{ path: string; newHash?: string }> + processedCount: number + }> { + const pointsForBatchUpsert: PointStruct[] = [] + const successfullyProcessedForUpsert: Array<{ path: string; newHash?: string }> = [] + const filesToProcessConcurrently = [...filesToUpsertDetails] + + for (let i = 0; i < filesToProcessConcurrently.length; i += this.FILE_PROCESSING_CONCURRENCY_LIMIT) { + const chunkToProcess = filesToProcessConcurrently.slice(i, i + this.FILE_PROCESSING_CONCURRENCY_LIMIT) + + const chunkProcessingPromises = chunkToProcess.map(async (fileDetail) => { + this._onBatchProgressUpdate.fire({ + processedInBatch: processedCountInBatch, + totalInBatch: totalFilesInBatch, + currentFile: fileDetail.path, + }) + try { + const result = await this.processFile(fileDetail.path) + return { path: fileDetail.path, result: result, error: undefined } + } catch (e) { + console.error(`[FileWatcher] Unhandled exception processing file ${fileDetail.path}:`, e) + return { path: fileDetail.path, result: undefined, error: e as Error } + } + }) + + const settledChunkResults = await Promise.allSettled(chunkProcessingPromises) + + for (const settledResult of settledChunkResults) { + let resultPath: string | undefined + + if (settledResult.status === "fulfilled") { + const { path, result, error: directError } = settledResult.value + resultPath = path + + if (directError) { + batchResults.push({ path, status: "error", error: directError }) + } else if (result) { + if (result.status === "skipped" || result.status === "local_error") { + batchResults.push(result) + } else if (result.status === "processed_for_batching" && result.pointsToUpsert) { + pointsForBatchUpsert.push(...result.pointsToUpsert) + if (result.path && result.newHash) { + successfullyProcessedForUpsert.push({ path: result.path, newHash: result.newHash }) + } else if (result.path && !result.newHash) { + successfullyProcessedForUpsert.push({ path: result.path }) + } + } else { + batchResults.push({ + path, + status: "error", + error: new Error( + `Unexpected result status from processFile: ${result.status} for file ${path}`, + ), + }) + } + } else { + batchResults.push({ + path, + status: "error", + error: new Error(`Fulfilled promise with no result or error for file ${path}`), + }) + } + } else { + console.error("[FileWatcher] A file processing promise was rejected:", settledResult.reason) + batchResults.push({ + path: settledResult.reason?.path || "unknown", + status: "error", + error: settledResult.reason as Error, + }) + } + + if (!pathsToExplicitlyDelete.includes(resultPath || "")) { + processedCountInBatch++ + } + this._onBatchProgressUpdate.fire({ + processedInBatch: processedCountInBatch, + totalInBatch: totalFilesInBatch, + currentFile: resultPath, + }) + } + } + + return { pointsForBatchUpsert, successfullyProcessedForUpsert, processedCount: processedCountInBatch } + } + + private async _executeBatchUpsertOperations( + pointsForBatchUpsert: PointStruct[], + successfullyProcessedForUpsert: Array<{ path: string; newHash?: string }>, + batchResults: FileProcessingResult[], + overallBatchError?: Error, + ): Promise { + if (pointsForBatchUpsert.length > 0 && this.vectorStore && !overallBatchError) { + try { + for (let i = 0; i < pointsForBatchUpsert.length; i += BATCH_SEGMENT_THRESHOLD) { + const batch = pointsForBatchUpsert.slice(i, i + BATCH_SEGMENT_THRESHOLD) + let retryCount = 0 + let upsertError: Error | undefined + + while (retryCount < MAX_BATCH_RETRIES) { + try { + await this.vectorStore.upsertPoints(batch) + break + } catch (error) { + upsertError = error as Error + retryCount++ + if (retryCount === MAX_BATCH_RETRIES) { + throw new Error( + `Failed to upsert batch after ${MAX_BATCH_RETRIES} retries: ${upsertError.message}`, + ) + } + await new Promise((resolve) => + setTimeout(resolve, INITIAL_RETRY_DELAY_MS * Math.pow(2, retryCount - 1)), + ) + } + } + } + + for (const { path, newHash } of successfullyProcessedForUpsert) { + if (newHash) { + this.cacheManager.updateHash(path, newHash) + } + batchResults.push({ path, status: "success" }) + } + } catch (error) { + overallBatchError = overallBatchError || (error as Error) + for (const { path } of successfullyProcessedForUpsert) { + batchResults.push({ path, status: "error", error: error as Error }) + } + } + } else if (overallBatchError && pointsForBatchUpsert.length > 0) { + for (const { path } of successfullyProcessedForUpsert) { + batchResults.push({ path, status: "error", error: overallBatchError }) + } + } + + return overallBatchError + } + + private async processBatch( + eventsToProcess: Map, + ): Promise { + const batchResults: FileProcessingResult[] = [] + let processedCountInBatch = 0 + const totalFilesInBatch = eventsToProcess.size + let overallBatchError: Error | undefined + + // Initial progress update + this._onBatchProgressUpdate.fire({ + processedInBatch: 0, + totalInBatch: totalFilesInBatch, + currentFile: undefined, + }) + + // Categorize events + const pathsToExplicitlyDelete: string[] = [] + const filesToUpsertDetails: Array<{ path: string; uri: vscode.Uri; originalType: "create" | "change" }> = [] + + for (const event of eventsToProcess.values()) { + if (event.type === "delete") { + pathsToExplicitlyDelete.push(event.uri.fsPath) + } else { + filesToUpsertDetails.push({ + path: event.uri.fsPath, + uri: event.uri, + originalType: event.type, + }) + } + } + + // Phase 1: Handle deletions + const { overallBatchError: deletionError, processedCount: deletionCount } = await this._handleBatchDeletions( + batchResults, + processedCountInBatch, + totalFilesInBatch, + pathsToExplicitlyDelete, + filesToUpsertDetails, + ) + overallBatchError = deletionError + processedCountInBatch = deletionCount + + // Phase 2: Process files and prepare upserts + const { + pointsForBatchUpsert, + successfullyProcessedForUpsert, + processedCount: upsertCount, + } = await this._processFilesAndPrepareUpserts( + filesToUpsertDetails, + batchResults, + processedCountInBatch, + totalFilesInBatch, + pathsToExplicitlyDelete, + ) + processedCountInBatch = upsertCount + + // Phase 3: Execute batch upsert + overallBatchError = await this._executeBatchUpsertOperations( + pointsForBatchUpsert, + successfullyProcessedForUpsert, + batchResults, + overallBatchError, + ) + + // Finalize + this._onDidFinishBatchProcessing.fire({ + processedFiles: batchResults, + batchError: overallBatchError, + }) + this._onBatchProgressUpdate.fire({ + processedInBatch: totalFilesInBatch, + totalInBatch: totalFilesInBatch, + }) + + if (this.accumulatedEvents.size === 0) { + this._onBatchProgressUpdate.fire({ + processedInBatch: 0, + totalInBatch: 0, + currentFile: undefined, + }) + } + } + + /** + * Processes a file + * @param filePath Path to the file to process + * @returns Promise resolving to processing result + */ + async processFile(filePath: string): Promise { + try { + // Check if file should be ignored + const relativeFilePath = generateRelativeFilePath(filePath) + if ( + !this.ignoreController.validateAccess(filePath) || + (this.ignoreInstance && this.ignoreInstance.ignores(relativeFilePath)) + ) { + return { + path: filePath, + status: "skipped" as const, + reason: "File is ignored by .rooignore or .gitignore", + } + } + + // Check file size + const fileStat = await vscode.workspace.fs.stat(vscode.Uri.file(filePath)) + if (fileStat.size > MAX_FILE_SIZE_BYTES) { + return { + path: filePath, + status: "skipped" as const, + reason: "File is too large", + } + } + + // Read file content + const fileContent = await vscode.workspace.fs.readFile(vscode.Uri.file(filePath)) + const content = fileContent.toString() + + // Calculate hash + const newHash = createHash("sha256").update(content).digest("hex") + + // Check if file has changed + if (this.cacheManager.getHash(filePath) === newHash) { + return { + path: filePath, + status: "skipped" as const, + reason: "File has not changed", + } + } + + // Parse file + const blocks = await codeParser.parseFile(filePath, { content, fileHash: newHash }) + + // Prepare points for batch processing + let pointsToUpsert: PointStruct[] = [] + if (this.embedder && blocks.length > 0) { + const texts = blocks.map((block) => block.content) + const { embeddings } = await this.embedder.createEmbeddings(texts) + + pointsToUpsert = blocks.map((block, index) => { + const normalizedAbsolutePath = generateNormalizedAbsolutePath(block.file_path) + const stableName = `${normalizedAbsolutePath}:${block.start_line}` + const pointId = uuidv5(stableName, QDRANT_CODE_BLOCK_NAMESPACE) + + return { + id: pointId, + vector: embeddings[index], + payload: { + filePath: generateRelativeFilePath(normalizedAbsolutePath), + codeChunk: block.content, + startLine: block.start_line, + endLine: block.end_line, + }, + } + }) + } + + return { + path: filePath, + status: "processed_for_batching" as const, + newHash, + pointsToUpsert, + } + } catch (error) { + return { + path: filePath, + status: "local_error" as const, + error: error as Error, + } + } + } +} diff --git a/src/services/code-index/processors/index.ts b/src/services/code-index/processors/index.ts new file mode 100644 index 0000000000..c244d9b875 --- /dev/null +++ b/src/services/code-index/processors/index.ts @@ -0,0 +1,3 @@ +export * from "./parser" +export * from "./scanner" +export * from "./file-watcher" diff --git a/src/services/code-index/processors/parser.ts b/src/services/code-index/processors/parser.ts new file mode 100644 index 0000000000..2197f17bf0 --- /dev/null +++ b/src/services/code-index/processors/parser.ts @@ -0,0 +1,375 @@ +import { readFile } from "fs/promises" +import { createHash } from "crypto" +import * as path from "path" +import * as treeSitter from "web-tree-sitter" +import { LanguageParser, loadRequiredLanguageParsers } from "../../tree-sitter/languageParser" +import { ICodeParser, CodeBlock } from "../interfaces" +import { scannerExtensions } from "../shared/supported-extensions" +import { MAX_BLOCK_CHARS, MIN_BLOCK_CHARS, MIN_CHUNK_REMAINDER_CHARS, MAX_CHARS_TOLERANCE_FACTOR } from "../constants" + +/** + * Implementation of the code parser interface + */ +export class CodeParser implements ICodeParser { + private loadedParsers: LanguageParser = {} + private pendingLoads: Map> = new Map() + // Markdown files are excluded because the current parser logic cannot effectively handle + // potentially large Markdown sections without a tree-sitter-like child node structure for chunking + + /** + * Parses a code file into code blocks + * @param filePath Path to the file to parse + * @param options Optional parsing options + * @returns Promise resolving to array of code blocks + */ + async parseFile( + filePath: string, + options?: { + content?: string + fileHash?: string + }, + ): Promise { + // Get file extension + const ext = path.extname(filePath).toLowerCase() + + // Skip if not a supported language + if (!this.isSupportedLanguage(ext)) { + return [] + } + + // Get file content + let content: string + let fileHash: string + + if (options?.content) { + content = options.content + fileHash = options.fileHash || this.createFileHash(content) + } else { + try { + content = await readFile(filePath, "utf8") + fileHash = this.createFileHash(content) + } catch (error) { + console.error(`Error reading file ${filePath}:`, error) + return [] + } + } + + // Parse the file + return this.parseContent(filePath, content, fileHash) + } + + /** + * Checks if a language is supported + * @param extension File extension + * @returns Boolean indicating if the language is supported + */ + private isSupportedLanguage(extension: string): boolean { + return scannerExtensions.includes(extension) + } + + /** + * Creates a hash for a file + * @param content File content + * @returns Hash string + */ + private createFileHash(content: string): string { + return createHash("sha256").update(content).digest("hex") + } + + /** + * Parses file content into code blocks + * @param filePath Path to the file + * @param content File content + * @param fileHash File hash + * @returns Array of code blocks + */ + private async parseContent(filePath: string, content: string, fileHash: string): Promise { + const ext = path.extname(filePath).slice(1).toLowerCase() + const seenSegmentHashes = new Set() + + // Check if we already have the parser loaded + if (!this.loadedParsers[ext]) { + const pendingLoad = this.pendingLoads.get(ext) + if (pendingLoad) { + try { + await pendingLoad + } catch (error) { + console.error(`Error in pending parser load for ${filePath}:`, error) + return [] + } + } else { + const loadPromise = loadRequiredLanguageParsers([filePath]) + this.pendingLoads.set(ext, loadPromise) + try { + const newParsers = await loadPromise + if (newParsers) { + this.loadedParsers = { ...this.loadedParsers, ...newParsers } + } + } catch (error) { + console.error(`Error loading language parser for ${filePath}:`, error) + return [] + } finally { + this.pendingLoads.delete(ext) + } + } + } + + const language = this.loadedParsers[ext] + if (!language) { + console.warn(`No parser available for file extension: ${ext}`) + return [] + } + + const tree = language.parser.parse(content) + + // We don't need to get the query string from languageQueries since it's already loaded + // in the language object + const captures = language.query.captures(tree.rootNode) + // Check if captures are empty + if (captures.length === 0) { + if (content.length >= MIN_BLOCK_CHARS) { + // Perform fallback chunking if content is large enough + const blocks = this._performFallbackChunking(filePath, content, fileHash, seenSegmentHashes) + return blocks + } else { + // Return empty if content is too small for fallback + return [] + } + } + + const results: CodeBlock[] = [] + + // Process captures if not empty + const queue: treeSitter.SyntaxNode[] = captures.map((capture: any) => capture.node) + + while (queue.length > 0) { + const currentNode = queue.shift()! + // const lineSpan = currentNode.endPosition.row - currentNode.startPosition.row + 1 // Removed as per lint error + + // Check if the node meets the minimum character requirement + if (currentNode.text.length >= MIN_BLOCK_CHARS) { + // If it also exceeds the maximum character limit, try to break it down + if (currentNode.text.length > MAX_BLOCK_CHARS * MAX_CHARS_TOLERANCE_FACTOR) { + if (currentNode.children.length > 0) { + // If it has children, process them instead + queue.push(...currentNode.children) + } else { + // If it's a leaf node, chunk it (passing MIN_BLOCK_CHARS as per Task 1 Step 5) + // Note: _chunkLeafNodeByLines logic might need further adjustment later + const chunkedBlocks = this._chunkLeafNodeByLines( + currentNode, + filePath, + fileHash, + seenSegmentHashes, + ) + results.push(...chunkedBlocks) + } + } else { + // Node meets min chars and is within max chars, create a block + const identifier = + currentNode.childForFieldName("name")?.text || + currentNode.children.find((c) => c.type === "identifier")?.text || + null + const type = currentNode.type + const start_line = currentNode.startPosition.row + 1 + const end_line = currentNode.endPosition.row + 1 + const content = currentNode.text + const segmentHash = createHash("sha256") + .update(`${filePath}-${start_line}-${end_line}-${content}`) + .digest("hex") + + if (!seenSegmentHashes.has(segmentHash)) { + seenSegmentHashes.add(segmentHash) + results.push({ + file_path: filePath, + identifier, + type, + start_line, + end_line, + content, + segmentHash, + fileHash, + }) + } + } + } + // Nodes smaller than MIN_BLOCK_CHARS are ignored + } + + return results + } + + /** + * Common helper function to chunk text by lines, avoiding tiny remainders. + */ + private _chunkTextByLines( + lines: string[], + filePath: string, + fileHash: string, + + chunkType: string, + seenSegmentHashes: Set, + baseStartLine: number = 1, // 1-based start line of the *first* line in the `lines` array + ): CodeBlock[] { + const chunks: CodeBlock[] = [] + let currentChunkLines: string[] = [] + let currentChunkLength = 0 + let chunkStartLineIndex = 0 // 0-based index within the `lines` array + const effectiveMaxChars = MAX_BLOCK_CHARS * MAX_CHARS_TOLERANCE_FACTOR + + const finalizeChunk = (endLineIndex: number) => { + if (currentChunkLength >= MIN_BLOCK_CHARS && currentChunkLines.length > 0) { + const chunkContent = currentChunkLines.join("\n") + const startLine = baseStartLine + chunkStartLineIndex + const endLine = baseStartLine + endLineIndex + const segmentHash = createHash("sha256") + .update(`${filePath}-${startLine}-${endLine}-${chunkContent}`) + .digest("hex") + + if (!seenSegmentHashes.has(segmentHash)) { + seenSegmentHashes.add(segmentHash) + chunks.push({ + file_path: filePath, + identifier: null, + type: chunkType, + start_line: startLine, + end_line: endLine, + content: chunkContent, + segmentHash, + fileHash, + }) + } + } + currentChunkLines = [] + currentChunkLength = 0 + chunkStartLineIndex = endLineIndex + 1 + } + + const createSegmentBlock = (segment: string, originalLineNumber: number, startCharIndex: number) => { + const segmentHash = createHash("sha256") + .update(`${filePath}-${originalLineNumber}-${originalLineNumber}-${startCharIndex}-${segment}`) + .digest("hex") + + if (!seenSegmentHashes.has(segmentHash)) { + seenSegmentHashes.add(segmentHash) + chunks.push({ + file_path: filePath, + identifier: null, + type: `${chunkType}_segment`, + start_line: originalLineNumber, + end_line: originalLineNumber, + content: segment, + segmentHash, + fileHash, + }) + } + } + + for (let i = 0; i < lines.length; i++) { + const line = lines[i] + const lineLength = line.length + (i < lines.length - 1 ? 1 : 0) // +1 for newline, except last line + const originalLineNumber = baseStartLine + i + + // Handle oversized lines (longer than effectiveMaxChars) + if (lineLength > effectiveMaxChars) { + // Finalize any existing normal chunk before processing the oversized line + if (currentChunkLines.length > 0) { + finalizeChunk(i - 1) + } + + // Split the oversized line into segments + let remainingLineContent = line + let currentSegmentStartChar = 0 + while (remainingLineContent.length > 0) { + const segment = remainingLineContent.substring(0, MAX_BLOCK_CHARS) + remainingLineContent = remainingLineContent.substring(MAX_BLOCK_CHARS) + createSegmentBlock(segment, originalLineNumber, currentSegmentStartChar) + currentSegmentStartChar += MAX_BLOCK_CHARS + } + continue + } + + // Handle normally sized lines + if (currentChunkLength > 0 && currentChunkLength + lineLength > effectiveMaxChars) { + // Re-balancing Logic + let splitIndex = i - 1 + let remainderLength = 0 + for (let j = i; j < lines.length; j++) { + remainderLength += lines[j].length + (j < lines.length - 1 ? 1 : 0) + } + + if ( + currentChunkLength >= MIN_BLOCK_CHARS && + remainderLength < MIN_CHUNK_REMAINDER_CHARS && + currentChunkLines.length > 1 + ) { + for (let k = i - 2; k >= chunkStartLineIndex; k--) { + const potentialChunkLines = lines.slice(chunkStartLineIndex, k + 1) + const potentialChunkLength = potentialChunkLines.join("\n").length + 1 + const potentialNextChunkLines = lines.slice(k + 1) + const potentialNextChunkLength = potentialNextChunkLines.join("\n").length + 1 + + if ( + potentialChunkLength >= MIN_BLOCK_CHARS && + potentialNextChunkLength >= MIN_CHUNK_REMAINDER_CHARS + ) { + splitIndex = k + break + } + } + } + + finalizeChunk(splitIndex) + + if (i >= chunkStartLineIndex) { + currentChunkLines.push(line) + currentChunkLength += lineLength + } else { + i = chunkStartLineIndex - 1 + continue + } + } else { + currentChunkLines.push(line) + currentChunkLength += lineLength + } + } + + // Process the last remaining chunk + if (currentChunkLines.length > 0) { + finalizeChunk(lines.length - 1) + } + + return chunks + } + + private _performFallbackChunking( + filePath: string, + content: string, + fileHash: string, + seenSegmentHashes: Set, + ): CodeBlock[] { + const lines = content.split("\n") + return this._chunkTextByLines(lines, filePath, fileHash, "fallback_chunk", seenSegmentHashes) + } + + private _chunkLeafNodeByLines( + node: treeSitter.SyntaxNode, + filePath: string, + fileHash: string, + seenSegmentHashes: Set, + ): CodeBlock[] { + const lines = node.text.split("\n") + const baseStartLine = node.startPosition.row + 1 + return this._chunkTextByLines( + lines, + filePath, + fileHash, + node.type, // Use the node's type + seenSegmentHashes, + baseStartLine, + ) + } +} + +// Export a singleton instance for convenience +export const codeParser = new CodeParser() diff --git a/src/services/code-index/processors/scanner.ts b/src/services/code-index/processors/scanner.ts new file mode 100644 index 0000000000..1233b0e0d5 --- /dev/null +++ b/src/services/code-index/processors/scanner.ts @@ -0,0 +1,340 @@ +import { listFiles } from "../../glob/list-files" +import { Ignore } from "ignore" +import { RooIgnoreController } from "../../../core/ignore/RooIgnoreController" +import { stat } from "fs/promises" +import * as path from "path" +import { generateNormalizedAbsolutePath, generateRelativeFilePath } from "../shared/get-relative-path" +import { scannerExtensions } from "../shared/supported-extensions" +import * as vscode from "vscode" +import { CodeBlock, ICodeParser, IEmbedder, IVectorStore, IDirectoryScanner } from "../interfaces" +import { createHash } from "crypto" +import { v5 as uuidv5 } from "uuid" +import pLimit from "p-limit" +import { Mutex } from "async-mutex" +import { CacheManager } from "../cache-manager" +import { + QDRANT_CODE_BLOCK_NAMESPACE, + MAX_FILE_SIZE_BYTES, + MAX_LIST_FILES_LIMIT, + BATCH_SEGMENT_THRESHOLD, + MAX_BATCH_RETRIES, + INITIAL_RETRY_DELAY_MS, + PARSING_CONCURRENCY, + BATCH_PROCESSING_CONCURRENCY, +} from "../constants" + +export class DirectoryScanner implements IDirectoryScanner { + constructor( + private readonly embedder: IEmbedder, + private readonly qdrantClient: IVectorStore, + private readonly codeParser: ICodeParser, + private readonly cacheManager: CacheManager, + private readonly ignoreInstance: Ignore, + ) {} + + /** + * Recursively scans a directory for code blocks in supported files. + * @param directoryPath The directory to scan + * @param rooIgnoreController Optional RooIgnoreController instance for filtering + * @param context VS Code ExtensionContext for cache storage + * @param onError Optional error handler callback + * @returns Promise<{codeBlocks: CodeBlock[], stats: {processed: number, skipped: number}}> Array of parsed code blocks and processing stats + */ + public async scanDirectory( + directory: string, + onError?: (error: Error) => void, + onBlocksIndexed?: (indexedCount: number) => void, + onFileParsed?: (fileBlockCount: number) => void, + ): Promise<{ codeBlocks: CodeBlock[]; stats: { processed: number; skipped: number }; totalBlockCount: number }> { + const directoryPath = directory + // Get all files recursively (handles .gitignore automatically) + const [allPaths, _] = await listFiles(directoryPath, true, MAX_LIST_FILES_LIMIT) + + // Filter out directories (marked with trailing '/') + const filePaths = allPaths.filter((p) => !p.endsWith("/")) + + // Initialize RooIgnoreController if not provided + const ignoreController = new RooIgnoreController(directoryPath) + + await ignoreController.initialize() + + // Filter paths using .rooignore + const allowedPaths = ignoreController.filterPaths(filePaths) + + // Filter by supported extensions and ignore patterns + const supportedPaths = allowedPaths.filter((filePath) => { + const ext = path.extname(filePath).toLowerCase() + const relativeFilePath = generateRelativeFilePath(filePath) + return scannerExtensions.includes(ext) && !this.ignoreInstance.ignores(relativeFilePath) + }) + + // Initialize tracking variables + const processedFiles = new Set() + const codeBlocks: CodeBlock[] = [] + let processedCount = 0 + let skippedCount = 0 + + // Initialize parallel processing tools + const parseLimiter = pLimit(PARSING_CONCURRENCY) // Concurrency for file parsing + const batchLimiter = pLimit(BATCH_PROCESSING_CONCURRENCY) // Concurrency for batch processing + const mutex = new Mutex() + + // Shared batch accumulators (protected by mutex) + let currentBatchBlocks: CodeBlock[] = [] + let currentBatchTexts: string[] = [] + let currentBatchFileInfos: { filePath: string; fileHash: string; isNew: boolean }[] = [] + const activeBatchPromises: Promise[] = [] + + // Initialize block counter + let totalBlockCount = 0 + + // Process all files in parallel with concurrency control + const parsePromises = supportedPaths.map((filePath) => + parseLimiter(async () => { + try { + // Check file size + const stats = await stat(filePath) + if (stats.size > MAX_FILE_SIZE_BYTES) { + skippedCount++ // Skip large files + return + } + + // Read file content + const content = await vscode.workspace.fs + .readFile(vscode.Uri.file(filePath)) + .then((buffer) => Buffer.from(buffer).toString("utf-8")) + + // Calculate current hash + const currentFileHash = createHash("sha256").update(content).digest("hex") + processedFiles.add(filePath) + + // Check against cache + const cachedFileHash = this.cacheManager.getHash(filePath) + if (cachedFileHash === currentFileHash) { + // File is unchanged + skippedCount++ + return + } + + // File is new or changed - parse it using the injected parser function + const blocks = await this.codeParser.parseFile(filePath, { content, fileHash: currentFileHash }) + const fileBlockCount = blocks.length + onFileParsed?.(fileBlockCount) + codeBlocks.push(...blocks) + processedCount++ + + // Process embeddings if configured + if (this.embedder && this.qdrantClient && blocks.length > 0) { + // Add to batch accumulators + let addedBlocksFromFile = false + for (const block of blocks) { + const trimmedContent = block.content.trim() + if (trimmedContent) { + const release = await mutex.acquire() + totalBlockCount += fileBlockCount + try { + currentBatchBlocks.push(block) + currentBatchTexts.push(trimmedContent) + addedBlocksFromFile = true + + if (addedBlocksFromFile) { + currentBatchFileInfos.push({ + filePath, + fileHash: currentFileHash, + isNew: !this.cacheManager.getHash(filePath), + }) + } + + // Check if batch threshold is met and not for Ollama + if ( + currentBatchBlocks.length >= BATCH_SEGMENT_THRESHOLD && + this.embedder.embedderInfo.name !== "ollama" + ) { + // Copy current batch data and clear accumulators + const batchBlocks = [...currentBatchBlocks] + const batchTexts = [...currentBatchTexts] + const batchFileInfos = [...currentBatchFileInfos] + currentBatchBlocks = [] + currentBatchTexts = [] + currentBatchFileInfos = [] + + // Queue batch processing + const batchPromise = batchLimiter(() => + this.processBatch( + batchBlocks, + batchTexts, + batchFileInfos, + onError, + onBlocksIndexed, + ), + ) + activeBatchPromises.push(batchPromise) + } + } finally { + release() + } + } + } + } else { + // Only update hash if not being processed in a batch + await this.cacheManager.updateHash(filePath, currentFileHash) + } + } catch (error) { + console.error(`Error processing file ${filePath}:`, error) + if (onError) { + onError(error instanceof Error ? error : new Error(`Unknown error processing file ${filePath}`)) + } + } + }), + ) + + // Wait for all parsing to complete + await Promise.all(parsePromises) + + // Process any remaining items in batch + if (currentBatchBlocks.length > 0) { + const release = await mutex.acquire() + try { + // Copy current batch data and clear accumulators + const batchBlocks = [...currentBatchBlocks] + const batchTexts = [...currentBatchTexts] + const batchFileInfos = [...currentBatchFileInfos] + currentBatchBlocks = [] + currentBatchTexts = [] + currentBatchFileInfos = [] + + // Queue final batch processing + const batchPromise = batchLimiter(() => + this.processBatch(batchBlocks, batchTexts, batchFileInfos, onError, onBlocksIndexed), + ) + activeBatchPromises.push(batchPromise) + } finally { + release() + } + } + + // Wait for all batch processing to complete + await Promise.all(activeBatchPromises) + + // Handle deleted files + const oldHashes = this.cacheManager.getAllHashes() + for (const cachedFilePath of Object.keys(oldHashes)) { + if (!processedFiles.has(cachedFilePath)) { + // File was deleted or is no longer supported/indexed + if (this.qdrantClient) { + try { + await this.qdrantClient.deletePointsByFilePath(cachedFilePath) + await this.cacheManager.deleteHash(cachedFilePath) + } catch (error) { + console.error(`[DirectoryScanner] Failed to delete points for ${cachedFilePath}:`, error) + if (onError) { + onError( + error instanceof Error + ? error + : new Error(`Unknown error deleting points for ${cachedFilePath}`), + ) + } + // Decide if we should re-throw or just log + } + } + } + } + + return { + codeBlocks, + stats: { + processed: processedCount, + skipped: skippedCount, + }, + totalBlockCount, + } + } + + private async processBatch( + batchBlocks: CodeBlock[], + batchTexts: string[], + batchFileInfos: { filePath: string; fileHash: string; isNew: boolean }[], + onError?: (error: Error) => void, + onBlocksIndexed?: (indexedCount: number) => void, + ): Promise { + if (batchBlocks.length === 0) return + + let attempts = 0 + let success = false + let lastError: Error | null = null + + while (attempts < MAX_BATCH_RETRIES && !success) { + attempts++ + try { + // --- Deletion Step --- + const uniqueFilePaths = [ + ...new Set( + batchFileInfos + .filter((info) => !info.isNew) // Only modified files (not new) + .map((info) => info.filePath), + ), + ] + if (uniqueFilePaths.length > 0) { + try { + await this.qdrantClient.deletePointsByMultipleFilePaths(uniqueFilePaths) + } catch (deleteError) { + console.error( + `[DirectoryScanner] Failed to delete points for ${uniqueFilePaths.length} files before upsert:`, + deleteError, + ) + // Re-throw the error to stop processing this batch attempt + throw deleteError + } + } + // --- End Deletion Step --- + + // Create embeddings for batch + const { embeddings } = await this.embedder.createEmbeddings(batchTexts) + + // Prepare points for Qdrant + const points = batchBlocks.map((block, index) => { + const normalizedAbsolutePath = generateNormalizedAbsolutePath(block.file_path) + + const stableName = `${normalizedAbsolutePath}:${block.start_line}` + const pointId = uuidv5(stableName, QDRANT_CODE_BLOCK_NAMESPACE) + + return { + id: pointId, + vector: embeddings[index], + payload: { + filePath: generateRelativeFilePath(normalizedAbsolutePath), + codeChunk: block.content, + startLine: block.start_line, + endLine: block.end_line, + }, + } + }) + + // Upsert points to Qdrant + await this.qdrantClient.upsertPoints(points) + onBlocksIndexed?.(batchBlocks.length) + + // Update hashes for successfully processed files in this batch + for (const fileInfo of batchFileInfos) { + await this.cacheManager.updateHash(fileInfo.filePath, fileInfo.fileHash) + } + success = true + } catch (error) { + lastError = error as Error + console.error(`[DirectoryScanner] Error processing batch (attempt ${attempts}):`, error) + + if (attempts < MAX_BATCH_RETRIES) { + const delay = INITIAL_RETRY_DELAY_MS * Math.pow(2, attempts - 1) + await new Promise((resolve) => setTimeout(resolve, delay)) + } + } + } + + if (!success && lastError) { + console.error(`[DirectoryScanner] Failed to process batch after ${MAX_BATCH_RETRIES} attempts`) + if (onError) { + onError(new Error(`Failed to process batch after ${MAX_BATCH_RETRIES} attempts: ${lastError.message}`)) + } + } + } +} diff --git a/src/services/code-index/search-service.ts b/src/services/code-index/search-service.ts new file mode 100644 index 0000000000..acf6afbf7e --- /dev/null +++ b/src/services/code-index/search-service.ts @@ -0,0 +1,63 @@ +import * as path from "path" +import { VectorStoreSearchResult } from "./interfaces" +import { IEmbedder } from "./interfaces/embedder" +import { IVectorStore } from "./interfaces/vector-store" +import { CodeIndexConfigManager } from "./config-manager" +import { CodeIndexStateManager } from "./state-manager" + +/** + * Service responsible for searching the code index. + */ +export class CodeIndexSearchService { + constructor( + private readonly configManager: CodeIndexConfigManager, + private readonly stateManager: CodeIndexStateManager, + private readonly embedder: IEmbedder, + private readonly vectorStore: IVectorStore, + ) {} + + /** + * Searches the code index for relevant content. + * @param query The search query + * @param limit Maximum number of results to return + * @param directoryPrefix Optional directory path to filter results by + * @returns Array of search results + * @throws Error if the service is not properly configured or ready + */ + public async searchIndex(query: string, directoryPrefix?: string): Promise { + if (!this.configManager.isFeatureEnabled || !this.configManager.isFeatureConfigured) { + throw new Error("Code index feature is disabled or not configured.") + } + + const minScore = this.configManager.currentSearchMinScore + + const currentState = this.stateManager.getCurrentStatus().systemStatus + if (currentState !== "Indexed" && currentState !== "Indexing") { + // Allow search during Indexing too + throw new Error(`Code index is not ready for search. Current state: ${currentState}`) + } + + try { + // Generate embedding for query + const embeddingResponse = await this.embedder.createEmbeddings([query]) + const vector = embeddingResponse?.embeddings[0] + if (!vector) { + throw new Error("Failed to generate embedding for query.") + } + + // Handle directory prefix + let normalizedPrefix: string | undefined = undefined + if (directoryPrefix) { + normalizedPrefix = path.normalize(directoryPrefix) + } + + // Perform search + const results = await this.vectorStore.search(vector, normalizedPrefix, minScore) + return results + } catch (error) { + console.error("[CodeIndexSearchService] Error during search:", error) + this.stateManager.setSystemState("Error", `Search failed: ${(error as Error).message}`) + throw error // Re-throw the error after setting state + } + } +} diff --git a/src/services/code-index/service-factory.ts b/src/services/code-index/service-factory.ts new file mode 100644 index 0000000000..84304a348c --- /dev/null +++ b/src/services/code-index/service-factory.ts @@ -0,0 +1,134 @@ +import * as vscode from "vscode" +import { OpenAiEmbedder } from "./embedders/openai" +import { CodeIndexOllamaEmbedder } from "./embedders/ollama" +import { EmbedderProvider, getDefaultModelId, getModelDimension } from "../../shared/embeddingModels" +import { QdrantVectorStore } from "./vector-store/qdrant-client" +import { codeParser, DirectoryScanner, FileWatcher } from "./processors" +import { ICodeParser, IEmbedder, IFileWatcher, IVectorStore } from "./interfaces" +import { CodeIndexConfigManager } from "./config-manager" +import { CacheManager } from "./cache-manager" +import { Ignore } from "ignore" + +/** + * Factory class responsible for creating and configuring code indexing service dependencies. + */ +export class CodeIndexServiceFactory { + constructor( + private readonly configManager: CodeIndexConfigManager, + private readonly workspacePath: string, + private readonly cacheManager: CacheManager, + ) {} + + /** + * Creates an embedder instance based on the current configuration. + */ + public createEmbedder(): IEmbedder { + const config = this.configManager.getConfig() + + const provider = config.embedderProvider as EmbedderProvider + + if (provider === "openai") { + if (!config.openAiOptions?.openAiNativeApiKey) { + throw new Error("OpenAI configuration missing for embedder creation") + } + return new OpenAiEmbedder(config.openAiOptions) // Reverted temporarily + } else if (provider === "ollama") { + if (!config.ollamaOptions?.ollamaBaseUrl) { + throw new Error("Ollama configuration missing for embedder creation") + } + return new CodeIndexOllamaEmbedder(config.ollamaOptions) // Reverted temporarily + } + + throw new Error(`Invalid embedder type configured: ${config.embedderProvider}`) + } + + /** + * Creates a vector store instance using the current configuration. + */ + public createVectorStore(): IVectorStore { + const config = this.configManager.getConfig() + + const provider = config.embedderProvider as EmbedderProvider + const defaultModel = getDefaultModelId(provider) + // Determine the modelId based on the provider and config, using apiModelId + const modelId = + provider === "openai" + ? (config.openAiOptions?.apiModelId ?? defaultModel) + : (config.ollamaOptions?.apiModelId ?? defaultModel) + + const vectorSize = getModelDimension(provider, modelId) + + if (vectorSize === undefined) { + throw new Error( + `Could not determine vector dimension for model '${modelId}'. Check model profiles or config.`, + ) + } + + if (!config.qdrantUrl) { + // This check remains important + throw new Error("Qdrant URL missing for vector store creation") + } + + // Assuming constructor is updated: new QdrantVectorStore(workspacePath, url, vectorSize, apiKey?) + return new QdrantVectorStore(this.workspacePath, config.qdrantUrl, vectorSize, config.qdrantApiKey) + } + + /** + * Creates a directory scanner instance with its required dependencies. + */ + public createDirectoryScanner( + embedder: IEmbedder, + vectorStore: IVectorStore, + parser: ICodeParser, + ignoreInstance: Ignore, + ): DirectoryScanner { + return new DirectoryScanner(embedder, vectorStore, parser, this.cacheManager, ignoreInstance) + } + + /** + * Creates a file watcher instance with its required dependencies. + */ + public createFileWatcher( + context: vscode.ExtensionContext, + embedder: IEmbedder, + vectorStore: IVectorStore, + cacheManager: CacheManager, + ignoreInstance: Ignore, + ): IFileWatcher { + return new FileWatcher(this.workspacePath, context, cacheManager, embedder, vectorStore, ignoreInstance) + } + + /** + * Creates all required service dependencies if the service is properly configured. + * @throws Error if the service is not properly configured + */ + public createServices( + context: vscode.ExtensionContext, + cacheManager: CacheManager, + ignoreInstance: Ignore, + ): { + embedder: IEmbedder + vectorStore: IVectorStore + parser: ICodeParser + scanner: DirectoryScanner + fileWatcher: IFileWatcher + } { + if (!this.configManager.isFeatureConfigured) { + throw new Error("Cannot create services: Code indexing is not properly configured") + } + + const embedder = this.createEmbedder() + const vectorStore = this.createVectorStore() + const parser = codeParser + const scanner = this.createDirectoryScanner(embedder, vectorStore, parser, ignoreInstance) + const fileWatcher = this.createFileWatcher(context, embedder, vectorStore, cacheManager, ignoreInstance) + + return { + embedder, + vectorStore, + parser, + scanner, + fileWatcher, + } + } +} diff --git a/src/services/code-index/shared/get-relative-path.ts b/src/services/code-index/shared/get-relative-path.ts new file mode 100644 index 0000000000..564afee955 --- /dev/null +++ b/src/services/code-index/shared/get-relative-path.ts @@ -0,0 +1,34 @@ +import path from "path" +import { getWorkspacePath } from "../../../utils/path" + +/** + * Generates a normalized absolute path from a given file path and workspace root. + * Handles path resolution and normalization to ensure consistent absolute paths. + * + * @param filePath - The file path to normalize (can be relative or absolute) + * @param workspaceRoot - The root directory of the workspace + * @returns The normalized absolute path + */ +export function generateNormalizedAbsolutePath(filePath: string): string { + const workspaceRoot = getWorkspacePath() + // Resolve the path to make it absolute if it's relative + const resolvedPath = path.resolve(workspaceRoot, filePath) + // Normalize to handle any . or .. segments and duplicate slashes + return path.normalize(resolvedPath) +} + +/** + * Generates a relative file path from a normalized absolute path and workspace root. + * Ensures consistent relative path generation across different platforms. + * + * @param normalizedAbsolutePath - The normalized absolute path to convert + * @param workspaceRoot - The root directory of the workspace + * @returns The relative path from workspaceRoot to the file + */ +export function generateRelativeFilePath(normalizedAbsolutePath: string): string { + const workspaceRoot = getWorkspacePath() + // Generate the relative path + const relativePath = path.relative(workspaceRoot, normalizedAbsolutePath) + // Normalize to ensure consistent path separators + return path.normalize(relativePath) +} diff --git a/src/services/code-index/shared/supported-extensions.ts b/src/services/code-index/shared/supported-extensions.ts new file mode 100644 index 0000000000..91e3d29c83 --- /dev/null +++ b/src/services/code-index/shared/supported-extensions.ts @@ -0,0 +1,4 @@ +import { extensions as allExtensions } from "../../tree-sitter" + +// Filter out markdown extensions for the scanner +export const scannerExtensions = allExtensions.filter((ext) => ext !== ".md" && ext !== ".markdown") diff --git a/src/services/code-index/state-manager.ts b/src/services/code-index/state-manager.ts new file mode 100644 index 0000000000..90257fdfb1 --- /dev/null +++ b/src/services/code-index/state-manager.ts @@ -0,0 +1,115 @@ +import * as vscode from "vscode" + +export type IndexingState = "Standby" | "Indexing" | "Indexed" | "Error" + +export class CodeIndexStateManager { + private _systemStatus: IndexingState = "Standby" + private _statusMessage: string = "" + private _processedItems: number = 0 + private _totalItems: number = 0 + private _currentItemUnit: string = "blocks" + private _progressEmitter = new vscode.EventEmitter>() + + // --- Public API --- + + public readonly onProgressUpdate = this._progressEmitter.event + + public get state(): IndexingState { + return this._systemStatus + } + + public getCurrentStatus() { + return { + systemStatus: this._systemStatus, + message: this._statusMessage, + processedItems: this._processedItems, + totalItems: this._totalItems, + currentItemUnit: this._currentItemUnit, + } + } + + // --- State Management --- + + public setSystemState(newState: IndexingState, message?: string): void { + const stateChanged = + newState !== this._systemStatus || (message !== undefined && message !== this._statusMessage) + + if (stateChanged) { + this._systemStatus = newState + if (message !== undefined) { + this._statusMessage = message + } + + // Reset progress counters if moving to a non-indexing state or starting fresh + if (newState !== "Indexing") { + this._processedItems = 0 + this._totalItems = 0 + this._currentItemUnit = "blocks" // Reset to default unit + // Optionally clear the message or set a default for non-indexing states + if (newState === "Standby" && message === undefined) this._statusMessage = "Ready." + if (newState === "Indexed" && message === undefined) this._statusMessage = "Index up-to-date." + if (newState === "Error" && message === undefined) this._statusMessage = "An error occurred." + } + + this._progressEmitter.fire(this.getCurrentStatus()) + } + } + + public reportBlockIndexingProgress(processedItems: number, totalItems: number): void { + const progressChanged = processedItems !== this._processedItems || totalItems !== this._totalItems + + // Update if progress changes OR if the system wasn't already in 'Indexing' state + if (progressChanged || this._systemStatus !== "Indexing") { + this._processedItems = processedItems + this._totalItems = totalItems + this._currentItemUnit = "blocks" + + const message = `Indexed ${this._processedItems} / ${this._totalItems} ${this._currentItemUnit} found` + const oldStatus = this._systemStatus + const oldMessage = this._statusMessage + + this._systemStatus = "Indexing" // Ensure state is Indexing + this._statusMessage = message + + // Only fire update if status, message or progress actually changed + if (oldStatus !== this._systemStatus || oldMessage !== this._statusMessage || progressChanged) { + this._progressEmitter.fire(this.getCurrentStatus()) + } + } + } + + public reportFileQueueProgress(processedFiles: number, totalFiles: number, currentFileBasename?: string): void { + const progressChanged = processedFiles !== this._processedItems || totalFiles !== this._totalItems + + if (progressChanged || this._systemStatus !== "Indexing") { + this._processedItems = processedFiles + this._totalItems = totalFiles + this._currentItemUnit = "files" + this._systemStatus = "Indexing" + + let message: string + if (totalFiles > 0 && processedFiles < totalFiles) { + message = `Processing ${processedFiles} / ${totalFiles} ${this._currentItemUnit}. Current: ${ + currentFileBasename || "..." + }` + } else if (totalFiles > 0 && processedFiles === totalFiles) { + message = `Finished processing ${totalFiles} ${this._currentItemUnit} from queue.` + } else { + message = `File queue processed.` + } + + const oldStatus = this._systemStatus + const oldMessage = this._statusMessage + + this._statusMessage = message + + if (oldStatus !== this._systemStatus || oldMessage !== this._statusMessage || progressChanged) { + this._progressEmitter.fire(this.getCurrentStatus()) + } + } + } + + public dispose(): void { + this._progressEmitter.dispose() + } +} diff --git a/src/services/code-index/vector-store/qdrant-client.ts b/src/services/code-index/vector-store/qdrant-client.ts new file mode 100644 index 0000000000..38d40d943e --- /dev/null +++ b/src/services/code-index/vector-store/qdrant-client.ts @@ -0,0 +1,277 @@ +import { QdrantClient } from "@qdrant/js-client-rest" +import { createHash } from "crypto" +import * as path from "path" +import { getWorkspacePath } from "../../../utils/path" +import { IVectorStore } from "../interfaces/vector-store" +import { Payload, VectorStoreSearchResult } from "../interfaces" +import { MAX_SEARCH_RESULTS, SEARCH_MIN_SCORE } from "../constants" + +/** + * Qdrant implementation of the vector store interface + */ +export class QdrantVectorStore implements IVectorStore { + private readonly QDRANT_URL = "http://localhost:6333" + private readonly vectorSize!: number + private readonly DISTANCE_METRIC = "Cosine" + + private client: QdrantClient + private readonly collectionName: string + + /** + * Creates a new Qdrant vector store + * @param workspacePath Path to the workspace + * @param url Optional URL to the Qdrant server + */ + constructor(workspacePath: string, url: string, vectorSize: number, apiKey?: string) { + this.client = new QdrantClient({ + url: url ?? this.QDRANT_URL, + apiKey, + headers: { + "User-Agent": "Roo-Code", + }, + }) + + // Generate collection name from workspace path + const hash = createHash("sha256").update(workspacePath).digest("hex") + this.vectorSize = vectorSize + this.collectionName = `ws-${hash.substring(0, 16)}` + } + + /** + * Initializes the vector store + * @returns Promise resolving to boolean indicating if a new collection was created + */ + async initialize(): Promise { + try { + let created = false + const collections = await this.client.getCollections() + const collectionExists = collections.collections.some( + (collection) => collection.name === this.collectionName, + ) + + if (!collectionExists) { + await this.client.createCollection(this.collectionName, { + vectors: { + size: this.vectorSize, + distance: this.DISTANCE_METRIC, + }, + }) + created = true + } + + // Create payload indexes for pathSegments up to depth 5 + for (let i = 0; i <= 4; i++) { + try { + await this.client.createPayloadIndex(this.collectionName, { + field_name: `pathSegments.${i}`, + field_schema: "keyword", + }) + } catch (indexError) { + console.warn( + `[QdrantVectorStore] Could not create payload index for pathSegments.${i} on ${this.collectionName}. It might already exist or there was an issue.`, + indexError, + ) + } + } + + return created + } catch (error) { + console.error("Failed to initialize Qdrant collection:", error) + throw error + } + } + + /** + * Upserts points into the vector store + * @param points Array of points to upsert + */ + async upsertPoints( + points: Array<{ + id: string + vector: number[] + payload: Record + }>, + ): Promise { + try { + const processedPoints = points.map((point) => { + if (point.payload?.filePath) { + const segments = point.payload.filePath.split(path.sep).filter(Boolean) + const pathSegments = segments.reduce( + (acc: Record, segment: string, index: number) => { + acc[index.toString()] = segment + return acc + }, + {}, + ) + return { + ...point, + payload: { + ...point.payload, + pathSegments, + }, + } + } + return point + }) + + await this.client.upsert(this.collectionName, { + points: processedPoints, + wait: true, + }) + } catch (error) { + console.error("Failed to upsert points:", error) + throw error + } + } + + /** + * Checks if a payload is valid + * @param payload Payload to check + * @returns Boolean indicating if the payload is valid + */ + private isPayloadValid(payload: Record): payload is Payload { + return "filePath" in payload && "codeChunk" in payload && "startLine" in payload && "endLine" in payload + } + + /** + * Searches for similar vectors + * @param queryVector Vector to search for + * @param limit Maximum number of results to return + * @returns Promise resolving to search results + */ + async search( + queryVector: number[], + directoryPrefix?: string, + minScore?: number, + ): Promise { + try { + let filter = undefined + + if (directoryPrefix) { + const segments = directoryPrefix.split(path.sep).filter(Boolean) + + filter = { + must: segments.map((segment, index) => ({ + key: `pathSegments.${index}`, + match: { value: segment }, + })), + } + } + + const searchRequest = { + query: queryVector, + filter, + score_threshold: SEARCH_MIN_SCORE, + limit: MAX_SEARCH_RESULTS, + params: { + hnsw_ef: 128, + exact: false, + }, + with_payload: { + include: ["filePath", "codeChunk", "startLine", "endLine", "pathSegments"], + }, + } + + if (minScore !== undefined) { + searchRequest.score_threshold = minScore + } + + const operationResult = await this.client.query(this.collectionName, searchRequest) + const filteredPoints = operationResult.points.filter((p) => this.isPayloadValid(p.payload!)) + + return filteredPoints as VectorStoreSearchResult[] + } catch (error) { + console.error("Failed to search points:", error) + throw error + } + } + + /** + * Deletes points by file path + * @param filePath Path of the file to delete points for + */ + async deletePointsByFilePath(filePath: string): Promise { + return this.deletePointsByMultipleFilePaths([filePath]) + } + + async deletePointsByMultipleFilePaths(filePaths: string[]): Promise { + if (filePaths.length === 0) { + return + } + + try { + const workspaceRoot = getWorkspacePath() + const normalizedPaths = filePaths.map((filePath) => { + const absolutePath = path.resolve(workspaceRoot, filePath) + return path.normalize(absolutePath) + }) + + const filter = { + should: normalizedPaths.map((normalizedPath) => ({ + key: "filePath", + match: { + value: normalizedPath, + }, + })), + } + + await this.client.delete(this.collectionName, { + filter, + wait: true, + }) + } catch (error) { + console.error("Failed to delete points by file paths:", error) + throw error + } + } + + /** + * Deletes the entire collection. + */ + async deleteCollection(): Promise { + try { + // Check if collection exists before attempting deletion to avoid errors + if (await this.collectionExists()) { + await this.client.deleteCollection(this.collectionName) + } + } catch (error) { + console.error(`[QdrantVectorStore] Failed to delete collection ${this.collectionName}:`, error) + throw error // Re-throw to allow calling code to handle it + } + } + + /** + * Clears all points from the collection + */ + async clearCollection(): Promise { + try { + await this.client.delete(this.collectionName, { + filter: { + must: [], + }, + wait: true, + }) + } catch (error) { + console.error("Failed to clear collection:", error) + throw error + } + } + + /** + * Checks if the collection exists + * @returns Promise resolving to boolean indicating if the collection exists + */ + async collectionExists(): Promise { + try { + // Prefer direct API call if supported + await this.client.getCollection(this.collectionName) + return true + } catch (error: any) { + if (error?.response?.status === 404) { + return false + } + console.error("Error checking collection existence:", error) + return false + } + } +} diff --git a/src/services/tree-sitter/index.ts b/src/services/tree-sitter/index.ts index 590a517b09..51eccad17c 100644 --- a/src/services/tree-sitter/index.ts +++ b/src/services/tree-sitter/index.ts @@ -90,6 +90,8 @@ const extensions = [ "erb", ].map((e) => `.${e}`) +export { extensions } + export async function parseSourceCodeDefinitionsForFile( filePath: string, rooIgnoreController?: RooIgnoreController, diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 8870dcaee0..cd1efbe983 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -71,6 +71,9 @@ export interface ExtensionMessage { | "vsCodeSetting" | "condenseTaskContextResponse" | "singleRouterModelFetchResponse" + | "indexingStatusUpdate" + | "indexCleared" + | "codebaseIndexConfig" text?: string action?: | "chatButtonClicked" @@ -173,6 +176,8 @@ export type ExtensionState = Pick< | "enhancementApiConfigId" | "condensingApiConfigId" | "customCondensingPrompt" + | "codebaseIndexConfig" + | "codebaseIndexModels" > & { version: string clineMessages: ClineMessage[] @@ -219,6 +224,7 @@ export interface ClineSayTool { | "editedExistingFile" | "appliedDiff" | "newFileCreated" + | "codebaseSearch" | "readFile" | "fetchInstructions" | "listFilesTopLevel" @@ -245,6 +251,7 @@ export interface ClineSayTool { startLine?: number endLine?: number lineNumber?: number + query?: string } // Must keep in sync with system prompt. diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index af9b637980..85a12aa238 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -135,6 +135,12 @@ export interface WebviewMessage { | "toggleApiConfigPin" | "setHistoryPreviewCollapsed" | "condenseTaskContextRequest" + | "requestIndexingStatus" + | "startIndexing" + | "clearIndexData" + | "indexingStatusUpdate" + | "indexCleared" + | "codebaseIndexConfig" text?: string disabled?: boolean askResponse?: ClineAskResponse @@ -183,4 +189,18 @@ export const checkoutRestorePayloadSchema = z.object({ export type CheckpointRestorePayload = z.infer -export type WebViewMessagePayload = CheckpointDiffPayload | CheckpointRestorePayload +export interface IndexingStatusPayload { + state: "Standby" | "Indexing" | "Indexed" | "Error" + message: string +} + +export interface IndexClearedPayload { + success: boolean + error?: string +} + +export type WebViewMessagePayload = + | CheckpointDiffPayload + | CheckpointRestorePayload + | IndexingStatusPayload + | IndexClearedPayload diff --git a/src/shared/embeddingModels.ts b/src/shared/embeddingModels.ts new file mode 100644 index 0000000000..abccd017d7 --- /dev/null +++ b/src/shared/embeddingModels.ts @@ -0,0 +1,86 @@ +/** + * Defines profiles for different embedding models, including their dimensions. + */ + +export type EmbedderProvider = "openai" | "ollama" // Add other providers as needed + +export interface EmbeddingModelProfile { + dimension: number + // Add other model-specific properties if needed, e.g., context window size +} + +export type EmbeddingModelProfiles = { + [provider in EmbedderProvider]?: { + [modelId: string]: EmbeddingModelProfile + } +} + +// Example profiles - expand this list as needed +export const EMBEDDING_MODEL_PROFILES: EmbeddingModelProfiles = { + openai: { + "text-embedding-3-small": { dimension: 1536 }, + "text-embedding-3-large": { dimension: 3072 }, + "text-embedding-ada-002": { dimension: 1536 }, + }, + ollama: { + "nomic-embed-text": { dimension: 768 }, + "mxbai-embed-large": { dimension: 1024 }, + "all-minilm": { dimension: 384 }, + // Add default Ollama model if applicable, e.g.: + // 'default': { dimension: 768 } // Assuming a default dimension + }, +} + +/** + * Retrieves the embedding dimension for a given provider and model ID. + * @param provider The embedder provider (e.g., "openai"). + * @param modelId The specific model ID (e.g., "text-embedding-3-small"). + * @returns The dimension size or undefined if the model is not found. + */ +export function getModelDimension(provider: EmbedderProvider, modelId: string): number | undefined { + const providerProfiles = EMBEDDING_MODEL_PROFILES[provider] + if (!providerProfiles) { + console.warn(`Provider not found in profiles: ${provider}`) + return undefined + } + + const modelProfile = providerProfiles[modelId] + if (!modelProfile) { + // Don't warn here, as it might be a custom model ID not in our profiles + // console.warn(`Model not found for provider ${provider}: ${modelId}`) + return undefined // Or potentially return a default/fallback dimension? + } + + return modelProfile.dimension +} + +/** + * Gets the default *specific* embedding model ID based on the provider. + * Does not include the provider prefix. + * Currently defaults to OpenAI's 'text-embedding-3-small'. + * TODO: Make this configurable or more sophisticated. + * @param provider The embedder provider. + * @returns The default specific model ID for the provider (e.g., "text-embedding-3-small"). + */ +export function getDefaultModelId(provider: EmbedderProvider): string { + // Simple default logic for now + if (provider === "openai") { + return "text-embedding-3-small" + } + if (provider === "ollama") { + // Choose a sensible default for Ollama, e.g., the first one listed or a specific one + const ollamaModels = EMBEDDING_MODEL_PROFILES.ollama + const defaultOllamaModel = ollamaModels && Object.keys(ollamaModels)[0] + if (defaultOllamaModel) { + return defaultOllamaModel + } + // Fallback if no Ollama models are defined (shouldn't happen with the constant) + console.warn("No default Ollama model found in profiles.") + // Return a placeholder or throw an error, depending on desired behavior + return "unknown-default" // Placeholder specific model ID + } + + // Fallback for unknown providers + console.warn(`Unknown provider for default model ID: ${provider}. Falling back to OpenAI default.`) + return "text-embedding-3-small" +} diff --git a/src/shared/tools.ts b/src/shared/tools.ts index 1a6eb84ad7..37ab53516e 100644 --- a/src/shared/tools.ts +++ b/src/shared/tools.ts @@ -63,6 +63,7 @@ export const toolParamNames = [ "ignore_case", "start_line", "end_line", + "query", ] as const export type ToolParamName = (typeof toolParamNames)[number] @@ -101,6 +102,11 @@ export interface InsertCodeBlockToolUse extends ToolUse { params: Partial, "path" | "line" | "content">> } +export interface CodebaseSearchToolUse extends ToolUse { + name: "codebase_search" + params: Partial, "query" | "path">> +} + export interface SearchFilesToolUse extends ToolUse { name: "search_files" params: Partial, "path" | "regex" | "file_pattern">> @@ -181,6 +187,7 @@ export const TOOL_DISPLAY_NAMES: Record = { new_task: "create new task", insert_content: "insert content", search_and_replace: "search and replace", + codebase_search: "codebase search", } as const export type { ToolGroup } @@ -188,7 +195,14 @@ export type { ToolGroup } // Define available tool groups. export const TOOL_GROUPS: Record = { read: { - tools: ["read_file", "fetch_instructions", "search_files", "list_files", "list_code_definition_names"], + tools: [ + "read_file", + "fetch_instructions", + "search_files", + "list_files", + "list_code_definition_names", + "codebase_search", + ], }, edit: { tools: ["apply_diff", "write_to_file", "insert_content", "search_and_replace"], diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index 328e917a4b..5c697a5a57 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -34,6 +34,7 @@ import { CommandExecution } from "./CommandExecution" import { CommandExecutionError } from "./CommandExecutionError" import { AutoApprovedRequestLimitWarning } from "./AutoApprovedRequestLimitWarning" import { CondensingContextRow, ContextCondenseRow } from "./ContextCondenseRow" +import CodebaseSearchResultsDisplay from "./CodebaseSearchResultsDisplay" interface ChatRowProps { message: ClineMessage @@ -351,6 +352,21 @@ export const ChatRowContent = ({ /> ) + case "codebaseSearch": { + return ( +
+ {toolIcon("search")} + + {tool.path + ? t("chat:codebaseSearch.wantsToSearchWithPath", { + query: tool.query, + path: tool.path, + }) + : t("chat:codebaseSearch.wantsToSearch", { query: tool.query })} + +
+ ) + } case "newFileCreated": return ( <> @@ -939,6 +955,36 @@ export const ChatRowContent = ({ return } return message.contextCondense ? : null + case "codebase_search_result": + let parsed: { + content: { + query: string + results: Array<{ + filePath: string + score: number + startLine: number + endLine: number + codeChunk: string + }> + } + } | null = null + + try { + if (message.text) { + parsed = JSON.parse(message.text) + } + } catch (error) { + console.error("Failed to parse codebaseSearch content:", error) + } + + if (parsed && !parsed?.content) { + console.error("Invalid codebaseSearch content structure:", parsed.content) + return
Error displaying search results.
+ } + + const { query = "", results = [] } = parsed?.content || {} + + return default: return ( <> diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 2f5eadc3ab..a466f2cd44 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -729,6 +729,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction = ({ filePath, score, startLine, endLine }) => { + const handleClick = () => { + console.log(filePath) + vscode.postMessage({ + type: "openFile", + text: "./" + filePath, + values: { + line: startLine, + }, + }) + } + + return ( +
+
+ + {filePath.split("/").at(-1)}:{startLine}-{endLine} + + + {filePath.split("/").slice(0, -1).join("/")} + +
+
+ ) +} + +export default CodebaseSearchResult diff --git a/webview-ui/src/components/chat/CodebaseSearchResultsDisplay.tsx b/webview-ui/src/components/chat/CodebaseSearchResultsDisplay.tsx new file mode 100644 index 0000000000..f17c0d2b00 --- /dev/null +++ b/webview-ui/src/components/chat/CodebaseSearchResultsDisplay.tsx @@ -0,0 +1,53 @@ +import React, { useState } from "react" +import CodebaseSearchResult from "./CodebaseSearchResult" +import { useTranslation } from "react-i18next" + +interface CodebaseSearchResultsDisplayProps { + query: string + results: Array<{ + filePath: string + score: number + startLine: number + endLine: number + codeChunk: string + }> +} + +const CodebaseSearchResultsDisplay: React.FC = ({ query, results }) => { + const { t } = useTranslation() + const [codebaseSearchResultsExpanded, setCodebaseSearchResultsExpanded] = useState(false) + + return ( +
+
setCodebaseSearchResultsExpanded(!codebaseSearchResultsExpanded)} + className="font-bold cursor-pointer flex items-center justify-between px-2 py-2 rounded border bg-[var(--vscode-editor-background)] border-[var(--vscode-editorGroup-border)]"> + + {t("chat:codebaseSearch.didSearch", { + query, + count: results.length, + })} + + +
+ + {codebaseSearchResultsExpanded && ( +
+ {results.map((result, idx) => ( + + ))} +
+ )} +
+ ) +} + +export default CodebaseSearchResultsDisplay diff --git a/webview-ui/src/components/settings/CodeIndexSettings.tsx b/webview-ui/src/components/settings/CodeIndexSettings.tsx new file mode 100644 index 0000000000..be65a2456c --- /dev/null +++ b/webview-ui/src/components/settings/CodeIndexSettings.tsx @@ -0,0 +1,348 @@ +import React, { useState, useEffect } from "react" +import * as ProgressPrimitive from "@radix-ui/react-progress" +import { useAppTranslation } from "@/i18n/TranslationContext" + +import { VSCodeCheckbox, VSCodeTextField, VSCodeButton } from "@vscode/webview-ui-toolkit/react" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "@/components/ui/alert-dialog" + +import { vscode } from "@/utils/vscode" +import { ExtensionStateContextType } from "@/context/ExtensionStateContext" +import { CodebaseIndexConfig, CodebaseIndexModels, ProviderSettings } from "../../../../src/schemas" +import { EmbedderProvider } from "../../../../src/shared/embeddingModels" +import { z } from "zod" + +import { SetCachedStateField } from "./types" + +interface CodeIndexSettingsProps { + codebaseIndexModels: CodebaseIndexModels | undefined + codebaseIndexConfig: CodebaseIndexConfig | undefined + apiConfiguration: ProviderSettings + setCachedStateField: SetCachedStateField<"codebaseIndexConfig"> + setApiConfigurationField: (field: K, value: ProviderSettings[K]) => void + areSettingsCommitted: boolean +} + +interface IndexingStatusUpdateMessage { + type: "indexingStatusUpdate" + values: { + systemStatus: string + message?: string + processedItems: number + totalItems: number + currentItemUnit?: string + } +} + +export const CodeIndexSettings: React.FC = ({ + codebaseIndexModels, + codebaseIndexConfig, + apiConfiguration, + setCachedStateField, + setApiConfigurationField, + areSettingsCommitted, +}) => { + const { t } = useAppTranslation() + const [indexingStatus, setIndexingStatus] = useState({ + systemStatus: "Standby", + message: "", + processedItems: 0, + totalItems: 0, + currentItemUnit: "items", + }) + + // Safely calculate available models for current provider + const currentProvider = codebaseIndexConfig?.codebaseIndexEmbedderProvider + const modelsForProvider = + currentProvider === "openai" || currentProvider === "ollama" + ? codebaseIndexModels?.[currentProvider] + : codebaseIndexModels?.openai + const availableModelIds = Object.keys(modelsForProvider || {}) + + useEffect(() => { + // Request initial indexing status from extension host + vscode.postMessage({ type: "requestIndexingStatus" }) + + // Set up interval for periodic status updates + + // Set up message listener for status updates + const handleMessage = (event: MessageEvent) => { + if (event.data.type === "indexingStatusUpdate") { + setIndexingStatus({ + systemStatus: event.data.values.systemStatus, + message: event.data.values.message || "", + processedItems: event.data.values.processedItems, + totalItems: event.data.values.totalItems, + currentItemUnit: event.data.values.currentItemUnit || "items", + }) + } + } + + window.addEventListener("message", handleMessage) + + // Cleanup function + return () => { + window.removeEventListener("message", handleMessage) + } + }, [codebaseIndexConfig, codebaseIndexModels]) + + function validateIndexingConfig(config: CodebaseIndexConfig | undefined, apiConfig: ProviderSettings): boolean { + if (!config) return false + + const baseSchema = z.object({ + codebaseIndexQdrantUrl: z.string().url("Qdrant URL must be a valid URL"), + codebaseIndexEmbedderModelId: z.string().min(1, "Model ID is required"), + }) + + const providerSchemas = { + openai: baseSchema.extend({ + codebaseIndexEmbedderProvider: z.literal("openai"), + codeIndexOpenAiKey: z.string().min(1, "OpenAI key is required"), + }), + ollama: baseSchema.extend({ + codebaseIndexEmbedderProvider: z.literal("ollama"), + codebaseIndexEmbedderBaseUrl: z.string().url("Ollama URL must be a valid URL"), + }), + } + + try { + const schema = + config.codebaseIndexEmbedderProvider === "openai" ? providerSchemas.openai : providerSchemas.ollama + + schema.parse({ + ...config, + codeIndexOpenAiKey: apiConfig.codeIndexOpenAiKey, + }) + return true + } catch { + return false + } + } + + const progressPercentage = + indexingStatus.totalItems > 0 + ? (indexingStatus.processedItems / indexingStatus.totalItems) * 100 + : indexingStatus.totalItems === 0 && indexingStatus.processedItems === 0 + ? 100 + : 0 + + const transformValue = 100 - progressPercentage + const transformStyleString = `translateX(-${transformValue}%)` + + return ( + <> + + setCachedStateField("codebaseIndexConfig", { + ...codebaseIndexConfig, + codebaseIndexEnabled: e.target.checked, + }) + }> + ⚠️ {t("settings:codeIndex.enableLabel")} + +
+ {t("settings:codeIndex.enableDescription")} +
+ + {codebaseIndexConfig?.codebaseIndexEnabled && ( +
+
+ + {indexingStatus.systemStatus} + {indexingStatus.message ? ` - ${indexingStatus.message}` : ""} +
+ + {indexingStatus.systemStatus === "Indexing" && ( +
+ + + +
+ )} + +
+ {t("settings:codeIndex.providerLabel")} +
+
+ +
+ + {codebaseIndexConfig?.codebaseIndexEmbedderProvider === "openai" && ( +
+ setApiConfigurationField("codeIndexOpenAiKey", e.target.value)} + style={{ width: "100%" }}> + {t("settings:codeIndex.openaiKeyLabel")} + +
+ )} + +
+ {t("settings:codeIndex.modelLabel")} +
+
+ +
+ + {codebaseIndexConfig?.codebaseIndexEmbedderProvider === "ollama" && ( + <> +
+ + setCachedStateField("codebaseIndexConfig", { + ...codebaseIndexConfig, + codebaseIndexEmbedderBaseUrl: e.target.value, + }) + } + style={{ width: "100%" }}> + {t("settings:codeIndex.ollamaUrlLabel")} + +
+ + )} + +
+ + setCachedStateField("codebaseIndexConfig", { + ...codebaseIndexConfig, + codebaseIndexQdrantUrl: e.target.value, + }) + } + style={{ width: "100%" }}> + {t("settings:codeIndex.qdrantUrlLabel")} + +
+ +
+ setApiConfigurationField("codeIndexQdrantApiKey", e.target.value)} + style={{ width: "100%" }}> + {t("settings:codeIndex.qdrantKeyLabel")} + +
+ +
+ {(indexingStatus.systemStatus === "Error" || indexingStatus.systemStatus === "Standby") && ( + vscode.postMessage({ type: "startIndexing" })} + disabled={ + !areSettingsCommitted || + !validateIndexingConfig(codebaseIndexConfig, apiConfiguration) + }> + {t("settings:codeIndex.startIndexingButton")} + + )} + {(indexingStatus.systemStatus === "Indexed" || indexingStatus.systemStatus === "Error") && ( + + + + {t("settings:codeIndex.clearIndexDataButton")} + + + + + + {t("settings:codeIndex.clearDataDialog.title")} + + + {t("settings:codeIndex.clearDataDialog.description")} + + + + + {t("settings:codeIndex.clearDataDialog.cancelButton")} + + vscode.postMessage({ type: "clearIndexData" })}> + {t("settings:codeIndex.clearDataDialog.confirmButton")} + + + + + )} +
+
+ )} + + ) +} diff --git a/webview-ui/src/components/settings/ExperimentalSettings.tsx b/webview-ui/src/components/settings/ExperimentalSettings.tsx index 4094a10de6..36b5c86a97 100644 --- a/webview-ui/src/components/settings/ExperimentalSettings.tsx +++ b/webview-ui/src/components/settings/ExperimentalSettings.tsx @@ -13,6 +13,9 @@ import { Section } from "./Section" import { ExperimentalFeature } from "./ExperimentalFeature" import { Button, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Slider } from "@/components/ui/" import { VSCodeTextArea } from "@vscode/webview-ui-toolkit/react" +import { CodebaseIndexConfig, CodebaseIndexModels, ProviderSettings } from "../../../../src/schemas" +import { CodeIndexSettings } from "./CodeIndexSettings" +import { ExtensionStateContextType } from "../../context/ExtensionStateContext" const SUMMARY_PROMPT = `\ Your task is to create a detailed summary of the conversation so far, paying close attention to the user's explicit requests and your previous actions. @@ -58,12 +61,18 @@ type ExperimentalSettingsProps = HTMLAttributes & { experiments: Record setExperimentEnabled: SetExperimentEnabled autoCondenseContextPercent: number - setCachedStateField: SetCachedStateField<"autoCondenseContextPercent"> + setCachedStateField: SetCachedStateField<"autoCondenseContextPercent" | "codebaseIndexConfig"> condensingApiConfigId?: string setCondensingApiConfigId: (value: string) => void customCondensingPrompt?: string setCustomCondensingPrompt: (value: string) => void listApiConfigMeta: any[] + // CodeIndexSettings props + codebaseIndexModels: CodebaseIndexModels | undefined + codebaseIndexConfig: CodebaseIndexConfig | undefined + apiConfiguration: ProviderSettings + setApiConfigurationField: (field: K, value: ProviderSettings[K]) => void + areSettingsCommitted: boolean } export const ExperimentalSettings = ({ @@ -76,6 +85,11 @@ export const ExperimentalSettings = ({ customCondensingPrompt, setCustomCondensingPrompt, listApiConfigMeta, + codebaseIndexModels, + codebaseIndexConfig, + apiConfiguration, + setApiConfigurationField, + areSettingsCommitted, className, ...props }: ExperimentalSettingsProps) => { @@ -210,6 +224,15 @@ export const ExperimentalSettings = ({ )} + + } + setApiConfigurationField={setApiConfigurationField} + areSettingsCommitted={areSettingsCommitted} + /> ) diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index 90031c6924..005bd23696 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -163,6 +163,8 @@ const SettingsView = forwardRef(({ onDone, t terminalCompressProgressBar, condensingApiConfigId, customCondensingPrompt, + codebaseIndexConfig, + codebaseIndexModels, } = cachedState const apiConfiguration = useMemo(() => cachedState.apiConfiguration ?? {}, [cachedState.apiConfiguration]) @@ -290,6 +292,7 @@ const SettingsView = forwardRef(({ onDone, t vscode.postMessage({ type: "updateCondensingPrompt", text: customCondensingPrompt || "" }) vscode.postMessage({ type: "upsertApiConfiguration", text: currentApiConfigName, apiConfiguration }) vscode.postMessage({ type: "telemetrySetting", text: telemetrySetting }) + vscode.postMessage({ type: "codebaseIndexConfig", values: codebaseIndexConfig }) setChangeDetected(false) } } @@ -638,12 +641,17 @@ const SettingsView = forwardRef(({ onDone, t setExperimentEnabled={setExperimentEnabled} experiments={experiments} autoCondenseContextPercent={autoCondenseContextPercent} - setCachedStateField={setCachedStateField} condensingApiConfigId={condensingApiConfigId} setCondensingApiConfigId={(value) => setCachedStateField("condensingApiConfigId", value)} customCondensingPrompt={customCondensingPrompt} setCustomCondensingPrompt={(value) => setCachedStateField("customCondensingPrompt", value)} listApiConfigMeta={listApiConfigMeta ?? []} + setCachedStateField={setCachedStateField} + codebaseIndexModels={codebaseIndexModels} + codebaseIndexConfig={codebaseIndexConfig} + apiConfiguration={apiConfiguration} + setApiConfigurationField={setApiConfigurationField} + areSettingsCommitted={!isChangeDetected} /> )} diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index 2ac1326189..9daa52dcab 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -185,6 +185,14 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode terminalCompressProgressBar: true, // Default to compress progress bar output historyPreviewCollapsed: false, // Initialize the new state (default to expanded) autoCondenseContextPercent: 100, + codebaseIndexConfig: { + codebaseIndexEnabled: false, + codebaseIndexQdrantUrl: "", + codebaseIndexEmbedderProvider: "openai", + codebaseIndexEmbedderBaseUrl: "", + codebaseIndexEmbedderModelId: "", + }, + codebaseIndexModels: { ollama: {}, openai: {} }, }) const [didHydrateState, setDidHydrateState] = useState(false) diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json index d75f6474ce..5ce0559106 100644 --- a/webview-ui/src/i18n/locales/ca/chat.json +++ b/webview-ui/src/i18n/locales/ca/chat.json @@ -260,5 +260,10 @@ "description": "Roo ha arribat al límit aprovat automàticament de {{count}} sol·licitud(s) d'API. Vols reiniciar el comptador i continuar amb la tasca?", "button": "Reiniciar i continuar" } + }, + "codebaseSearch": { + "wantsToSearch": "Roo vol cercar a la base de codi '{{query}}':", + "wantsToSearchWithPath": "Roo vol cercar a la base de codi '{{query}}' a '{{path}}':", + "didSearch": "S'han trobat {{count}} resultat(s) per a '{{query}}':" } } diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index e252e1ba9c..e7ee54172c 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -32,6 +32,29 @@ "language": "Idioma", "about": "Sobre Roo Code" }, + "codeIndex": { + "title": "Indexació de codi", + "enableLabel": "Habilitar indexació de codi", + "enableDescription": "Habilitar aquesta característica indexarà la vostra base de codi. Això pot consumir recursos del sistema i trigar un temps depenent de la mida del vostre projecte.", + "providerLabel": "Proveïdor d'embeddings", + "selectProviderPlaceholder": "Seleccionar proveïdor", + "openaiProvider": "OpenAI", + "ollamaProvider": "Ollama", + "openaiKeyLabel": "Clau OpenAI:", + "modelLabel": "Model", + "selectModelPlaceholder": "Seleccionar model", + "ollamaUrlLabel": "URL d'Ollama:", + "qdrantUrlLabel": "URL de Qdrant", + "qdrantKeyLabel": "Clau de Qdrant:", + "startIndexingButton": "Iniciar indexació", + "clearIndexDataButton": "Esborrar dades d'índex", + "clearDataDialog": { + "title": "Esteu segur?", + "description": "Aquesta acció no es pot desfer. Eliminarà permanentment les dades d'índex de la vostra base de codi.", + "cancelButton": "Cancel·lar", + "confirmButton": "Esborrar dades" + } + }, "autoApprove": { "description": "Permet que Roo realitzi operacions automàticament sense requerir aprovació. Activeu aquesta configuració només si confieu plenament en la IA i enteneu els riscos de seguretat associats.", "readOnly": { diff --git a/webview-ui/src/i18n/locales/de/chat.json b/webview-ui/src/i18n/locales/de/chat.json index c7b11e33e5..2d291a6324 100644 --- a/webview-ui/src/i18n/locales/de/chat.json +++ b/webview-ui/src/i18n/locales/de/chat.json @@ -260,5 +260,10 @@ "description": "Roo hat das automatisch genehmigte Limit von {{count}} API-Anfrage(n) erreicht. Möchtest du den Zähler zurücksetzen und mit der Aufgabe fortfahren?", "button": "Zurücksetzen und fortfahren" } + }, + "codebaseSearch": { + "wantsToSearch": "Roo möchte den Codebase nach '{{query}}' durchsuchen:", + "wantsToSearchWithPath": "Roo möchte den Codebase nach '{{query}}' in '{{path}}' durchsuchen:", + "didSearch": "{{count}} Ergebnis(se) für '{{query}}' gefunden:" } } diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index 57307ab6ef..aa319793bb 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -32,6 +32,29 @@ "language": "Sprache", "about": "Über Roo Code" }, + "codeIndex": { + "title": "Codebase-Indexierung", + "enableLabel": "Codebase-Indexierung aktivieren", + "enableDescription": "Die Aktivierung dieser Funktion wird Ihre Codebase indexieren. Dies kann Systemressourcen verbrauchen und je nach Größe Ihres Projekts einige Zeit in Anspruch nehmen.", + "providerLabel": "Embeddings-Anbieter", + "selectProviderPlaceholder": "Anbieter auswählen", + "openaiProvider": "OpenAI", + "ollamaProvider": "Ollama", + "openaiKeyLabel": "OpenAI-Schlüssel:", + "modelLabel": "Modell", + "selectModelPlaceholder": "Modell auswählen", + "ollamaUrlLabel": "Ollama-URL:", + "qdrantUrlLabel": "Qdrant-URL", + "qdrantKeyLabel": "Qdrant-Schlüssel:", + "startIndexingButton": "Indexierung starten", + "clearIndexDataButton": "Indexdaten löschen", + "clearDataDialog": { + "title": "Sind Sie sicher?", + "description": "Diese Aktion kann nicht rückgängig gemacht werden. Dies wird Ihre Codebase-Indexdaten dauerhaft löschen.", + "cancelButton": "Abbrechen", + "confirmButton": "Daten löschen" + } + }, "autoApprove": { "description": "Erlaubt Roo, Operationen automatisch ohne Genehmigung durchzuführen. Aktiviere diese Einstellungen nur, wenn du der KI vollständig vertraust und die damit verbundenen Sicherheitsrisiken verstehst.", "readOnly": { diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index 10d497f21b..7f8516fa0f 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -161,6 +161,11 @@ "wantsToSearch": "Roo wants to search this directory for {{regex}}:", "didSearch": "Roo searched this directory for {{regex}}:" }, + "codebaseSearch": { + "wantsToSearch": "Roo wants to search the codebase for '{{query}}':", + "wantsToSearchWithPath": "Roo wants to search the codebase for '{{query}}' in '{{path}}':", + "didSearch": "Found {{count}} result(s) for '{{query}}':" + }, "commandOutput": "Command Output", "response": "Response", "arguments": "Arguments", diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index e284d1a557..9340531c37 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -32,6 +32,29 @@ "language": "Language", "about": "About Roo Code" }, + "codeIndex": { + "title": "Codebase Indexing", + "enableLabel": "Enable Codebase Indexing", + "enableDescription": "Enabling this feature will index your codebase. This might consume system resources and take some time depending on the size of your project.", + "providerLabel": "Embeddings Provider", + "selectProviderPlaceholder": "Select provider", + "openaiProvider": "OpenAI", + "ollamaProvider": "Ollama", + "openaiKeyLabel": "OpenAI Key:", + "modelLabel": "Model", + "selectModelPlaceholder": "Select model", + "ollamaUrlLabel": "Ollama URL:", + "qdrantUrlLabel": "Qdrant URL", + "qdrantKeyLabel": "Qdrant Key:", + "startIndexingButton": "Start Indexing", + "clearIndexDataButton": "Clear Index Data", + "clearDataDialog": { + "title": "Are you sure?", + "description": "This action cannot be undone. This will permanently delete your codebase index data.", + "cancelButton": "Cancel", + "confirmButton": "Clear Data" + } + }, "autoApprove": { "description": "Allow Roo to automatically perform operations without requiring approval. Enable these settings only if you fully trust the AI and understand the associated security risks.", "readOnly": { diff --git a/webview-ui/src/i18n/locales/es/chat.json b/webview-ui/src/i18n/locales/es/chat.json index 4b816ff4ab..491a7fbeb9 100644 --- a/webview-ui/src/i18n/locales/es/chat.json +++ b/webview-ui/src/i18n/locales/es/chat.json @@ -260,5 +260,10 @@ "description": "Roo ha alcanzado el límite auto-aprobado de {{count}} solicitud(es) API. ¿Deseas reiniciar el contador y continuar con la tarea?", "button": "Reiniciar y Continuar" } + }, + "codebaseSearch": { + "wantsToSearch": "Roo quiere buscar en la base de código '{{query}}':", + "wantsToSearchWithPath": "Roo quiere buscar en la base de código '{{query}}' en '{{path}}':", + "didSearch": "Se encontraron {{count}} resultado(s) para '{{query}}':" } } diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index 85dea9070f..84a957d192 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -32,6 +32,29 @@ "language": "Idioma", "about": "Acerca de Roo Code" }, + "codeIndex": { + "title": "Indexación de código", + "enableLabel": "Habilitar indexación de código", + "enableDescription": "Habilitar esta función indexará tu base de código. Esto puede consumir recursos del sistema y llevar tiempo dependiendo del tamaño de tu proyecto.", + "providerLabel": "Proveedor de embeddings", + "selectProviderPlaceholder": "Seleccionar proveedor", + "openaiProvider": "OpenAI", + "ollamaProvider": "Ollama", + "openaiKeyLabel": "Clave de OpenAI:", + "modelLabel": "Modelo", + "selectModelPlaceholder": "Seleccionar modelo", + "ollamaUrlLabel": "URL de Ollama:", + "qdrantUrlLabel": "URL de Qdrant", + "qdrantKeyLabel": "Clave de Qdrant:", + "startIndexingButton": "Iniciar indexación", + "clearIndexDataButton": "Borrar datos de índice", + "clearDataDialog": { + "title": "¿Estás seguro?", + "description": "Esta acción no se puede deshacer. Esto eliminará permanentemente los datos de índice de tu base de código.", + "cancelButton": "Cancelar", + "confirmButton": "Borrar datos" + } + }, "autoApprove": { "description": "Permitir que Roo realice operaciones automáticamente sin requerir aprobación. Habilite esta configuración solo si confía plenamente en la IA y comprende los riesgos de seguridad asociados.", "readOnly": { diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json index 53d95e4d29..d2e585f034 100644 --- a/webview-ui/src/i18n/locales/fr/chat.json +++ b/webview-ui/src/i18n/locales/fr/chat.json @@ -260,5 +260,10 @@ "description": "Roo a atteint la limite auto-approuvée de {{count}} requête(s) API. Souhaitez-vous réinitialiser le compteur et poursuivre la tâche ?", "button": "Réinitialiser et continuer" } + }, + "codebaseSearch": { + "wantsToSearch": "Roo veut rechercher dans la base de code '{{query}}' :", + "wantsToSearchWithPath": "Roo veut rechercher dans la base de code '{{query}}' dans '{{path}}' :", + "didSearch": "{{count}} résultat(s) trouvé(s) pour '{{query}}' :" } } diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index 76ddb98230..b29d8fa43a 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -32,6 +32,29 @@ "language": "Langue", "about": "À propos de Roo Code" }, + "codeIndex": { + "title": "Indexation de la base de code", + "enableLabel": "Activer l'indexation de la base de code", + "enableDescription": "L'activation de cette fonctionnalité indexera votre base de code. Cela peut consommer des ressources système et prendre du temps selon la taille de votre projet.", + "providerLabel": "Fournisseur d'embeddings", + "selectProviderPlaceholder": "Sélectionner un fournisseur", + "openaiProvider": "OpenAI", + "ollamaProvider": "Ollama", + "openaiKeyLabel": "Clé OpenAI :", + "modelLabel": "Modèle", + "selectModelPlaceholder": "Sélectionner un modèle", + "ollamaUrlLabel": "URL Ollama :", + "qdrantUrlLabel": "URL Qdrant", + "qdrantKeyLabel": "Clé Qdrant :", + "startIndexingButton": "Démarrer l'indexation", + "clearIndexDataButton": "Effacer les données d'index", + "clearDataDialog": { + "title": "Êtes-vous sûr ?", + "description": "Cette action ne peut pas être annulée. Cela supprimera définitivement les données d'index de votre base de code.", + "cancelButton": "Annuler", + "confirmButton": "Effacer les données" + } + }, "autoApprove": { "description": "Permettre à Roo d'effectuer automatiquement des opérations sans requérir d'approbation. Activez ces paramètres uniquement si vous faites entièrement confiance à l'IA et que vous comprenez les risques de sécurité associés.", "readOnly": { diff --git a/webview-ui/src/i18n/locales/hi/chat.json b/webview-ui/src/i18n/locales/hi/chat.json index 4560c4c5f4..15b0f3f306 100644 --- a/webview-ui/src/i18n/locales/hi/chat.json +++ b/webview-ui/src/i18n/locales/hi/chat.json @@ -260,5 +260,10 @@ "description": "Roo {{count}} API अनुरोध(धों) की स्वत:-स्वीकृत सीमा तक पहुंच गया है। क्या आप गणना को रीसेट करके कार्य जारी रखना चाहते हैं?", "button": "रीसेट करें और जारी रखें" } + }, + "codebaseSearch": { + "wantsToSearch": "Roo कोडबेस में '{{query}}' खोजना चाहता है:", + "wantsToSearchWithPath": "Roo '{{path}}' में कोडबेस में '{{query}}' खोजना चाहता है:", + "didSearch": "'{{query}}' के लिए {{count}} परिणाम मिले:" } } diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index ee33485921..66628a8044 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -32,6 +32,29 @@ "language": "भाषा", "about": "परिचय" }, + "codeIndex": { + "title": "कोडबेस इंडेक्सिंग", + "enableLabel": "कोडबेस इंडेक्सिंग सक्षम करें", + "enableDescription": "इस सुविधा को सक्षम करने से आपका कोडबेस इंडेक्स किया जाएगा। यह आपके प्रोजेक्ट के आकार के आधार पर सिस्टम संसाधनों का उपयोग कर सकता है और कुछ समय ले सकता है।", + "providerLabel": "एम्बेडिंग प्रदाता", + "selectProviderPlaceholder": "प्रदाता चुनें", + "openaiProvider": "OpenAI", + "ollamaProvider": "Ollama", + "openaiKeyLabel": "OpenAI कुंजी:", + "modelLabel": "मॉडल", + "selectModelPlaceholder": "मॉडल चुनें", + "ollamaUrlLabel": "Ollama URL:", + "qdrantUrlLabel": "Qdrant URL", + "qdrantKeyLabel": "Qdrant कुंजी:", + "startIndexingButton": "इंडेक्सिंग शुरू करें", + "clearIndexDataButton": "इंडेक्स डेटा साफ़ करें", + "clearDataDialog": { + "title": "क्या आप सुनिश्चित हैं?", + "description": "यह क्रिया पूर्ववत नहीं की जा सकती। यह आपके कोडबेस इंडेक्स डेटा को स्थायी रूप से हटा देगी।", + "cancelButton": "रद्द करें", + "confirmButton": "डेटा साफ़ करें" + } + }, "autoApprove": { "description": "Roo को अनुमोदन की आवश्यकता के बिना स्वचालित रूप से ऑपरेशन करने की अनुमति दें। इन सेटिंग्स को केवल तभी सक्षम करें जब आप AI पर पूरी तरह से भरोसा करते हों और संबंधित सुरक्षा जोखिमों को समझते हों।", "readOnly": { diff --git a/webview-ui/src/i18n/locales/it/chat.json b/webview-ui/src/i18n/locales/it/chat.json index 70d2adbf90..6472ccf7df 100644 --- a/webview-ui/src/i18n/locales/it/chat.json +++ b/webview-ui/src/i18n/locales/it/chat.json @@ -260,5 +260,10 @@ "description": "Roo ha raggiunto il limite auto-approvato di {{count}} richiesta/e API. Vuoi reimpostare il contatore e procedere con l'attività?", "button": "Reimposta e Continua" } + }, + "codebaseSearch": { + "wantsToSearch": "Roo vuole cercare nella base di codice '{{query}}':", + "wantsToSearchWithPath": "Roo vuole cercare nella base di codice '{{query}}' in '{{path}}':", + "didSearch": "Trovato {{count}} risultato/i per '{{query}}':" } } diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index 03b10281de..8dd5656e04 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -32,6 +32,29 @@ "language": "Lingua", "about": "Informazioni su Roo Code" }, + "codeIndex": { + "title": "Indicizzazione del codice", + "enableLabel": "Abilita indicizzazione del codice", + "enableDescription": "L'attivazione di questa funzionalità indicizzerà il tuo codice. Questo potrebbe consumare risorse di sistema e richiedere tempo a seconda delle dimensioni del tuo progetto.", + "providerLabel": "Fornitore di embedding", + "selectProviderPlaceholder": "Seleziona fornitore", + "openaiProvider": "OpenAI", + "ollamaProvider": "Ollama", + "openaiKeyLabel": "Chiave OpenAI:", + "modelLabel": "Modello", + "selectModelPlaceholder": "Seleziona modello", + "ollamaUrlLabel": "URL Ollama:", + "qdrantUrlLabel": "URL Qdrant", + "qdrantKeyLabel": "Chiave Qdrant:", + "startIndexingButton": "Avvia indicizzazione", + "clearIndexDataButton": "Cancella dati indice", + "clearDataDialog": { + "title": "Sei sicuro?", + "description": "Questa azione non può essere annullata. Eliminerà permanentemente i dati di indice del tuo codice.", + "cancelButton": "Annulla", + "confirmButton": "Cancella dati" + } + }, "autoApprove": { "description": "Permetti a Roo di eseguire automaticamente operazioni senza richiedere approvazione. Abilita queste impostazioni solo se ti fidi completamente dell'IA e comprendi i rischi di sicurezza associati.", "readOnly": { diff --git a/webview-ui/src/i18n/locales/ja/chat.json b/webview-ui/src/i18n/locales/ja/chat.json index a642b21d51..e5e35cf115 100644 --- a/webview-ui/src/i18n/locales/ja/chat.json +++ b/webview-ui/src/i18n/locales/ja/chat.json @@ -260,5 +260,10 @@ "description": "Rooは{{count}}件のAPI自動承認リクエスト制限に達しました。カウントをリセットしてタスクを続行しますか?", "button": "リセットして続行" } + }, + "codebaseSearch": { + "wantsToSearch": "Rooはコードベースで '{{query}}' を検索したい:", + "wantsToSearchWithPath": "Rooは '{{path}}' 内のコードベースで '{{query}}' を検索したい:", + "didSearch": "'{{query}}' の検索結果: {{count}} 件" } } diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index b4b4191b1a..d3a08ed5fa 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -32,6 +32,29 @@ "language": "言語", "about": "Roo Codeについて" }, + "codeIndex": { + "title": "コードベースのインデックス作成", + "enableLabel": "コードベースのインデックス作成を有効化", + "enableDescription": "この機能を有効にするとコードベースのインデックスが作成されます。これはシステムリソースを消費し、プロジェクトのサイズによっては時間がかかる場合があります。", + "providerLabel": "埋め込みプロバイダー", + "selectProviderPlaceholder": "プロバイダーを選択", + "openaiProvider": "OpenAI", + "ollamaProvider": "Ollama", + "openaiKeyLabel": "OpenAIキー:", + "modelLabel": "モデル", + "selectModelPlaceholder": "モデルを選択", + "ollamaUrlLabel": "Ollama URL:", + "qdrantUrlLabel": "Qdrant URL", + "qdrantKeyLabel": "Qdrantキー:", + "startIndexingButton": "インデックス作成を開始", + "clearIndexDataButton": "インデックスデータをクリア", + "clearDataDialog": { + "title": "本当によろしいですか?", + "description": "この操作は元に戻せません。コードベースのインデックスデータが完全に削除されます。", + "cancelButton": "キャンセル", + "confirmButton": "データをクリア" + } + }, "autoApprove": { "description": "Rooが承認なしで自動的に操作を実行できるようにします。AIを完全に信頼し、関連するセキュリティリスクを理解している場合にのみ、これらの設定を有効にしてください。", "readOnly": { diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json index 68e4c944f3..6fd1b96ed3 100644 --- a/webview-ui/src/i18n/locales/ko/chat.json +++ b/webview-ui/src/i18n/locales/ko/chat.json @@ -260,5 +260,10 @@ "description": "Roo가 {{count}}개의 API 요청(들)에 대한 자동 승인 한도에 도달했습니다. 카운트를 재설정하고 작업을 계속하시겠습니까?", "button": "재설정 후 계속" } + }, + "codebaseSearch": { + "wantsToSearch": "Roo가 코드베이스에서 '{{query}}'을(를) 검색하고 싶어합니다:", + "wantsToSearchWithPath": "Roo가 '{{path}}'에서 '{{query}}'을(를) 검색하고 싶어합니다:", + "didSearch": "'{{query}}'에 대한 검색 결과 {{count}}개 찾음:" } } diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index d95c7b7ea9..52a35e1e76 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -32,6 +32,29 @@ "language": "언어", "about": "Roo Code 정보" }, + "codeIndex": { + "title": "코드베이스 인덱싱", + "enableLabel": "코드베이스 인덱싱 활성화", + "enableDescription": "이 기능을 활성화하면 코드베이스가 인덱싱됩니다. 프로젝트 크기에 따라 시스템 리소스를 소비하고 시간이 걸릴 수 있습니다.", + "providerLabel": "임베딩 제공자", + "selectProviderPlaceholder": "제공자 선택", + "openaiProvider": "OpenAI", + "ollamaProvider": "Ollama", + "openaiKeyLabel": "OpenAI 키:", + "modelLabel": "모델", + "selectModelPlaceholder": "모델 선택", + "ollamaUrlLabel": "Ollama URL:", + "qdrantUrlLabel": "Qdrant URL", + "qdrantKeyLabel": "Qdrant 키:", + "startIndexingButton": "인덱싱 시작", + "clearIndexDataButton": "인덱스 데이터 지우기", + "clearDataDialog": { + "title": "확실합니까?", + "description": "이 작업은 취소할 수 없습니다. 코드베이스 인덱스 데이터가 영구적으로 삭제됩니다.", + "cancelButton": "취소", + "confirmButton": "데이터 지우기" + } + }, "autoApprove": { "description": "Roo가 승인 없이 자동으로 작업을 수행할 수 있도록 허용합니다. AI를 완전히 신뢰하고 관련 보안 위험을 이해하는 경우에만 이러한 설정을 활성화하세요.", "readOnly": { diff --git a/webview-ui/src/i18n/locales/nl/chat.json b/webview-ui/src/i18n/locales/nl/chat.json index cca17a339d..10c92a3971 100644 --- a/webview-ui/src/i18n/locales/nl/chat.json +++ b/webview-ui/src/i18n/locales/nl/chat.json @@ -260,5 +260,10 @@ "description": "Roo heeft de automatisch goedgekeurde limiet van {{count}} API-verzoek(en) bereikt. Wil je de teller resetten en doorgaan met de taak?", "button": "Resetten en doorgaan" } + }, + "codebaseSearch": { + "wantsToSearch": "Roo wil de codebase doorzoeken op '{{query}}':", + "wantsToSearchWithPath": "Roo wil de codebase doorzoeken op '{{query}}' in '{{path}}':", + "didSearch": "{{count}} resultaat/resultaten gevonden voor '{{query}}':" } } diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index dedae007ee..ca980d90b9 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -32,6 +32,29 @@ "language": "Taal", "about": "Over Roo Code" }, + "codeIndex": { + "title": "Codebase indexering", + "enableLabel": "Codebase indexering inschakelen", + "enableDescription": "Het inschakelen van deze functie zal je codebase indexeren. Dit kan systeembronnen verbruiken en enige tijd duren, afhankelijk van de grootte van je project.", + "providerLabel": "Embeddings provider", + "selectProviderPlaceholder": "Selecteer provider", + "openaiProvider": "OpenAI", + "ollamaProvider": "Ollama", + "openaiKeyLabel": "OpenAI-sleutel:", + "modelLabel": "Model", + "selectModelPlaceholder": "Selecteer model", + "ollamaUrlLabel": "Ollama URL:", + "qdrantUrlLabel": "Qdrant URL", + "qdrantKeyLabel": "Qdrant-sleutel:", + "startIndexingButton": "Indexering starten", + "clearIndexDataButton": "Indexgegevens wissen", + "clearDataDialog": { + "title": "Weet je het zeker?", + "description": "Deze actie kan niet ongedaan worden gemaakt. Dit zal je codebase-indexgegevens permanent verwijderen.", + "cancelButton": "Annuleren", + "confirmButton": "Gegevens wissen" + } + }, "autoApprove": { "description": "Sta Roo toe om automatisch handelingen uit te voeren zonder goedkeuring. Schakel deze instellingen alleen in als je de AI volledig vertrouwt en de bijbehorende beveiligingsrisico's begrijpt.", "readOnly": { diff --git a/webview-ui/src/i18n/locales/pl/chat.json b/webview-ui/src/i18n/locales/pl/chat.json index e09bc4bc75..ed44bb6bf6 100644 --- a/webview-ui/src/i18n/locales/pl/chat.json +++ b/webview-ui/src/i18n/locales/pl/chat.json @@ -260,5 +260,10 @@ "description": "Roo osiągnął automatycznie zatwierdzony limit {{count}} żądania/żądań API. Czy chcesz zresetować licznik i kontynuować zadanie?", "button": "Zresetuj i kontynuuj" } + }, + "codebaseSearch": { + "wantsToSearch": "Roo chce przeszukać bazę kodu w poszukiwaniu '{{query}}':", + "wantsToSearchWithPath": "Roo chce przeszukać bazę kodu w poszukiwaniu '{{query}}' w '{{path}}':", + "didSearch": "Znaleziono {{count}} wynik(ów) dla '{{query}}':" } } diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index 3695a89545..cacab09af7 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -32,6 +32,29 @@ "language": "Język", "about": "O Roo Code" }, + "codeIndex": { + "title": "Indeksowanie kodu", + "enableLabel": "Włącz indeksowanie kodu", + "enableDescription": "Włączenie tej funkcji spowoduje zaindeksowanie Twojego kodu. Może to zużywać zasoby systemowe i zająć trochę czasu w zależności od rozmiaru projektu.", + "providerLabel": "Dostawca osadzania", + "selectProviderPlaceholder": "Wybierz dostawcę", + "openaiProvider": "OpenAI", + "ollamaProvider": "Ollama", + "openaiKeyLabel": "Klucz OpenAI:", + "modelLabel": "Model", + "selectModelPlaceholder": "Wybierz model", + "ollamaUrlLabel": "URL Ollama:", + "qdrantUrlLabel": "URL Qdrant", + "qdrantKeyLabel": "Klucz Qdrant:", + "startIndexingButton": "Rozpocznij indeksowanie", + "clearIndexDataButton": "Wyczyść dane indeksu", + "clearDataDialog": { + "title": "Czy jesteś pewien?", + "description": "Tej akcji nie można cofnąć. Spowoduje to trwałe usunięcie danych indeksu Twojego kodu.", + "cancelButton": "Anuluj", + "confirmButton": "Wyczyść dane" + } + }, "autoApprove": { "description": "Pozwól Roo na automatyczne wykonywanie operacji bez wymagania zatwierdzenia. Włącz te ustawienia tylko jeśli w pełni ufasz AI i rozumiesz związane z tym zagrożenia bezpieczeństwa.", "readOnly": { diff --git a/webview-ui/src/i18n/locales/pt-BR/chat.json b/webview-ui/src/i18n/locales/pt-BR/chat.json index 22d335c9f3..23efad9a4a 100644 --- a/webview-ui/src/i18n/locales/pt-BR/chat.json +++ b/webview-ui/src/i18n/locales/pt-BR/chat.json @@ -260,5 +260,10 @@ "description": "Roo atingiu o limite auto-aprovado de {{count}} solicitação(ões) de API. Deseja redefinir a contagem e prosseguir com a tarefa?", "button": "Redefinir e Continuar" } + }, + "codebaseSearch": { + "wantsToSearch": "Roo quer pesquisar na base de código por '{{query}}':", + "wantsToSearchWithPath": "Roo quer pesquisar na base de código por '{{query}}' em '{{path}}':", + "didSearch": "Encontrado {{count}} resultado(s) para '{{query}}':" } } diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index 0657c5f8ce..150c57f253 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -32,6 +32,29 @@ "language": "Idioma", "about": "Sobre" }, + "codeIndex": { + "title": "Indexação de Código", + "enableLabel": "Ativar Indexação de Código", + "enableDescription": "Ativar este recurso indexará sua base de código. Isso pode consumir recursos do sistema e levar algum tempo dependendo do tamanho do seu projeto.", + "providerLabel": "Provedor de Embeddings", + "selectProviderPlaceholder": "Selecionar provedor", + "openaiProvider": "OpenAI", + "ollamaProvider": "Ollama", + "openaiKeyLabel": "Chave OpenAI:", + "modelLabel": "Modelo", + "selectModelPlaceholder": "Selecionar modelo", + "ollamaUrlLabel": "URL Ollama:", + "qdrantUrlLabel": "URL Qdrant", + "qdrantKeyLabel": "Chave Qdrant:", + "startIndexingButton": "Iniciar Indexação", + "clearIndexDataButton": "Limpar Dados de Índice", + "clearDataDialog": { + "title": "Tem certeza?", + "description": "Esta ação não pode ser desfeita. Isso excluirá permanentemente os dados de índice da sua base de código.", + "cancelButton": "Cancelar", + "confirmButton": "Limpar Dados" + } + }, "autoApprove": { "description": "Permitir que o Roo realize operações automaticamente sem exigir aprovação. Ative essas configurações apenas se confiar totalmente na IA e compreender os riscos de segurança associados.", "readOnly": { diff --git a/webview-ui/src/i18n/locales/ru/chat.json b/webview-ui/src/i18n/locales/ru/chat.json index 858db12609..1e44aa5fe6 100644 --- a/webview-ui/src/i18n/locales/ru/chat.json +++ b/webview-ui/src/i18n/locales/ru/chat.json @@ -260,5 +260,10 @@ "description": "Roo достиг автоматически одобренного лимита в {{count}} API-запрос(ов). Хотите сбросить счетчик и продолжить задачу?", "button": "Сбросить и продолжить" } + }, + "codebaseSearch": { + "wantsToSearch": "Roo хочет выполнить поиск в кодовой базе по '{{query}}':", + "wantsToSearchWithPath": "Roo хочет выполнить поиск в кодовой базе по '{{query}}' в '{{path}}':", + "didSearch": "Найдено {{count}} результат(ов) для '{{query}}':" } } diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index 87002bcd1f..b20236d362 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -32,6 +32,29 @@ "language": "Язык", "about": "О Roo Code" }, + "codeIndex": { + "title": "Индексация кодовой базы", + "enableLabel": "Включить индексацию кодовой базы", + "enableDescription": "Включение этой функции проиндексирует вашу кодовую базу. Это может потреблять системные ресурсы и занять некоторое время в зависимости от размера вашего проекта.", + "providerLabel": "Провайдер эмбеддингов", + "selectProviderPlaceholder": "Выберите провайдера", + "openaiProvider": "OpenAI", + "ollamaProvider": "Ollama", + "openaiKeyLabel": "Ключ OpenAI:", + "modelLabel": "Модель", + "selectModelPlaceholder": "Выберите модель", + "ollamaUrlLabel": "URL Ollama:", + "qdrantUrlLabel": "URL Qdrant", + "qdrantKeyLabel": "Ключ Qdrant:", + "startIndexingButton": "Начать индексацию", + "clearIndexDataButton": "Очистить данные индекса", + "clearDataDialog": { + "title": "Вы уверены?", + "description": "Это действие нельзя отменить. Оно навсегда удалит данные индекса вашей кодовой базы.", + "cancelButton": "Отмена", + "confirmButton": "Очистить данные" + } + }, "autoApprove": { "description": "Разрешить Roo автоматически выполнять операции без необходимости одобрения. Включайте эти параметры только если полностью доверяете ИИ и понимаете связанные с этим риски безопасности.", "readOnly": { diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json index 625d791f57..91cd0794f9 100644 --- a/webview-ui/src/i18n/locales/tr/chat.json +++ b/webview-ui/src/i18n/locales/tr/chat.json @@ -260,5 +260,10 @@ "description": "Roo, {{count}} API isteği/istekleri için otomatik onaylanan limite ulaştı. Sayacı sıfırlamak ve göreve devam etmek istiyor musunuz?", "button": "Sıfırla ve Devam Et" } + }, + "codebaseSearch": { + "wantsToSearch": "Roo kod tabanında '{{query}}' aramak istiyor:", + "wantsToSearchWithPath": "Roo '{{path}}' içinde kod tabanında '{{query}}' aramak istiyor:", + "didSearch": "'{{query}}' için {{count}} sonuç bulundu:" } } diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index a89c139899..ecd6165738 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -32,6 +32,29 @@ "language": "Dil", "about": "Roo Code Hakkında" }, + "codeIndex": { + "title": "Kod Tabanı İndeksleme", + "enableLabel": "Kod Tabanı İndekslemeyi Etkinleştir", + "enableDescription": "Bu özelliği etkinleştirmek kod tabanınızı indeksleyecektir. Bu, projenizin boyutuna bağlı olarak sistem kaynaklarını tüketebilir ve biraz zaman alabilir.", + "providerLabel": "Gömme Sağlayıcısı", + "selectProviderPlaceholder": "Sağlayıcı seç", + "openaiProvider": "OpenAI", + "ollamaProvider": "Ollama", + "openaiKeyLabel": "OpenAI Anahtarı:", + "modelLabel": "Model", + "selectModelPlaceholder": "Model seç", + "ollamaUrlLabel": "Ollama URL:", + "qdrantUrlLabel": "Qdrant URL", + "qdrantKeyLabel": "Qdrant Anahtarı:", + "startIndexingButton": "İndekslemeyi Başlat", + "clearIndexDataButton": "İndeks Verilerini Temizle", + "clearDataDialog": { + "title": "Emin misiniz?", + "description": "Bu işlem geri alınamaz. Bu, kod tabanı indeks verilerinizi kalıcı olarak silecektir.", + "cancelButton": "İptal", + "confirmButton": "Verileri Temizle" + } + }, "autoApprove": { "description": "Roo'nun onay gerektirmeden otomatik olarak işlemler gerçekleştirmesine izin verin. Bu ayarları yalnızca yapay zekaya tamamen güveniyorsanız ve ilgili güvenlik risklerini anlıyorsanız etkinleştirin.", "readOnly": { diff --git a/webview-ui/src/i18n/locales/vi/chat.json b/webview-ui/src/i18n/locales/vi/chat.json index e981f16da5..c410bc85a3 100644 --- a/webview-ui/src/i18n/locales/vi/chat.json +++ b/webview-ui/src/i18n/locales/vi/chat.json @@ -260,5 +260,10 @@ "description": "Roo đã đạt đến giới hạn tự động phê duyệt là {{count}} yêu cầu API. Bạn có muốn đặt lại bộ đếm và tiếp tục nhiệm vụ không?", "button": "Đặt lại và Tiếp tục" } + }, + "codebaseSearch": { + "wantsToSearch": "Roo muốn tìm kiếm trong cơ sở mã cho '{{query}}':", + "wantsToSearchWithPath": "Roo muốn tìm kiếm trong cơ sở mã cho '{{query}}' trong '{{path}}':", + "didSearch": "Đã tìm thấy {{count}} kết quả cho '{{query}}':" } } diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index 3cef200f7c..e9452c688b 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -32,6 +32,29 @@ "language": "Ngôn ngữ", "about": "Giới thiệu" }, + "codeIndex": { + "title": "Lập chỉ mục mã nguồn", + "enableLabel": "Bật lập chỉ mục mã nguồn", + "enableDescription": "Bật tính năng này sẽ lập chỉ mục cho mã nguồn của bạn. Điều này có thể tiêu tốn tài nguyên hệ thống và mất một khoảng thời gian tùy thuộc vào kích thước dự án của bạn.", + "providerLabel": "Nhà cung cấp nhúng", + "selectProviderPlaceholder": "Chọn nhà cung cấp", + "openaiProvider": "OpenAI", + "ollamaProvider": "Ollama", + "openaiKeyLabel": "Khóa OpenAI:", + "modelLabel": "Mô hình", + "selectModelPlaceholder": "Chọn mô hình", + "ollamaUrlLabel": "URL Ollama:", + "qdrantUrlLabel": "URL Qdrant", + "qdrantKeyLabel": "Khóa Qdrant:", + "startIndexingButton": "Bắt đầu lập chỉ mục", + "clearIndexDataButton": "Xóa dữ liệu chỉ mục", + "clearDataDialog": { + "title": "Bạn có chắc không?", + "description": "Hành động này không thể hoàn tác. Điều này sẽ xóa vĩnh viễn dữ liệu chỉ mục mã nguồn của bạn.", + "cancelButton": "Hủy", + "confirmButton": "Xóa dữ liệu" + } + }, "autoApprove": { "description": "Cho phép Roo tự động thực hiện các hoạt động mà không cần phê duyệt. Chỉ bật những cài đặt này nếu bạn hoàn toàn tin tưởng AI và hiểu rõ các rủi ro bảo mật liên quan.", "readOnly": { diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json index 67b140b5b0..795c395935 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -260,5 +260,10 @@ "description": "Roo 已达到 {{count}} 次 API 请求的自动批准限制。您想重置计数并继续任务吗?", "button": "重置并继续" } + }, + "codebaseSearch": { + "wantsToSearch": "Roo 需要搜索代码库: '{{query}}'", + "wantsToSearchWithPath": "Roo 需要在 '{{path}}' 中搜索: '{{query}}'", + "didSearch": "找到 {{count}} 个结果: '{{query}}'" } } diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index 6958c5f5cc..4df61e0847 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -32,6 +32,29 @@ "language": "语言", "about": "关于 Roo Code" }, + "codeIndex": { + "title": "代码库索引", + "enableLabel": "启用代码库索引", + "enableDescription": "启用此功能将为您的代码库建立索引。这可能会消耗系统资源,并且根据项目大小可能需要一些时间。", + "providerLabel": "嵌入提供商", + "selectProviderPlaceholder": "选择提供商", + "openaiProvider": "OpenAI", + "ollamaProvider": "Ollama", + "openaiKeyLabel": "OpenAI 密钥:", + "modelLabel": "模型", + "selectModelPlaceholder": "选择模型", + "ollamaUrlLabel": "Ollama URL:", + "qdrantUrlLabel": "Qdrant URL", + "qdrantKeyLabel": "Qdrant 密钥:", + "startIndexingButton": "开始索引", + "clearIndexDataButton": "清除索引数据", + "clearDataDialog": { + "title": "确定要继续吗?", + "description": "此操作无法撤消。这将永久删除您的代码库索引数据。", + "cancelButton": "取消", + "confirmButton": "清除数据" + } + }, "autoApprove": { "description": "允许 Roo 自动执行操作而无需批准。只有在您完全信任 AI 并了解相关安全风险的情况下才启用这些设置。", "readOnly": { diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json index 452271947c..8b0e3dbd1e 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -260,5 +260,10 @@ "description": "Roo 已達到 {{count}} 次 API 請求的自動核准限制。您想要重設計數並繼續工作嗎?", "button": "重設並繼續" } + }, + "codebaseSearch": { + "wantsToSearch": "Roo 想要搜尋程式碼庫:「{{query}}」", + "wantsToSearchWithPath": "Roo 想要在「{{path}}」中搜尋:「{{query}}」", + "didSearch": "找到 {{count}} 個結果:「{{query}}」" } } diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index 3b1a5fe619..b03978c9e6 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -32,6 +32,29 @@ "language": "語言", "about": "關於 Roo Code" }, + "codeIndex": { + "title": "程式碼庫索引", + "enableLabel": "啟用程式碼庫索引", + "enableDescription": "啟用此功能將為您的程式碼庫建立索引。這可能會消耗系統資源,並且根據專案大小可能需要一些時間。", + "providerLabel": "嵌入提供者", + "selectProviderPlaceholder": "選擇提供者", + "openaiProvider": "OpenAI", + "ollamaProvider": "Ollama", + "openaiKeyLabel": "OpenAI 金鑰:", + "modelLabel": "模型", + "selectModelPlaceholder": "選擇模型", + "ollamaUrlLabel": "Ollama URL:", + "qdrantUrlLabel": "Qdrant URL", + "qdrantKeyLabel": "Qdrant 金鑰:", + "startIndexingButton": "開始索引", + "clearIndexDataButton": "清除索引資料", + "clearDataDialog": { + "title": "確定要繼續嗎?", + "description": "此操作無法復原。這將永久刪除您的程式碼庫索引資料。", + "cancelButton": "取消", + "confirmButton": "清除資料" + } + }, "autoApprove": { "description": "允許 Roo 無需核准即執行操作。僅在您完全信任 AI 並了解相關安全風險時啟用這些設定。", "readOnly": {