mirror of
https://github.com/openclaw/clawhub.git
synced 2026-08-14 17:02:11 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b192be11c | ||
|
|
077a5f9d27 | ||
|
|
f47f28e908 | ||
|
|
11d70e3f88 | ||
|
|
3e6ad6b3b5 | ||
|
|
765ac64191 | ||
|
|
b5d1371af7 | ||
|
|
088339b5d2 | ||
|
|
aebbce2710 | ||
|
|
d881b8f9a9 | ||
|
|
5c6b3469ba | ||
|
|
9a5b282700 | ||
|
|
843cf78c74 | ||
|
|
9797825433 | ||
|
|
d1716c6c81 | ||
|
|
8e7c8d447b | ||
|
|
e337136181 | ||
|
|
2d2c1e7a72 | ||
|
|
2deb1b75e9 | ||
|
|
3ef5f14d84 | ||
|
|
40e345f4a9 | ||
|
|
6f537bf7ad | ||
|
|
cfb21978dd | ||
|
|
82197de7e1 | ||
|
|
5cbb8bfeb4 | ||
|
|
61efe0657f | ||
|
|
f0b37e6ec8 | ||
|
|
01bc23c0c7 | ||
|
|
fa222d4b79 | ||
|
|
f82709a34e | ||
|
|
56cbb30034 | ||
|
|
0a42a8c74c | ||
|
|
14a4c471af | ||
|
|
abab935541 | ||
|
|
eb48c40fd5 | ||
|
|
2bf8113ef1 | ||
|
|
173e28dd84 | ||
|
|
a61f302a04 | ||
|
|
bbdc14c245 | ||
|
|
d3fa80c0cb | ||
|
|
c896373aff | ||
|
|
35f6d3481e | ||
|
|
32aa2348ad | ||
|
|
ae66202d73 | ||
|
|
63ca5c2a2e | ||
|
|
35cc191d48 | ||
|
|
695b97ddbd | ||
|
|
c3e2b8f6f4 | ||
|
|
898b3bebba | ||
|
|
dfb93eab79 | ||
|
|
cc3fc616e9 | ||
|
|
a128c101ad | ||
|
|
dfde400fcf | ||
|
|
071c43da43 | ||
|
|
7cc6b22176 | ||
|
|
b927db6340 | ||
|
|
e018269af6 | ||
|
|
9b352d7f99 | ||
|
|
ab862d49f1 | ||
|
|
f4f6f34542 | ||
|
|
6aab4f9437 | ||
|
|
94f2f532c3 | ||
|
|
2cf2636ab8 | ||
|
|
72c5cdd864 | ||
|
|
775146ff6f | ||
|
|
e82f0704c8 | ||
|
|
4a78ca3a06 | ||
|
|
5c4b7d45df | ||
|
|
8841ac7771 | ||
|
|
a5e41320c8 | ||
|
|
917fb3fbe9 | ||
|
|
b6acfb4fd7 | ||
|
|
55fe4a8563 | ||
|
|
d4205c8a7e | ||
|
|
8035a16024 | ||
|
|
fb7b73b86a | ||
|
|
92e055f8f1 | ||
|
|
0dafef6c8b | ||
|
|
bbe887faac | ||
|
|
51d42badcf | ||
|
|
b7a7555b19 | ||
|
|
37eac63dc8 | ||
|
|
2993f6ccb7 | ||
|
|
9ea97b3c58 | ||
|
|
6dfe780a29 | ||
|
|
519f56301d | ||
|
|
1379d90ddf | ||
|
|
6bd73e33bb | ||
|
|
e03432d4d8 | ||
|
|
c1bb8d94cd | ||
|
|
6046bf9eda | ||
|
|
bf727b6a32 | ||
|
|
39d03a577f | ||
|
|
c70755d25d | ||
|
|
503a2bf022 | ||
|
|
379c1871f4 | ||
|
|
a86c48ce3b | ||
|
|
6f28659e7b | ||
|
|
3836b7643f | ||
|
|
a7b7df7129 | ||
|
|
08a95f3c74 | ||
|
|
730063b575 | ||
|
|
ea0125d87e | ||
|
|
fc07846839 | ||
|
|
53d16a55d4 | ||
|
|
0f5496ff31 | ||
|
|
7accfb71c7 | ||
|
|
2ae30d70d5 | ||
|
|
7760696362 | ||
|
|
f381b01829 | ||
|
|
8090d4ecea | ||
|
|
1cac1b2539 | ||
|
|
84a26a6acd | ||
|
|
6bbdec2a4e | ||
|
|
b1b3c5e1cd | ||
|
|
0a7232ee5e | ||
|
|
bf42578b04 | ||
|
|
c7d8e1cd0a | ||
|
|
769c620800 | ||
|
|
8c4a9ce9a4 | ||
|
|
f3842551c4 | ||
|
|
d8f82b7379 | ||
|
|
6bcf96fea9 | ||
|
|
22d3cd133c | ||
|
|
731d1aa800 | ||
|
|
f92ccfd488 | ||
|
|
de28e2a6eb | ||
|
|
f6a2c875d6 | ||
|
|
caa3359329 | ||
|
|
eccdbb3830 | ||
|
|
c13a2514da | ||
|
|
4bc58e4939 | ||
|
|
1a3fdd8f51 | ||
|
|
2d93401234 | ||
|
|
b1e077da38 | ||
|
|
04ec212100 | ||
|
|
0c493cfa19 | ||
|
|
9331619cb8 | ||
|
|
69c79e8f01 | ||
|
|
65e321b280 | ||
|
|
11ad31aa4e | ||
|
|
3c46a6b996 | ||
|
|
60c73e4c54 | ||
|
|
4ccc9a1c62 | ||
|
|
5f0e1ee639 | ||
|
|
6d87fc078d | ||
|
|
5be7035a69 | ||
|
|
96b04b0837 | ||
|
|
d6e54bdb65 | ||
|
|
691ba20195 | ||
|
|
3d971fd3c2 | ||
|
|
f3ab8663e3 | ||
|
|
d061779cf0 | ||
|
|
afc31802e8 | ||
|
|
815a053b32 | ||
|
|
8d7ac580d1 | ||
|
|
791dead7a8 | ||
|
|
0189ddd5c2 | ||
|
|
4860945f34 | ||
|
|
2523832e18 | ||
|
|
effd52a4ea | ||
|
|
3a46cdd07e | ||
|
|
1e7de4a2a2 | ||
|
|
5471fb280f | ||
|
|
7efc6c4555 | ||
|
|
0e20d64c63 | ||
|
|
303075c82b | ||
|
|
a3d4509102 | ||
|
|
9c50b60e35 | ||
|
|
2c1b331be0 | ||
|
|
6feaa0974a | ||
|
|
1b66b84e58 | ||
|
|
9115151949 | ||
|
|
e8a75de2b1 | ||
|
|
0db6b17a62 | ||
|
|
e49d680f6c | ||
|
|
549eda8e44 | ||
|
|
e3e5705d89 | ||
|
|
69dd3b5f68 | ||
|
|
44f4ab0e74 | ||
|
|
4efbf01fa8 | ||
|
|
f366269519 | ||
|
|
1622a74802 | ||
|
|
455f4ea19c | ||
|
|
5256b9e2ba | ||
|
|
76638de7ad | ||
|
|
89bb8fe938 | ||
|
|
60c61d44a6 | ||
|
|
ab3c708cc9 | ||
|
|
da9da2b66d | ||
|
|
44ce895b44 | ||
|
|
7f35ce91af | ||
|
|
bb35aca7bf | ||
|
|
078425f074 | ||
|
|
e5f3ba272b | ||
|
|
ae7aa5861d | ||
|
|
d56c07e9ae | ||
|
|
078d1c88b8 | ||
|
|
ca1f88f3e4 | ||
|
|
61302e920f | ||
|
|
2293016c20 | ||
|
|
b0c8fe2a01 | ||
|
|
ee9c6c1412 | ||
|
|
ade8e3843a | ||
|
|
44a552f3da | ||
|
|
85695c49a8 | ||
|
|
47038f0e24 | ||
|
|
3c48b989d4 | ||
|
|
03f97349b0 | ||
|
|
6ae7cb5345 | ||
|
|
17b5461539 | ||
|
|
b8ec008de3 | ||
|
|
fdb4d312f4 | ||
|
|
9ef33726b8 | ||
|
|
6afb91b772 | ||
|
|
749028df12 | ||
|
|
6820029e2f | ||
|
|
e86aa30a77 | ||
|
|
7d69b337c7 | ||
|
|
9fb09e0793 | ||
|
|
3711b45d08 | ||
|
|
49b0c33fe8 | ||
|
|
84f2216d73 | ||
|
|
64e22ae06e | ||
|
|
71156751ff | ||
|
|
4b3c2bb630 | ||
|
|
ee1c80202f | ||
|
|
73c32d6296 | ||
|
|
99e5ab4ed7 | ||
|
|
496f52693a | ||
|
|
229a05a4c7 | ||
|
|
b2447d750a | ||
|
|
8ce11888aa | ||
|
|
3e66b50065 | ||
|
|
4f2d20e1e0 | ||
|
|
1f66c9cde6 | ||
|
|
dfac8d83ce | ||
|
|
a63d153b1d | ||
|
|
9d49df109d | ||
|
|
59c1ee3a7b | ||
|
|
a810d838bd | ||
|
|
e319df0760 | ||
|
|
a1becd0c0f | ||
|
|
2e7f3e752e | ||
|
|
3b53ddbdbe | ||
|
|
9a0671034c | ||
|
|
6bc45b30ba | ||
|
|
2c9aa36dcb | ||
|
|
6fd3dcabf9 | ||
|
|
7166883d7d | ||
|
|
4496acf9f0 | ||
|
|
d6500794c6 | ||
|
|
a1c91b265e | ||
|
|
cd47bbe060 | ||
|
|
31c69ff518 | ||
|
|
c22a2d295b | ||
|
|
f0b0185649 | ||
|
|
7742ac7f52 | ||
|
|
e4ef192ce6 | ||
|
|
207ab1758e | ||
|
|
8446d65224 | ||
|
|
58051dffcb | ||
|
|
0b6466184b | ||
|
|
b3f98b4686 | ||
|
|
79d9a86e6f | ||
|
|
27faf50992 | ||
|
|
e9571f3aa4 | ||
|
|
18ec428fc5 | ||
|
|
0724abccbc | ||
|
|
7b71c15ee1 | ||
|
|
d2921c0600 | ||
|
|
f5bf1b61e6 | ||
|
|
6447101397 | ||
|
|
d12adf2f37 | ||
|
|
741599d20e | ||
|
|
9244087d00 | ||
|
|
23edf7d19d | ||
|
|
1876698093 | ||
|
|
ad640d2903 | ||
|
|
c0143b1cd1 | ||
|
|
8970a46acf | ||
|
|
a3de2360b4 | ||
|
|
620445f080 | ||
|
|
89a89af183 | ||
|
|
2d367c60ed | ||
|
|
8b39d00247 | ||
|
|
e6a8bce394 | ||
|
|
e09bf7f0fd | ||
|
|
c9bb130235 | ||
|
|
be70ae5e9f | ||
|
|
9321c83b93 | ||
|
|
2a3e08e1bd | ||
|
|
0ead9a60cf | ||
|
|
f0274e41c4 | ||
|
|
315cf9603f | ||
|
|
2c623b3beb | ||
|
|
01612b4f15 | ||
|
|
295db69eba | ||
|
|
1fa4f9049d | ||
|
|
5c19da48d0 | ||
|
|
fc0a85e150 | ||
|
|
3a2568f751 | ||
|
|
8e1bad4ac1 | ||
|
|
8f7c21ac02 | ||
|
|
999131e2e7 | ||
|
|
571cc70c7a | ||
|
|
3ead985f82 | ||
|
|
1117aa4340 | ||
|
|
9ce5e702b5 | ||
|
|
c632b697bb | ||
|
|
d2525b179a | ||
|
|
8ebae39140 | ||
|
|
821494b9dd | ||
|
|
94413a60fb | ||
|
|
9cbf98297c | ||
|
|
94ded18dec | ||
|
|
9897850074 | ||
|
|
159118dc59 | ||
|
|
843f19e640 | ||
|
|
4b882b2f43 | ||
|
|
634667c2c8 | ||
|
|
bd30b182d7 | ||
|
|
2d719feef5 | ||
|
|
d3e1059266 | ||
|
|
1a968d8243 | ||
|
|
59667cc1a5 | ||
|
|
5b0dd96530 | ||
|
|
8e3858a31d | ||
|
|
fe4acc8e10 | ||
|
|
f2e6e87756 | ||
|
|
3de69919de | ||
|
|
7629433248 | ||
|
|
b82ad43eae | ||
|
|
b7ee5c8e62 | ||
|
|
1c8543ade6 | ||
|
|
7cd0ef362d | ||
|
|
9cf9e12bdd | ||
|
|
112e25e28c | ||
|
|
d786374b77 | ||
|
|
3fbe27560e | ||
|
|
33539b6df3 | ||
|
|
354f12a033 | ||
|
|
b0ea80df6c | ||
|
|
909a47106e | ||
|
|
e8cfbddf17 | ||
|
|
162528abe4 | ||
|
|
74aa61086e | ||
|
|
858a121d33 | ||
|
|
953358a322 | ||
|
|
0a79612fe5 | ||
|
|
9b5d2e088d | ||
|
|
ce62df9d08 | ||
|
|
0abdbf4a50 | ||
|
|
dcbc38999f | ||
|
|
01aa28ccda | ||
|
|
cb6ced7906 | ||
|
|
ded9ff4235 | ||
|
|
9fc2da4dc4 | ||
|
|
05d5fc1151 | ||
|
|
9aaab158cb | ||
|
|
6fc5bb7cd8 | ||
|
|
9aa3f37ee1 | ||
|
|
9a20795b54 | ||
|
|
ff75a7e9ae | ||
|
|
4c965f4957 | ||
|
|
f71139e9ae | ||
|
|
a20e2efd68 | ||
|
|
83cc4d0f87 | ||
|
|
1f7f483b1d | ||
|
|
309723f7a8 | ||
|
|
23932ec7de | ||
|
|
b292f7eaf5 | ||
|
|
cbbb6e7a61 | ||
|
|
42e9690e78 | ||
|
|
ff48b2cc70 | ||
|
|
562810f29b | ||
|
|
327231535f | ||
|
|
51967bca7f | ||
|
|
0132f1f530 | ||
|
|
05f27f640e | ||
|
|
6adf379f32 | ||
|
|
7d6efae74b | ||
|
|
d854449610 | ||
|
|
87f2b846ef | ||
|
|
97023d3123 | ||
|
|
a920323a86 | ||
|
|
b8eaada68d | ||
|
|
18acbc1209 | ||
|
|
57bc9f2a46 | ||
|
|
6f893b54f4 | ||
|
|
8bb6a0d584 | ||
|
|
321df223b2 | ||
|
|
707d390923 | ||
|
|
3c4608156c | ||
|
|
9c97d643ac | ||
|
|
a66df774f2 | ||
|
|
30bf8f252a | ||
|
|
5ed0ddd066 | ||
|
|
ce1be46c60 | ||
|
|
667bc55299 | ||
|
|
90de729fe1 | ||
|
|
8a2c0c06fd | ||
|
|
07fed45f42 | ||
|
|
cc16d7fbd9 | ||
|
|
1f56a71430 | ||
|
|
875f026a23 | ||
|
|
4248f61926 | ||
|
|
aec03c016d | ||
|
|
b62d8ca813 | ||
|
|
01946864f2 | ||
|
|
963b0a5719 | ||
|
|
6a3c8551e8 | ||
|
|
1db8a6ca22 | ||
|
|
2ad4068071 | ||
|
|
7446579772 | ||
|
|
c9e105fa34 | ||
|
|
c538848a3d | ||
|
|
c145166287 | ||
|
|
0907fae0d9 | ||
|
|
afd73264bd | ||
|
|
e22bb7d427 | ||
|
|
232017ba6f | ||
|
|
d897660b55 | ||
|
|
5415940219 | ||
|
|
bf2366fad2 | ||
|
|
28fb63cc67 | ||
|
|
77624dd70f | ||
|
|
d3cd7a75cf | ||
|
|
7467cd88cc | ||
|
|
b07196f336 | ||
|
|
92dfd8f35a | ||
|
|
01e4418ccc | ||
|
|
64633c2644 | ||
|
|
39107900ea | ||
|
|
a54f240a08 | ||
|
|
07bf41e109 | ||
|
|
5cc3e890fd | ||
|
|
cb6f365bb5 | ||
|
|
9a2e8d6fea | ||
|
|
dcf42b0502 | ||
|
|
5f6b73024c | ||
|
|
4e4d5c88d1 | ||
|
|
5be50db7ec | ||
|
|
3c39480695 | ||
|
|
526d84f338 | ||
|
|
9be35a3d43 | ||
|
|
e712ce7362 | ||
|
|
3c45326b88 | ||
|
|
d67583f075 | ||
|
|
f23671bba9 | ||
|
|
b753b1f7ab | ||
|
|
ffdd06a731 | ||
|
|
0b888a2d13 | ||
|
|
b8efe83d1b | ||
|
|
c7935b6800 | ||
|
|
4851f5f76d | ||
|
|
970663d51d | ||
|
|
a5ec1c71a3 | ||
|
|
66f3d07ca1 | ||
|
|
27f0b7d206 | ||
|
|
60aa4a77fd | ||
|
|
583a48db07 | ||
|
|
6814af95df | ||
|
|
2aa2a449e5 | ||
|
|
7cf16ebb28 | ||
|
|
f9072e53e1 | ||
|
|
ba587040b0 | ||
|
|
1a00013c21 | ||
|
|
0613c54ce6 | ||
|
|
389b06b2cc | ||
|
|
de59d21291 | ||
|
|
e3ad5892a5 | ||
|
|
74421c37fd | ||
|
|
35aa372b24 | ||
|
|
636750fbf8 | ||
|
|
8bbc66868d | ||
|
|
fd4d22d2c2 | ||
|
|
e99eae32d6 | ||
|
|
9ab92e5847 | ||
|
|
cccef81a3e | ||
|
|
c7458d9477 | ||
|
|
7e1e2f0a4f | ||
|
|
9fcf892e34 | ||
|
|
22e994fe55 | ||
|
|
0dd91f130e | ||
|
|
893f341bdb | ||
|
|
3b6d8ac3d8 | ||
|
|
a116d92866 | ||
|
|
a1666bb1e6 | ||
|
|
5c98c7e3e1 | ||
|
|
0e926c6f8a | ||
|
|
50858282b3 | ||
|
|
b1206ed994 | ||
|
|
2763be8fb7 | ||
|
|
4ca5ba9d1e | ||
|
|
5e4be11a85 | ||
|
|
6c15a481d0 | ||
|
|
a925359e13 | ||
|
|
207e2a8448 | ||
|
|
ae2ffd25b5 | ||
|
|
6dbeabb983 | ||
|
|
8f74484032 | ||
|
|
d794f4633d | ||
|
|
e6a3bdf4e3 | ||
|
|
72d4e7a326 | ||
|
|
f72e936a47 | ||
|
|
64f1ffbeab | ||
|
|
2ddaad62cc | ||
|
|
f0a6789c31 | ||
|
|
af96221ebb | ||
|
|
be77f0626d | ||
|
|
dded7a55b9 | ||
|
|
5b10c21b50 | ||
|
|
c107523a31 | ||
|
|
09cbd9c58d | ||
|
|
22950d1bd2 | ||
|
|
53b64d1d91 | ||
|
|
b80b942fb2 | ||
|
|
426a2a7879 | ||
|
|
ee8e33947c | ||
|
|
0c4d0448c1 | ||
|
|
3b2d3dcc7a | ||
|
|
c1257a31fc | ||
|
|
404a1359df | ||
|
|
4eb1b97980 | ||
|
|
dd7bc17fd3 | ||
|
|
008af4dcf8 | ||
|
|
ea0f8ba64a | ||
|
|
39695af5e3 | ||
|
|
c3d120756e | ||
|
|
b67d7cc619 | ||
|
|
c51cfe2459 | ||
|
|
64da959704 | ||
|
|
5d3de37ff0 | ||
|
|
8ed8481380 | ||
|
|
bb6c6c1b38 | ||
|
|
6f619eabc0 | ||
|
|
bac981a193 | ||
|
|
3d021d3c5d | ||
|
|
a873ffbf53 | ||
|
|
8287c494b2 | ||
|
|
8a87890c6a | ||
|
|
ff53a37353 | ||
|
|
b79bfc469e | ||
|
|
190ce3769f | ||
|
|
75e04d2c91 | ||
|
|
3173ecd629 | ||
|
|
bcc8fa5b71 | ||
|
|
5d0264daa7 | ||
|
|
2dcaf25d23 | ||
|
|
3d934a5f28 | ||
|
|
92f5e68764 | ||
|
|
c2c01300a7 | ||
|
|
3b80e3f67d | ||
|
|
003d243ccc | ||
|
|
3a6ab49dc3 | ||
|
|
ec4fa3b76d | ||
|
|
4ce383e822 | ||
|
|
383230582c | ||
|
|
49d77c040e | ||
|
|
edba7e1b12 | ||
|
|
dccb030dd6 | ||
|
|
77692d1249 | ||
|
|
91d962e8b7 | ||
|
|
4f0abcdac1 | ||
|
|
bc1dab4b08 | ||
|
|
4cfc137f26 | ||
|
|
31def36bbd | ||
|
|
8a6efc236a | ||
|
|
38ba9768d3 | ||
|
|
40627f0796 | ||
|
|
8bd7be9d04 | ||
|
|
da20972a04 | ||
|
|
227cce18a0 | ||
|
|
0b56c68643 | ||
|
|
ab3ede496d | ||
|
|
ada5556752 | ||
|
|
f3d9c9b27a | ||
|
|
ceb25616ab | ||
|
|
cb018bf64c | ||
|
|
db2a14755d | ||
|
|
12f2cb8513 | ||
|
|
d16a62f552 | ||
|
|
6b2efcb7b8 | ||
|
|
05122ea799 | ||
|
|
b65d05ba9f | ||
|
|
7fe9451bda | ||
|
|
4c8f9d98fb | ||
|
|
dde3a019bd | ||
|
|
df7e0f77a6 | ||
|
|
b6875e60f6 | ||
|
|
5b63d5df60 | ||
|
|
38c2134590 | ||
|
|
f14d70759d | ||
|
|
a292a60a36 | ||
|
|
4d3b7dedba | ||
|
|
1aab139775 | ||
|
|
88756d5997 | ||
|
|
8c86d6f570 | ||
|
|
86898837fb | ||
|
|
d7c774996e | ||
|
|
b8e5486f63 | ||
|
|
571a85f539 | ||
|
|
0f938fbabd | ||
|
|
5e7797df72 | ||
|
|
0b058d10bf | ||
|
|
0749f16499 | ||
|
|
52c3b649e0 | ||
|
|
03328f7523 | ||
|
|
5019f2a78a | ||
|
|
e4a68d2a76 | ||
|
|
bde371360d | ||
|
|
e62935762b | ||
|
|
ac08267403 | ||
|
|
9fe7532e27 | ||
|
|
3618d296af | ||
|
|
cab18339e6 | ||
|
|
5b1cfb4574 | ||
|
|
2bd6ed9198 | ||
|
|
4f4d7dd563 | ||
|
|
d5d58a9dbc | ||
|
|
7a2733947e | ||
|
|
4592e66879 | ||
|
|
06500ea4ca | ||
|
|
d89e2ce1a1 | ||
|
|
c4d1fcdbc6 | ||
|
|
e8deec13a2 | ||
|
|
678935d014 | ||
|
|
bb592363a7 | ||
|
|
e3b59fce38 | ||
|
|
1da5c53ce9 | ||
|
|
bfb16ceddf | ||
|
|
f7fbd6bde4 | ||
|
|
bec9362361 | ||
|
|
c4688b3526 | ||
|
|
8d5eb14919 | ||
|
|
5aa4d13560 | ||
|
|
a046cff693 | ||
|
|
57308e6059 | ||
|
|
32011a1f9a | ||
|
|
b735a529c2 | ||
|
|
6925ec761c | ||
|
|
521fd2796a | ||
|
|
2b00f0b37e | ||
|
|
88b6a941ec | ||
|
|
0c7607bd64 | ||
|
|
bb6ef2ba44 | ||
|
|
df61771b7e | ||
|
|
9605bb3d8e | ||
|
|
33176522da | ||
|
|
102f47174d | ||
|
|
cd9995c676 | ||
|
|
6679f36a2f | ||
|
|
19993f93ed | ||
|
|
9028a7402a | ||
|
|
0a32b9857d | ||
|
|
0a5b648f78 | ||
|
|
ebe77f7f63 | ||
|
|
caac39ce29 | ||
|
|
d24422a005 | ||
|
|
1c62c5fff0 | ||
|
|
395862fadf | ||
|
|
ba7a108af1 | ||
|
|
6c3f911e8e | ||
|
|
facf20ceb6 | ||
|
|
0690891781 | ||
|
|
0df30649ca | ||
|
|
bbdde7fd53 | ||
|
|
3d6f3b49a5 | ||
|
|
0b842636dc | ||
|
|
2d2d791e9f | ||
|
|
96e3d7ebd4 | ||
|
|
768a50149e | ||
|
|
199e6a0cdf | ||
|
|
59fc54ff64 | ||
|
|
343781a668 | ||
|
|
62b10f829d | ||
|
|
eb3113c1f3 | ||
|
|
887e81eb85 | ||
|
|
d6cfc891f0 | ||
|
|
cf5778d7d5 | ||
|
|
e76b72cdb1 | ||
|
|
f53b49041a | ||
|
|
21abd07672 | ||
|
|
6085ee4852 | ||
|
|
05653453ea | ||
|
|
86f8aa88af | ||
|
|
2d42c3d57a | ||
|
|
cf5a6f6e8b | ||
|
|
46354c9967 | ||
|
|
063ee210a7 | ||
|
|
f84c894e4e | ||
|
|
f8141bc517 | ||
|
|
ca4899078d | ||
|
|
51d4633df0 | ||
|
|
6139dcd052 | ||
|
|
ab48c07b98 | ||
|
|
0a49b75e2f | ||
|
|
5e9c61a185 | ||
|
|
8234c92dcf | ||
|
|
9edff6fd38 | ||
|
|
2ebcdd4ed0 | ||
|
|
f5183cae9b | ||
|
|
4196789c6d | ||
|
|
4c69f2af2e | ||
|
|
4c52dc23c1 | ||
|
|
8916167505 | ||
|
|
f4f2da7fe7 | ||
|
|
01529aaaf1 | ||
|
|
05efb81669 | ||
|
|
ca0d0bd1bd | ||
|
|
f82e07fd3a | ||
|
|
f2a61c9d94 | ||
|
|
3c09df3b77 | ||
|
|
6d4cf0cfe7 | ||
|
|
e4aa4c7459 | ||
|
|
3aff30b955 | ||
|
|
c9a225aef7 | ||
|
|
0fe234e68d | ||
|
|
56743ce3d8 | ||
|
|
1fdfbcd51f | ||
|
|
bf1e112d5a | ||
|
|
7266f4f927 | ||
|
|
77927830f3 | ||
|
|
e599d23f69 | ||
|
|
4c8738f1ef | ||
|
|
e01c7a9f31 | ||
|
|
c9a5b8508d | ||
|
|
cb320fe2ab | ||
|
|
773df44f17 | ||
|
|
402ddddbd7 | ||
|
|
238f3f6b14 | ||
|
|
539bf60e97 | ||
|
|
6527ab6a9f | ||
|
|
63164eb762 | ||
|
|
669e14b92c | ||
|
|
28da510571 | ||
|
|
6e5578ee6d | ||
|
|
68017740e7 | ||
|
|
ff68eeb5d1 | ||
|
|
276760d703 | ||
|
|
1b33c949f1 | ||
|
|
417537a13f | ||
|
|
6e15ed65e0 | ||
|
|
58dcd55076 | ||
|
|
c9ad1305ff | ||
|
|
964fc0fa87 | ||
|
|
bc234c7d89 | ||
|
|
87a286fe1f | ||
|
|
00970bbee9 | ||
|
|
7979ff4249 | ||
|
|
f3c4cbb99a | ||
|
|
bed2d4b1b0 | ||
|
|
81759fd857 | ||
|
|
f3cf886ce5 | ||
|
|
2176dbf4c2 | ||
|
|
80e8b599f9 | ||
|
|
7d636b771b | ||
|
|
e94cc91b8e | ||
|
|
0774d0fe92 | ||
|
|
1261062585 | ||
|
|
88d0cc7888 | ||
|
|
87848016ff | ||
|
|
86e58d6031 | ||
|
|
48e66714ac | ||
|
|
0c705e159f | ||
|
|
5409df4123 | ||
|
|
ac15e5adea | ||
|
|
880d9e0572 | ||
|
|
63dfbd8876 | ||
|
|
4a7b7b7024 | ||
|
|
34e26093ab | ||
|
|
601d29b0e9 | ||
|
|
bff959c8f0 | ||
|
|
34a2c657b6 | ||
|
|
fc6555fa1c | ||
|
|
e7ad7c628d | ||
|
|
7c61d55833 | ||
|
|
89becd866a | ||
|
|
eada4d5dcb | ||
|
|
7f15dcc225 | ||
|
|
bb945c740e | ||
|
|
dfc0d540d8 | ||
|
|
cd37acadbb | ||
|
|
c9fe6db34d | ||
|
|
5fe321a43f | ||
|
|
9e15c5a6fa | ||
|
|
026b911d58 | ||
|
|
08326f7718 | ||
|
|
881514f444 | ||
|
|
3f17fd55e5 | ||
|
|
3f2153e678 | ||
|
|
9ea3ed896f | ||
|
|
7ea5fc085c | ||
|
|
1306ab6640 | ||
|
|
f7c5ae5a16 | ||
|
|
631b357a10 | ||
|
|
42bc312151 | ||
|
|
12c72366f6 | ||
|
|
27d7d4afa4 | ||
|
|
cb3852ef16 | ||
|
|
50768641f9 | ||
|
|
651e54ed7c | ||
|
|
3bbbd858d4 | ||
|
|
9a8607038e | ||
|
|
2bac472615 | ||
|
|
b27072312b | ||
|
|
6bebc0f572 | ||
|
|
efa349c856 | ||
|
|
21f2cfbd9c | ||
|
|
b96af7391c |
@@ -0,0 +1,345 @@
|
||||
---
|
||||
name: autoreview
|
||||
description: "Pre-commit/ship code review: Codex default; optional Claude, Pi, Droid, Copilot, or OpenCode."
|
||||
---
|
||||
|
||||
# Auto Review
|
||||
|
||||
Run the bundled structured review helper as a closeout check. This is code review, not Guardian `auto_review` approval routing.
|
||||
|
||||
Codex review is the default when no engine is set. It uses `gpt-5.5` by default, usually delivers the best review results, and should remain the normal final closeout engine. Claude review is optional and uses `claude-fable-5` by default.
|
||||
|
||||
Use when:
|
||||
|
||||
- user asks for Codex review / Claude review / Pi review / Droid review / OpenCode review / autoreview / second-model review
|
||||
- after non-trivial code edits, before final/commit/ship
|
||||
- reviewing a local branch or PR branch after fixes
|
||||
|
||||
## Contract
|
||||
|
||||
- Treat review output as advisory. Never blindly apply it.
|
||||
- Verify every finding by reading the real code path and adjacent files.
|
||||
- Read dependency docs/source/types when the finding depends on external behavior.
|
||||
- Reject unrealistic edge cases, speculative risks, broad rewrites, and fixes that over-complicate the codebase.
|
||||
- Prefer small fixes at the right ownership boundary; no refactor unless it clearly improves the bug class.
|
||||
- When an accepted finding shows a bug class or repeated pattern, inspect the current PR scope for sibling instances before fixing.
|
||||
- Fix the scoped bug class at once when practical; stop at touched surfaces, owner boundaries, and clear follow-up territory.
|
||||
- Keep going until structured review returns no accepted/actionable findings only while the work remains inside the original task scope.
|
||||
- If a review-triggered fix changes code, rerun focused tests and rerun the structured review helper.
|
||||
- For security-audit suppression changes, verify accepted findings remain auditable: suppressed findings stay in structured output, active output keeps an unsuppressible suppression notice, and aggregate findings cannot hide unrelated active risk.
|
||||
- Never switch or override the requested review engine/model. If the review hits model capacity, retry the same command a few times with the same engine/model.
|
||||
- Be patient with large bundles. Structured review can take up to 30 minutes while the model call is active, especially with Codex tools or web search.
|
||||
- Treat heartbeat lines like `review still running: ... elapsed=... pid=...` as healthy progress, not a hang. Let the helper continue while heartbeats are advancing. Pass `--stream-engine-output` when live engine text is useful; Codex and Claude filter tool/file chatter, other engines pass raw output through.
|
||||
- Do not kill a review just because it has been quiet for 2-5 minutes, or because it is still running under the 30-minute window. Inspect the process only after missing multiple expected heartbeats, after 30 minutes, or after an obviously failed subprocess; prefer letting the same helper command finish.
|
||||
- Tools are useful in review mode. The helper allows read-only inspection tools and web search by default so reviewers can check dependency contracts, upstream docs, and current behavior.
|
||||
- Security perspective is always included, but it should not cripple legitimate functionality. Report security findings only when the change creates a concrete, actionable risk or removes an important safety check.
|
||||
- For regression provenance, keep roles separate: blamed code author, blamed PR author, PR merger/committer, current PR author, and PR/date. If no blamed PR is traceable, use the blamed commit as the provenance: commit SHA, date, and author username. Do not guess a merger or frame missing PR metadata as a separate finding.
|
||||
- If the blamed PR was merged by `clawsweeper[bot]` or another automation, identify the human trigger when practical. Check timeline/comments first; if rate-limited, use gitcrawl/cache or public PR HTML. Look for maintainer commands such as `@clawsweeper automerge`, `/landpr`, or labels/status comments that armed automerge. Report `automerge triggered by @login`; if not found, say trigger unknown.
|
||||
- Do not invoke built-in `codex review`, nested reviewers, or reviewer panels from inside the review. The helper builds one bundle, calls one selected engine, validates one structured result, and stops.
|
||||
- Stop as soon as the helper exits 0 with no accepted/actionable findings. Do not run an extra review just to get a nicer "clean" line, a second opinion, or clearer closeout wording.
|
||||
- Treat the helper's successful exit plus absence of actionable findings as the clean review result, even if the underlying Codex CLI output is terse.
|
||||
- Multi-reviewer panels are opt-in only. Use them when explicitly requested or when risk justifies the extra spend; the main agent still verifies every accepted finding before fixing.
|
||||
- If rejecting a finding as intentional/not worth fixing, add a brief inline code comment only when it explains a real invariant or ownership decision that future reviewers should know.
|
||||
- If `gh`/Gitcrawl reports `database disk image is malformed`, run `gitcrawl doctor --json` once to let the portable cache repair before retrying review; do not bypass the shim unless repair fails and freshness requires live GitHub.
|
||||
- If Gitcrawl reports a portable manifest mismatch, source/runtime DB health error, or stale portable-store checkout, run `gitcrawl doctor --json` and inspect `source_db_health`, `runtime_db_health`, and `portable_store_status` before falling back to live GitHub.
|
||||
- Do not push just to review. Push only when the user requested push/ship/PR update.
|
||||
|
||||
## Scope Governor
|
||||
|
||||
Autoreview is a closeout gate, not permission to rewrite the task.
|
||||
|
||||
Before the first review, freeze a scope baseline: original request or issue, target branch, intended behavior, owner boundary, changed files, and non-test LOC. For inherited or already-bloated branches, use the intended PR diff as the baseline rather than accepting all existing branch drift.
|
||||
|
||||
Before patching a finding, classify it:
|
||||
|
||||
- **In-scope blocker**: the finding is introduced by the current diff, affects the same owner boundary, and can be fixed without changing the task's contract.
|
||||
- **Follow-up**: the finding is real but belongs to an adjacent bug class, sibling surface, cleanup, or broader hardening track.
|
||||
- **Stop-and-escalate**: the finding requires a new protocol/config/storage/public API contract, a different owner boundary, a release-process change, or a design choice outside the original request.
|
||||
|
||||
Stop patching and report the scope break instead of continuing when:
|
||||
|
||||
- a narrow PR turns into an architecture change, protocol change, migration, or release-process change;
|
||||
- the diff grows past 2x the original files or non-test LOC without explicit approval to expand scope;
|
||||
- two review-triggered patch cycles have not converged; pause and reclassify every remaining finding before another edit;
|
||||
- the best fix is "define the canonical contract first" rather than another local inference layer;
|
||||
- fixing the accepted finding would make the PR no longer describe the same behavior, issue, or owner boundary.
|
||||
|
||||
After the two-cycle pause, continue only when every remaining accepted finding is still an in-scope blocker. Otherwise preserve the useful analysis, identify the smallest safe landed subset if one exists, and open or request a follow-up for the larger fix. Do not keep committing speculative fixes just to satisfy the reviewer.
|
||||
|
||||
Do not stack or push review-triggered fix commits while scope classification or focused proof is unresolved. Keep exploratory edits local until the cycle is proven in scope; if scope breaks, remove them from the landing lane instead of preserving them as branch history.
|
||||
|
||||
Critical exceptions must be explicit: active data loss, crash, broken install/upgrade, release blocker, or concrete security exposure. If the exception is not one of those, it is not critical enough to blow up scope.
|
||||
|
||||
## Release Branches And Release Process
|
||||
|
||||
On release, beta, stable, hotfix, signing, notarization, appcast, package-publish, or release-check work, use freeze discipline even when the branch name is not release-like:
|
||||
|
||||
- Fix only release blockers, failed release infrastructure, exact backports, install/upgrade breakage, data loss, crashes, or concrete security exposure.
|
||||
- Treat non-blocking autoreview findings as follow-ups for `main`, not reasons to broaden the release branch.
|
||||
- Do not introduce new product behavior, config surface, protocol shape, migration, plugin ownership, docs narrative, or process policy unless it directly unblocks the release.
|
||||
- Keep proof tied to the release target: exact branch/ref, failing check or shipped-risk reason, smallest command/proof, and whether the fix must also forward-port to `main`.
|
||||
- If review discovers a real but non-critical design problem during release closeout, stop with a follow-up issue/PR plan; do not use the release branch as the refactor lane.
|
||||
|
||||
## Skill Path (set once)
|
||||
|
||||
Set the skill script paths once, then use `"$AUTOREVIEW"` and `"$AUTOREVIEW_HARNESS"` in the examples below.
|
||||
|
||||
Choose one:
|
||||
|
||||
```bash
|
||||
# Project-local skill in the current repo:
|
||||
export AUTOREVIEW=".agents/skills/autoreview/scripts/autoreview"
|
||||
export AUTOREVIEW_HARNESS=".agents/skills/autoreview/scripts/test-review-harness"
|
||||
```
|
||||
|
||||
```bash
|
||||
# Source checkout of openclaw/agent-skills:
|
||||
export AUTOREVIEW="skills/autoreview/scripts/autoreview"
|
||||
export AUTOREVIEW_HARNESS="skills/autoreview/scripts/test-review-harness"
|
||||
```
|
||||
|
||||
```bash
|
||||
# Global skill:
|
||||
export AGENTS_HOME="${AGENTS_HOME:-$HOME/.agents}"
|
||||
export AUTOREVIEW="$AGENTS_HOME/skills/autoreview/scripts/autoreview"
|
||||
export AUTOREVIEW_HARNESS="$AGENTS_HOME/skills/autoreview/scripts/test-review-harness"
|
||||
```
|
||||
|
||||
When using Claude Code, set `AGENTS_HOME="$HOME/.claude"` for global skills. Project-local skills live under `.claude/skills/` in the current repo.
|
||||
|
||||
## Pick Target
|
||||
|
||||
Dirty local work:
|
||||
|
||||
```bash
|
||||
"$AUTOREVIEW" --mode local
|
||||
```
|
||||
|
||||
Use this only when the patch is actually unstaged/staged/untracked in the
|
||||
current checkout. `--mode uncommitted` is accepted as an alias for `--mode local`.
|
||||
For committed, pushed, or PR work, point the helper at the commit
|
||||
or branch diff instead; do not force dirty modes just
|
||||
because the helper docs mention dirty work first. A clean local review
|
||||
only proves there is no local patch.
|
||||
|
||||
Branch/PR work:
|
||||
|
||||
```bash
|
||||
"$AUTOREVIEW" --mode branch --base origin/main
|
||||
```
|
||||
|
||||
Optional review context is first-class. Prompt files and datasets must be repo-relative so review bundles cannot pull arbitrary host files:
|
||||
|
||||
```bash
|
||||
"$AUTOREVIEW" --mode branch --base origin/main --prompt-file review-notes.md --dataset evidence.json
|
||||
```
|
||||
|
||||
If an open PR exists, use its actual base:
|
||||
|
||||
```bash
|
||||
base=$(gh pr view --json baseRefName --jq .baseRefName)
|
||||
"$AUTOREVIEW" --mode branch --base "origin/$base"
|
||||
```
|
||||
|
||||
Committed single change:
|
||||
|
||||
```bash
|
||||
"$AUTOREVIEW" --mode commit --commit HEAD
|
||||
```
|
||||
|
||||
Use commit review for already-landed or already-pushed work on `main`. Reviewing
|
||||
clean `main` against `origin/main` is usually an empty diff after push. For a
|
||||
small stack, review each commit explicitly or review the branch before merging
|
||||
with `--base`.
|
||||
|
||||
## Parallel Closeout
|
||||
|
||||
Format first if formatting can change line locations. Then it is OK to run tests and review in parallel:
|
||||
|
||||
```bash
|
||||
"$AUTOREVIEW" --parallel-tests "<focused test command>"
|
||||
```
|
||||
|
||||
On Windows, the default `--parallel-tests` shell preserves the platform `cmd.exe`
|
||||
semantics used by Python `shell=True`. Use `--parallel-tests-shell powershell`
|
||||
or `--parallel-tests-shell pwsh` when the focused test command is PowerShell-specific.
|
||||
|
||||
Tradeoff: tests may force code changes that stale the review. If tests or review lead to code edits, rerun the affected tests and rerun review until no accepted/actionable findings remain. Once that rerun exits cleanly, stop; do not spend another long review cycle on redundant confirmation.
|
||||
|
||||
## Review Panels
|
||||
|
||||
Run multiple reviewers against one frozen bundle:
|
||||
|
||||
```bash
|
||||
"$AUTOREVIEW" --reviewers codex,claude,pi,droid
|
||||
```
|
||||
|
||||
`--panel` is shorthand for Codex plus Claude unless `--engine` changes the first reviewer:
|
||||
|
||||
```bash
|
||||
"$AUTOREVIEW" --panel
|
||||
```
|
||||
|
||||
Set reviewer models and thinking/effort explicitly:
|
||||
|
||||
```bash
|
||||
"$AUTOREVIEW" --reviewers codex,claude --model codex=gpt-5.5 --thinking codex=high --model claude=claude-fable-5 --thinking claude=max
|
||||
```
|
||||
|
||||
Inline syntax is also supported for simple model IDs:
|
||||
|
||||
```bash
|
||||
"$AUTOREVIEW" --reviewers codex:gpt-5.5:high,claude:claude-fable-5:max
|
||||
```
|
||||
|
||||
For models with slashes or extra colons, prefer keyed form:
|
||||
|
||||
```bash
|
||||
"$AUTOREVIEW" --engine pi --model anthropic/claude-sonnet-4 --thinking high
|
||||
"$AUTOREVIEW" --engine opencode --model opencode/north-mini-code-free --thinking high
|
||||
"$AUTOREVIEW" --engine droid --model claude-opus-4-8 --thinking low
|
||||
"$AUTOREVIEW" --reviewers codex,pi --model codex=gpt-5.5 --model pi=anthropic/claude-sonnet-4
|
||||
"$AUTOREVIEW" --reviewers codex,opencode --model codex=gpt-5.5 --model opencode=opencode/north-mini-code-free
|
||||
"$AUTOREVIEW" --reviewers codex,droid --model codex=gpt-5.5 --model droid=claude-opus-4-8
|
||||
```
|
||||
|
||||
## Models and thinking
|
||||
|
||||
The helper accepts `--model` globally or per engine (`engine=model`) and `--thinking` globally or per engine (`engine=level`). Repeat either flag for multiple reviewers.
|
||||
|
||||
Recommended model defaults:
|
||||
|
||||
| Engine | Default model | Source note |
|
||||
| ------------------- | ---------------- | ----------------------------------------------------- |
|
||||
| **codex** (default) | `gpt-5.5` | OpenAI's current GPT-5.5 alias |
|
||||
| **claude** | `claude-fable-5` | Anthropic's most capable widely released Claude model |
|
||||
|
||||
CLI flags and environment variables override these defaults. Droid, Copilot, Pi, and OpenCode do not get built-in model defaults here because their provider catalogs are external to the Codex/Claude closeout path and may vary by installation.
|
||||
|
||||
| Engine | Model flag | Example model IDs | Thinking flag | Accepted levels |
|
||||
| ------------------- | -------------------------- | ---------------------------------------------------------------------------- | ----------------------------- | --------------------------------------------------- |
|
||||
| **codex** (default) | `codex --model X exec ...` | `gpt-5.5`, `gpt-5.5-2026-04-23` | `-c model_reasoning_effort=Y` | `none`, `minimal`, `low`, `medium`, `high`, `xhigh` |
|
||||
| **claude** | `claude --model X` | `claude-fable-5`, `claude-opus-4-8`, `claude-sonnet-4-6`, `claude-haiku-4-5` | `--effort Y` | `low`, `medium`, `high`, `xhigh`, `max` |
|
||||
| **droid** | `droid exec --model X` | `claude-opus-4-8`, Factory model IDs | `-r, --reasoning-effort Y` | `off`, `none`, `low`, `medium`, `high` |
|
||||
| **copilot** | `copilot --model X` | `gpt-5.2`, Copilot model aliases | not supported | n/a |
|
||||
| **pi** | `pi --model X` | `anthropic/claude-sonnet-4`, `openai/gpt-4o` | `--thinking Y` | `off`, `minimal`, `low`, `medium`, `high`, `xhigh` |
|
||||
| **opencode** | `opencode run -m X` | `opencode/north-mini-code-free`, OpenCode provider/model IDs | `--variant Y` | `minimal`, `low`, `medium`, `high`, `max` |
|
||||
|
||||
Claude also supports `--fallback-model a,b` for availability-based fallback chains ([model-config](https://code.claude.com/docs/en/model-config)). Current Claude docs note that auth, billing, rate-limit, request-size, and transport errors do not trigger fallback, and the changelog documents interactive-session support in `v2.1.166`.
|
||||
|
||||
Examples matching current `main` behavior:
|
||||
|
||||
```bash
|
||||
# Codex with explicit model and reasoning
|
||||
"$AUTOREVIEW" --engine codex --model gpt-5.5 --thinking high
|
||||
|
||||
# Claude Code aliases or full model names, with optional availability fallback
|
||||
"$AUTOREVIEW" --engine claude --model claude-fable-5 --thinking max
|
||||
"$AUTOREVIEW" --engine claude --model claude-fable-5 --fallback-model claude-opus-4-8,claude-sonnet-4-6
|
||||
|
||||
# Factory Droid with explicit model and reasoning effort
|
||||
"$AUTOREVIEW" --engine droid --model claude-opus-4-8 --thinking low
|
||||
|
||||
# GitHub Copilot (model only; no thinking knob)
|
||||
"$AUTOREVIEW" --engine copilot --model gpt-5.2
|
||||
|
||||
# Pi with explicit model and thinking level
|
||||
"$AUTOREVIEW" --engine pi --model anthropic/claude-sonnet-4 --thinking high --pi-bin pi
|
||||
|
||||
# OpenCode with explicit provider/model and variant
|
||||
"$AUTOREVIEW" --engine opencode --model opencode/north-mini-code-free --thinking high
|
||||
```
|
||||
|
||||
### Environment defaults
|
||||
|
||||
CLI flags take precedence over environment variables.
|
||||
|
||||
| Variable | Purpose |
|
||||
| ---------------------------------- | ----------------------------------------------------------------------- |
|
||||
| `AUTOREVIEW_MODEL` | Override the built-in default `--model` for all engines |
|
||||
| `AUTOREVIEW_THINKING` | Default `--thinking` for all engines |
|
||||
| `AUTOREVIEW_FALLBACK_MODEL` | Default Claude `--fallback-model` chain |
|
||||
| `AUTOREVIEW_<ENGINE>_MODEL` | Per-engine model override, for example `AUTOREVIEW_CODEX_MODEL=gpt-5.5` |
|
||||
| `AUTOREVIEW_<ENGINE>_THINKING` | Per-engine thinking override |
|
||||
| `AUTOREVIEW_CLAUDE_FALLBACK_MODEL` | Claude-only fallback chain |
|
||||
|
||||
Codex maps thinking to `model_reasoning_effort`. Claude maps thinking to `--effort`. Droid maps thinking to `-r, --reasoning-effort`. Pi maps thinking to `--thinking`. OpenCode maps thinking to `--variant`. Copilot rejects `--thinking`. Only Claude accepts `--fallback-model`; global CLI/env fallback requires at least one Claude reviewer, and engine-specific fallback overrides require that reviewer to be selected. Non-Claude fallback overrides, including `AUTOREVIEW_<NONCLAUDE>_FALLBACK_MODEL`, fail closed instead of being silently ignored.
|
||||
|
||||
## Review engine isolation
|
||||
|
||||
When autoreview runs inside the repository under review, external reviewer CLIs must not load project-local trust or configuration that the branch controls.
|
||||
|
||||
| Engine | Isolation flags | Reference |
|
||||
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
|
||||
| **codex** | Auth-only config overrides, `-c project_doc_max_bytes=0`, repo `trust_level="untrusted"`, `exec --ignore-user-config --ignore-rules`, plus read-only sandbox | Codex CLI `exec --help` |
|
||||
| **claude** | `--safe-mode --setting-sources user --strict-mcp-config --disallowedTools mcp__*` plus explicit `--allowedTools` (`--safe-mode` requires Claude Code `v2.1.169+`) | Claude Code [CLI reference](https://code.claude.com/docs/en/cli-reference) |
|
||||
| **pi** | `--no-approve --no-session --no-context-files --no-extensions --no-skills --no-prompt-templates --no-themes`, plus read-only tool allowlist | Pi CLI `--help`; requires Pi `v0.79.0+` |
|
||||
| **opencode** | `opencode run --dir <repo> --pure --format json`, prompt over stdin, neutral subprocess cwd, injected deny-by-default permissions, project config disabled | OpenCode CLI `--help` |
|
||||
|
||||
Codex `--ignore-user-config` skips config loading for the exec run. Autoreview reconstructs only the documented `cli_auth_credentials_store`, `forced_login_method`, and `forced_chatgpt_workspace_id` settings from `CODEX_HOME/config.toml`, keeping authentication and workspace restrictions usable without forwarding unrelated user configuration. The explicit repo trust override and zero project-doc budget keep reviewed-repo `AGENTS.md` and `.codex/` trust surfaces out of the review prompt. `--ignore-rules` skips user/project execpolicy rules. Claude `--safe-mode` disables project hooks, skills, plugins, MCP servers, and CLAUDE.md while preserving normal authentication, model selection, built-in tools, and permissions; managed settings policy can still apply. `--setting-sources user` avoids project/local settings from the reviewed checkout, and current Claude Code docs note the project-skill blocking behavior was fixed in `v2.1.69`. `--strict-mcp-config` and `--disallowedTools mcp__*` keep MCP unavailable to the review run. `--bare` is not used here because Claude's headless docs say it skips OAuth and keychain reads. Pi `--no-approve` ignores project-local files for one run; the helper requires Pi `v0.79.0+` plus help output that advertises every required isolation flag because older legacy binaries can ignore unknown flags. The current package is `@earendil-works/pi-coding-agent`; deprecated `@mariozechner/pi-coding-agent` `0.73.x` is intentionally rejected. Pi version/help probes and the review command run from neutral temporary directories, not the reviewed repo. Pi `--no-context-files` removes `AGENTS.md`/`CLAUDE.md`, the resource-disable flags keep `.pi` extensions, skills, prompts, and themes out of the run, `--no-session` avoids writing review sessions, and the read-only allowlist omits `bash`, `edit`, and `write`. OpenCode starts from a neutral temporary directory, points at the reviewed repo with `--dir`, disables project config through `OPENCODE_DISABLE_PROJECT_CONFIG=1`, and injects `OPENCODE_CONFIG_CONTENT`; permissions default to deny, allow read/grep/glob, preserve OpenCode's `.env` ask rules, and gate `websearch`/`webfetch` with `--no-web-search`. The injected config also clears command/instruction/plugin arrays and disables write/edit/bash/task/skill/todowrite tools without changing user auth storage. The helper sends the review prompt over stdin rather than argv and extracts the final structured JSON from `type: "text"` events. OpenCode rejects `--no-tools`.
|
||||
|
||||
## Context Efficiency
|
||||
|
||||
Run the helper directly so target selection, engine choice, structured validation, and exit status all stay in one path. If output is noisy, summarize the completed helper output after it returns; do not ask another agent or reviewer to rerun the review.
|
||||
|
||||
## Helper
|
||||
|
||||
After setting `AUTOREVIEW` and `AUTOREVIEW_HARNESS` above:
|
||||
|
||||
```bash
|
||||
"$AUTOREVIEW" --help
|
||||
```
|
||||
|
||||
The smoke harness has thin shell wrappers over a shared Python implementation:
|
||||
|
||||
```bash
|
||||
"$AUTOREVIEW_HARNESS" --fixture benign --engine codex
|
||||
```
|
||||
|
||||
On native Windows, invoke the extensionless Python helper through Python:
|
||||
|
||||
```powershell
|
||||
python skills\autoreview\scripts\autoreview --help
|
||||
```
|
||||
|
||||
and the smoke harness:
|
||||
|
||||
```powershell
|
||||
skills\autoreview\scripts\test-review-harness.ps1 -Fixture benign -Engine codex
|
||||
```
|
||||
|
||||
The helper:
|
||||
|
||||
- chooses dirty local changes first
|
||||
- accepts `--mode uncommitted` as an alias for `--mode local`
|
||||
- otherwise uses current PR base if `gh pr view` works
|
||||
- otherwise uses `origin/main` for non-main branches
|
||||
- does not fetch automatically during branch review; the selected base ref must already resolve locally
|
||||
- supports `--engine codex`, `claude`, `droid`, `copilot`, `pi`, and `opencode`; default is `AUTOREVIEW_ENGINE` or `codex`; Codex should remain the default when nothing is set
|
||||
- resolves bare `git`, `gh`, reviewer, and PowerShell shell commands from absolute `PATH` entries only, never from the reviewed checkout; explicit relative `--*-bin` paths are resolved from the reviewed repository root
|
||||
- use `--mode commit --commit <ref>` for already-committed work, especially clean `main` after landing
|
||||
- should be left in `--mode auto` or forced to `--mode branch` for PR/branch work; do not force `--mode local` after committing
|
||||
- writes only to stdout unless `--output`, `--json-output`, or live streamed engine stderr is set
|
||||
- supports `--dry-run`, `--parallel-tests`, `--parallel-tests-shell`, `--prompt`, repo-relative `--prompt-file`, repo-relative `--dataset`, `--no-tools`, `--no-web-search`, and commit refs
|
||||
- supports `--stream-engine-output` or `AUTOREVIEW_STREAM_ENGINE_OUTPUT=1` for live engine text while preserving structured validation; Codex and Claude hide tool/file event details, emit compact activity summaries, and report usage at turn completion
|
||||
- supports opt-in review panels with `--panel` / `--reviewers`, plus per-engine `--model`, `--thinking`, and Claude `--fallback-model`
|
||||
- uses built-in model defaults `codex=gpt-5.5` and `claude=claude-fable-5`; honors `AUTOREVIEW_MODEL`, `AUTOREVIEW_THINKING`, `AUTOREVIEW_FALLBACK_MODEL`, and per-engine `AUTOREVIEW_<ENGINE>_MODEL` / `AUTOREVIEW_<ENGINE>_THINKING` environment overrides when CLI flags are omitted
|
||||
- allows read-only tools and web search by default where the selected CLI supports them; forbids nested review in the prompt; Codex is run through `codex exec` with auth-only user settings, read-only sandbox, reviewed-repo instruction/config/rule isolation flags, and structured output
|
||||
- runs Claude with `--safe-mode` (`v2.1.169+`), `--setting-sources user`, MCP disabled, explicit allowed tools, and `--fallback-model` when set, so reviewed-repo hooks/skills/MCP do not affect the review run while normal auth still works; managed settings policy can still apply
|
||||
- runs Droid with `droid exec` in read-only mode, forwards `--model` and `-r, --reasoning-effort`, and switches `--output-format` to `stream-json` when streaming is enabled
|
||||
- runs Pi `v0.79.0+` from neutral temporary directories with `--no-approve`, `--no-session`, disabled Pi context/resource loading, and built-in read-only tools (`read,grep,find,ls`) when tools are enabled
|
||||
- runs OpenCode with `opencode run --dir <repo> --pure --format json` from a neutral temporary directory, forwards `--model` and `--variant`, injects deny-by-default permissions, disables project config loading, and passes the review prompt over stdin
|
||||
- prints `review still running: <engine> elapsed=<seconds>s pid=<pid>` to stderr at long-running intervals while waiting for the selected review engine, unless streamed output or compact Codex activity has been visible recently
|
||||
- prints `autoreview clean: no accepted/actionable findings reported` when the selected review command exits 0
|
||||
- exits nonzero when accepted/actionable findings are present
|
||||
|
||||
## Final Report
|
||||
|
||||
Include:
|
||||
|
||||
- review command used
|
||||
- tests/proof run
|
||||
- findings accepted/rejected, briefly why
|
||||
- the clean review result from the final helper/review run, or why a remaining finding was consciously rejected
|
||||
|
||||
Do not run another review solely to improve the final report wording. If the final helper run exited 0 and produced no accepted/actionable findings, report that exact run as clean.
|
||||
Executable
+3046
File diff suppressed because it is too large
Load Diff
+16
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
harness="$script_dir/test-review-harness.py"
|
||||
|
||||
if command -v python3 >/dev/null 2>&1; then
|
||||
exec python3 "$harness" "$@"
|
||||
fi
|
||||
|
||||
if command -v python >/dev/null 2>&1; then
|
||||
exec python "$harness" "$@"
|
||||
fi
|
||||
|
||||
echo "Python 3 is required to run test-review-harness." >&2
|
||||
exit 127
|
||||
@@ -0,0 +1,45 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[ValidateSet('malicious', 'benign')]
|
||||
[string] $Fixture,
|
||||
|
||||
[ValidateSet('codex', 'claude', 'droid', 'copilot', 'pi', 'opencode')]
|
||||
[string[]] $Engine,
|
||||
|
||||
[Alias('h')]
|
||||
[switch] $Help
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$Harness = Join-Path $PSScriptRoot 'test-review-harness.py'
|
||||
$ForwardedArgs = @()
|
||||
|
||||
if ($Help) {
|
||||
$ForwardedArgs += '--help'
|
||||
}
|
||||
|
||||
if ($PSBoundParameters.ContainsKey('Fixture')) {
|
||||
$ForwardedArgs += @('--fixture', $Fixture)
|
||||
}
|
||||
|
||||
if ($PSBoundParameters.ContainsKey('Engine')) {
|
||||
foreach ($SelectedEngine in $Engine) {
|
||||
$ForwardedArgs += @('--engine', $SelectedEngine)
|
||||
}
|
||||
}
|
||||
|
||||
$PyLauncher = Get-Command py -ErrorAction SilentlyContinue
|
||||
if ($null -ne $PyLauncher) {
|
||||
& $PyLauncher.Source -3 $Harness @ForwardedArgs
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
|
||||
$Python = Get-Command python -ErrorAction SilentlyContinue
|
||||
if ($null -ne $Python) {
|
||||
& $Python.Source $Harness @ForwardedArgs
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
|
||||
Write-Error 'Python 3 is required to run test-review-harness.'
|
||||
exit 127
|
||||
@@ -0,0 +1,215 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import runpy
|
||||
import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ENGINES = ("codex", "claude", "droid", "copilot", "pi", "opencode")
|
||||
DEFAULT_ENGINES = ("codex", "claude")
|
||||
|
||||
MALICIOUS_INITIAL = """export function uploadPath(name) {
|
||||
return `uploads/${name.replaceAll("/", "")}`;
|
||||
}
|
||||
|
||||
export function publicUser(user) {
|
||||
return { id: user.id, name: user.name };
|
||||
}
|
||||
"""
|
||||
|
||||
BENIGN_INITIAL = r"""import path from "node:path";
|
||||
|
||||
const uploadsRoot = path.resolve("uploads");
|
||||
|
||||
function safeChildPath(root, name) {
|
||||
if (name.includes("/") || name.includes("\\") || name.includes("\0")) {
|
||||
throw new Error("invalid child name");
|
||||
}
|
||||
const resolved = path.resolve(root, name);
|
||||
if (!resolved.startsWith(root + path.sep)) throw new Error("path escaped root");
|
||||
return resolved;
|
||||
}
|
||||
|
||||
export function uploadPath(name) {
|
||||
return safeChildPath(uploadsRoot, name);
|
||||
}
|
||||
|
||||
export function publicUser(user) {
|
||||
return { id: user.id, name: user.name };
|
||||
}
|
||||
"""
|
||||
|
||||
MALICIOUS_CHANGED = """import { execSync } from "node:child_process";
|
||||
|
||||
export function uploadPath(name) {
|
||||
return `uploads/${name}`;
|
||||
}
|
||||
|
||||
export function deleteUpload(name) {
|
||||
return execSync(`rm -rf uploads/${name}`);
|
||||
}
|
||||
|
||||
export function publicUser(user) {
|
||||
return { id: user.id, name: user.name, password: user.password };
|
||||
}
|
||||
"""
|
||||
|
||||
BENIGN_CHANGED = r"""import { execFile } from "node:child_process";
|
||||
import path from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
|
||||
const uploadsRoot = path.resolve("uploads");
|
||||
const reposRoot = path.resolve("repos");
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
function safeChildPath(root, name) {
|
||||
if (name.includes("/") || name.includes("\\") || name.includes("\0")) {
|
||||
throw new Error("invalid child name");
|
||||
}
|
||||
const resolved = path.resolve(root, name);
|
||||
if (!resolved.startsWith(root + path.sep)) throw new Error("path escaped root");
|
||||
return resolved;
|
||||
}
|
||||
|
||||
function repoChildPath(root, name) {
|
||||
if (!/^[A-Za-z0-9._-]+$/.test(name)) throw new Error("invalid repo name");
|
||||
return safeChildPath(root, name);
|
||||
}
|
||||
|
||||
export function uploadPath(name) {
|
||||
return safeChildPath(uploadsRoot, name);
|
||||
}
|
||||
|
||||
export async function repoProbe(repoName) {
|
||||
const { stdout } = await execFileAsync(process.execPath, ["--version"], {
|
||||
cwd: repoChildPath(reposRoot, repoName),
|
||||
encoding: "utf8",
|
||||
maxBuffer: 16 * 1024 * 1024,
|
||||
});
|
||||
return stdout;
|
||||
}
|
||||
|
||||
export function publicUser(user) {
|
||||
return { id: user.id, name: user.name };
|
||||
}
|
||||
|
||||
export function accountSettingsForOwner(user, requesterId) {
|
||||
if (String(requesterId) !== String(user.id)) throw new Error("forbidden");
|
||||
return { id: user.id, hasPassword: Boolean(user.passwordHash) };
|
||||
}
|
||||
"""
|
||||
|
||||
MALICIOUS_PROMPT = "This is an acceptance test fixture. The changed app.js patch contains real security bugs. Review normally and report only actionable defects from the patch."
|
||||
BENIGN_PROMPT = "Security calibration fixture: this patch intentionally uses filesystem paths, async execFile, and owner-gated password-adjacent state safely. Do not flag legitimate shell/filesystem/auth-adjacent functionality unless there is a concrete exploitable risk in the diff."
|
||||
|
||||
|
||||
def parse_args(argv: list[str]) -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="test-review-harness",
|
||||
description=(
|
||||
"Creates a temporary git repo with either a deliberately unsafe patch "
|
||||
"or a security-sensitive-but-safe patch, then verifies each selected "
|
||||
"engine through autoreview."
|
||||
),
|
||||
epilog="Default engines: codex, claude.",
|
||||
)
|
||||
parser.add_argument("--fixture", choices=("malicious", "benign"), default="malicious")
|
||||
parser.add_argument("--engine", action="append", choices=ENGINES, dest="engines")
|
||||
return parser.parse_args(argv)
|
||||
|
||||
|
||||
def write_fixture_file(repo: Path, content: str) -> None:
|
||||
with (repo / "app.js").open("w", encoding="utf-8", newline="\n") as handle:
|
||||
handle.write(content)
|
||||
|
||||
|
||||
def run(command: list[str], cwd: Path) -> None:
|
||||
subprocess.run(command, cwd=cwd, check=True)
|
||||
|
||||
|
||||
def create_fixture_repo(repo: Path, fixture: str) -> None:
|
||||
run(["git", "init", "--quiet"], repo)
|
||||
run(["git", "config", "user.name", "Review Fixture"], repo)
|
||||
run(["git", "config", "user.email", "review-fixture@example.com"], repo)
|
||||
|
||||
write_fixture_file(repo, MALICIOUS_INITIAL if fixture == "malicious" else BENIGN_INITIAL)
|
||||
run(["git", "add", "app.js"], repo)
|
||||
run(["git", "commit", "--quiet", "-m", "initial safe version"], repo)
|
||||
write_fixture_file(repo, MALICIOUS_CHANGED if fixture == "malicious" else BENIGN_CHANGED)
|
||||
|
||||
|
||||
def validate_prompt_policy(repo: Path, autoreview: Path) -> None:
|
||||
namespace = runpy.run_path(str(autoreview))
|
||||
prompt = namespace["build_prompt"](repo, "local", None, "fixture diff", "", "")
|
||||
required = (
|
||||
"This helper is a closeout gate.",
|
||||
"Do not turn a narrow patch into a broad",
|
||||
"If this is release-branch or release-process work",
|
||||
"Non-blocking design,",
|
||||
)
|
||||
missing = [needle for needle in required if needle not in prompt]
|
||||
if missing:
|
||||
raise RuntimeError(f"autoreview prompt missing scope policy: {missing}")
|
||||
|
||||
|
||||
def run_reviews(repo: Path, script_dir: Path, fixture: str, engines: list[str]) -> None:
|
||||
autoreview = script_dir / "autoreview"
|
||||
validate_prompt_policy(repo, autoreview)
|
||||
for engine in engines:
|
||||
print(f"== {engine} ==", flush=True)
|
||||
command = [
|
||||
sys.executable,
|
||||
str(autoreview),
|
||||
"--mode",
|
||||
"local",
|
||||
"--engine",
|
||||
engine,
|
||||
"--prompt",
|
||||
MALICIOUS_PROMPT if fixture == "malicious" else BENIGN_PROMPT,
|
||||
]
|
||||
if fixture == "malicious":
|
||||
command.extend(["--require-finding", "command", "--expect-findings"])
|
||||
run(command, repo)
|
||||
|
||||
|
||||
def cleanup_repo(repo: Path) -> None:
|
||||
def make_writable_and_retry(function: Callable[[str], object], path: str, _exc_info: object) -> None:
|
||||
try:
|
||||
os.chmod(path, stat.S_IREAD | stat.S_IWRITE)
|
||||
function(path)
|
||||
except OSError as exc:
|
||||
print(f"warning: unable to remove temp path {path}: {exc}", file=sys.stderr)
|
||||
|
||||
if not repo.exists():
|
||||
return
|
||||
try:
|
||||
shutil.rmtree(repo, onerror=make_writable_and_retry)
|
||||
except OSError as exc:
|
||||
print(f"warning: unable to remove temp repo {repo}: {exc}", file=sys.stderr)
|
||||
|
||||
|
||||
def main(argv: list[str]) -> int:
|
||||
args = parse_args(argv)
|
||||
script_dir = Path(__file__).resolve().parent
|
||||
engines = args.engines or list(DEFAULT_ENGINES)
|
||||
repo = Path(tempfile.mkdtemp(prefix="autoreview-fixture."))
|
||||
try:
|
||||
create_fixture_repo(repo, args.fixture)
|
||||
run_reviews(repo, script_dir, args.fixture, engines)
|
||||
except subprocess.CalledProcessError as exc:
|
||||
return int(exc.returncode or 1)
|
||||
finally:
|
||||
cleanup_repo(repo)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(sys.argv[1:]))
|
||||
@@ -0,0 +1,209 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import runpy
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
SCRIPT = Path(__file__).resolve().parents[1] / "scripts" / "autoreview"
|
||||
|
||||
|
||||
def load_helper() -> dict[str, object]:
|
||||
return runpy.run_path(str(SCRIPT), run_name="autoreview_under_test")
|
||||
|
||||
|
||||
def git(repo: Path, *args: str) -> str:
|
||||
env = os.environ.copy()
|
||||
env.update(
|
||||
{
|
||||
"GIT_AUTHOR_NAME": "Autoreview Test",
|
||||
"GIT_AUTHOR_EMAIL": "autoreview@example.invalid",
|
||||
"GIT_COMMITTER_NAME": "Autoreview Test",
|
||||
"GIT_COMMITTER_EMAIL": "autoreview@example.invalid",
|
||||
}
|
||||
)
|
||||
result = subprocess.run(
|
||||
["git", *args],
|
||||
cwd=repo,
|
||||
env=env,
|
||||
check=True,
|
||||
text=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
return result.stdout
|
||||
|
||||
|
||||
def init_repo(tempdir: Path) -> Path:
|
||||
repo = tempdir / "repo"
|
||||
repo.mkdir()
|
||||
git(repo, "init", "-q")
|
||||
git(repo, "config", "user.name", "Autoreview Test")
|
||||
git(repo, "config", "user.email", "autoreview@example.invalid")
|
||||
return repo
|
||||
|
||||
|
||||
class AutoreviewHardeningTests(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.helper = load_helper()
|
||||
|
||||
def test_local_bundle_blocks_sensitive_untracked_file(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tempdir:
|
||||
repo = init_repo(Path(tempdir))
|
||||
(repo / ".env").write_text("placeholder=true\n", encoding="utf-8")
|
||||
|
||||
with self.assertRaisesRegex(SystemExit, "untracked sensitive files"):
|
||||
self.helper["local_bundle"](repo)
|
||||
|
||||
def test_local_bundle_omits_safe_untracked_binary_content(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tempdir:
|
||||
repo = init_repo(Path(tempdir))
|
||||
(repo / "image.bin").write_bytes(b"\x89PNG\r\n\0binary-content")
|
||||
|
||||
bundle = self.helper["local_bundle"](repo)
|
||||
|
||||
self.assertIn("## image.bin\n[binary file omitted]", bundle)
|
||||
|
||||
def test_branch_bundle_rejects_unsafe_or_unknown_base_before_diff(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tempdir:
|
||||
repo = init_repo(Path(tempdir))
|
||||
(repo / "tracked.txt").write_text("base\n", encoding="utf-8")
|
||||
git(repo, "add", "tracked.txt")
|
||||
git(repo, "commit", "-q", "-m", "base")
|
||||
|
||||
with self.assertRaisesRegex(SystemExit, "unsafe base ref"):
|
||||
self.helper["branch_bundle"](repo, "--help")
|
||||
with self.assertRaisesRegex(SystemExit, "unknown base ref"):
|
||||
self.helper["branch_bundle"](repo, "origin/main")
|
||||
|
||||
def test_git_path_list_preserves_newline_filenames(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tempdir:
|
||||
repo = init_repo(Path(tempdir))
|
||||
rel = "line\nbreak.txt"
|
||||
(repo / rel).write_text("content\n", encoding="utf-8")
|
||||
git(repo, "add", rel)
|
||||
|
||||
paths = self.helper["git_path_list"](repo, "ls-files", "-z")
|
||||
|
||||
self.assertIn(rel, paths)
|
||||
|
||||
def test_bounded_truncates_large_bundle_component(self) -> None:
|
||||
bounded = self.helper["bounded"]("x" * 25, 10)
|
||||
|
||||
self.assertEqual(bounded, "x" * 10 + "\n\n[truncated at 10 characters]\n")
|
||||
|
||||
def test_read_text_truncates_without_scanning_tail(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tempdir:
|
||||
path = Path(tempdir) / "large.txt"
|
||||
path.write_bytes(b"x" * 200_000 + b"\0tail")
|
||||
|
||||
text = self.helper["read_text"](path)
|
||||
|
||||
self.assertIn("[truncated at 180000 characters]", text)
|
||||
self.assertNotEqual(text, "[binary file omitted]")
|
||||
|
||||
def test_evidence_file_must_be_repo_relative_and_not_symlinked(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tempdir:
|
||||
root = Path(tempdir)
|
||||
repo = init_repo(root)
|
||||
outside = root / "outside.md"
|
||||
outside.write_text("outside\n", encoding="utf-8")
|
||||
|
||||
with self.assertRaisesRegex(SystemExit, "repo-relative"):
|
||||
self.helper["validate_evidence_file"](repo, str(outside), "--prompt-file")
|
||||
|
||||
target = repo / "notes.md"
|
||||
target.write_text("notes\n", encoding="utf-8")
|
||||
link = repo / "link.md"
|
||||
link.symlink_to(target)
|
||||
with self.assertRaisesRegex(SystemExit, "symlinked"):
|
||||
self.helper["validate_evidence_file"](repo, "link.md", "--dataset")
|
||||
|
||||
def test_safe_engine_env_strips_process_injection_variables(self) -> None:
|
||||
old = os.environ.copy()
|
||||
with tempfile.TemporaryDirectory() as tempdir:
|
||||
repo = init_repo(Path(tempdir))
|
||||
try:
|
||||
os.environ["GIT_DIR"] = "/tmp/unsafe-git-dir"
|
||||
os.environ["GIT_CONFIG_COUNT"] = "99"
|
||||
os.environ["DYLD_INSERT_LIBRARIES"] = "/tmp/unsafe.dylib"
|
||||
os.environ["NODE_OPTIONS"] = "--require=/tmp/unsafe.js"
|
||||
|
||||
env = self.helper["safe_engine_env"](repo)
|
||||
|
||||
self.assertNotEqual(env.get("GIT_DIR"), "/tmp/unsafe-git-dir")
|
||||
self.assertEqual(
|
||||
env["GIT_CONFIG_COUNT"],
|
||||
str(len(self.helper["ENGINE_GIT_CONFIG_OVERRIDES"])),
|
||||
)
|
||||
self.assertNotIn("DYLD_INSERT_LIBRARIES", env)
|
||||
self.assertNotIn("NODE_OPTIONS", env)
|
||||
finally:
|
||||
os.environ.clear()
|
||||
os.environ.update(old)
|
||||
|
||||
def test_safe_engine_env_excludes_repo_local_path_entries(self) -> None:
|
||||
old_path = os.environ.get("PATH", "")
|
||||
with tempfile.TemporaryDirectory() as tempdir:
|
||||
repo = init_repo(Path(tempdir))
|
||||
os.environ["PATH"] = f"{repo}{os.pathsep}{old_path}"
|
||||
try:
|
||||
env = self.helper["safe_engine_env"](repo)
|
||||
finally:
|
||||
os.environ["PATH"] = old_path
|
||||
|
||||
self.assertNotIn(str(repo.resolve()), env["PATH"].split(os.pathsep))
|
||||
|
||||
def test_large_repo_relative_evidence_file_is_truncated(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tempdir:
|
||||
repo = init_repo(Path(tempdir))
|
||||
evidence = repo / "evidence.txt"
|
||||
evidence.write_text("x" * 600_000, encoding="utf-8")
|
||||
|
||||
_, content = self.helper["validate_evidence_file"](repo, "evidence.txt", "--dataset")
|
||||
|
||||
self.assertIn("[truncated at 180000 characters]", content)
|
||||
|
||||
def test_copilot_allows_web_fetch_only_when_web_search_is_enabled(self) -> None:
|
||||
captured: list[list[str]] = []
|
||||
|
||||
def fake_run_with_heartbeat(
|
||||
cmd: list[str],
|
||||
cwd: Path,
|
||||
**kwargs: object,
|
||||
) -> subprocess.CompletedProcess[str]:
|
||||
captured.append(cmd)
|
||||
return subprocess.CompletedProcess(cmd, 0, '{"findings":[]}', "")
|
||||
|
||||
self.helper["run_copilot"].__globals__["run_with_heartbeat"] = fake_run_with_heartbeat
|
||||
self.helper["run_copilot"].__globals__["resolve_command"] = (
|
||||
lambda command, repo: f"/resolved/{command}"
|
||||
)
|
||||
args = argparse.Namespace(
|
||||
copilot_bin="copilot",
|
||||
thinking=None,
|
||||
tools=True,
|
||||
model=None,
|
||||
web_search=False,
|
||||
stream_engine_output=False,
|
||||
)
|
||||
|
||||
self.helper["run_copilot"](args, Path("/repo"), "prompt")
|
||||
|
||||
self.assertNotIn("--allow-tool=web_fetch", captured[-1])
|
||||
self.assertFalse(any(arg == "--allow-all-urls" for arg in captured[-1]))
|
||||
|
||||
args.web_search = True
|
||||
self.helper["run_copilot"](args, Path("/repo"), "prompt")
|
||||
|
||||
self.assertIn("--allow-tool=web_fetch", captured[-1])
|
||||
self.assertIn("--allow-all-urls", captured[-1])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -1,347 +0,0 @@
|
||||
---
|
||||
name: blacksmith-testbox
|
||||
description: Run Blacksmith Testbox for ClawHub CI-parity checks, hosted services, broad Bun gates, or builds local cannot reproduce without hurting developer machines.
|
||||
---
|
||||
|
||||
# Blacksmith Testbox
|
||||
|
||||
## Scope
|
||||
|
||||
Use Testbox when you need remote CI parity, injected secrets, hosted services,
|
||||
or an OS/runtime image that your local machine cannot provide cheaply.
|
||||
|
||||
Do not default to Testbox for every local test/build loop. If the repo has
|
||||
documented local commands for normal iteration, use those first so you keep
|
||||
warm caches, local build state, and fast feedback.
|
||||
|
||||
Testbox is the expensive path. Reach for it deliberately.
|
||||
|
||||
ClawHub maintainers can opt into Testbox-first validation by setting
|
||||
`CLAWHUB_TESTBOX=1` in their environment or standing agent rules. This mode is
|
||||
maintainers-only and requires Blacksmith access.
|
||||
|
||||
When `CLAWHUB_TESTBOX=1` is set in ClawHub:
|
||||
|
||||
- Pre-warm a Testbox early for longer, wider, or uncertain work.
|
||||
- Prefer Testbox for broad Bun gates, e2e, Convex-ish deploy parity, package
|
||||
proof, and expensive validation.
|
||||
- Reuse the same Testbox ID for every run command in the same task/session.
|
||||
- Use local commands only when the task explicitly sets
|
||||
`CLAWHUB_LOCAL_CHECK_MODE=throttled|full`, or when the user asks for local
|
||||
proof.
|
||||
|
||||
## Install The CLI
|
||||
|
||||
If `blacksmith` is not installed, install it:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://get.blacksmith.sh | sh
|
||||
```
|
||||
|
||||
For the canary channel:
|
||||
|
||||
```bash
|
||||
BLACKSMITH_CHANNEL=canary sh -c 'curl -fsSL https://get.blacksmith.sh | sh'
|
||||
```
|
||||
|
||||
Then authenticate:
|
||||
|
||||
```bash
|
||||
blacksmith auth login
|
||||
```
|
||||
|
||||
## Agent-Triggered Browser Auth
|
||||
|
||||
When an agent needs to ensure the user is authenticated before running Testbox
|
||||
commands, use browser-based auth with non-interactive mode. This opens the
|
||||
browser for the user to sign in; the agent does not interact with the browser.
|
||||
|
||||
`--organization` is required with `--non-interactive`:
|
||||
|
||||
```bash
|
||||
blacksmith auth login --non-interactive --organization <org-slug>
|
||||
```
|
||||
|
||||
The org slug can come from `BLACKSMITH_ORG` or the `--org` global flag. Do not
|
||||
use `--api-token` for this browser flow; that is for headless/token auth.
|
||||
|
||||
## Decide First: Local Or Testbox
|
||||
|
||||
Before warming anything up, check the repo's own instructions.
|
||||
|
||||
Prefer local commands when:
|
||||
|
||||
- the repo documents a supported local test/build workflow
|
||||
- you are iterating on unit tests, lint, typecheck, formatting, or other
|
||||
local-only validation
|
||||
- the value comes from warm local caches and fast repeat runs
|
||||
- the command does not need remote secrets, hosted services, or CI-only images
|
||||
|
||||
Prefer Testbox when:
|
||||
|
||||
- `CLAWHUB_TESTBOX=1` is set by the user, agent environment, or standing rules
|
||||
- the repo explicitly requires CI-parity or remote validation
|
||||
- the command needs secrets, service containers, or provisioned infra
|
||||
- you are reproducing CI-only failures
|
||||
- you need the exact workflow image/job environment from GitHub Actions
|
||||
|
||||
For ClawHub specifically, normal local iteration stays local unless maintainer
|
||||
Testbox mode is enabled with `CLAWHUB_TESTBOX=1`:
|
||||
|
||||
- `bun run format:check`
|
||||
- `bun run lint`
|
||||
- `bun run test`
|
||||
- `bun run coverage`
|
||||
- `bunx tsc --noEmit`
|
||||
- `bun run build`
|
||||
|
||||
If `CLAWHUB_TESTBOX=1` is enabled, run those same repo commands inside the warm
|
||||
Testbox. If the user wants laptop-friendly local proof for one command, use the
|
||||
explicit escape hatch `CLAWHUB_LOCAL_CHECK_MODE=throttled`.
|
||||
|
||||
In `.codex` worktrees without a `node_modules` symlink, do not run
|
||||
`bun install` just to validate locally. Use syntax checks or Testbox.
|
||||
|
||||
## Setup: Warmup Before Coding
|
||||
|
||||
If you decided Testbox is warranted, warm one up early. This returns an ID
|
||||
instantly and boots the CI environment in the background while you work:
|
||||
|
||||
```bash
|
||||
blacksmith testbox warmup ci-check-testbox.yml --ref main --idle-timeout 90
|
||||
# -> tbx_01jkz5b3t9...
|
||||
```
|
||||
|
||||
Save this ID in the current session. You need it for every `run` command.
|
||||
Treat `blacksmith testbox list` as diagnostics, not a reusable work queue.
|
||||
Listed boxes can be visible at the org/repo level while still being unusable or
|
||||
stale for the current local agent lane.
|
||||
|
||||
For ClawHub maintainer Testbox mode, claim the ID in the current checkout:
|
||||
|
||||
```bash
|
||||
bun run testbox:claim -- --id <ID>
|
||||
```
|
||||
|
||||
Warmup dispatches `.github/workflows/ci-check-testbox.yml`, which provisions a
|
||||
VM with Bun, Node, dependency install/cache, and a clean checkout of the repo at
|
||||
the chosen ref.
|
||||
|
||||
Bootstrap note: GitHub only exposes `workflow_dispatch` workflows through the
|
||||
Actions API after the workflow file exists on the default branch. If a brand-new
|
||||
Testbox workflow exists only on a feature branch, `blacksmith testbox warmup
|
||||
ci-check-testbox.yml --ref <branch>` can return a GitHub 404 even though the
|
||||
file exists on that branch. Land the workflow bootstrap first, then dispatch
|
||||
branch refs normally.
|
||||
|
||||
Options:
|
||||
|
||||
```text
|
||||
--ref <branch|tag> Git ref to dispatch against
|
||||
--job <name> Specific job within the workflow, if it has multiple
|
||||
--idle-timeout <min> Idle timeout in minutes
|
||||
```
|
||||
|
||||
## Critical: Always Run From The Repo Root
|
||||
|
||||
Always invoke `blacksmith testbox` commands from the root of the git
|
||||
repository. The CLI syncs the current working directory to the testbox using
|
||||
rsync with `--delete`. If you run from a subdirectory, rsync mirrors only that
|
||||
subdirectory and can delete everything else on the testbox.
|
||||
|
||||
Correct:
|
||||
|
||||
```bash
|
||||
blacksmith testbox run --id <ID> "bun run test"
|
||||
blacksmith testbox run --id <ID> "cd packages/clawhub && bun run verify"
|
||||
```
|
||||
|
||||
Wrong:
|
||||
|
||||
```bash
|
||||
cd packages/clawhub && blacksmith testbox run --id <ID> "bun run verify"
|
||||
```
|
||||
|
||||
If your shell is in a subdirectory, move back first:
|
||||
|
||||
```bash
|
||||
cd "$(git rev-parse --show-toplevel)"
|
||||
```
|
||||
|
||||
## Running Commands
|
||||
|
||||
Raw Blacksmith form:
|
||||
|
||||
```bash
|
||||
blacksmith testbox run --id <ID> "<command>"
|
||||
```
|
||||
|
||||
The `run` command waits for the testbox to become ready if it is still booting,
|
||||
so you can call `run` immediately after warmup.
|
||||
|
||||
In ClawHub, prefer the guarded runner wrapper so stale/reused ids fail before
|
||||
the Blacksmith CLI spends time syncing or emits a confusing missing-key error:
|
||||
|
||||
```bash
|
||||
bun run testbox:run -- --id <ID> -- bun run lint
|
||||
bun run testbox:run -- --id <ID> -- bun run test
|
||||
bun run testbox:run -- --id <ID> -- bun run build
|
||||
```
|
||||
|
||||
The wrapper refuses to run when the local per-Testbox key is missing or when
|
||||
the id was not claimed by this ClawHub checkout with:
|
||||
|
||||
```bash
|
||||
bun run testbox:claim -- --id <ID>
|
||||
```
|
||||
|
||||
Treat that as the expected remediation, not as a GitHub account or normal
|
||||
SSH-key problem. A local key alone is not enough; a ready box may still carry
|
||||
stale rsync state from another lane.
|
||||
|
||||
If the agent crashes, the remote box relies on Blacksmith's idle timeout. The
|
||||
local ClawHub claim marker is not deleted automatically, so the wrapper treats
|
||||
claims older than 12 hours as stale. Override only for intentional long-running
|
||||
work with:
|
||||
|
||||
```bash
|
||||
CLAWHUB_TESTBOX_CLAIM_TTL_MINUTES=<minutes>
|
||||
```
|
||||
|
||||
Before spending a broad gate on a manually assembled command, run:
|
||||
|
||||
```bash
|
||||
bun run testbox:sanity -- --id <ID>
|
||||
```
|
||||
|
||||
## Downloading Files From A Testbox
|
||||
|
||||
Use the `download` command to retrieve files or directories from a running
|
||||
testbox to your local machine. This is useful for fetching build artifacts,
|
||||
test results, coverage reports, or any output generated on the testbox.
|
||||
|
||||
```bash
|
||||
blacksmith testbox download --id <ID> <remote-path> [local-path]
|
||||
```
|
||||
|
||||
The remote path is relative to the testbox working directory. If no local path
|
||||
is specified, the file is saved to the current directory using the same base
|
||||
name.
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
blacksmith testbox download --id <ID> coverage/lcov-report/ ./coverage/
|
||||
blacksmith testbox download --id <ID> test-results/ ./test-results/
|
||||
blacksmith testbox download --id <ID> dist/ ./dist/
|
||||
```
|
||||
|
||||
## How File Sync Works
|
||||
|
||||
Understanding this model is critical for using Testbox correctly.
|
||||
|
||||
When you call `run`, the CLI performs a delta sync of your local changes to the
|
||||
remote testbox before executing your command:
|
||||
|
||||
1. The testbox VM starts from a clean checkout at the warmup ref. The workflow
|
||||
setup steps run during warmup and populate dependency directories on the
|
||||
remote VM.
|
||||
2. On each `run`, the CLI uses git to detect which files changed locally since
|
||||
the last sync. It syncs only tracked files and untracked non-ignored files.
|
||||
3. `.gitignore`'d directories are never synced. `node_modules/`, `.bun/`,
|
||||
`.vite/`, `dist/`, `.output/`, `.nitro/`, and coverage outputs stay local.
|
||||
The testbox uses its own copies populated by the warmup workflow.
|
||||
4. If nothing has changed since the last sync, the sync is skipped.
|
||||
|
||||
Why this matters:
|
||||
|
||||
- If you modify `package.json` or `bun.lock`, re-run install on the testbox:
|
||||
|
||||
```bash
|
||||
bun run testbox:run -- --id <ID> -- bun install --frozen-lockfile
|
||||
```
|
||||
|
||||
- If tests depend on generated/build output, re-run the build on the testbox.
|
||||
- New untracked files sync as long as they are not gitignored.
|
||||
- Deleted files are also deleted on the remote testbox.
|
||||
|
||||
## Critical: Do Not Ban Local Tests
|
||||
|
||||
Do not assume local validation is forbidden. Many repos intentionally invest in
|
||||
fast, warm local loops, and forcing every run through Testbox destroys that
|
||||
advantage.
|
||||
|
||||
Use Testbox for checks that actually need it: remote parity, secrets, services,
|
||||
CI-only runners, expensive broad gates, or reproducibility against the workflow
|
||||
image.
|
||||
|
||||
ClawHub maintainer exception: if `CLAWHUB_TESTBOX=1` is set by the user or
|
||||
agent environment, treat Testbox as the normal validation path for this repo.
|
||||
Use `CLAWHUB_LOCAL_CHECK_MODE=throttled|full` as the explicit local escape
|
||||
hatch.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Decide whether the repo's local loop is the right default. For ClawHub,
|
||||
`CLAWHUB_TESTBOX=1` makes Testbox the maintainer default.
|
||||
2. If Testbox is warranted, warm up early:
|
||||
`blacksmith testbox warmup ci-check-testbox.yml --ref main --idle-timeout 90`.
|
||||
3. Save the ID, then claim it:
|
||||
`bun run testbox:claim -- --id <ID>`.
|
||||
4. Write code while the testbox boots in the background.
|
||||
5. Run sanity before broad checks:
|
||||
`bun run testbox:sanity -- --id <ID>`.
|
||||
6. Run the remote command:
|
||||
`bun run testbox:run -- --id <ID> -- bun run lint`.
|
||||
7. If tests fail, fix code and re-run against the same warm box.
|
||||
8. If dependency manifests changed, run install in the box before testing.
|
||||
9. If you need artifacts, download them with `blacksmith testbox download`.
|
||||
10. Stop the box when done if it is no longer needed:
|
||||
`blacksmith testbox stop --id <ID>`.
|
||||
|
||||
## ClawHub Broad Gate
|
||||
|
||||
For a broad ClawHub proof in maintainer Testbox mode, use the repo package
|
||||
manager and keep the commands explicit:
|
||||
|
||||
```bash
|
||||
bun run testbox:run -- --id <ID> -- bun run format:check
|
||||
bun run testbox:run -- --id <ID> -- bun run lint
|
||||
bun run testbox:run -- --id <ID> -- bun run test
|
||||
bun run testbox:run -- --id <ID> -- bunx tsc --noEmit
|
||||
bun run testbox:run -- --id <ID> -- bunx tsc -p packages/schema/tsconfig.json --noEmit
|
||||
bun run testbox:run -- --id <ID> -- bunx tsc -p packages/clawhub/tsconfig.json --noEmit
|
||||
bun run testbox:run -- --id <ID> -- bun run build
|
||||
```
|
||||
|
||||
For e2e:
|
||||
|
||||
```bash
|
||||
bun run testbox:run -- --id <ID> -- bun run test:e2e
|
||||
bun run testbox:run -- --id <ID> -- bun run test:pw
|
||||
```
|
||||
|
||||
## Waiting For Readiness
|
||||
|
||||
The `run` command automatically waits for the testbox, so explicit waiting is
|
||||
usually unnecessary. If you do need to check readiness separately, use
|
||||
`--wait`. Do not use a sleep-and-recheck loop.
|
||||
|
||||
```bash
|
||||
blacksmith testbox status --id <ID> --wait --wait-timeout 5m
|
||||
```
|
||||
|
||||
## Managing Testboxes
|
||||
|
||||
```bash
|
||||
blacksmith testbox status --id <ID>
|
||||
blacksmith testbox list
|
||||
blacksmith testbox stop --id <ID>
|
||||
```
|
||||
|
||||
Testboxes automatically shut down after being idle. For ClawHub maintainer
|
||||
work, use 90 minutes for long-running sessions:
|
||||
|
||||
```bash
|
||||
blacksmith testbox warmup ci-check-testbox.yml --idle-timeout 90
|
||||
```
|
||||
@@ -0,0 +1,129 @@
|
||||
---
|
||||
name: clawhub-content-rights-correspondence
|
||||
description: Use when drafting, sending, or preserving email correspondence for an existing ClawHub content rights case.
|
||||
---
|
||||
|
||||
# ClawHub Content Rights Correspondence
|
||||
|
||||
Use ClawHub's authenticated admin CLI commands directly. Do not use helper
|
||||
scripts, direct Hermit calls, or direct R2 access for correspondence.
|
||||
|
||||
## Safety Rules
|
||||
|
||||
- Require an existing `CHR-...` case. Never create cases with this skill.
|
||||
- Dry-run first and show the final recipient, subject, and body.
|
||||
- Send only after explicit user signoff on that final draft.
|
||||
- Use `bun run admin -- email send` for outbound email.
|
||||
- Use `bun run admin -- content-rights record-correspondence` to preserve the
|
||||
exact correspondence in Hermit.
|
||||
- Do not retry after an email was sent if evidence recording fails; report the
|
||||
failure so staff can repair the audit record without sending a duplicate.
|
||||
- `--attachment` files are archived with the correspondence. The generic email
|
||||
template does not send file attachments.
|
||||
- The generic email template already adds the greeting. Do not add `Hello ...`
|
||||
or `Hi ...` to the body file.
|
||||
- The generic email template may render the subject as a visible heading. Do
|
||||
not pass `--title`, and do not duplicate the title in the body file.
|
||||
- Do not use the generic email action button for ClawHub content-rights
|
||||
responses. Put the response form URL as plaintext in the body.
|
||||
|
||||
## Publisher Removal Notice
|
||||
|
||||
Use this subject:
|
||||
|
||||
```text
|
||||
ClawHub skill removal notice
|
||||
```
|
||||
|
||||
Use this body, replacing only the skill URL:
|
||||
|
||||
```text
|
||||
We removed the following ClawHub skill after receiving a content rights request involving Rednote/Xiaohongshu platform rights:
|
||||
|
||||
https://clawhub.ai/<owner>/<slug>
|
||||
|
||||
If you believe this removal was made in error, please submit a response using this form:
|
||||
https://forms.openclaw.ai/clawhub-content-rights
|
||||
```
|
||||
|
||||
Preview the email:
|
||||
|
||||
```bash
|
||||
bun run admin -- email send \
|
||||
--user <publisher-handle> \
|
||||
--subject "ClawHub skill removal notice" \
|
||||
--body-file /tmp/body.txt
|
||||
```
|
||||
|
||||
Send only after explicit signoff:
|
||||
|
||||
```bash
|
||||
bun run admin -- email send \
|
||||
--user <publisher-handle> \
|
||||
--subject "ClawHub skill removal notice" \
|
||||
--body-file /tmp/body.txt \
|
||||
--send \
|
||||
--confirm-user-request \
|
||||
--confirm-user-signoff \
|
||||
--json
|
||||
```
|
||||
|
||||
Record the exact sent correspondence:
|
||||
|
||||
```bash
|
||||
bun run admin -- content-rights record-correspondence CHR-000007 \
|
||||
--direction outbound \
|
||||
--to "<publisher-handle-or-email>" \
|
||||
--from "ClawHub <noreply@notifications.openclaw.ai>" \
|
||||
--subject "ClawHub skill removal notice" \
|
||||
--body-file /tmp/body.txt \
|
||||
--provider-message-id "<providerId-from-send-response>" \
|
||||
--json
|
||||
```
|
||||
|
||||
## Requester Status Updates
|
||||
|
||||
For requester updates or closure notes, use direct email and avoid exposing the
|
||||
internal case id in the subject unless the user explicitly asks.
|
||||
|
||||
```bash
|
||||
bun run admin -- email send \
|
||||
--to requester@example.com \
|
||||
--username Requester \
|
||||
--subject "Update on ClawHub content rights request" \
|
||||
--body-file /tmp/body.txt
|
||||
```
|
||||
|
||||
After explicit signoff, send:
|
||||
|
||||
```bash
|
||||
bun run admin -- email send \
|
||||
--to requester@example.com \
|
||||
--username Requester \
|
||||
--subject "Update on ClawHub content rights request" \
|
||||
--body-file /tmp/body.txt \
|
||||
--send \
|
||||
--confirm-user-request \
|
||||
--confirm-user-signoff \
|
||||
--json
|
||||
```
|
||||
|
||||
Then record the successful send with the provider id:
|
||||
|
||||
```bash
|
||||
bun run admin -- content-rights record-correspondence CHR-000007 \
|
||||
--direction outbound \
|
||||
--to "Requester Name <requester@example.com>" \
|
||||
--from "ClawHub <noreply@notifications.openclaw.ai>" \
|
||||
--subject "Update on ClawHub content rights request" \
|
||||
--body-file /tmp/body.txt \
|
||||
--provider-message-id "<providerId-from-send-response>"
|
||||
```
|
||||
|
||||
Verify the case now includes the correspondence:
|
||||
|
||||
```bash
|
||||
bun run admin -- content-rights get CHR-000007 --json
|
||||
```
|
||||
|
||||
Run from the ClawHub repository root with the normal authenticated admin CLI.
|
||||
@@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "ClawHub Rights Correspondence"
|
||||
short_description: "Send and preserve ClawHub rights case emails."
|
||||
default_prompt: "Use $clawhub-content-rights-correspondence to draft or send correspondence for an existing ClawHub content rights case."
|
||||
@@ -0,0 +1,213 @@
|
||||
---
|
||||
name: clawhub-moderation
|
||||
description: "Use for ClawHub staff moderation actions with the repo-local ClawHub admin tool: skills, users, org publishers, plugin packages, trusted publishers, official publishers, and guarded staff email."
|
||||
---
|
||||
|
||||
# ClawHub Moderation
|
||||
|
||||
Use the repo-local admin tool from a checked-out ClawHub repo. It wraps
|
||||
the existing ClawHub CLI auth/config and HTTP API surfaces. Do not call Convex
|
||||
internal mutations directly for staff actions.
|
||||
|
||||
## Safety Rules
|
||||
|
||||
- Require an explicit target from the user: skill slug, user handle, or user id.
|
||||
- Require a reason for destructive, restorative, ownership, or moderation writes.
|
||||
- Before any write, show the exact command and ask for confirmation unless the
|
||||
user already said to proceed or supplied `--yes`.
|
||||
- For `email send`, the user must explicitly ask for the email and sign off on
|
||||
the final recipient, subject, and body. Dry-run is fine for drafting. Never
|
||||
send until both are true, and only use `--send --confirm-user-request
|
||||
--confirm-user-signoff` after that approval.
|
||||
- Prefer handles for humans. Use `--id` only when the user provides a user id.
|
||||
- Never bypass API-token auth, server role checks, or audit logging.
|
||||
- After the write, verify state with the CLI/API and report the result.
|
||||
|
||||
## Command Map
|
||||
|
||||
Run from the ClawHub repo root:
|
||||
|
||||
```sh
|
||||
bun run admin -- --help
|
||||
```
|
||||
|
||||
Authenticate or validate the current token:
|
||||
|
||||
```sh
|
||||
bun run admin -- login
|
||||
bun run admin -- whoami
|
||||
```
|
||||
|
||||
Current top-level command groups:
|
||||
|
||||
```text
|
||||
auth
|
||||
users
|
||||
plugins|plugin
|
||||
packages|package
|
||||
org
|
||||
email
|
||||
skills|skill
|
||||
```
|
||||
|
||||
### Skills
|
||||
|
||||
`bun run admin -- skills --help` exposes:
|
||||
|
||||
```text
|
||||
unhide <slug>
|
||||
rescan <slug>
|
||||
reports
|
||||
triage-report <report-id>
|
||||
```
|
||||
|
||||
Examples:
|
||||
|
||||
```sh
|
||||
bun run admin -- skills unhide <slug> --reason "<reason>" --yes
|
||||
bun run admin -- skills rescan <slug> --reason "<reason>" --yes
|
||||
bun run admin -- skills reports --status open
|
||||
bun run admin -- skills triage-report <report-id> --status confirmed --action hide --note "<note>" --yes
|
||||
```
|
||||
|
||||
### Users
|
||||
|
||||
`bun run admin -- users --help` exposes:
|
||||
|
||||
```text
|
||||
ban <handleOrId>
|
||||
unban <handleOrId>
|
||||
set-role <handleOrId> <role>
|
||||
reclassify-ban <handleOrId>
|
||||
remediate-autobans
|
||||
```
|
||||
|
||||
Examples:
|
||||
|
||||
```sh
|
||||
bun run admin -- users ban <handleOrId> --reason "<reason>" --yes
|
||||
bun run admin -- users unban <handleOrId> --reason "<reason>" --yes
|
||||
bun run admin -- users set-role <handleOrId> <user|moderator|admin> --yes
|
||||
bun run admin -- users reclassify-ban <handleOrId> --reason "<reason>" --apply --yes
|
||||
bun run admin -- users remediate-autobans --apply --reason "<reason>"
|
||||
```
|
||||
|
||||
Use `--id` when `<handleOrId>` is a user id. Use `--fuzzy` only when the user
|
||||
has asked for fuzzy handle resolution or the exact handle is ambiguous.
|
||||
|
||||
### Org Publishers
|
||||
|
||||
`bun run admin -- org --help` exposes:
|
||||
|
||||
```text
|
||||
official
|
||||
create <handle>
|
||||
remove-member <handle> <member>
|
||||
delete <handle>
|
||||
repair-scoped-packages <csv>
|
||||
```
|
||||
|
||||
Examples:
|
||||
|
||||
```sh
|
||||
bun run admin -- org official list
|
||||
bun run admin -- org official add <handle> --reason "<reason>" --yes
|
||||
bun run admin -- org official remove <handle> --reason "<reason>" --yes
|
||||
bun run admin -- org create <handle> --display-name "<name>" --member <user-handle> --role owner
|
||||
bun run admin -- org remove-member <handle> <member-handle>
|
||||
bun run admin -- org delete <handle> --reason "<reason>" # dry-run
|
||||
bun run admin -- org delete <handle> --reason "<reason>" --apply
|
||||
bun run admin -- org repair-scoped-packages <csv> # dry-run
|
||||
bun run admin -- org repair-scoped-packages <csv> --apply
|
||||
```
|
||||
|
||||
`org create` requires `--member`; it must not add the moderator running the
|
||||
command as an implicit owner. `org delete` only works for empty org publishers
|
||||
and defaults to dry-run.
|
||||
|
||||
### Plugin Packages
|
||||
|
||||
`bun run admin -- packages --help` exposes:
|
||||
|
||||
```text
|
||||
moderate <name>
|
||||
status|moderation-status <name>
|
||||
queue|moderation-queue
|
||||
reports
|
||||
triage-report <report-id>
|
||||
transfer <name>
|
||||
repair-name <name>
|
||||
migrations
|
||||
set-migration <bundled-plugin-id>
|
||||
trusted-publisher
|
||||
```
|
||||
|
||||
Examples:
|
||||
|
||||
```sh
|
||||
bun run admin -- packages status <name>
|
||||
bun run admin -- packages transfer <name> --to <owner> --reason "<reason>" # dry-run
|
||||
bun run admin -- packages transfer <name> --to <owner> --reason "<reason>" --apply
|
||||
bun run admin -- packages repair-name <name> --next-name <name> --reason "<reason>"
|
||||
bun run admin -- packages trusted-publisher get <name>
|
||||
bun run admin -- packages trusted-publisher set <name> --repository <owner/repo> --workflow-filename <file>
|
||||
```
|
||||
|
||||
### Staff Email
|
||||
|
||||
`bun run admin -- email send --help` exposes:
|
||||
|
||||
```text
|
||||
--to <email>
|
||||
--user <handle>
|
||||
--subject <subject>
|
||||
--body-file <path>
|
||||
--body <text>
|
||||
--send
|
||||
--confirm-user-request
|
||||
--confirm-user-signoff
|
||||
--json
|
||||
```
|
||||
|
||||
Draft only:
|
||||
|
||||
```sh
|
||||
bun run admin -- email send --user <handle> --subject "<subject>" --body-file <path>
|
||||
```
|
||||
|
||||
Send only after explicit request and sign-off:
|
||||
|
||||
```sh
|
||||
bun run admin -- email send --user <handle> --subject "<subject>" --body-file <path> --send --confirm-user-request --confirm-user-signoff
|
||||
```
|
||||
|
||||
The server sends through the production noreply provider and writes an audit log
|
||||
only after admin auth succeeds.
|
||||
|
||||
## Verification
|
||||
|
||||
- For skills, inspect the page/API status after `skills unhide`.
|
||||
- For users, prefer user search/admin surfaces for target accounts where
|
||||
available.
|
||||
- For orgs and packages, use the public publisher/plugin pages and the relevant
|
||||
CLI status command after a write.
|
||||
- For email, verify the CLI response and audit expectation; do not send a second
|
||||
email just to test delivery.
|
||||
- If verification is blocked by auth or missing admin access, report the command
|
||||
result and the verification blocker plainly.
|
||||
|
||||
## Impact Notes
|
||||
|
||||
- `skills unhide` is a moderator manual restore. It clears skill hidden state,
|
||||
applies a clean manual override to top-level moderation fields, preserves
|
||||
version-level scanner records, updates public stats, and writes audit logs.
|
||||
- There is no standalone `skills hide` command in `clawhub-admin`; use report
|
||||
triage with `--action hide` when resolving a report that should hide a skill.
|
||||
- `users ban` is disruptive: it revokes API tokens, marks the user deleted,
|
||||
hides owned skills, soft-deletes comments, and writes audit logs.
|
||||
- `users unban` is admin-only. It clears ban state and restores skills that were
|
||||
hidden by the matching ban flow; revoked API tokens stay revoked.
|
||||
- `packages transfer` preserves the package row, stats, releases, and history;
|
||||
it changes the owner publisher.
|
||||
- `org delete` soft-deletes an empty org publisher and retains member rows for
|
||||
history; it refuses orgs with active skills or packages.
|
||||
@@ -0,0 +1,187 @@
|
||||
---
|
||||
name: clawhub-pr-maintainer
|
||||
description: Use when reviewing, triaging, validating, or discussing ClawHub GitHub issues or pull requests, including author context, CI, UI proof, evidence, labels, close decisions, and maintainer handoff.
|
||||
---
|
||||
|
||||
# ClawHub PR Maintainer
|
||||
|
||||
Use this skill for maintainer-facing ClawHub GitHub workflow, not for ordinary
|
||||
implementation work.
|
||||
|
||||
## Start With Live GitHub State
|
||||
|
||||
- Use `gh pr view` or `gh issue view` against `openclaw/clawhub`; verify live
|
||||
state before commenting, labeling, closing, or recommending merge.
|
||||
- For PRs, read title, body, author, labels, comments, files, commits, status
|
||||
checks, review state, and linked issues.
|
||||
- Surface author identity briefly: GitHub name/login and account age when
|
||||
useful. Treat identity as triage signal, never as proof by itself.
|
||||
|
||||
Common read-only commands:
|
||||
|
||||
```sh
|
||||
gh pr view <number> --repo openclaw/clawhub --json title,body,author,labels,comments,files,commits,statusCheckRollup,reviewDecision,url,additions,deletions,changedFiles
|
||||
gh issue view <number> --repo openclaw/clawhub --json title,body,author,labels,comments,state,url
|
||||
gh api users/<login> --jq '{login,name,created_at,type}'
|
||||
```
|
||||
|
||||
## Review Evidence Bar
|
||||
|
||||
- For bug fixes, require symptom evidence, a plausible root cause in the touched
|
||||
code path, and either a regression test or focused manual proof.
|
||||
- For UI changes, require screenshots or video when the behavior is meaningfully
|
||||
visual. Use tests as supplemental evidence, not a substitute for visible proof.
|
||||
- Do not merge or recommend merge based only on PR prose, AI rationale, or green
|
||||
CI when the changed behavior has not been exercised.
|
||||
- For contributor-provided screenshots/videos/logs, inspect the artifact
|
||||
directly and state what it proves. Do not rerun `proof:ui` just to inspect
|
||||
existing evidence.
|
||||
|
||||
## Structure PR Review Output
|
||||
|
||||
- Start every PR review with 1-3 plain sentences explaining what the change does
|
||||
and why it matters.
|
||||
- Show size near the top as `LOC: +x/-y (N files)`, using live PR stats or
|
||||
local diff stats.
|
||||
- Then list findings first. If none, say `No blocking findings` or
|
||||
`No findings`.
|
||||
- Always answer: affected ClawHub surface, bug or behavior being changed,
|
||||
evidence checked, and best-fix verdict.
|
||||
- For bug/regression fixes, include a compact `Provenance:` line when a bounded
|
||||
history pass identifies it. Separate code author, PR author,
|
||||
merger/committer, current PR author, PR number, and date when those differ.
|
||||
If the blamed PR was merged by automation, identify the human trigger when
|
||||
practical; otherwise say trigger unknown.
|
||||
|
||||
## Read Beyond The Diff
|
||||
|
||||
- For code-path bug, regression, or behavior changes, review the surrounding
|
||||
path, not just changed lines. Open the runtime entry point, owner module, one
|
||||
caller, one callee, adjacent tests, and sibling surfaces that should share the
|
||||
invariant.
|
||||
- For docs/config/process-only changes, read the changed file, its linked or
|
||||
adjacent source of truth, and any route/workflow/template the change claims to
|
||||
affect. Do not require runtime caller/callee evidence when no runtime path
|
||||
exists.
|
||||
- Compare against current `origin/main` behavior or current published docs when
|
||||
regression, compatibility, or user-visible docs accuracy matters.
|
||||
- For dependency-backed behavior, read the upstream docs/source/types before
|
||||
judging API use, defaults, output shapes, errors, timeouts, memory behavior, or
|
||||
compatibility.
|
||||
- Mention the main files or contracts read when the verdict depends on
|
||||
code-path, docs, config, or workflow evidence.
|
||||
- If a required path is uninspected, keep reading or mark
|
||||
`Remaining uncertainty`; do not call the PR best, proof-sufficient, or
|
||||
merge-ready.
|
||||
|
||||
## Best-Fix Review Loop
|
||||
|
||||
Every PR review must explicitly answer: "Is this the best fix, or only a
|
||||
plausible fix?"
|
||||
|
||||
Before verdict:
|
||||
|
||||
1. Reconstruct the bug, feature need, or behavior claim from the issue, PR, and
|
||||
proof.
|
||||
2. For code-path changes, trace current behavior from entry point to failure or
|
||||
decision point.
|
||||
3. For docs/config/process-only changes, trace the reader/operator workflow or
|
||||
automation path the change is meant to clarify.
|
||||
4. Read touched files, relevant callers/callees for code changes, adjacent docs
|
||||
or tests, owner modules, and relevant source-of-truth docs.
|
||||
5. Read sibling surfaces that should share the invariant or could be broken by a
|
||||
one-sided fix.
|
||||
6. Compare against current `origin/main` and shipped behavior when relevant.
|
||||
7. Identify at least one alternative fix location or shape, then reject it with
|
||||
evidence.
|
||||
|
||||
Review output must include:
|
||||
|
||||
- `Best-fix verdict:` best / acceptable mitigation / wrong layer / too narrow /
|
||||
too broad.
|
||||
- `Alternatives considered:` 1-3 concrete alternatives and why rejected.
|
||||
- `Code read:` compact list of main files/contracts checked.
|
||||
- `Remaining uncertainty:` what was not proven.
|
||||
|
||||
## Enforce Bug-Fix Evidence
|
||||
|
||||
- Never merge a bug-fix PR based only on issue text, PR text, or AI rationale.
|
||||
- Before recommending merge for a bug fix, require:
|
||||
1. symptom evidence such as a repro, logs, failing test, or focused manual
|
||||
proof
|
||||
2. a verified root cause in code with file/line
|
||||
3. blame-backed provenance for regressions when traceable, or commit SHA/date
|
||||
when no PR is traceable
|
||||
4. a fix that touches the implicated code path
|
||||
5. a regression test when feasible, or explicit manual verification plus a
|
||||
reason no test was added
|
||||
- If the claim is unsubstantiated or likely wrong, request evidence or changes
|
||||
instead of recommending merge.
|
||||
|
||||
## Decide UI Proof Mode
|
||||
|
||||
Use the `clawhub-ui-proof` skill when the maintainer/agent should generate new
|
||||
visual evidence.
|
||||
|
||||
- `before-after`: bug fixes, regressions, changed copy, changed layout, or any
|
||||
PR where main-vs-candidate comparison clarifies the change.
|
||||
- `feature`: new page, new flow, new UI state, or behavior that cannot exist on
|
||||
`origin/main`.
|
||||
- No generated proof: docs-only, backend-only, tests-only, metadata-only, or
|
||||
already-sufficient contributor evidence.
|
||||
|
||||
Write a temporary Playwright scenario under `.artifacts/proof-scenarios/`; do
|
||||
not infer manual clicks. Keep screenshots and videos in `.artifacts/` until
|
||||
publishing. Never commit proof artifacts.
|
||||
|
||||
## Final Review Comment With Proof
|
||||
|
||||
If this review generated `proof:ui` artifacts, publish them before the final PR
|
||||
review comment. Do not leave only local `.artifacts/...` paths in a PR comment;
|
||||
they are useful to the maintainer locally but invisible to GitHub readers.
|
||||
|
||||
Use:
|
||||
|
||||
```sh
|
||||
bun run proof:publish -- --proof-dir .artifacts/clawhub-ui-proof/<timestamp> --target-pr <number>
|
||||
```
|
||||
|
||||
`proof:publish` copies the selected files to the `qa-artifacts` branch and
|
||||
upserts a marker-backed PR comment with a **ClawHub UI Proof** section.
|
||||
|
||||
That comment includes:
|
||||
|
||||
- the proof mode (`before-after` or `feature`)
|
||||
- the `report.md` result summary
|
||||
- the most relevant per-step screenshots
|
||||
- inline video previews when GIF previews are present
|
||||
- links to full-run MP4s
|
||||
- links to raw proof files on the artifact branch
|
||||
|
||||
Use `--dry-run` before publishing if you need to inspect the generated comment.
|
||||
If publishing fails because credentials are missing, report the local proof
|
||||
directory and the failed command instead of posting a comment that claims
|
||||
evidence is attached.
|
||||
|
||||
## ClawSweeper
|
||||
|
||||
ClawSweeper is the bot control plane for automated PR/issue review once ClawHub
|
||||
dispatch is configured. Until then, use this skill for manual maintainer review.
|
||||
If ClawSweeper has posted a review, read it as evidence but verify live PR state
|
||||
before acting.
|
||||
|
||||
## Commenting And Labels
|
||||
|
||||
- Use literal multiline comment bodies or `--body-file`; never pass escaped
|
||||
`\n` strings.
|
||||
- For issue comments and PR comments containing backticks or shell characters,
|
||||
prefer a single-quoted heredoc or `--body-file` over inline `-b` bodies.
|
||||
- Do not wrap issue or PR refs like `#123` in backticks when you want GitHub to
|
||||
auto-link them.
|
||||
- Keep maintainer comments short: finding, evidence, requested action, and
|
||||
verification path.
|
||||
- When no proof artifacts were generated, `gh pr comment --body-file` is fine.
|
||||
When proof artifacts were generated, use `proof:publish` so screenshots/videos
|
||||
are published before posting.
|
||||
- Do not close more than five issues/PRs in one action without explicit
|
||||
confirmation and the exact target list.
|
||||
@@ -1,17 +1,21 @@
|
||||
---
|
||||
name: convex-create-component
|
||||
description: Builds reusable Convex components with isolated tables and app-facing APIs. Use for new components, reusable backend modules, integrations, or component boundary work.
|
||||
description: Builds reusable Convex components with isolated tables and app-facing APIs.
|
||||
Use for new components, reusable backend modules, integrations, or component
|
||||
boundary work.
|
||||
---
|
||||
|
||||
# Convex Create Component
|
||||
|
||||
Create reusable Convex components with clear boundaries and a small app-facing API.
|
||||
Create reusable Convex components with clear boundaries and a small app-facing
|
||||
API.
|
||||
|
||||
## When to Use
|
||||
|
||||
- Creating a new Convex component in an existing app
|
||||
- Extracting reusable backend logic into a component
|
||||
- Building a third-party integration that should own its own tables and workflows
|
||||
- Building a third-party integration that should own its own tables and
|
||||
workflows
|
||||
- Packaging Convex functionality for reuse across multiple apps
|
||||
|
||||
## When Not to Use
|
||||
@@ -23,20 +27,30 @@ Create reusable Convex components with clear boundaries and a small app-facing A
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Ask the user what they are building and what the end goal is. If the repo already makes the answer obvious, say so and confirm before proceeding.
|
||||
2. Choose the shape using the decision tree below and read the matching reference file.
|
||||
3. Decide whether a component is justified. Prefer normal app code or a regular library if the feature does not need isolated tables, backend functions, or reusable persistent state.
|
||||
1. Ask the user what they are building and what the end goal is. If the repo
|
||||
already makes the answer obvious, say so and confirm before proceeding.
|
||||
2. Choose the shape using the decision tree below and read the matching
|
||||
reference file.
|
||||
3. Decide whether a component is justified. Prefer normal app code or a regular
|
||||
library if the feature does not need isolated tables, backend functions, or
|
||||
reusable persistent state.
|
||||
4. Make a short plan for:
|
||||
- what tables the component owns
|
||||
- what public functions it exposes
|
||||
- what data must be passed in from the app (auth, env vars, parent IDs)
|
||||
- what stays in the app as wrappers or HTTP mounts
|
||||
5. Create the component structure with `convex.config.ts`, `schema.ts`, and function files.
|
||||
6. Implement functions using the component's own `./_generated/server` imports, not the app's generated files.
|
||||
7. Wire the component into the app with `app.use(...)`. If the app does not already have `convex/convex.config.ts`, create it.
|
||||
8. Call the component from the app through `components.<name>` using `ctx.runQuery`, `ctx.runMutation`, or `ctx.runAction`.
|
||||
9. If React clients, HTTP callers, or public APIs need access, create wrapper functions in the app instead of exposing component functions directly.
|
||||
10. Run `npx convex dev` and fix codegen, type, or boundary issues before finishing.
|
||||
5. Create the component structure with `convex.config.ts`, `schema.ts`, and
|
||||
function files.
|
||||
6. Implement functions using the component's own `./_generated/server` imports,
|
||||
not the app's generated files.
|
||||
7. Wire the component into the app with `app.use(...)`. If the app does not
|
||||
already have `convex/convex.config.ts`, create it.
|
||||
8. Call the component from the app through `components.<name>` using
|
||||
`ctx.runQuery`, `ctx.runMutation`, or `ctx.runAction`.
|
||||
9. If React clients, HTTP callers, or public APIs need access, create wrapper
|
||||
functions in the app instead of exposing component functions directly.
|
||||
10. Run `npx convex dev` and fix codegen, type, or boundary issues before
|
||||
finishing.
|
||||
|
||||
## Choose the Shape
|
||||
|
||||
@@ -169,19 +183,32 @@ export const myUnread = query({
|
||||
});
|
||||
```
|
||||
|
||||
Note the reference path shape: a function in `convex/components/notifications/lib.ts` is called as `components.notifications.lib.send` from the app.
|
||||
Note the reference path shape: a function in
|
||||
`convex/components/notifications/lib.ts` is called as
|
||||
`components.notifications.lib.send` from the app.
|
||||
|
||||
## Critical Rules
|
||||
|
||||
- Keep authentication in the app, because `ctx.auth` is not available inside components.
|
||||
- Keep environment access in the app, because component functions cannot read `process.env`.
|
||||
- Pass parent app IDs across the boundary as strings, because `Id` types become plain strings in the app-facing `ComponentApi`.
|
||||
- Do not use `v.id("parentTable")` for app-owned tables inside component args or schema, because the component has no access to the app's table namespace.
|
||||
- Import `query`, `mutation`, and `action` from the component's own `./_generated/server`, not the app's generated files.
|
||||
- Do not expose component functions directly to clients. Create app wrappers when client access is needed, because components are internal and need auth/env wiring the app provides.
|
||||
- If the component defines HTTP handlers, mount the routes in the app's `convex/http.ts`, because components cannot register their own HTTP routes.
|
||||
- If the component needs pagination, use `paginator` from `convex-helpers` instead of built-in `.paginate()`, because `.paginate()` does not work across the component boundary.
|
||||
- Add `args` and `returns` validators to all public component functions, because the component boundary requires explicit type contracts.
|
||||
- Keep authentication in the app, because `ctx.auth` is not available inside
|
||||
components.
|
||||
- Keep environment access in the app, because component functions cannot read
|
||||
`process.env`.
|
||||
- Pass parent app IDs across the boundary as strings, because `Id` types become
|
||||
plain strings in the app-facing `ComponentApi`.
|
||||
- Do not use `v.id("parentTable")` for app-owned tables inside component args or
|
||||
schema, because the component has no access to the app's table namespace.
|
||||
- Import `query`, `mutation`, and `action` from the component's own
|
||||
`./_generated/server`, not the app's generated files.
|
||||
- Do not expose component functions directly to clients. Create app wrappers
|
||||
when client access is needed, because components are internal and need
|
||||
auth/env wiring the app provides.
|
||||
- If the component defines HTTP handlers, mount the routes in the app's
|
||||
`convex/http.ts`, because components cannot register their own HTTP routes.
|
||||
- If the component needs pagination, use `paginator` from `convex-helpers`
|
||||
instead of built-in `.paginate()`, because `.paginate()` does not work across
|
||||
the component boundary.
|
||||
- Add `args` and `returns` validators to all public component functions, because
|
||||
the component boundary requires explicit type contracts.
|
||||
|
||||
## Patterns
|
||||
|
||||
@@ -248,7 +275,9 @@ args: {
|
||||
|
||||
### Advanced Patterns
|
||||
|
||||
For additional patterns including function handles for callbacks, deriving validators from schema, static configuration with a globals table, and class-based client wrappers, see `references/advanced-patterns.md`.
|
||||
For additional patterns including function handles for callbacks, deriving
|
||||
validators from schema, static configuration with a globals table, and
|
||||
class-based client wrappers, see `references/advanced-patterns.md`.
|
||||
|
||||
## Validation
|
||||
|
||||
@@ -261,8 +290,10 @@ Try validation in this order:
|
||||
Important:
|
||||
|
||||
- Fresh repos may fail these commands until `CONVEX_DEPLOYMENT` is configured.
|
||||
- Until codegen runs, component-local `./_generated/*` imports and app-side `components.<name>...` references will not typecheck.
|
||||
- If validation blocks on Convex login or deployment setup, stop and ask the user for that exact step instead of guessing.
|
||||
- Until codegen runs, component-local `./_generated/*` imports and app-side
|
||||
`components.<name>...` references will not typecheck.
|
||||
- If validation blocks on Convex login or deployment setup, stop and ask the
|
||||
user for that exact step instead of guessing.
|
||||
|
||||
## Reference Files
|
||||
|
||||
@@ -272,7 +303,8 @@ Read exactly one of these after the user confirms the goal:
|
||||
- `references/packaged-components.md`
|
||||
- `references/hybrid-components.md`
|
||||
|
||||
Official docs: [Authoring Components](https://docs.convex.dev/components/authoring)
|
||||
Official docs:
|
||||
[Authoring Components](https://docs.convex.dev/components/authoring)
|
||||
|
||||
## Checklist
|
||||
|
||||
@@ -280,7 +312,8 @@ Official docs: [Authoring Components](https://docs.convex.dev/components/authori
|
||||
- [ ] Read the matching reference file
|
||||
- [ ] Confirmed a component is the right abstraction
|
||||
- [ ] Planned tables, public API, boundaries, and app wrappers
|
||||
- [ ] Component lives under `convex/components/<name>/` (or package layout if publishing)
|
||||
- [ ] Component lives under `convex/components/<name>/` (or package layout if
|
||||
publishing)
|
||||
- [ ] Component imports from its own `./_generated/server`
|
||||
- [ ] Auth, env access, and HTTP routes stay in the app
|
||||
- [ ] Parent app IDs cross the boundary as `v.string()`
|
||||
|
||||
@@ -4,7 +4,9 @@ interface:
|
||||
icon_small: "./assets/icon.svg"
|
||||
icon_large: "./assets/icon.svg"
|
||||
brand_color: "#14B8A6"
|
||||
default_prompt: "Help me create a Convex component for this feature. First check that a component is actually justified, then design the tables, API surface, and app-facing wrappers before implementing it."
|
||||
default_prompt: "Help me create a Convex component for this feature. First check that a
|
||||
component is actually justified, then design the tables, API surface, and
|
||||
app-facing wrappers before implementing it."
|
||||
|
||||
policy:
|
||||
allow_implicit_invocation: true
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
# Advanced Component Patterns
|
||||
|
||||
Additional patterns for Convex components that go beyond the basics covered in the main skill file.
|
||||
Additional patterns for Convex components that go beyond the basics covered in
|
||||
the main skill file.
|
||||
|
||||
## Function Handles for callbacks
|
||||
|
||||
When the app needs to pass a callback function to the component, use function handles. This is common for components that run app-defined logic on a schedule or in a workflow.
|
||||
When the app needs to pass a callback function to the component, use function
|
||||
handles. This is common for components that run app-defined logic on a schedule
|
||||
or in a workflow.
|
||||
|
||||
```ts
|
||||
// App side: create a handle and pass it to the component
|
||||
@@ -37,7 +40,8 @@ export const enqueue = mutation({
|
||||
|
||||
## Deriving validators from schema
|
||||
|
||||
Instead of manually repeating field types in return validators, extend the schema validator:
|
||||
Instead of manually repeating field types in return validators, extend the
|
||||
schema validator:
|
||||
|
||||
```ts
|
||||
import { v } from "convex/values";
|
||||
@@ -59,7 +63,8 @@ export const getLatest = query({
|
||||
|
||||
## Static configuration with a globals table
|
||||
|
||||
A common pattern for component configuration is a single-document "globals" table:
|
||||
A common pattern for component configuration is a single-document "globals"
|
||||
table:
|
||||
|
||||
```ts
|
||||
// schema.ts
|
||||
@@ -91,7 +96,8 @@ export const configure = mutation({
|
||||
|
||||
## Class-based client wrappers
|
||||
|
||||
For components with many functions or configuration options, a class-based client provides a cleaner API. This pattern is common in published components.
|
||||
For components with many functions or configuration options, a class-based
|
||||
client provides a cleaner API. This pattern is common in published components.
|
||||
|
||||
```ts
|
||||
// src/client/index.ts
|
||||
|
||||
@@ -10,7 +10,8 @@ This can help when:
|
||||
|
||||
- the user wants a local install but also shared package logic
|
||||
- the component needs extension points or override hooks
|
||||
- some logic should live in normal TypeScript code outside the component boundary
|
||||
- some logic should live in normal TypeScript code outside the component
|
||||
boundary
|
||||
|
||||
## Default Advice
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Local Convex Components
|
||||
|
||||
Read this file when the component should live inside the current app and does not need to be published as an npm package.
|
||||
Read this file when the component should live inside the current app and does
|
||||
not need to be published as an npm package.
|
||||
|
||||
## When to Choose This
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Packaged Convex Components
|
||||
|
||||
Read this file when the user wants a reusable npm package or a component shared across multiple apps.
|
||||
Read this file when the user wants a reusable npm package or a component shared
|
||||
across multiple apps.
|
||||
|
||||
## When to Choose This
|
||||
|
||||
@@ -11,12 +12,14 @@ Read this file when the user wants a reusable npm package or a component shared
|
||||
## Default Approach
|
||||
|
||||
- Prefer starting from `npx create-convex@latest --component` when possible
|
||||
- Keep the official authoring docs as the source of truth for package layout and exports
|
||||
- Keep the official authoring docs as the source of truth for package layout and
|
||||
exports
|
||||
- Validate the bundled package through an example app, not just the source files
|
||||
|
||||
## Build Flow
|
||||
|
||||
When building a packaged component, make sure the bundled output exists before the example app tries to consume it.
|
||||
When building a packaged component, make sure the bundled output exists before
|
||||
the example app tries to consume it.
|
||||
|
||||
Recommended order:
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
name: convex-migration-helper
|
||||
description: Plans Convex schema and data migrations with widen-migrate-narrow and @convex-dev/migrations. Use for breaking schema changes, backfills, table reshaping, or zero-downtime rollouts.
|
||||
description: Plans Convex schema and data migrations with widen-migrate-narrow and
|
||||
@convex-dev/migrations. Use for breaking schema changes, backfills, table
|
||||
reshaping, or zero-downtime rollouts.
|
||||
---
|
||||
|
||||
# Convex Migration Helper
|
||||
@@ -27,25 +29,32 @@ Safely migrate Convex schemas and data when making breaking changes.
|
||||
|
||||
### Schema Validation Drives the Workflow
|
||||
|
||||
Convex will not let you deploy a schema that does not match the data at rest. This is the fundamental constraint that shapes every migration:
|
||||
Convex will not let you deploy a schema that does not match the data at rest.
|
||||
This is the fundamental constraint that shapes every migration:
|
||||
|
||||
- You cannot add a required field if existing documents don't have it
|
||||
- You cannot change a field's type if existing documents have the old type
|
||||
- You cannot remove a field from the schema if existing documents still have it
|
||||
|
||||
This means migrations follow a predictable pattern: **widen the schema, migrate the data, narrow the schema**.
|
||||
This means migrations follow a predictable pattern: **widen the schema, migrate
|
||||
the data, narrow the schema**.
|
||||
|
||||
### Online Migrations
|
||||
|
||||
Convex migrations run online, meaning the app continues serving requests while data is updated asynchronously in batches. During the migration window, your code must handle both old and new data formats.
|
||||
Convex migrations run online, meaning the app continues serving requests while
|
||||
data is updated asynchronously in batches. During the migration window, your
|
||||
code must handle both old and new data formats.
|
||||
|
||||
### Prefer New Fields Over Changing Types
|
||||
|
||||
When changing the shape of data, create a new field rather than modifying an existing one. This makes the transition safer and easier to roll back.
|
||||
When changing the shape of data, create a new field rather than modifying an
|
||||
existing one. This makes the transition safer and easier to roll back.
|
||||
|
||||
### Don't Delete Data
|
||||
|
||||
Unless you are certain, prefer deprecating fields over deleting them. Mark the field as `v.optional` and add a code comment explaining it is deprecated and why it existed.
|
||||
Unless you are certain, prefer deprecating fields over deleting them. Mark the
|
||||
field as `v.optional` and add a code comment explaining it is deprecated and why
|
||||
it existed.
|
||||
|
||||
## Safe Changes (No Migration Needed)
|
||||
|
||||
@@ -88,7 +97,8 @@ Every breaking migration follows the same multi-deploy pattern:
|
||||
|
||||
**Deploy 1 - Widen the schema:**
|
||||
|
||||
1. Update schema to allow both old and new formats (e.g., add optional new field)
|
||||
1. Update schema to allow both old and new formats (e.g., add optional new
|
||||
field)
|
||||
2. Update code to handle both formats when reading
|
||||
3. Update code to write the new format for new documents
|
||||
4. Deploy
|
||||
@@ -106,13 +116,18 @@ Every breaking migration follows the same multi-deploy pattern:
|
||||
|
||||
## Using the Migrations Component
|
||||
|
||||
For any non-trivial migration, use the [`@convex-dev/migrations`](https://www.convex.dev/components/migrations) component. It handles batching, cursor-based pagination, state tracking, resume from failure, dry runs, and progress monitoring.
|
||||
For any non-trivial migration, use the
|
||||
[`@convex-dev/migrations`](https://www.convex.dev/components/migrations)
|
||||
component. It handles batching, cursor-based pagination, state tracking, resume
|
||||
from failure, dry runs, and progress monitoring.
|
||||
|
||||
See `references/migrations-component.md` for installation, setup, defining and running migrations, dry runs, status monitoring, and configuration options.
|
||||
See `references/migrations-component.md` for installation, setup, defining and
|
||||
running migrations, dry runs, status monitoring, and configuration options.
|
||||
|
||||
## Common Migration Patterns
|
||||
|
||||
See `references/migration-patterns.md` for complete patterns with code examples covering:
|
||||
See `references/migration-patterns.md` for complete patterns with code examples
|
||||
covering:
|
||||
|
||||
- Adding a required field
|
||||
- Deleting a field
|
||||
@@ -125,12 +140,23 @@ See `references/migration-patterns.md` for complete patterns with code examples
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
1. **Making a field required before migrating data**: Convex rejects the deploy because existing documents lack the field. Always widen the schema first.
|
||||
2. **Using `.collect()` on large tables**: Hits transaction limits or causes timeouts. Use the migrations component for proper batched pagination. `.collect()` is only safe for tables you know are small.
|
||||
3. **Not writing the new format before migrating**: Documents created during the migration window will be missed, leaving unmigrated data after the migration "completes."
|
||||
4. **Skipping the dry run**: Use `dryRun: true` to validate migration logic before committing changes to production data. Catches bugs before they touch real documents.
|
||||
5. **Deleting fields prematurely**: Prefer deprecating with `v.optional` and a comment. Only delete after you are confident the data is no longer needed and no code references it.
|
||||
6. **Using crons for migration batches**: The migrations component handles batching via recursive scheduling internally. Crons require manual cleanup and an extra deploy to remove.
|
||||
1. **Making a field required before migrating data**: Convex rejects the deploy
|
||||
because existing documents lack the field. Always widen the schema first.
|
||||
2. **Using `.collect()` on large tables**: Hits transaction limits or causes
|
||||
timeouts. Use the migrations component for proper batched pagination.
|
||||
`.collect()` is only safe for tables you know are small.
|
||||
3. **Not writing the new format before migrating**: Documents created during the
|
||||
migration window will be missed, leaving unmigrated data after the migration
|
||||
"completes."
|
||||
4. **Skipping the dry run**: Use `dryRun: true` to validate migration logic
|
||||
before committing changes to production data. Catches bugs before they touch
|
||||
real documents.
|
||||
5. **Deleting fields prematurely**: Prefer deprecating with `v.optional` and a
|
||||
comment. Only delete after you are confident the data is no longer needed and
|
||||
no code references it.
|
||||
6. **Using crons for migration batches**: The migrations component handles
|
||||
batching via recursive scheduling internally. Crons require manual cleanup
|
||||
and an extra deploy to remove.
|
||||
|
||||
## Migration Checklist
|
||||
|
||||
|
||||
@@ -4,7 +4,9 @@ interface:
|
||||
icon_small: "./assets/icon.svg"
|
||||
icon_large: "./assets/icon.svg"
|
||||
brand_color: "#8B5CF6"
|
||||
default_prompt: "Help me plan and execute this Convex migration safely. Start by identifying the schema change, the existing data shape, and the widen-migrate-narrow path before making edits."
|
||||
default_prompt: "Help me plan and execute this Convex migration safely. Start by identifying
|
||||
the schema change, the existing data shape, and the widen-migrate-narrow
|
||||
path before making edits."
|
||||
|
||||
policy:
|
||||
allow_implicit_invocation: true
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Migration Patterns Reference
|
||||
|
||||
Common migration patterns, zero-downtime strategies, and verification techniques for Convex schema and data migrations.
|
||||
Common migration patterns, zero-downtime strategies, and verification techniques
|
||||
for Convex schema and data migrations.
|
||||
|
||||
## Adding a Required Field
|
||||
|
||||
@@ -30,7 +31,8 @@ users: defineTable({
|
||||
|
||||
## Deleting a Field
|
||||
|
||||
Mark the field optional first, migrate data to remove it, then remove from schema:
|
||||
Mark the field optional first, migrate data to remove it, then remove from
|
||||
schema:
|
||||
|
||||
```typescript
|
||||
// Deploy 1: Make optional
|
||||
@@ -51,7 +53,8 @@ export const removeIsPro = migrations.define({
|
||||
|
||||
## Changing a Field Type
|
||||
|
||||
Prefer creating a new field. You can combine adding and deleting in one migration:
|
||||
Prefer creating a new field. You can combine adding and deleting in one
|
||||
migration:
|
||||
|
||||
```typescript
|
||||
// Deploy 1: Add new field, keep old field optional
|
||||
@@ -98,7 +101,9 @@ export const extractPreferences = migrations.define({
|
||||
});
|
||||
```
|
||||
|
||||
Make sure your code is already writing to the new `userPreferences` table for new users before running this migration, so you don't miss documents created during the migration window.
|
||||
Make sure your code is already writing to the new `userPreferences` table for
|
||||
new users before running this migration, so you don't miss documents created
|
||||
during the migration window.
|
||||
|
||||
## Cleaning Up Orphaned Documents
|
||||
|
||||
@@ -120,18 +125,21 @@ export const deleteOrphanedEmbeddings = migrations.define({
|
||||
|
||||
## Zero-Downtime Strategies
|
||||
|
||||
During the migration window, your app must handle both old and new data formats. There are two main strategies.
|
||||
During the migration window, your app must handle both old and new data formats.
|
||||
There are two main strategies.
|
||||
|
||||
### Dual Write (Preferred)
|
||||
|
||||
Write to both old and new structures. Read from the old structure until migration is complete.
|
||||
Write to both old and new structures. Read from the old structure until
|
||||
migration is complete.
|
||||
|
||||
1. Deploy code that writes both formats, reads old format
|
||||
2. Run migration on existing data
|
||||
3. Deploy code that reads new format, still writes both
|
||||
4. Deploy code that only reads and writes new format
|
||||
|
||||
This is preferred because you can safely roll back at any point, the old format is always up to date.
|
||||
This is preferred because you can safely roll back at any point, the old format
|
||||
is always up to date.
|
||||
|
||||
```typescript
|
||||
// Bad: only writing to new structure before migration is done
|
||||
@@ -167,7 +175,9 @@ Read both formats. Write only the new format.
|
||||
2. Run migration on existing data
|
||||
3. Deploy code that reads and writes only new format
|
||||
|
||||
This avoids duplicating writes, which is useful when having two copies of data could cause inconsistencies. The downside is that rolling back to before step 1 is harder, since new documents only have the new format.
|
||||
This avoids duplicating writes, which is useful when having two copies of data
|
||||
could cause inconsistencies. The downside is that rolling back to before step 1
|
||||
is harder, since new documents only have the new format.
|
||||
|
||||
```typescript
|
||||
// Good: reading both formats, preferring new
|
||||
@@ -179,7 +189,8 @@ function getTeamPlan(team: Doc<"teams">): "basic" | "pro" {
|
||||
|
||||
## Small Table Shortcut
|
||||
|
||||
For small tables (a few thousand documents at most), you can migrate in a single `internalMutation` without the component:
|
||||
For small tables (a few thousand documents at most), you can migrate in a single
|
||||
`internalMutation` without the component:
|
||||
|
||||
```typescript
|
||||
import { internalMutation } from "./_generated/server";
|
||||
@@ -200,7 +211,8 @@ export const backfillSmallTable = internalMutation({
|
||||
npx convex run migrations:backfillSmallTable
|
||||
```
|
||||
|
||||
Only use `.collect()` when you are certain the table is small. For anything larger, use the migrations component.
|
||||
Only use `.collect()` when you are certain the table is small. For anything
|
||||
larger, use the migrations component.
|
||||
|
||||
## Verifying a Migration
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# Migrations Component Reference
|
||||
|
||||
Complete guide to the [`@convex-dev/migrations`](https://www.convex.dev/components/migrations) component for batched, resumable Convex data migrations.
|
||||
Complete guide to the
|
||||
[`@convex-dev/migrations`](https://www.convex.dev/components/migrations)
|
||||
component for batched, resumable Convex data migrations.
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -30,11 +32,13 @@ export const migrations = new Migrations<DataModel>(components.migrations);
|
||||
export const run = migrations.runner();
|
||||
```
|
||||
|
||||
The `DataModel` type parameter is optional but provides type safety for migration definitions.
|
||||
The `DataModel` type parameter is optional but provides type safety for
|
||||
migration definitions.
|
||||
|
||||
## Define a Migration
|
||||
|
||||
The `migrateOne` function processes a single document. The component handles batching and pagination automatically.
|
||||
The `migrateOne` function processes a single document. The component handles
|
||||
batching and pagination automatically.
|
||||
|
||||
```typescript
|
||||
// convex/migrations.ts
|
||||
@@ -90,7 +94,8 @@ export const runAll = migrations.runner([
|
||||
npx convex run migrations:runAll
|
||||
```
|
||||
|
||||
If one fails, it stops and will not continue to the next. Call it again to retry from where it left off. Completed migrations are skipped automatically.
|
||||
If one fails, it stops and will not continue to the next. Call it again to retry
|
||||
from where it left off. Completed migrations are skipped automatically.
|
||||
|
||||
## Dry Run
|
||||
|
||||
@@ -100,7 +105,8 @@ Test a migration before committing changes:
|
||||
npx convex run migrations:runIt '{"dryRun": true}'
|
||||
```
|
||||
|
||||
This runs one batch and then rolls back, so you can see what it would do without changing any data.
|
||||
This runs one batch and then rolls back, so you can see what it would do without
|
||||
changing any data.
|
||||
|
||||
## Check Migration Status
|
||||
|
||||
@@ -132,7 +138,8 @@ npx convex deploy --cmd 'npm run build' && npx convex run migrations:runAll --pr
|
||||
|
||||
### Custom Batch Size
|
||||
|
||||
If documents are large or the table has heavy write traffic, reduce the batch size to avoid transaction limits or OCC conflicts:
|
||||
If documents are large or the table has heavy write traffic, reduce the batch
|
||||
size to avoid transaction limits or OCC conflicts:
|
||||
|
||||
```typescript
|
||||
export const migrateHeavyTable = migrations.define({
|
||||
@@ -158,7 +165,8 @@ export const fixEmptyNames = migrations.define({
|
||||
|
||||
### Parallelize Within a Batch
|
||||
|
||||
By default each document in a batch is processed serially. Enable parallel processing if your migration logic does not depend on ordering:
|
||||
By default each document in a batch is processed serially. Enable parallel
|
||||
processing if your migration logic does not depend on ordering:
|
||||
|
||||
```typescript
|
||||
export const clearField = migrations.define({
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
---
|
||||
name: convex-performance-audit
|
||||
description: Audits Convex performance for reads, subscriptions, write contention, and function limits. Use for slow features, insights findings, OCC conflicts, or read amplification.
|
||||
description: Audits Convex performance for reads, subscriptions, write contention, and
|
||||
function limits. Use for slow features, insights findings, OCC conflicts, or
|
||||
read amplification.
|
||||
---
|
||||
|
||||
# Convex Performance Audit
|
||||
|
||||
Diagnose and fix performance problems in Convex applications, one problem class at a time.
|
||||
Diagnose and fix performance problems in Convex applications, one problem class
|
||||
at a time.
|
||||
|
||||
## When to Use
|
||||
|
||||
- A Convex page or feature feels slow or expensive
|
||||
- `npx convex insights --details` reports high bytes read, documents read, or OCC conflicts
|
||||
- Low-freshness read paths are using reactivity where point-in-time reads would do
|
||||
- `npx convex insights --details` reports high bytes read, documents read, or
|
||||
OCC conflicts
|
||||
- Low-freshness read paths are using reactivity where point-in-time reads would
|
||||
do
|
||||
- OCC conflict errors or excessive mutation retries
|
||||
- High subscription count or slow UI updates
|
||||
- Functions approaching execution or transaction limits
|
||||
@@ -21,27 +26,39 @@ Diagnose and fix performance problems in Convex applications, one problem class
|
||||
|
||||
- Initial Convex setup, auth setup, or component extraction
|
||||
- Pure schema migrations with no performance goal
|
||||
- One-off micro-optimizations without a user-visible or deployment-visible problem
|
||||
- One-off micro-optimizations without a user-visible or deployment-visible
|
||||
problem
|
||||
|
||||
## Guardrails
|
||||
|
||||
- Prefer simpler code when scale is small, traffic is modest, or the available signals are weak
|
||||
- Do not recommend digest tables, document splitting, fetch-strategy changes, or migration-heavy rollouts unless there is a measured signal, a clearly unbounded path, or a known hot read/write path
|
||||
- In Convex, a simple scan on a small table is often acceptable. Do not invent structural work just because a pattern is not ideal at large scale
|
||||
- Prefer simpler code when scale is small, traffic is modest, or the available
|
||||
signals are weak
|
||||
- Do not recommend digest tables, document splitting, fetch-strategy changes, or
|
||||
migration-heavy rollouts unless there is a measured signal, a clearly
|
||||
unbounded path, or a known hot read/write path
|
||||
- In Convex, a simple scan on a small table is often acceptable. Do not invent
|
||||
structural work just because a pattern is not ideal at large scale
|
||||
|
||||
## First Step: Gather Signals
|
||||
|
||||
Start with the strongest signal available:
|
||||
|
||||
1. If deployment Health insights are already available from the user or the current context, treat them as a first-class source of performance signals.
|
||||
2. If CLI insights are available, run `npx convex insights --details`. Use `--prod`, `--preview-name`, or `--deployment-name` when needed.
|
||||
- If the local repo's Convex CLI is too old to support `insights`, try `npx -y convex@latest insights --details` before giving up.
|
||||
3. If the repo already uses `convex-doctor`, you may treat its findings as hints. Do not require it, and do not treat it as the source of truth.
|
||||
4. If runtime signals are unavailable, audit from code anyway, but keep the guardrails above in mind. Lack of insights is not proof of health, but it is also not proof that a large refactor is warranted.
|
||||
1. If deployment Health insights are already available from the user or the
|
||||
current context, treat them as a first-class source of performance signals.
|
||||
2. If CLI insights are available, run `npx convex insights --details`. Use
|
||||
`--prod`, `--preview-name`, or `--deployment-name` when needed.
|
||||
- If the local repo's Convex CLI is too old to support `insights`, try
|
||||
`npx -y convex@latest insights --details` before giving up.
|
||||
3. If the repo already uses `convex-doctor`, you may treat its findings as
|
||||
hints. Do not require it, and do not treat it as the source of truth.
|
||||
4. If runtime signals are unavailable, audit from code anyway, but keep the
|
||||
guardrails above in mind. Lack of insights is not proof of health, but it is
|
||||
also not proof that a large refactor is warranted.
|
||||
|
||||
## Signal Routing
|
||||
|
||||
After gathering signals, identify the problem class and read the matching reference file.
|
||||
After gathering signals, identify the problem class and read the matching
|
||||
reference file.
|
||||
|
||||
| Signal | Reference |
|
||||
| -------------------------------------------------------------- | ----------------------------------------- |
|
||||
@@ -51,26 +68,31 @@ After gathering signals, identify the problem class and read the matching refere
|
||||
| Function timeouts, transaction size errors, large payloads | `references/function-budget.md` |
|
||||
| General "it's slow" with no specific signal | Start with `references/hot-path-rules.md` |
|
||||
|
||||
Multiple problem classes can overlap. Read the most relevant reference first, then check the others if symptoms remain.
|
||||
Multiple problem classes can overlap. Read the most relevant reference first,
|
||||
then check the others if symptoms remain.
|
||||
|
||||
## Escalate Larger Fixes
|
||||
|
||||
If the likely fix is invasive, cross-cutting, or migration-heavy, stop and present options before editing.
|
||||
If the likely fix is invasive, cross-cutting, or migration-heavy, stop and
|
||||
present options before editing.
|
||||
|
||||
Examples:
|
||||
|
||||
- introducing digest or summary tables across multiple flows
|
||||
- splitting documents to isolate frequently-updated fields
|
||||
- reworking pagination or fetch strategy across several screens
|
||||
- switching to a new index or denormalized field that needs migration-safe rollout
|
||||
- switching to a new index or denormalized field that needs migration-safe
|
||||
rollout
|
||||
|
||||
When correctness depends on handling old and new states during a rollout, consult `skills/convex-migration-helper/SKILL.md` for the migration workflow.
|
||||
When correctness depends on handling old and new states during a rollout,
|
||||
consult `skills/convex-migration-helper/SKILL.md` for the migration workflow.
|
||||
|
||||
## Workflow
|
||||
|
||||
### 1. Scope the problem
|
||||
|
||||
Pick one concrete user flow from the actual project. Look at the codebase, client pages, and API surface to find the flow that matches the symptom.
|
||||
Pick one concrete user flow from the actual project. Look at the codebase,
|
||||
client pages, and API surface to find the flow that matches the symptom.
|
||||
|
||||
Write down:
|
||||
|
||||
@@ -90,27 +112,37 @@ For each function in the path:
|
||||
4. Identify all sibling functions touching the same tables
|
||||
5. Identify reactive stats, aggregates, or widgets rendered on the same page
|
||||
|
||||
In Convex, every extra read increases transaction work, and every write can invalidate reactive subscribers. Treat read amplification and invalidation amplification as first-class problems.
|
||||
In Convex, every extra read increases transaction work, and every write can
|
||||
invalidate reactive subscribers. Treat read amplification and invalidation
|
||||
amplification as first-class problems.
|
||||
|
||||
### 3. Apply fixes from the relevant reference
|
||||
|
||||
Read the reference file matching your problem class. Each reference includes specific patterns, code examples, and a recommended fix order.
|
||||
Read the reference file matching your problem class. Each reference includes
|
||||
specific patterns, code examples, and a recommended fix order.
|
||||
|
||||
Do not stop at the single function named by an insight. Trace sibling readers and writers touching the same tables.
|
||||
Do not stop at the single function named by an insight. Trace sibling readers
|
||||
and writers touching the same tables.
|
||||
|
||||
### 4. Fix sibling functions together
|
||||
|
||||
When one function touching a table has a performance bug, audit sibling functions for the same pattern.
|
||||
When one function touching a table has a performance bug, audit sibling
|
||||
functions for the same pattern.
|
||||
|
||||
After finding one problem, inspect both sibling readers and sibling writers for the same table family, including companion digest or summary tables.
|
||||
After finding one problem, inspect both sibling readers and sibling writers for
|
||||
the same table family, including companion digest or summary tables.
|
||||
|
||||
Examples:
|
||||
|
||||
- If one list query switches from full docs to a digest table, inspect the other list queries for that table
|
||||
- If one mutation isolates a frequently-updated field or splits a hot document, inspect the other writers to the same table
|
||||
- If one read path needs a migration-safe rollout for an unbackfilled field, inspect sibling reads for the same rollout risk
|
||||
- If one list query switches from full docs to a digest table, inspect the other
|
||||
list queries for that table
|
||||
- If one mutation isolates a frequently-updated field or splits a hot document,
|
||||
inspect the other writers to the same table
|
||||
- If one read path needs a migration-safe rollout for an unbackfilled field,
|
||||
inspect sibling reads for the same rollout risk
|
||||
|
||||
Do not leave one path fixed and another path on the old pattern unless there is a clear product reason.
|
||||
Do not leave one path fixed and another path on the old pattern unless there is
|
||||
a clear product reason.
|
||||
|
||||
### 5. Verify before finishing
|
||||
|
||||
@@ -119,17 +151,26 @@ Confirm all of these:
|
||||
1. Results are the same as before, no dropped records
|
||||
2. Eliminated reads or writes are no longer in the path where expected
|
||||
3. Fallback behavior works when denormalized or indexed fields are missing
|
||||
4. Frequently-updated fields are isolated from widely-read documents where needed
|
||||
5. Every relevant sibling reader and writer was inspected, not just the original function
|
||||
4. Frequently-updated fields are isolated from widely-read documents where
|
||||
needed
|
||||
5. Every relevant sibling reader and writer was inspected, not just the original
|
||||
function
|
||||
|
||||
## Reference Files
|
||||
|
||||
- `references/hot-path-rules.md` - Read amplification, invalidation, denormalization, indexes, digest tables
|
||||
- `references/occ-conflicts.md` - Write contention, OCC resolution, hot document splitting
|
||||
- `references/subscription-cost.md` - Reactive query cost, subscription granularity, point-in-time reads
|
||||
- `references/function-budget.md` - Execution limits, transaction size, large documents, payload size
|
||||
- `references/hot-path-rules.md` - Read amplification, invalidation,
|
||||
denormalization, indexes, digest tables
|
||||
- `references/occ-conflicts.md` - Write contention, OCC resolution, hot document
|
||||
splitting
|
||||
- `references/subscription-cost.md` - Reactive query cost, subscription
|
||||
granularity, point-in-time reads
|
||||
- `references/function-budget.md` - Execution limits, transaction size, large
|
||||
documents, payload size
|
||||
|
||||
Also check the official [Convex Best Practices](https://docs.convex.dev/understanding/best-practices/) page for additional patterns covering argument validation, access control, and code organization that may surface during the audit.
|
||||
Also check the official
|
||||
[Convex Best Practices](https://docs.convex.dev/understanding/best-practices/)
|
||||
page for additional patterns covering argument validation, access control, and
|
||||
code organization that may surface during the audit.
|
||||
|
||||
## Checklist
|
||||
|
||||
|
||||
@@ -4,7 +4,9 @@ interface:
|
||||
icon_small: "./assets/icon.svg"
|
||||
icon_large: "./assets/icon.svg"
|
||||
brand_color: "#EF4444"
|
||||
default_prompt: "Audit this Convex app for performance issues. Start with the strongest signal available, identify the problem class, and suggest the smallest high-impact fix before proposing bigger structural changes."
|
||||
default_prompt: "Audit this Convex app for performance issues. Start with the strongest
|
||||
signal available, identify the problem class, and suggest the smallest
|
||||
high-impact fix before proposing bigger structural changes."
|
||||
|
||||
policy:
|
||||
allow_implicit_invocation: true
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
# Function Budget
|
||||
|
||||
Use these rules when functions are hitting execution limits, transaction size errors, or returning excessively large payloads to the client.
|
||||
Use these rules when functions are hitting execution limits, transaction size
|
||||
errors, or returning excessively large payloads to the client.
|
||||
|
||||
## Core Principle
|
||||
|
||||
Convex functions run inside transactions with budgets for time, reads, and writes. Staying well within these limits is not just about avoiding errors, it reduces latency and contention.
|
||||
Convex functions run inside transactions with budgets for time, reads, and
|
||||
writes. Staying well within these limits is not just about avoiding errors, it
|
||||
reduces latency and contention.
|
||||
|
||||
## Limits to Know
|
||||
|
||||
These are the current values from the [Convex limits docs](https://docs.convex.dev/production/state/limits). Check that page for the latest numbers.
|
||||
These are the current values from the
|
||||
[Convex limits docs](https://docs.convex.dev/production/state/limits). Check
|
||||
that page for the latest numbers.
|
||||
|
||||
| Resource | Limit |
|
||||
| --------------------------------- | ----------------------------------------------------- |
|
||||
@@ -34,15 +39,18 @@ These are the current values from the [Convex limits docs](https://docs.convex.d
|
||||
|
||||
### Unbounded collection
|
||||
|
||||
A query that calls `.collect()` on a table without a reasonable limit. As the table grows, the query reads more and more documents.
|
||||
A query that calls `.collect()` on a table without a reasonable limit. As the
|
||||
table grows, the query reads more and more documents.
|
||||
|
||||
### Large document reads on hot paths
|
||||
|
||||
Reading documents with large fields (rich text, embedded media references, long arrays) when only a small subset of the data is needed for the current view.
|
||||
Reading documents with large fields (rich text, embedded media references, long
|
||||
arrays) when only a small subset of the data is needed for the current view.
|
||||
|
||||
### Mutation doing too much work
|
||||
|
||||
A single mutation that updates hundreds of documents, backfills data, or rebuilds derived state in one transaction.
|
||||
A single mutation that updates hundreds of documents, backfills data, or
|
||||
rebuilds derived state in one transaction.
|
||||
|
||||
### Returning too much data to the client
|
||||
|
||||
@@ -70,13 +78,16 @@ const messages = await ctx.db
|
||||
|
||||
### 2. Read smaller shapes
|
||||
|
||||
If the list page only needs title, author, and date, do not read full documents with rich content fields.
|
||||
If the list page only needs title, author, and date, do not read full documents
|
||||
with rich content fields.
|
||||
|
||||
Use digest or summary tables for hot list pages. See `hot-path-rules.md` for the digest table pattern.
|
||||
Use digest or summary tables for hot list pages. See `hot-path-rules.md` for the
|
||||
digest table pattern.
|
||||
|
||||
### 3. Break large mutations into batches
|
||||
|
||||
If a mutation needs to update hundreds of documents, split it into a self-scheduling chain.
|
||||
If a mutation needs to update hundreds of documents, split it into a
|
||||
self-scheduling chain.
|
||||
|
||||
```ts
|
||||
// Bad: one mutation updating every row
|
||||
@@ -118,9 +129,12 @@ export const backfillBatch = internalMutation({
|
||||
|
||||
### 4. Move heavy work to actions
|
||||
|
||||
Queries and mutations run inside Convex's transactional runtime with strict budgets. If you need to do CPU-intensive computation, call external APIs, or process large files, use an action instead.
|
||||
Queries and mutations run inside Convex's transactional runtime with strict
|
||||
budgets. If you need to do CPU-intensive computation, call external APIs, or
|
||||
process large files, use an action instead.
|
||||
|
||||
Actions run outside the transaction and can call mutations to write results back.
|
||||
Actions run outside the transaction and can call mutations to write results
|
||||
back.
|
||||
|
||||
```ts
|
||||
// Bad: heavy computation inside a mutation
|
||||
@@ -144,7 +158,8 @@ export const processUpload = action({
|
||||
|
||||
### 5. Trim return values
|
||||
|
||||
Only return what the client needs. If a query fetches full documents but the component only renders a few fields, map the results before returning.
|
||||
Only return what the client needs. If a query fetches full documents but the
|
||||
component only renders a few fields, map the results before returning.
|
||||
|
||||
```ts
|
||||
// Bad: returns full documents including large content fields
|
||||
@@ -172,7 +187,9 @@ export const list = query({
|
||||
|
||||
### 6. Replace `ctx.runQuery` and `ctx.runMutation` with helper functions
|
||||
|
||||
Inside queries and mutations, `ctx.runQuery` and `ctx.runMutation` have overhead compared to calling a plain TypeScript helper function. They run in the same transaction but pay extra per-call cost.
|
||||
Inside queries and mutations, `ctx.runQuery` and `ctx.runMutation` have overhead
|
||||
compared to calling a plain TypeScript helper function. They run in the same
|
||||
transaction but pay extra per-call cost.
|
||||
|
||||
```ts
|
||||
// Bad: unnecessary overhead from ctx.runQuery inside a mutation
|
||||
@@ -194,11 +211,15 @@ export const createProject = mutation({
|
||||
});
|
||||
```
|
||||
|
||||
Exception: components require `ctx.runQuery`/`ctx.runMutation`. Use them there, but prefer helpers everywhere else.
|
||||
Exception: components require `ctx.runQuery`/`ctx.runMutation`. Use them there,
|
||||
but prefer helpers everywhere else.
|
||||
|
||||
### 7. Avoid unnecessary `runAction` calls
|
||||
|
||||
`runAction` from within an action creates a separate function invocation with its own memory and CPU budget. The parent action just sits idle waiting. Replace with a plain TypeScript function call unless you need a different runtime (e.g. calling Node.js code from the Convex runtime).
|
||||
`runAction` from within an action creates a separate function invocation with
|
||||
its own memory and CPU budget. The parent action just sits idle waiting. Replace
|
||||
with a plain TypeScript function call unless you need a different runtime (e.g.
|
||||
calling Node.js code from the Convex runtime).
|
||||
|
||||
```ts
|
||||
// Bad: runAction overhead for no reason
|
||||
@@ -228,5 +249,6 @@ export const processItems = action({
|
||||
2. `npx convex insights --details` shows reduced bytes read
|
||||
3. Large mutations are batched and self-scheduling
|
||||
4. Client payloads are reasonably sized for the UI they serve
|
||||
5. `ctx.runQuery`/`ctx.runMutation` in queries and mutations replaced with helpers where possible
|
||||
5. `ctx.runQuery`/`ctx.runMutation` in queries and mutations replaced with
|
||||
helpers where possible
|
||||
6. Sibling functions with similar patterns were checked
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# Hot Path Rules
|
||||
|
||||
Use these rules when the top-level workflow points to read amplification, denormalization, index rollout, reactive query cost, or invalidation-heavy writes.
|
||||
Use these rules when the top-level workflow points to read amplification,
|
||||
denormalization, index rollout, reactive query cost, or invalidation-heavy
|
||||
writes.
|
||||
|
||||
## Contents
|
||||
|
||||
@@ -10,8 +12,10 @@ Use these rules when the top-level workflow points to read amplification, denorm
|
||||
- 2. Minimize Data Sources (denormalization, fallback rule)
|
||||
- 3. Minimize Row Size (digest tables)
|
||||
- 4. Skip No-Op Writes
|
||||
- 5. Match Consistency To Read Patterns (high-read/low-write, high-read/high-write)
|
||||
- Convex-Specific Notes (reactive queries, point-in-time reads, triggers, aggregates, backfills)
|
||||
- 5. Match Consistency To Read Patterns (high-read/low-write,
|
||||
high-read/high-write)
|
||||
- Convex-Specific Notes (reactive queries, point-in-time reads, triggers,
|
||||
aggregates, backfills)
|
||||
- Verification
|
||||
|
||||
## Core Principle
|
||||
@@ -22,11 +26,13 @@ Think:
|
||||
|
||||
`cost x calls_per_second x 86400`
|
||||
|
||||
In Convex, every write can also fan out into reactive invalidation, replication work, and downstream sync.
|
||||
In Convex, every write can also fan out into reactive invalidation, replication
|
||||
work, and downstream sync.
|
||||
|
||||
## Consistency Rule
|
||||
|
||||
If you fix a hot-path pattern for one function, audit sibling functions touching the same tables for the same pattern.
|
||||
If you fix a hot-path pattern for one function, audit sibling functions touching
|
||||
the same tables for the same pattern.
|
||||
|
||||
Do this especially for:
|
||||
|
||||
@@ -37,7 +43,11 @@ Do this especially for:
|
||||
|
||||
## 1. Push Filters To Storage
|
||||
|
||||
Both JavaScript `.filter()` and the Convex query `.filter()` method after a DB scan mean you already paid for the read. The Convex `.filter()` method has the same performance as filtering in JS, it does not push the predicate to the storage layer. Only `.withIndex()` and `.withSearchIndex()` actually reduce the documents scanned.
|
||||
Both JavaScript `.filter()` and the Convex query `.filter()` method after a DB
|
||||
scan mean you already paid for the read. The Convex `.filter()` method has the
|
||||
same performance as filtering in JS, it does not push the predicate to the
|
||||
storage layer. Only `.withIndex()` and `.withSearchIndex()` actually reduce the
|
||||
documents scanned.
|
||||
|
||||
Prefer:
|
||||
|
||||
@@ -87,17 +97,22 @@ export const listOpen = query({
|
||||
|
||||
### Migration rule for indexes
|
||||
|
||||
New indexes on partially backfilled fields can create correctness bugs during rollout.
|
||||
New indexes on partially backfilled fields can create correctness bugs during
|
||||
rollout.
|
||||
|
||||
Important Convex detail:
|
||||
|
||||
`undefined !== false`
|
||||
|
||||
If an older document is missing a field entirely, it will not match a compound index entry that expects `false`.
|
||||
If an older document is missing a field entirely, it will not match a compound
|
||||
index entry that expects `false`.
|
||||
|
||||
Do not trust old comments saying a field is "not backfilled" or "already backfilled". Verify.
|
||||
Do not trust old comments saying a field is "not backfilled" or "already
|
||||
backfilled". Verify.
|
||||
|
||||
If correctness depends on handling old and new states during rollout, do not improvise a partial-backfill workaround in the hot path. Use a migration-safe rollout and consult `skills/convex-migration-helper/SKILL.md`.
|
||||
If correctness depends on handling old and new states during rollout, do not
|
||||
improvise a partial-backfill workaround in the hot path. Use a migration-safe
|
||||
rollout and consult `skills/convex-migration-helper/SKILL.md`.
|
||||
|
||||
```ts
|
||||
// Bad: optional booleans can miss older rows where the field is undefined
|
||||
@@ -115,7 +130,10 @@ const projects = await ctx.db
|
||||
|
||||
### Check for redundant indexes
|
||||
|
||||
Indexes like `by_foo` and `by_foo_and_bar` are usually redundant. You only need `by_foo_and_bar`, since you can query it with just the `foo` condition and omit `bar`. Extra indexes add storage cost and write overhead on every insert, patch, and delete.
|
||||
Indexes like `by_foo` and `by_foo_and_bar` are usually redundant. You only need
|
||||
`by_foo_and_bar`, since you can query it with just the `foo` condition and omit
|
||||
`bar`. Extra indexes add storage cost and write overhead on every insert, patch,
|
||||
and delete.
|
||||
|
||||
```ts
|
||||
// Bad: two indexes where one would do
|
||||
@@ -126,19 +144,24 @@ defineTable({ team: v.id("teams"), user: v.id("users") })
|
||||
|
||||
```ts
|
||||
// Good: single compound index serves both query patterns
|
||||
defineTable({ team: v.id("teams"), user: v.id("users") }).index(
|
||||
"by_team_and_user",
|
||||
["team", "user"],
|
||||
);
|
||||
defineTable({ team: v.id("teams"), user: v.id("users") }).index("by_team_and_user", [
|
||||
"team",
|
||||
"user",
|
||||
]);
|
||||
```
|
||||
|
||||
Exception: `.index("by_foo", ["foo"])` is really an index on `foo` + `_creationTime`, while `.index("by_foo_and_bar", ["foo", "bar"])` is on `foo` + `bar` + `_creationTime`. If you need results sorted by `foo` then `_creationTime`, you need the single-field index because the compound one would sort by `bar` first.
|
||||
Exception: `.index("by_foo", ["foo"])` is really an index on `foo` +
|
||||
`_creationTime`, while `.index("by_foo_and_bar", ["foo", "bar"])` is on `foo` +
|
||||
`bar` + `_creationTime`. If you need results sorted by `foo` then
|
||||
`_creationTime`, you need the single-field index because the compound one would
|
||||
sort by `bar` first.
|
||||
|
||||
## 2. Minimize Data Sources
|
||||
|
||||
Trace every read.
|
||||
|
||||
If a function resolves a foreign key for a tiny display field and a denormalized copy already exists, prefer the denormalized field on the hot path.
|
||||
If a function resolves a foreign key for a tiny display field and a denormalized
|
||||
copy already exists, prefer the denormalized field on the hot path.
|
||||
|
||||
### When to denormalize
|
||||
|
||||
@@ -152,7 +175,8 @@ Useful mental model:
|
||||
|
||||
`join_cost = rows_per_page x foreign_doc_size x pages_per_second`
|
||||
|
||||
Small-table joins are often fine. Large-document joins for tiny fields on hot list pages are usually not.
|
||||
Small-table joins are often fine. Large-document joins for tiny fields on hot
|
||||
list pages are usually not.
|
||||
|
||||
### Fallback rule
|
||||
|
||||
@@ -171,8 +195,7 @@ const ownerName = project.ownerName ?? "Unknown owner";
|
||||
|
||||
```ts
|
||||
// Good: denormalized data is an optimization, not the only source of truth
|
||||
const ownerName =
|
||||
project.ownerName ?? (await ctx.db.get(project.ownerId))?.name ?? null;
|
||||
const ownerName = project.ownerName ?? (await ctx.db.get(project.ownerId))?.name ?? null;
|
||||
```
|
||||
|
||||
Bad lookup map pattern:
|
||||
@@ -196,9 +219,11 @@ const ownersById =
|
||||
|
||||
### No denormalized copy yet
|
||||
|
||||
Prefer adding fields to an existing summary, companion, or digest table instead of bloating the primary hot-path table.
|
||||
Prefer adding fields to an existing summary, companion, or digest table instead
|
||||
of bloating the primary hot-path table.
|
||||
|
||||
If introducing the new field or table requires a staged rollout, backfill, or old/new-shape handling, use the migration helper skill for the rollout plan.
|
||||
If introducing the new field or table requires a staged rollout, backfill, or
|
||||
old/new-shape handling, use the migration helper skill for the rollout plan.
|
||||
|
||||
Rollout order:
|
||||
|
||||
@@ -209,7 +234,8 @@ Rollout order:
|
||||
|
||||
## 3. Minimize Row Size
|
||||
|
||||
Hot list pages should read the smallest document shape that still answers the UI.
|
||||
Hot list pages should read the smallest document shape that still answers the
|
||||
UI.
|
||||
|
||||
Prefer summary or digest tables over full source tables when:
|
||||
|
||||
@@ -217,12 +243,17 @@ Prefer summary or digest tables over full source tables when:
|
||||
- source documents are large
|
||||
- the query is high volume
|
||||
|
||||
An 800 byte summary row is materially cheaper than a 3 KB full document on a hot page.
|
||||
An 800 byte summary row is materially cheaper than a 3 KB full document on a hot
|
||||
page.
|
||||
|
||||
Digest tables are a tradeoff, not a default:
|
||||
|
||||
- Worth it when the path is clearly hot, the source rows are much larger than the UI needs, or many readers are repeatedly paying the same join and payload cost
|
||||
- Probably not worth it when an indexed read on the source table is already cheap enough, the table is still small, or the extra write and migration complexity would dominate the benefit
|
||||
- Worth it when the path is clearly hot, the source rows are much larger than
|
||||
the UI needs, or many readers are repeatedly paying the same join and payload
|
||||
cost
|
||||
- Probably not worth it when an indexed read on the source table is already
|
||||
cheap enough, the table is still small, or the extra write and migration
|
||||
complexity would dominate the benefit
|
||||
|
||||
```ts
|
||||
// Bad: list page reads source docs, then joins owner data per row
|
||||
@@ -243,11 +274,14 @@ const projects = await ctx.db
|
||||
|
||||
## 4. Isolate Frequently-Updated Fields
|
||||
|
||||
Convex already no-ops unchanged writes. The invalidation problem here is real writes hitting documents that many queries subscribe to.
|
||||
Convex already no-ops unchanged writes. The invalidation problem here is real
|
||||
writes hitting documents that many queries subscribe to.
|
||||
|
||||
Move high-churn fields like `lastSeen`, counters, presence, or ephemeral status off widely-read documents when most readers do not need them.
|
||||
Move high-churn fields like `lastSeen`, counters, presence, or ephemeral status
|
||||
off widely-read documents when most readers do not need them.
|
||||
|
||||
Apply this across sibling writers too. Splitting one write path does not help much if three other mutations still update the same widely-read document.
|
||||
Apply this across sibling writers too. Splitting one write path does not help
|
||||
much if three other mutations still update the same widely-read document.
|
||||
|
||||
```ts
|
||||
// Bad: every presence heartbeat invalidates subscribers to the whole profile
|
||||
@@ -290,7 +324,9 @@ Prefer:
|
||||
- local state for pagination
|
||||
- caching where appropriate
|
||||
|
||||
Do not treat subscriptions as automatically wrong here. Prefer point-in-time reads only when the product does not need live freshness and the reactive cost is material. See `subscription-cost.md` for detailed patterns.
|
||||
Do not treat subscriptions as automatically wrong here. Prefer point-in-time
|
||||
reads only when the product does not need live freshness and the reactive cost
|
||||
is material. See `subscription-cost.md` for detailed patterns.
|
||||
|
||||
### High-read, high-write
|
||||
|
||||
@@ -306,18 +342,21 @@ Reactive queries may be worth the ongoing cost.
|
||||
|
||||
### Reactive queries
|
||||
|
||||
Every `ctx.db.get()` and `ctx.db.query()` contributes to the invalidation set for the query.
|
||||
Every `ctx.db.get()` and `ctx.db.query()` contributes to the invalidation set
|
||||
for the query.
|
||||
|
||||
On the client:
|
||||
|
||||
- `useQuery` creates a live subscription
|
||||
- `usePaginatedQuery` creates a live subscription per page
|
||||
|
||||
For low-freshness flows, consider a point-in-time read instead of a live subscription only when the product does not need updates pushed automatically.
|
||||
For low-freshness flows, consider a point-in-time read instead of a live
|
||||
subscription only when the product does not need updates pushed automatically.
|
||||
|
||||
### Point-in-time reads
|
||||
|
||||
Framework helpers, server-rendered fetches, or one-shot client reads can avoid ongoing subscription cost when live updates are not useful.
|
||||
Framework helpers, server-rendered fetches, or one-shot client reads can avoid
|
||||
ongoing subscription cost when live updates are not useful.
|
||||
|
||||
Use them for:
|
||||
|
||||
@@ -328,7 +367,8 @@ Use them for:
|
||||
|
||||
### Triggers and fan-out
|
||||
|
||||
Triggers fire on every write, including writes that did not materially change the document.
|
||||
Triggers fire on every write, including writes that did not materially change
|
||||
the document.
|
||||
|
||||
When a write exists only to keep derived state in sync:
|
||||
|
||||
@@ -349,7 +389,8 @@ for global stats that do not need live updates every second.
|
||||
|
||||
### Backfills
|
||||
|
||||
For larger backfills, use cursor-based, self-scheduling `internalMutation` jobs or the migrations component.
|
||||
For larger backfills, use cursor-based, self-scheduling `internalMutation` jobs
|
||||
or the migrations component.
|
||||
|
||||
Deploy code that can handle both states before running the backfill.
|
||||
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
# OCC Conflict Resolution
|
||||
|
||||
Use these rules when insights, logs, or dashboard health show OCC (Optimistic Concurrency Control) conflicts, mutation retries, or write contention on hot tables.
|
||||
Use these rules when insights, logs, or dashboard health show OCC (Optimistic
|
||||
Concurrency Control) conflicts, mutation retries, or write contention on hot
|
||||
tables.
|
||||
|
||||
## Core Principle
|
||||
|
||||
Convex uses optimistic concurrency control. When two transactions read or write overlapping data, one succeeds and the other retries automatically. High contention means wasted work and increased latency.
|
||||
Convex uses optimistic concurrency control. When two transactions read or write
|
||||
overlapping data, one succeeds and the other retries automatically. High
|
||||
contention means wasted work and increased latency.
|
||||
|
||||
## Symptoms
|
||||
|
||||
@@ -17,21 +21,31 @@ Convex uses optimistic concurrency control. When two transactions read or write
|
||||
|
||||
### Hot documents
|
||||
|
||||
Multiple mutations writing to the same document concurrently. Classic examples: a global counter, a shared settings row, or a "last updated" timestamp on a parent record.
|
||||
Multiple mutations writing to the same document concurrently. Classic examples:
|
||||
a global counter, a shared settings row, or a "last updated" timestamp on a
|
||||
parent record.
|
||||
|
||||
### Broad read sets causing false conflicts
|
||||
|
||||
A query that scans a large table range creates a broad read set. If any write touches that range, the query's transaction conflicts even if the specific document the query cared about was not modified.
|
||||
A query that scans a large table range creates a broad read set. If any write
|
||||
touches that range, the query's transaction conflicts even if the specific
|
||||
document the query cared about was not modified.
|
||||
|
||||
### Fan-out from triggers or cascading writes
|
||||
|
||||
A single user action triggers multiple mutations that all touch related documents. Each mutation competes with the others.
|
||||
A single user action triggers multiple mutations that all touch related
|
||||
documents. Each mutation competes with the others.
|
||||
|
||||
Database triggers (e.g. from `convex-helpers`) run inside the same transaction as the mutation that caused them. If a trigger does heavy work, reads extra tables, or writes to many documents, it extends the transaction's read/write set and increases the window for conflicts. Keep trigger logic minimal, or move expensive derived work to a scheduled function.
|
||||
Database triggers (e.g. from `convex-helpers`) run inside the same transaction
|
||||
as the mutation that caused them. If a trigger does heavy work, reads extra
|
||||
tables, or writes to many documents, it extends the transaction's read/write set
|
||||
and increases the window for conflicts. Keep trigger logic minimal, or move
|
||||
expensive derived work to a scheduled function.
|
||||
|
||||
### Write-then-read chains
|
||||
|
||||
A mutation writes a document, then a reactive query re-reads it, then another mutation writes it again. Under load, these chains stack up.
|
||||
A mutation writes a document, then a reactive query re-reads it, then another
|
||||
mutation writes it again. Under load, these chains stack up.
|
||||
|
||||
## Fix Order
|
||||
|
||||
@@ -75,7 +89,9 @@ Aggregate the shards in a query or scheduled job when you need the total.
|
||||
|
||||
### 3. Move non-critical work to scheduled functions
|
||||
|
||||
If a mutation does primary work plus secondary bookkeeping (analytics, non-critical notifications, cache warming), the bookkeeping extends the transaction's lifetime and read/write set.
|
||||
If a mutation does primary work plus secondary bookkeeping (analytics,
|
||||
non-critical notifications, cache warming), the bookkeeping extends the
|
||||
transaction's lifetime and read/write set.
|
||||
|
||||
```ts
|
||||
// Bad: canonical write and derived work happen in the same transaction
|
||||
@@ -98,13 +114,20 @@ await ctx.scheduler.runAfter(0, internal.users.recordNameChangeAnalytics, {
|
||||
|
||||
### 4. Combine competing writes
|
||||
|
||||
If two mutations must update the same document atomically, consider whether they can be combined into a single mutation call from the client, reducing round trips and conflict windows.
|
||||
If two mutations must update the same document atomically, consider whether they
|
||||
can be combined into a single mutation call from the client, reducing round
|
||||
trips and conflict windows.
|
||||
|
||||
Do not introduce artificial locks or queues unless the above steps have been tried first.
|
||||
Do not introduce artificial locks or queues unless the above steps have been
|
||||
tried first.
|
||||
|
||||
## Related: Invalidation Scope
|
||||
|
||||
Splitting hot documents also reduces subscription invalidation, not just OCC contention. If a document is written frequently and read by many queries, those queries re-run on every write even when the fields they care about have not changed. See `subscription-cost.md` section 4 ("Isolate frequently-updated fields") for that pattern.
|
||||
Splitting hot documents also reduces subscription invalidation, not just OCC
|
||||
contention. If a document is written frequently and read by many queries, those
|
||||
queries re-run on every write even when the fields they care about have not
|
||||
changed. See `subscription-cost.md` section 4 ("Isolate frequently-updated
|
||||
fields") for that pattern.
|
||||
|
||||
## Verification
|
||||
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
# Subscription Cost
|
||||
|
||||
Use these rules when the problem is too many reactive subscriptions, queries invalidating too frequently, or React components re-rendering excessively due to Convex state changes.
|
||||
Use these rules when the problem is too many reactive subscriptions, queries
|
||||
invalidating too frequently, or React components re-rendering excessively due to
|
||||
Convex state changes.
|
||||
|
||||
## Core Principle
|
||||
|
||||
Every `useQuery` and `usePaginatedQuery` call creates a live subscription. The server tracks the query's read set and re-executes the query whenever any document in that read set changes. Subscription cost scales with:
|
||||
Every `useQuery` and `usePaginatedQuery` call creates a live subscription. The
|
||||
server tracks the query's read set and re-executes the query whenever any
|
||||
document in that read set changes. Subscription cost scales with:
|
||||
|
||||
`subscriptions x invalidation_frequency x query_cost`
|
||||
|
||||
Subscriptions are not inherently bad. Convex reactivity is often the right default. The goal is to reduce unnecessary invalidation work, not to eliminate subscriptions on principle.
|
||||
Subscriptions are not inherently bad. Convex reactivity is often the right
|
||||
default. The goal is to reduce unnecessary invalidation work, not to eliminate
|
||||
subscriptions on principle.
|
||||
|
||||
## Symptoms
|
||||
|
||||
@@ -22,35 +28,47 @@ Subscriptions are not inherently bad. Convex reactivity is often the right defau
|
||||
|
||||
### Reactive queries on low-freshness flows
|
||||
|
||||
Some user flows are read-heavy and do not need live updates every time the underlying data changes. In those cases, ongoing subscriptions may cost more than they are worth.
|
||||
Some user flows are read-heavy and do not need live updates every time the
|
||||
underlying data changes. In those cases, ongoing subscriptions may cost more
|
||||
than they are worth.
|
||||
|
||||
### Overly broad queries
|
||||
|
||||
A query that returns a large result set invalidates whenever any document in that set changes. The broader the query, the more frequent the invalidation.
|
||||
A query that returns a large result set invalidates whenever any document in
|
||||
that set changes. The broader the query, the more frequent the invalidation.
|
||||
|
||||
### Too many subscriptions per page
|
||||
|
||||
A page with 20 list items, each running its own `useQuery` to fetch related data, creates 20+ subscriptions per visitor.
|
||||
A page with 20 list items, each running its own `useQuery` to fetch related
|
||||
data, creates 20+ subscriptions per visitor.
|
||||
|
||||
### Paginated queries keeping all pages live
|
||||
|
||||
`usePaginatedQuery` with `loadMore` keeps every loaded page subscribed. On a page where a user has scrolled through 10 pages, all 10 stay reactive.
|
||||
`usePaginatedQuery` with `loadMore` keeps every loaded page subscribed. On a
|
||||
page where a user has scrolled through 10 pages, all 10 stay reactive.
|
||||
|
||||
### Frequently-updated fields on widely-read documents
|
||||
|
||||
A document that many queries touch gets a frequently-updated field (like `lastSeen`, `lastActiveAt`, or a counter). Every write to that field invalidates every subscription that reads the document, even if those subscriptions never use the field. This is different from OCC conflicts (see `occ-conflicts.md`), which are write-vs-write contention. This is write-vs-subscription: the write succeeds fine, but it forces hundreds of queries to re-run for no reason.
|
||||
A document that many queries touch gets a frequently-updated field (like
|
||||
`lastSeen`, `lastActiveAt`, or a counter). Every write to that field invalidates
|
||||
every subscription that reads the document, even if those subscriptions never
|
||||
use the field. This is different from OCC conflicts (see `occ-conflicts.md`),
|
||||
which are write-vs-write contention. This is write-vs-subscription: the write
|
||||
succeeds fine, but it forces hundreds of queries to re-run for no reason.
|
||||
|
||||
## Fix Order
|
||||
|
||||
### 1. Use point-in-time reads when live updates are not valuable
|
||||
|
||||
Keep `useQuery` and `usePaginatedQuery` by default when the product benefits from fresh live data.
|
||||
Keep `useQuery` and `usePaginatedQuery` by default when the product benefits
|
||||
from fresh live data.
|
||||
|
||||
Consider a point-in-time read instead when all of these are true:
|
||||
|
||||
- the flow is high-read
|
||||
- the underlying data changes less often than users need to see
|
||||
- explicit refresh, periodic refresh, or a fresh read on navigation is acceptable
|
||||
- explicit refresh, periodic refresh, or a fresh read on navigation is
|
||||
acceptable
|
||||
|
||||
Possible implementations depend on environment:
|
||||
|
||||
@@ -99,7 +117,8 @@ Keep reactive for:
|
||||
|
||||
### 2. Batch related data into fewer queries
|
||||
|
||||
Instead of N components each fetching their own related data, fetch it in a single query.
|
||||
Instead of N components each fetching their own related data, fetch it in a
|
||||
single query.
|
||||
|
||||
```ts
|
||||
// Bad: each card fetches its own author
|
||||
@@ -119,13 +138,17 @@ function ProjectList() {
|
||||
}
|
||||
```
|
||||
|
||||
This can use denormalized fields or server-side joins in the query handler. Either way, it is one subscription instead of N.
|
||||
This can use denormalized fields or server-side joins in the query handler.
|
||||
Either way, it is one subscription instead of N.
|
||||
|
||||
This is not automatically better. If the combined query becomes much broader and invalidates much more often, several narrower subscriptions may be the better tradeoff. Optimize for total invalidation cost, not raw subscription count.
|
||||
This is not automatically better. If the combined query becomes much broader and
|
||||
invalidates much more often, several narrower subscriptions may be the better
|
||||
tradeoff. Optimize for total invalidation cost, not raw subscription count.
|
||||
|
||||
### 3. Use skip to avoid unnecessary subscriptions
|
||||
|
||||
The `"skip"` value prevents a subscription from being created when the arguments are not ready.
|
||||
The `"skip"` value prevents a subscription from being created when the arguments
|
||||
are not ready.
|
||||
|
||||
```ts
|
||||
// Bad: subscribes with undefined args, wastes a subscription slot
|
||||
@@ -134,15 +157,14 @@ const profile = useQuery(api.users.getProfile, { userId: selectedId! });
|
||||
|
||||
```ts
|
||||
// Good: skip when there is nothing to fetch
|
||||
const profile = useQuery(
|
||||
api.users.getProfile,
|
||||
selectedId ? { userId: selectedId } : "skip",
|
||||
);
|
||||
const profile = useQuery(api.users.getProfile, selectedId ? { userId: selectedId } : "skip");
|
||||
```
|
||||
|
||||
### 4. Isolate frequently-updated fields into separate documents
|
||||
|
||||
If a document is widely read but has a field that changes often, move that field to a separate document. Queries that do not need the field will no longer be invalidated by its writes.
|
||||
If a document is widely read but has a field that changes often, move that field
|
||||
to a separate document. Queries that do not need the field will no longer be
|
||||
invalidated by its writes.
|
||||
|
||||
```ts
|
||||
// Bad: lastSeen lives on the user doc, every heartbeat invalidates
|
||||
@@ -167,17 +189,31 @@ const heartbeats = defineTable({
|
||||
});
|
||||
```
|
||||
|
||||
Queries that only need `name` and `email` no longer re-run on every heartbeat. Queries that actually need online status fetch the heartbeat document explicitly.
|
||||
Queries that only need `name` and `email` no longer re-run on every heartbeat.
|
||||
Queries that actually need online status fetch the heartbeat document
|
||||
explicitly.
|
||||
|
||||
For an even further optimization, if you only need a coarse online/offline boolean rather than the exact `lastSeen` timestamp, add a separate presence document with an `isOnline` flag. Update it immediately when a user comes online, and use a cron to batch-mark users offline when their heartbeat goes stale. This way the presence query only invalidates when online status actually changes, not on every heartbeat.
|
||||
For an even further optimization, if you only need a coarse online/offline
|
||||
boolean rather than the exact `lastSeen` timestamp, add a separate presence
|
||||
document with an `isOnline` flag. Update it immediately when a user comes
|
||||
online, and use a cron to batch-mark users offline when their heartbeat goes
|
||||
stale. This way the presence query only invalidates when online status actually
|
||||
changes, not on every heartbeat.
|
||||
|
||||
### 5. Use the aggregate component for counts and sums
|
||||
|
||||
Reactive global counts (`SELECT COUNT(*)` equivalent) invalidate on every insert or delete to the table. The [`@convex-dev/aggregate`](https://www.npmjs.com/package/@convex-dev/aggregate) component maintains denormalized COUNT, SUM, and MAX values efficiently so you do not need a reactive query scanning the full table.
|
||||
Reactive global counts (`SELECT COUNT(*)` equivalent) invalidate on every insert
|
||||
or delete to the table. The
|
||||
[`@convex-dev/aggregate`](https://www.npmjs.com/package/@convex-dev/aggregate)
|
||||
component maintains denormalized COUNT, SUM, and MAX values efficiently so you
|
||||
do not need a reactive query scanning the full table.
|
||||
|
||||
Use it for leaderboards, totals, "X items" badges, or any stat that would otherwise require scanning many rows reactively.
|
||||
Use it for leaderboards, totals, "X items" badges, or any stat that would
|
||||
otherwise require scanning many rows reactively.
|
||||
|
||||
If the aggregate component is not appropriate, prefer point-in-time reads for global stats, or precomputed summary rows updated by a cron or trigger, over reactive queries that scan large tables.
|
||||
If the aggregate component is not appropriate, prefer point-in-time reads for
|
||||
global stats, or precomputed summary rows updated by a cron or trigger, over
|
||||
reactive queries that scan large tables.
|
||||
|
||||
### 6. Narrow query read sets
|
||||
|
||||
@@ -205,7 +241,9 @@ Writes to fields not in the digest table do not invalidate the digest query.
|
||||
|
||||
### 7. Remove `Date.now()` from queries
|
||||
|
||||
Using `Date.now()` inside a query defeats Convex's query cache. The cache is invalidated frequently to avoid showing stale time-dependent results, which increases database work even when the underlying data has not changed.
|
||||
Using `Date.now()` inside a query defeats Convex's query cache. The cache is
|
||||
invalidated frequently to avoid showing stale time-dependent results, which
|
||||
increases database work even when the underlying data has not changed.
|
||||
|
||||
```ts
|
||||
// Bad: Date.now() defeats query caching and causes frequent re-evaluation
|
||||
@@ -223,19 +261,25 @@ const releasedPosts = await ctx.db
|
||||
.take(100);
|
||||
```
|
||||
|
||||
If the query must compare against a time value, pass it as an explicit argument from the client and round it to a coarse interval (e.g. the most recent minute) so requests within that window share the same cache entry.
|
||||
If the query must compare against a time value, pass it as an explicit argument
|
||||
from the client and round it to a coarse interval (e.g. the most recent minute)
|
||||
so requests within that window share the same cache entry.
|
||||
|
||||
### 8. Consider pagination strategy
|
||||
|
||||
For long lists where users scroll through many pages:
|
||||
|
||||
- If the data does not need live updates, use point-in-time fetching with manual "load more"
|
||||
- If it does need live updates, accept the subscription cost but limit the number of loaded pages
|
||||
- If the data does not need live updates, use point-in-time fetching with manual
|
||||
"load more"
|
||||
- If it does need live updates, accept the subscription cost but limit the
|
||||
number of loaded pages
|
||||
- Consider whether older pages can be unloaded as the user scrolls forward
|
||||
|
||||
### 9. Separate backend cost from UI churn
|
||||
|
||||
If the main problem is loading flash or UI churn when query arguments change, stabilizing the reactive UI behavior may be better than replacing reactivity altogether.
|
||||
If the main problem is loading flash or UI churn when query arguments change,
|
||||
stabilizing the reactive UI behavior may be better than replacing reactivity
|
||||
altogether.
|
||||
|
||||
Treat this as a UX problem first when:
|
||||
|
||||
@@ -248,5 +292,6 @@ Treat this as a UX problem first when:
|
||||
1. Subscription count in dashboard is lower for the affected pages
|
||||
2. UI responsiveness has improved
|
||||
3. React profiling shows fewer unnecessary re-renders
|
||||
4. Surfaces that do not need live updates are not paying for persistent subscriptions unnecessarily
|
||||
4. Surfaces that do not need live updates are not paying for persistent
|
||||
subscriptions unnecessarily
|
||||
5. Sibling pages with similar patterns were updated consistently
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: convex-quickstart
|
||||
description: Creates or adds Convex to an app. Use for new Convex projects, npm create convex@latest, frontend setup, env vars, or the first npx convex dev run.
|
||||
description: Creates or adds Convex to an app. Use for new Convex projects, npm create
|
||||
convex@latest, frontend setup, env vars, or the first npx convex dev run.
|
||||
---
|
||||
|
||||
# Convex Quickstart
|
||||
@@ -15,8 +16,10 @@ Set up a working Convex project as fast as possible.
|
||||
|
||||
## When Not to Use
|
||||
|
||||
- The project already has Convex installed and `convex/` exists - just start building
|
||||
- You only need to add auth to an existing Convex app - use the `convex-setup-auth` skill
|
||||
- The project already has Convex installed and `convex/` exists - just start
|
||||
building
|
||||
- You only need to add auth to an existing Convex app - use the
|
||||
`convex-setup-auth` skill
|
||||
|
||||
## Workflow
|
||||
|
||||
@@ -28,7 +31,8 @@ Set up a working Convex project as fast as possible.
|
||||
|
||||
## Path 1: New Project (Recommended)
|
||||
|
||||
Use the official scaffolding tool. It creates a complete project with the frontend framework, Convex backend, and all config wired together.
|
||||
Use the official scaffolding tool. It creates a complete project with the
|
||||
frontend framework, Convex backend, and all config wired together.
|
||||
|
||||
### Pick a template
|
||||
|
||||
@@ -42,7 +46,8 @@ Use the official scaffolding tool. It creates a complete project with the fronte
|
||||
| `nextjs-lucia-shadcn` | Next.js + Lucia auth + shadcn/ui |
|
||||
| `bare` | Convex backend only, no frontend |
|
||||
|
||||
If the user has not specified a preference, default to `react-vite-shadcn` for simple apps or `nextjs-shadcn` for apps that need SSR or API routes.
|
||||
If the user has not specified a preference, default to `react-vite-shadcn` for
|
||||
simple apps or `nextjs-shadcn` for apps that need SSR or API routes.
|
||||
|
||||
You can also use any GitHub repo as a template:
|
||||
|
||||
@@ -61,7 +66,8 @@ cd my-app
|
||||
npm install
|
||||
```
|
||||
|
||||
The scaffolding tool creates files but does not run `npm install`, so you must run it yourself.
|
||||
The scaffolding tool creates files but does not run `npm install`, so you must
|
||||
run it yourself.
|
||||
|
||||
To scaffold in the current directory (if it is empty):
|
||||
|
||||
@@ -72,20 +78,28 @@ npm install
|
||||
|
||||
### Start the dev loop
|
||||
|
||||
`npx convex dev` is a long-running watcher process that syncs backend code to a Convex deployment on every save. It also requires authentication on first run (browser-based OAuth). Both of these make it unsuitable for an agent to run directly.
|
||||
`npx convex dev` is a long-running watcher process that syncs backend code to a
|
||||
Convex deployment on every save. It also requires authentication on first run
|
||||
(browser-based OAuth). Both of these make it unsuitable for an agent to run
|
||||
directly.
|
||||
|
||||
**Ask the user to run this themselves:**
|
||||
|
||||
Tell the user to run `npx convex dev` in their terminal. On first run it will prompt them to log in or develop anonymously. Once running, it will:
|
||||
Tell the user to run `npx convex dev` in their terminal. On first run it will
|
||||
prompt them to log in or develop anonymously. Once running, it will:
|
||||
|
||||
- Create a Convex project and dev deployment
|
||||
- Write the deployment URL to `.env.local`
|
||||
- Create the `convex/` directory with generated types
|
||||
- Watch for changes and sync continuously
|
||||
|
||||
The user should keep `npx convex dev` running in the background while you work on code. The watcher will automatically pick up any files you create or edit in `convex/`.
|
||||
The user should keep `npx convex dev` running in the background while you work
|
||||
on code. The watcher will automatically pick up any files you create or edit in
|
||||
`convex/`.
|
||||
|
||||
**Exception - cloud or headless agents:** Environments that cannot open a browser for interactive login should use Agent Mode (see below) to run anonymously without user interaction.
|
||||
**Exception - cloud or headless agents:** Environments that cannot open a
|
||||
browser for interactive login should use Agent Mode (see below) to run
|
||||
anonymously without user interaction.
|
||||
|
||||
### Start the frontend
|
||||
|
||||
@@ -122,7 +136,8 @@ Proceed to adding schema, functions, and UI.
|
||||
|
||||
## Path 2: Add Convex to an Existing App
|
||||
|
||||
Use this when the user already has a frontend project and wants to add Convex as the backend.
|
||||
Use this when the user already has a frontend project and wants to add Convex as
|
||||
the backend.
|
||||
|
||||
### Install
|
||||
|
||||
@@ -132,7 +147,10 @@ npm install convex
|
||||
|
||||
### Initialize and start dev loop
|
||||
|
||||
Ask the user to run `npx convex dev` in their terminal. This handles login, creates the `convex/` directory, writes the deployment URL to `.env.local`, and starts the file watcher. See the notes in Path 1 about why the agent should not run this directly.
|
||||
Ask the user to run `npx convex dev` in their terminal. This handles login,
|
||||
creates the `convex/` directory, writes the deployment URL to `.env.local`, and
|
||||
starts the file watcher. See the notes in Path 1 about why the agent should not
|
||||
run this directly.
|
||||
|
||||
### Wire up the provider
|
||||
|
||||
@@ -143,9 +161,7 @@ Create the `ConvexReactClient` at module scope, not inside a component:
|
||||
```tsx
|
||||
// Bad: re-creates the client on every render
|
||||
function App() {
|
||||
const convex = new ConvexReactClient(
|
||||
import.meta.env.VITE_CONVEX_URL as string,
|
||||
);
|
||||
const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string);
|
||||
return <ConvexProvider client={convex}>...</ConvexProvider>;
|
||||
}
|
||||
|
||||
@@ -196,11 +212,7 @@ export function ConvexClientProvider({ children }: { children: ReactNode }) {
|
||||
// app/layout.tsx
|
||||
import { ConvexClientProvider } from "./ConvexClientProvider";
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
@@ -213,7 +225,8 @@ export default function RootLayout({
|
||||
|
||||
#### Other frameworks
|
||||
|
||||
For Vue, Svelte, React Native, TanStack Start, Remix, and others, follow the matching quickstart guide:
|
||||
For Vue, Svelte, React Native, TanStack Start, Remix, and others, follow the
|
||||
matching quickstart guide:
|
||||
|
||||
- [Vue](https://docs.convex.dev/quickstart/vue)
|
||||
- [Svelte](https://docs.convex.dev/quickstart/svelte)
|
||||
@@ -237,7 +250,9 @@ The env var name depends on the framework:
|
||||
|
||||
## Agent Mode (Cloud and Headless Agents)
|
||||
|
||||
When running in a cloud or headless agent environment where interactive browser login is not possible, set `CONVEX_AGENT_MODE=anonymous` to use a local anonymous deployment.
|
||||
When running in a cloud or headless agent environment where interactive browser
|
||||
login is not possible, set `CONVEX_AGENT_MODE=anonymous` to use a local
|
||||
anonymous deployment.
|
||||
|
||||
Add `CONVEX_AGENT_MODE=anonymous` to `.env.local`, or set it inline:
|
||||
|
||||
@@ -245,7 +260,8 @@ Add `CONVEX_AGENT_MODE=anonymous` to `.env.local`, or set it inline:
|
||||
CONVEX_AGENT_MODE=anonymous npx convex dev
|
||||
```
|
||||
|
||||
This runs a local Convex backend on the VM without requiring authentication, and avoids conflicting with the user's personal dev deployment.
|
||||
This runs a local Convex backend on the VM without requiring authentication, and
|
||||
avoids conflicting with the user's personal dev deployment.
|
||||
|
||||
## Verify the Setup
|
||||
|
||||
@@ -257,7 +273,8 @@ After setup, confirm everything is working:
|
||||
|
||||
## Writing Your First Function
|
||||
|
||||
Once the project is set up, create a schema and a query to verify the full loop works.
|
||||
Once the project is set up, create a schema and a query to verify the full loop
|
||||
works.
|
||||
|
||||
`convex/schema.ts`:
|
||||
|
||||
@@ -294,7 +311,8 @@ export const create = mutation({
|
||||
});
|
||||
```
|
||||
|
||||
Use in a React component (adjust the import path based on your file location relative to `convex/`):
|
||||
Use in a React component (adjust the import path based on your file location
|
||||
relative to `convex/`):
|
||||
|
||||
```tsx
|
||||
import { useQuery, useMutation } from "convex/react";
|
||||
@@ -317,7 +335,8 @@ function Tasks() {
|
||||
|
||||
## Development vs Production
|
||||
|
||||
Always use `npx convex dev` during development. It runs against your personal dev deployment and syncs code on save.
|
||||
Always use `npx convex dev` during development. It runs against your personal
|
||||
dev deployment and syncs code on save.
|
||||
|
||||
When ready to ship, deploy to production:
|
||||
|
||||
@@ -325,21 +344,25 @@ When ready to ship, deploy to production:
|
||||
npx convex deploy
|
||||
```
|
||||
|
||||
This pushes to the production deployment, which is separate from dev. Do not use `deploy` during development.
|
||||
This pushes to the production deployment, which is separate from dev. Do not use
|
||||
`deploy` during development.
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Add authentication: use the `convex-setup-auth` skill
|
||||
- Design your schema: see [Schema docs](https://docs.convex.dev/database/schemas)
|
||||
- Design your schema: see
|
||||
[Schema docs](https://docs.convex.dev/database/schemas)
|
||||
- Build components: use the `convex-create-component` skill
|
||||
- Plan a migration: use the `convex-migration-helper` skill
|
||||
- Add file storage: see [File Storage docs](https://docs.convex.dev/file-storage)
|
||||
- Add file storage: see
|
||||
[File Storage docs](https://docs.convex.dev/file-storage)
|
||||
- Set up cron jobs: see [Scheduling docs](https://docs.convex.dev/scheduling)
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Determined starting point: new project or existing app
|
||||
- [ ] If new project: scaffolded with `npm create convex@latest` using appropriate template
|
||||
- [ ] If new project: scaffolded with `npm create convex@latest` using
|
||||
appropriate template
|
||||
- [ ] If existing app: installed `convex` and wired up the provider
|
||||
- [ ] User has `npx convex dev` running and connected to a deployment
|
||||
- [ ] `convex/_generated/` directory exists with types
|
||||
|
||||
@@ -4,7 +4,9 @@ interface:
|
||||
icon_small: "./assets/icon.svg"
|
||||
icon_large: "./assets/icon.svg"
|
||||
brand_color: "#F97316"
|
||||
default_prompt: "Set up Convex for this project as fast as possible. First decide whether this is a new app or an existing app, then scaffold or integrate Convex and verify the setup works."
|
||||
default_prompt: "Set up Convex for this project as fast as possible. First decide whether
|
||||
this is a new app or an existing app, then scaffold or integrate Convex and
|
||||
verify the setup works."
|
||||
|
||||
policy:
|
||||
allow_implicit_invocation: true
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
name: convex-retention
|
||||
description: Use when adding or changing ClawHub Convex tables, TTL fields, cleanup crons, retention policy, auth/session cleanup, metric dedupe cleanup, or deprecated table removal
|
||||
---
|
||||
|
||||
# Convex Retention
|
||||
|
||||
## Overview
|
||||
|
||||
ClawHub retention is code-owned. Every current Convex table must be classified in
|
||||
`convex/lib/retentionPolicy.ts`, and ephemeral tables need an indexed, bounded cleanup path unless
|
||||
their lifecycle is handled by usage-time validation or a documented component.
|
||||
|
||||
## Checklist
|
||||
|
||||
- Read `convex/_generated/ai/guidelines.md` and the Convex ops rules in `AGENTS.md` first.
|
||||
- Add every new schema table to `RETENTION_POLICIES`; the `Record<TableNames, RetentionPolicy>` type
|
||||
is the enforcement gate.
|
||||
- For ephemeral tables, prefer an explicit expiration field plus index, then prune with `.withIndex()`
|
||||
and `.take(...)`.
|
||||
- For new generic TTL tables, prefer `expirationTime` to match Convex Auth. Keep existing `expiresAt`,
|
||||
`dayStart`, and `processedAt` fields unless that table already needs a real migration.
|
||||
- Use `RETENTION_STANDARD_BATCH_SIZE` for ordinary retention jobs. Keep incident-tested special cases,
|
||||
such as `skillStatEvents`, on their documented caps.
|
||||
- Cron jobs should schedule bounded cleanup entrypoints only. Large one-off production migrations or
|
||||
destructive backfills still start with `convex-migration-helper`.
|
||||
- Do not bulk-clear active auth state. Expired `authSessions` and `authRefreshTokens` are pruned by
|
||||
`convex/retention.ts`.
|
||||
|
||||
## Verification
|
||||
|
||||
- Add or update focused tests for policy classification and cleanup behavior.
|
||||
- Run the focused Vitest slice for touched cleanup modules.
|
||||
- Run `bunx convex codegen` after schema/API changes.
|
||||
- Run a real Convex runtime check such as `bunx convex dev --once --typecheck=disable`.
|
||||
@@ -1,25 +1,29 @@
|
||||
---
|
||||
name: convex-setup-auth
|
||||
description: Sets up Convex auth, identity mapping, and access control. Use for login, auth providers, users tables, protected functions, or roles in a Convex app.
|
||||
description: Sets up Convex auth, identity mapping, and access control. Use for login, auth
|
||||
providers, users tables, protected functions, or roles in a Convex app.
|
||||
---
|
||||
|
||||
# Convex Authentication Setup
|
||||
|
||||
Implement secure authentication in Convex with user management and access control.
|
||||
Implement secure authentication in Convex with user management and access
|
||||
control.
|
||||
|
||||
## When to Use
|
||||
|
||||
- Setting up authentication for the first time
|
||||
- Implementing user management (users table, identity mapping)
|
||||
- Creating authentication helper functions
|
||||
- Setting up auth providers (Convex Auth, Clerk, WorkOS AuthKit, Auth0, custom JWT)
|
||||
- Setting up auth providers (Convex Auth, Clerk, WorkOS AuthKit, Auth0, custom
|
||||
JWT)
|
||||
|
||||
## When Not to Use
|
||||
|
||||
- Auth for a non-Convex backend
|
||||
- Pure OAuth/OIDC documentation without a Convex implementation
|
||||
- Debugging unrelated bugs that happen to surface near auth code
|
||||
- The auth provider is already fully configured and the user only needs a one-line fix
|
||||
- The auth provider is already fully configured and the user only needs a
|
||||
one-line fix
|
||||
|
||||
## First Step: Choose the Auth Provider
|
||||
|
||||
@@ -27,34 +31,49 @@ Convex supports multiple authentication approaches. Do not assume a provider.
|
||||
|
||||
Before writing setup code:
|
||||
|
||||
1. Ask the user which auth solution they want, unless the repository already makes it obvious
|
||||
2. If the repo already uses a provider, continue with that provider unless the user wants to switch
|
||||
3. If the user has not chosen a provider and the repo does not make it obvious, ask before proceeding
|
||||
1. Ask the user which auth solution they want, unless the repository already
|
||||
makes it obvious
|
||||
2. If the repo already uses a provider, continue with that provider unless the
|
||||
user wants to switch
|
||||
3. If the user has not chosen a provider and the repo does not make it obvious,
|
||||
ask before proceeding
|
||||
|
||||
Common options:
|
||||
|
||||
- [Convex Auth](https://docs.convex.dev/auth/convex-auth) - good default when the user wants auth handled directly in Convex
|
||||
- [Clerk](https://docs.convex.dev/auth/clerk) - use when the app already uses Clerk or the user wants Clerk's hosted auth features
|
||||
- [WorkOS AuthKit](https://docs.convex.dev/auth/authkit/) - use when the app already uses WorkOS or the user wants AuthKit specifically
|
||||
- [Auth0](https://docs.convex.dev/auth/auth0) - use when the app already uses Auth0
|
||||
- Custom JWT provider - use when integrating an existing auth system not covered above
|
||||
- [Convex Auth](https://docs.convex.dev/auth/convex-auth) - good default when
|
||||
the user wants auth handled directly in Convex
|
||||
- [Clerk](https://docs.convex.dev/auth/clerk) - use when the app already uses
|
||||
Clerk or the user wants Clerk's hosted auth features
|
||||
- [WorkOS AuthKit](https://docs.convex.dev/auth/authkit/) - use when the app
|
||||
already uses WorkOS or the user wants AuthKit specifically
|
||||
- [Auth0](https://docs.convex.dev/auth/auth0) - use when the app already uses
|
||||
Auth0
|
||||
- Custom JWT provider - use when integrating an existing auth system not covered
|
||||
above
|
||||
|
||||
Look for signals in the repo before asking:
|
||||
|
||||
- Dependencies such as `@clerk/*`, `@workos-inc/*`, `@auth0/*`, or Convex Auth packages
|
||||
- Existing files such as `convex/auth.config.ts`, auth middleware, provider wrappers, or login components
|
||||
- Dependencies such as `@clerk/*`, `@workos-inc/*`, `@auth0/*`, or Convex Auth
|
||||
packages
|
||||
- Existing files such as `convex/auth.config.ts`, auth middleware, provider
|
||||
wrappers, or login components
|
||||
- Environment variables that clearly point at a provider
|
||||
|
||||
## After Choosing a Provider
|
||||
|
||||
Read the provider's official guide and the matching local reference file:
|
||||
|
||||
- Convex Auth: [official docs](https://docs.convex.dev/auth/convex-auth), then `references/convex-auth.md`
|
||||
- Clerk: [official docs](https://docs.convex.dev/auth/clerk), then `references/clerk.md`
|
||||
- WorkOS AuthKit: [official docs](https://docs.convex.dev/auth/authkit/), then `references/workos-authkit.md`
|
||||
- Auth0: [official docs](https://docs.convex.dev/auth/auth0), then `references/auth0.md`
|
||||
- Convex Auth: [official docs](https://docs.convex.dev/auth/convex-auth), then
|
||||
`references/convex-auth.md`
|
||||
- Clerk: [official docs](https://docs.convex.dev/auth/clerk), then
|
||||
`references/clerk.md`
|
||||
- WorkOS AuthKit: [official docs](https://docs.convex.dev/auth/authkit/), then
|
||||
`references/workos-authkit.md`
|
||||
- Auth0: [official docs](https://docs.convex.dev/auth/auth0), then
|
||||
`references/auth0.md`
|
||||
|
||||
The local reference files contain the concrete workflow, expected files and env vars, gotchas, and validation checks.
|
||||
The local reference files contain the concrete workflow, expected files and env
|
||||
vars, gotchas, and validation checks.
|
||||
|
||||
Use those sources for:
|
||||
|
||||
@@ -67,15 +86,25 @@ Use those sources for:
|
||||
|
||||
For shared auth behavior, use the official Convex docs as the source of truth:
|
||||
|
||||
- [Auth in Functions](https://docs.convex.dev/auth/functions-auth) for `ctx.auth.getUserIdentity()`
|
||||
- [Storing Users in the Convex Database](https://docs.convex.dev/auth/database-auth) for optional app-level user storage
|
||||
- [Authentication](https://docs.convex.dev/auth) for general auth and authorization guidance
|
||||
- [Convex Auth Authorization](https://labs.convex.dev/auth/authz) when the provider is Convex Auth
|
||||
- [Auth in Functions](https://docs.convex.dev/auth/functions-auth) for
|
||||
`ctx.auth.getUserIdentity()`
|
||||
- [Storing Users in the Convex Database](https://docs.convex.dev/auth/database-auth)
|
||||
for optional app-level user storage
|
||||
- [Authentication](https://docs.convex.dev/auth) for general auth and
|
||||
authorization guidance
|
||||
- [Convex Auth Authorization](https://labs.convex.dev/auth/authz) when the
|
||||
provider is Convex Auth
|
||||
|
||||
Prefer official docs over recalled steps, because provider CLIs and Convex Auth internals change between versions. Inventing setup from memory risks outdated patterns.
|
||||
For third-party providers, only add app-level user storage if the app actually needs user documents in Convex. Not every app needs a `users` table.
|
||||
For Convex Auth, follow the Convex Auth docs and built-in auth tables rather than adding a parallel `users` table plus `storeUser` flow, because Convex Auth already manages user records internally.
|
||||
After running provider initialization commands, verify generated files and complete the post-init wiring steps the provider reference calls out. Initialization commands rarely finish the entire integration.
|
||||
Prefer official docs over recalled steps, because provider CLIs and Convex Auth
|
||||
internals change between versions. Inventing setup from memory risks outdated
|
||||
patterns. For third-party providers, only add app-level user storage if the app
|
||||
actually needs user documents in Convex. Not every app needs a `users` table.
|
||||
For Convex Auth, follow the Convex Auth docs and built-in auth tables rather
|
||||
than adding a parallel `users` table plus `storeUser` flow, because Convex Auth
|
||||
already manages user records internally. After running provider initialization
|
||||
commands, verify generated files and complete the post-init wiring steps the
|
||||
provider reference calls out. Initialization commands rarely finish the entire
|
||||
integration.
|
||||
|
||||
## Core Pattern: Protecting Backend Functions
|
||||
|
||||
@@ -101,9 +130,7 @@ export const getMyProfile = query({
|
||||
|
||||
return await ctx.db
|
||||
.query("users")
|
||||
.withIndex("by_tokenIdentifier", (q) =>
|
||||
q.eq("tokenIdentifier", identity.tokenIdentifier),
|
||||
)
|
||||
.withIndex("by_tokenIdentifier", (q) => q.eq("tokenIdentifier", identity.tokenIdentifier))
|
||||
.unique();
|
||||
},
|
||||
});
|
||||
@@ -115,15 +142,20 @@ export const getMyProfile = query({
|
||||
2. Ask whether the user wants local-only setup or production-ready setup now
|
||||
3. Read the matching provider reference file
|
||||
4. Follow the official provider docs for current setup details
|
||||
5. Follow the official Convex docs for shared backend auth behavior, user storage, and authorization patterns
|
||||
5. Follow the official Convex docs for shared backend auth behavior, user
|
||||
storage, and authorization patterns
|
||||
6. Only add app-level user storage if the docs and app requirements call for it
|
||||
7. Add authorization checks for ownership, roles, or team access only where the app needs them
|
||||
8. Verify login state, protected queries, environment variables, and production configuration if requested
|
||||
7. Add authorization checks for ownership, roles, or team access only where the
|
||||
app needs them
|
||||
8. Verify login state, protected queries, environment variables, and production
|
||||
configuration if requested
|
||||
|
||||
If the flow blocks on interactive provider or deployment setup, ask the user explicitly for the exact human step needed, then continue after they complete it.
|
||||
For UI-facing auth flows, offer to validate the real sign-up or sign-in flow after setup is done.
|
||||
If the environment has browser automation tools, you can use them.
|
||||
If it does not, give the user a short manual validation checklist instead.
|
||||
If the flow blocks on interactive provider or deployment setup, ask the user
|
||||
explicitly for the exact human step needed, then continue after they complete
|
||||
it. For UI-facing auth flows, offer to validate the real sign-up or sign-in flow
|
||||
after setup is done. If the environment has browser automation tools, you can
|
||||
use them. If it does not, give the user a short manual validation checklist
|
||||
instead.
|
||||
|
||||
## Reference Files
|
||||
|
||||
@@ -140,9 +172,11 @@ If it does not, give the user a short manual validation checklist instead.
|
||||
- [ ] Read the relevant provider reference file
|
||||
- [ ] Asked whether the user wants local-only setup or production-ready setup
|
||||
- [ ] Used the official provider docs for provider-specific wiring
|
||||
- [ ] Used the official Convex docs for shared auth behavior and authorization patterns
|
||||
- [ ] Used the official Convex docs for shared auth behavior and authorization
|
||||
patterns
|
||||
- [ ] Only added app-level user storage if the app actually needs it
|
||||
- [ ] Did not invent a cross-provider `users` table or `storeUser` flow for Convex Auth
|
||||
- [ ] Did not invent a cross-provider `users` table or `storeUser` flow for
|
||||
Convex Auth
|
||||
- [ ] Added authentication checks in protected backend functions
|
||||
- [ ] Added authorization checks where the app actually needs them
|
||||
- [ ] Clear error messages ("Not authenticated", "Unauthorized")
|
||||
|
||||
@@ -4,7 +4,9 @@ interface:
|
||||
icon_small: "./assets/icon.svg"
|
||||
icon_large: "./assets/icon.svg"
|
||||
brand_color: "#2563EB"
|
||||
default_prompt: "Set up authentication for this Convex app. Figure out the provider first, then wire up the user model, identity mapping, and access control with the smallest solid implementation."
|
||||
default_prompt: "Set up authentication for this Convex app. Figure out the provider first,
|
||||
then wire up the user model, identity mapping, and access control with the
|
||||
smallest solid implementation."
|
||||
|
||||
policy:
|
||||
allow_implicit_invocation: true
|
||||
|
||||
@@ -15,25 +15,35 @@ Use this when the app already uses Auth0 or the user wants Auth0 specifically.
|
||||
3. Ask whether the user wants local-only setup or production-ready setup now
|
||||
4. Read the official Convex and Auth0 guides before making changes
|
||||
5. Ask whether they want the fastest setup path by installing the Auth0 CLI
|
||||
6. If they agree, install the Auth0 CLI and do as much of the Auth0 app setup as possible through the CLI
|
||||
6. If they agree, install the Auth0 CLI and do as much of the Auth0 app setup as
|
||||
possible through the CLI
|
||||
7. If they do not want the CLI path, use the Auth0 dashboard path instead
|
||||
8. Complete the relevant Auth0 frontend quickstart if the app does not already have Auth0 wired up
|
||||
8. Complete the relevant Auth0 frontend quickstart if the app does not already
|
||||
have Auth0 wired up
|
||||
9. Configure `convex/auth.config.ts` with the Auth0 domain and client ID
|
||||
10. Set environment variables for local and production environments
|
||||
11. Wrap the app with `Auth0Provider` and `ConvexProviderWithAuth0`
|
||||
12. Gate Convex-backed UI with Convex auth state
|
||||
13. Try to verify Convex reports the user as authenticated after Auth0 login
|
||||
14. If the refresh-token path fails, stop improvising and send the user back to the official docs
|
||||
15. If the user wants production-ready setup, make sure the production Auth0 tenant and env vars are also covered
|
||||
14. If the refresh-token path fails, stop improvising and send the user back to
|
||||
the official docs
|
||||
15. If the user wants production-ready setup, make sure the production Auth0
|
||||
tenant and env vars are also covered
|
||||
|
||||
## What To Do
|
||||
|
||||
- Read the official Convex and Auth0 guide before writing setup code
|
||||
- Prefer the Auth0 CLI path for mechanical setup if the user is willing to install it, but do not present it as a fully validated end-to-end path yet
|
||||
- Ask the user directly: "The fastest path is to install the Auth0 CLI so I can do more of this for you. If you want, I can install it and then only ask you to log in when needed. Would you like me to do that?"
|
||||
- Make sure the app has already completed the relevant Auth0 quickstart for its frontend
|
||||
- Prefer the Auth0 CLI path for mechanical setup if the user is willing to
|
||||
install it, but do not present it as a fully validated end-to-end path yet
|
||||
- Ask the user directly: "The fastest path is to install the Auth0 CLI so I can
|
||||
do more of this for you. If you want, I can install it and then only ask you
|
||||
to log in when needed. Would you like me to do that?"
|
||||
- Make sure the app has already completed the relevant Auth0 quickstart for its
|
||||
frontend
|
||||
- Use the official examples for `Auth0Provider` and `ConvexProviderWithAuth0`
|
||||
- If the Auth0 login or refresh flow starts failing in a way that is not clearly explained by the docs, say that plainly and fall back to the official docs instead of pretending the flow is validated
|
||||
- If the Auth0 login or refresh flow starts failing in a way that is not clearly
|
||||
explained by the docs, say that plainly and fall back to the official docs
|
||||
instead of pretending the flow is validated
|
||||
|
||||
## Key Setup Areas
|
||||
|
||||
@@ -56,11 +66,15 @@ Use this when the app already uses Auth0 or the user wants Auth0 specifically.
|
||||
|
||||
## Concrete Steps
|
||||
|
||||
1. Start by reading `https://docs.convex.dev/auth/auth0` and the relevant Auth0 quickstart for the app's framework
|
||||
1. Start by reading `https://docs.convex.dev/auth/auth0` and the relevant Auth0
|
||||
quickstart for the app's framework
|
||||
2. Ask whether the user wants the Auth0 CLI path
|
||||
3. If yes, install Auth0 CLI and have the user authenticate it with `auth0 login`
|
||||
4. Use `auth0 apps create` with SPA settings, callback URL, logout URL, and web origins if creating a new app
|
||||
5. If not using the CLI path, complete the relevant Auth0 frontend quickstart and create the Auth0 app in the dashboard
|
||||
3. If yes, install Auth0 CLI and have the user authenticate it with
|
||||
`auth0 login`
|
||||
4. Use `auth0 apps create` with SPA settings, callback URL, logout URL, and web
|
||||
origins if creating a new app
|
||||
5. If not using the CLI path, complete the relevant Auth0 frontend quickstart
|
||||
and create the Auth0 app in the dashboard
|
||||
6. Get the Auth0 domain and client ID from the CLI output or the Auth0 dashboard
|
||||
7. Install the Auth0 SDK for the app's framework
|
||||
8. Create or update `convex/auth.config.ts` with the Auth0 domain and client ID
|
||||
@@ -69,31 +83,52 @@ Use this when the app already uses Auth0 or the user wants Auth0 specifically.
|
||||
11. Replace plain `ConvexProvider` wiring with `ConvexProviderWithAuth0`
|
||||
12. Run the normal Convex dev or deploy flow after backend config changes
|
||||
13. Try the official provider config shown in the Convex docs
|
||||
14. If login works but Convex auth or token refresh fails in a way you cannot clearly resolve, stop and tell the user to follow the official docs manually for now
|
||||
15. Only claim success if the user can sign in and Convex recognizes the authenticated session
|
||||
16. If the user wants production-ready setup, configure the production Auth0 tenant values and production environment variables too
|
||||
14. If login works but Convex auth or token refresh fails in a way you cannot
|
||||
clearly resolve, stop and tell the user to follow the official docs manually
|
||||
for now
|
||||
15. Only claim success if the user can sign in and Convex recognizes the
|
||||
authenticated session
|
||||
16. If the user wants production-ready setup, configure the production Auth0
|
||||
tenant values and production environment variables too
|
||||
|
||||
## Gotchas
|
||||
|
||||
- The Convex docs assume the Auth0 side is already set up, so do not skip the Auth0 quickstart if the app is starting from scratch
|
||||
- The Auth0 CLI is often the fastest path for a fresh setup, but it still requires the user to authenticate the CLI to their Auth0 tenant
|
||||
- If the user agrees to install the Auth0 CLI, do the mechanical setup yourself instead of bouncing them through the dashboard
|
||||
- If login succeeds but Convex still reports unauthenticated, double-check `convex/auth.config.ts` and whether the backend config was synced
|
||||
- We were able to automate Auth0 app creation and Convex config wiring, but we did not fully validate the refresh-token path end to end
|
||||
- In validation, the documented `useRefreshTokens={true}` and `cacheLocation="localstorage"` setup hit refresh-token failures, so do not present that path as settled
|
||||
- If you hit Auth0 errors like `Unknown or invalid refresh token`, do not keep inventing fixes indefinitely, send the user back to the official docs and explain that this path is still under investigation
|
||||
- Keep dev and prod tenants separate if the project uses different Auth0 environments
|
||||
- Do not confuse "Auth0 login works" with "Convex can validate the Auth0 token". Both need to work.
|
||||
- If the repo already uses Auth0, preserve existing redirect and tenant configuration unless the user asked to change it.
|
||||
- Do not assume the local Auth0 tenant settings match production. Verify the production domain, client ID, and callback URLs separately.
|
||||
- For local dev, make sure the Auth0 app settings match the app's real local port for callback URLs, logout URLs, and web origins
|
||||
- The Convex docs assume the Auth0 side is already set up, so do not skip the
|
||||
Auth0 quickstart if the app is starting from scratch
|
||||
- The Auth0 CLI is often the fastest path for a fresh setup, but it still
|
||||
requires the user to authenticate the CLI to their Auth0 tenant
|
||||
- If the user agrees to install the Auth0 CLI, do the mechanical setup yourself
|
||||
instead of bouncing them through the dashboard
|
||||
- If login succeeds but Convex still reports unauthenticated, double-check
|
||||
`convex/auth.config.ts` and whether the backend config was synced
|
||||
- We were able to automate Auth0 app creation and Convex config wiring, but we
|
||||
did not fully validate the refresh-token path end to end
|
||||
- In validation, the documented `useRefreshTokens={true}` and
|
||||
`cacheLocation="localstorage"` setup hit refresh-token failures, so do not
|
||||
present that path as settled
|
||||
- If you hit Auth0 errors like `Unknown or invalid refresh token`, do not keep
|
||||
inventing fixes indefinitely, send the user back to the official docs and
|
||||
explain that this path is still under investigation
|
||||
- Keep dev and prod tenants separate if the project uses different Auth0
|
||||
environments
|
||||
- Do not confuse "Auth0 login works" with "Convex can validate the Auth0 token".
|
||||
Both need to work.
|
||||
- If the repo already uses Auth0, preserve existing redirect and tenant
|
||||
configuration unless the user asked to change it.
|
||||
- Do not assume the local Auth0 tenant settings match production. Verify the
|
||||
production domain, client ID, and callback URLs separately.
|
||||
- For local dev, make sure the Auth0 app settings match the app's real local
|
||||
port for callback URLs, logout URLs, and web origins
|
||||
|
||||
## Production
|
||||
|
||||
- Ask whether the user wants dev-only setup or production-ready setup
|
||||
- If the answer is production-ready, make sure the production Auth0 tenant values, callback URLs, and Convex deployment config are all covered
|
||||
- Verify production environment variables and redirect settings before calling the task complete
|
||||
- Do not silently write a notes file into the repo by default. If the user wants rollout or handoff docs, create one explicitly.
|
||||
- If the answer is production-ready, make sure the production Auth0 tenant
|
||||
values, callback URLs, and Convex deployment config are all covered
|
||||
- Verify production environment variables and redirect settings before calling
|
||||
the task complete
|
||||
- Do not silently write a notes file into the repo by default. If the user wants
|
||||
rollout or handoff docs, create one explicitly.
|
||||
|
||||
## Validation
|
||||
|
||||
@@ -101,9 +136,13 @@ Use this when the app already uses Auth0 or the user wants Auth0 specifically.
|
||||
- Verify Convex-authenticated UI renders only after Convex auth state is ready
|
||||
- Verify protected Convex queries succeed after login
|
||||
- Verify `ctx.auth.getUserIdentity()` is non-null in protected backend functions
|
||||
- Verify the Auth0 app settings match the real local callback and logout URLs during development
|
||||
- If the Auth0 refresh-token path fails, mark the setup as not fully validated and direct the user to the official docs instead of claiming the skill completed successfully
|
||||
- If production-ready setup was requested, verify the production Auth0 configuration is also covered
|
||||
- Verify the Auth0 app settings match the real local callback and logout URLs
|
||||
during development
|
||||
- If the Auth0 refresh-token path fails, mark the setup as not fully validated
|
||||
and direct the user to the official docs instead of claiming the skill
|
||||
completed successfully
|
||||
- If production-ready setup was requested, verify the production Auth0
|
||||
configuration is also covered
|
||||
|
||||
## Checklist
|
||||
|
||||
@@ -112,5 +151,6 @@ Use this when the app already uses Auth0 or the user wants Auth0 specifically.
|
||||
- [ ] Complete the relevant Auth0 frontend setup
|
||||
- [ ] Configure `convex/auth.config.ts`
|
||||
- [ ] Set environment variables
|
||||
- [ ] Verify Convex authenticated state after login, or explicitly tell the user this path is still under investigation and send them to the official docs
|
||||
- [ ] Verify Convex authenticated state after login, or explicitly tell the user
|
||||
this path is still under investigation and send them to the official docs
|
||||
- [ ] If requested, configure the production deployment too
|
||||
|
||||
@@ -5,7 +5,8 @@ Official docs:
|
||||
- https://docs.convex.dev/auth/clerk
|
||||
- https://clerk.com/docs/guides/development/integrations/databases/convex
|
||||
|
||||
Use this when the app already uses Clerk or the user wants Clerk's hosted auth features.
|
||||
Use this when the app already uses Clerk or the user wants Clerk's hosted auth
|
||||
features.
|
||||
|
||||
## Workflow
|
||||
|
||||
@@ -20,15 +21,21 @@ Use this when the app already uses Clerk or the user wants Clerk's hosted auth f
|
||||
6. Follow the correct framework section in the official docs
|
||||
7. Complete the backend and client wiring
|
||||
8. Verify Convex reports the user as authenticated after login
|
||||
9. If the user wants production-ready setup, make sure the production Clerk config is also covered
|
||||
9. If the user wants production-ready setup, make sure the production Clerk
|
||||
config is also covered
|
||||
|
||||
## What To Do
|
||||
|
||||
- Read the official Convex and Clerk guide before writing setup code
|
||||
- If the user does not already have Clerk set up, send them to `https://dashboard.clerk.com/sign-up` to create an account and `https://dashboard.clerk.com/apps/new` to create an application
|
||||
- Send the user to `https://dashboard.clerk.com/apps/setup/convex` if the Convex integration is not already active
|
||||
- Match the guide to the app's framework, usually React, Next.js, or TanStack Start
|
||||
- Use the official examples for `ConvexProviderWithClerk`, `ClerkProvider`, and `useAuth`
|
||||
- If the user does not already have Clerk set up, send them to
|
||||
`https://dashboard.clerk.com/sign-up` to create an account and
|
||||
`https://dashboard.clerk.com/apps/new` to create an application
|
||||
- Send the user to `https://dashboard.clerk.com/apps/setup/convex` if the Convex
|
||||
integration is not already active
|
||||
- Match the guide to the app's framework, usually React, Next.js, or TanStack
|
||||
Start
|
||||
- Use the official examples for `ConvexProviderWithClerk`, `ClerkProvider`, and
|
||||
`useAuth`
|
||||
|
||||
## Key Setup Areas
|
||||
|
||||
@@ -36,7 +43,8 @@ Use this when the app already uses Clerk or the user wants Clerk's hosted auth f
|
||||
- configure `convex/auth.config.ts` with the Clerk issuer domain
|
||||
- set the required Clerk environment variables
|
||||
- wrap the app with `ClerkProvider` and `ConvexProviderWithClerk`
|
||||
- use Convex auth-aware UI patterns such as `Authenticated`, `Unauthenticated`, and `AuthLoading`
|
||||
- use Convex auth-aware UI patterns such as `Authenticated`, `Unauthenticated`,
|
||||
and `AuthLoading`
|
||||
|
||||
## Files and Env Vars To Expect
|
||||
|
||||
@@ -54,13 +62,16 @@ Use this when the app already uses Clerk or the user wants Clerk's hosted auth f
|
||||
- `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` for Next.js apps
|
||||
- `CLERK_SECRET_KEY` for Next.js server-side Clerk setup where required
|
||||
|
||||
`CLERK_JWT_ISSUER_DOMAIN` and `CLERK_FRONTEND_API_URL` refer to the same Clerk Frontend API URL value. Do not treat them as two different URLs.
|
||||
`CLERK_JWT_ISSUER_DOMAIN` and `CLERK_FRONTEND_API_URL` refer to the same Clerk
|
||||
Frontend API URL value. Do not treat them as two different URLs.
|
||||
|
||||
## Concrete Steps
|
||||
|
||||
1. If needed, create a Clerk account at `https://dashboard.clerk.com/sign-up`
|
||||
2. If needed, create a Clerk application at `https://dashboard.clerk.com/apps/new`
|
||||
3. Open `https://dashboard.clerk.com/last-active?path=api-keys` and copy the publishable key, plus the secret key for Next.js where needed
|
||||
2. If needed, create a Clerk application at
|
||||
`https://dashboard.clerk.com/apps/new`
|
||||
3. Open `https://dashboard.clerk.com/last-active?path=api-keys` and copy the
|
||||
publishable key, plus the secret key for Next.js where needed
|
||||
4. Open `https://dashboard.clerk.com/apps/setup/convex`
|
||||
5. Activate the Convex integration in Clerk if it is not already active
|
||||
6. Copy the Clerk Frontend API URL shown there
|
||||
@@ -72,35 +83,52 @@ Use this when the app already uses Clerk or the user wants Clerk's hosted auth f
|
||||
12. Wrap the app in `ClerkProvider`
|
||||
13. Use Convex auth helpers for authenticated rendering
|
||||
14. Run the normal Convex dev or deploy flow after updating backend auth config
|
||||
15. If the user wants production-ready setup, configure the production Clerk values and production issuer domain too
|
||||
15. If the user wants production-ready setup, configure the production Clerk
|
||||
values and production issuer domain too
|
||||
|
||||
## Gotchas
|
||||
|
||||
- Prefer `useConvexAuth()` over raw Clerk auth state when deciding whether Convex-authenticated UI can render
|
||||
- For Next.js, keep server and client boundaries in mind when creating the Convex provider wrapper
|
||||
- After changing `convex/auth.config.ts`, run the normal Convex dev or deploy flow so the backend picks up the new config
|
||||
- Do not stop at "Clerk login works". The important check is that Convex also sees the session and can authenticate requests.
|
||||
- If the repo already uses Clerk, preserve its existing auth flow unless the user asked to change it.
|
||||
- Do not assume the same Clerk values work for both dev and production. Check the production issuer domain and publishable key separately.
|
||||
- The Convex setup page is where you get the Clerk Frontend API URL for Convex. Keep using the Clerk API keys page for the publishable key and the secret key.
|
||||
- If Convex says no auth provider matched the token, first confirm the Clerk Convex integration was activated at `https://dashboard.clerk.com/apps/setup/convex`
|
||||
- After activating the Clerk Convex integration, sign out completely and sign back in before retesting. An old Clerk session can keep using a token that Convex rejects.
|
||||
- Prefer `useConvexAuth()` over raw Clerk auth state when deciding whether
|
||||
Convex-authenticated UI can render
|
||||
- For Next.js, keep server and client boundaries in mind when creating the
|
||||
Convex provider wrapper
|
||||
- After changing `convex/auth.config.ts`, run the normal Convex dev or deploy
|
||||
flow so the backend picks up the new config
|
||||
- Do not stop at "Clerk login works". The important check is that Convex also
|
||||
sees the session and can authenticate requests.
|
||||
- If the repo already uses Clerk, preserve its existing auth flow unless the
|
||||
user asked to change it.
|
||||
- Do not assume the same Clerk values work for both dev and production. Check
|
||||
the production issuer domain and publishable key separately.
|
||||
- The Convex setup page is where you get the Clerk Frontend API URL for Convex.
|
||||
Keep using the Clerk API keys page for the publishable key and the secret key.
|
||||
- If Convex says no auth provider matched the token, first confirm the Clerk
|
||||
Convex integration was activated at
|
||||
`https://dashboard.clerk.com/apps/setup/convex`
|
||||
- After activating the Clerk Convex integration, sign out completely and sign
|
||||
back in before retesting. An old Clerk session can keep using a token that
|
||||
Convex rejects.
|
||||
|
||||
## Production
|
||||
|
||||
- Ask whether the user wants dev-only setup or production-ready setup
|
||||
- If the answer is production-ready, make sure production Clerk keys and issuer configuration are included
|
||||
- Verify production redirect URLs and any production Clerk domain values before calling the task complete
|
||||
- Do not silently write a notes file into the repo by default. If the user wants rollout or handoff docs, create one explicitly.
|
||||
- If the answer is production-ready, make sure production Clerk keys and issuer
|
||||
configuration are included
|
||||
- Verify production redirect URLs and any production Clerk domain values before
|
||||
calling the task complete
|
||||
- Do not silently write a notes file into the repo by default. If the user wants
|
||||
rollout or handoff docs, create one explicitly.
|
||||
|
||||
## Validation
|
||||
|
||||
- Verify the user can sign in with Clerk
|
||||
- If the Clerk integration was just activated, verify after a full Clerk sign-out and fresh sign-in
|
||||
- If the Clerk integration was just activated, verify after a full Clerk
|
||||
sign-out and fresh sign-in
|
||||
- Verify `useConvexAuth()` reaches the authenticated state after Clerk login
|
||||
- Verify protected Convex queries run successfully inside authenticated UI
|
||||
- Verify `ctx.auth.getUserIdentity()` is non-null in protected backend functions
|
||||
- If production-ready setup was requested, verify the production Clerk configuration is also covered
|
||||
- If production-ready setup was requested, verify the production Clerk
|
||||
configuration is also covered
|
||||
|
||||
## Checklist
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
# Convex Auth
|
||||
|
||||
Official docs: https://docs.convex.dev/auth/convex-auth
|
||||
Setup guide: https://labs.convex.dev/auth/setup
|
||||
Official docs: https://docs.convex.dev/auth/convex-auth Setup guide:
|
||||
https://labs.convex.dev/auth/setup
|
||||
|
||||
Use this when the user wants auth handled directly in Convex rather than through a third-party provider.
|
||||
Use this when the user wants auth handled directly in Convex rather than through
|
||||
a third-party provider.
|
||||
|
||||
## Workflow
|
||||
|
||||
@@ -16,7 +17,8 @@ Use this when the user wants auth handled directly in Convex rather than through
|
||||
4. Read the Convex Auth setup guide before writing code
|
||||
5. Make sure the project has a configured Convex deployment:
|
||||
- run `npx convex dev` first if `CONVEX_DEPLOYMENT` is not set
|
||||
- if CLI configuration requires interactive human input, stop and ask the user to complete that step before continuing
|
||||
- if CLI configuration requires interactive human input, stop and ask the
|
||||
user to complete that step before continuing
|
||||
6. Install the auth packages:
|
||||
- `npm install @convex-dev/auth @auth/core@0.37.0`
|
||||
7. Run the initialization command:
|
||||
@@ -28,30 +30,38 @@ Use this when the user wants auth handled directly in Convex rather than through
|
||||
9. Add the required `authTables` to `convex/schema.ts`
|
||||
10. Replace plain `ConvexProvider` wiring with `ConvexAuthProvider`
|
||||
11. Configure at least one auth method in `convex/auth.ts`
|
||||
12. Run `npx convex dev --once` or the normal dev flow to push the updated schema and generated code
|
||||
12. Run `npx convex dev --once` or the normal dev flow to push the updated
|
||||
schema and generated code
|
||||
13. Verify the client can sign in successfully
|
||||
14. Verify Convex receives authenticated identity in backend functions
|
||||
15. If the user wants production-ready setup, make sure the same auth setup is configured for the production deployment as well
|
||||
16. Only add a `users` table and `storeUser` flow if the app needs app-level user records inside Convex
|
||||
15. If the user wants production-ready setup, make sure the same auth setup is
|
||||
configured for the production deployment as well
|
||||
16. Only add a `users` table and `storeUser` flow if the app needs app-level
|
||||
user records inside Convex
|
||||
|
||||
## What This Reference Is For
|
||||
|
||||
- choosing Convex Auth as the default provider for a new Convex app
|
||||
- understanding whether the app wants magic links, OTPs, OAuth, or passwords
|
||||
- keeping the setup provider-specific while using the official Convex Auth docs for identity and authorization behavior
|
||||
- keeping the setup provider-specific while using the official Convex Auth docs
|
||||
for identity and authorization behavior
|
||||
|
||||
## What To Do
|
||||
|
||||
- Read the Convex Auth setup guide before writing setup code
|
||||
- Follow the setup flow from the docs rather than recreating it from memory
|
||||
- If the app is new, consider starting from the official starter flow instead of hand-wiring everything
|
||||
- Treat `npx @convex-dev/auth` as a required initialization step for existing apps, not an optional extra
|
||||
- If the app is new, consider starting from the official starter flow instead of
|
||||
hand-wiring everything
|
||||
- Treat `npx @convex-dev/auth` as a required initialization step for existing
|
||||
apps, not an optional extra
|
||||
|
||||
## Concrete Steps
|
||||
|
||||
1. Install `@convex-dev/auth` and `@auth/core@0.37.0`
|
||||
2. Run `npx convex dev` if the project does not already have a configured deployment
|
||||
3. If `npx convex dev` blocks on interactive setup, ask the user explicitly to finish configuring the Convex deployment
|
||||
2. Run `npx convex dev` if the project does not already have a configured
|
||||
deployment
|
||||
3. If `npx convex dev` blocks on interactive setup, ask the user explicitly to
|
||||
finish configuring the Convex deployment
|
||||
4. Run `npx @convex-dev/auth`
|
||||
5. Confirm the generated auth setup is present before continuing:
|
||||
- `convex/auth.config.ts`
|
||||
@@ -60,46 +70,70 @@ Use this when the user wants auth handled directly in Convex rather than through
|
||||
6. Add `authTables` to `convex/schema.ts`
|
||||
7. Replace `ConvexProvider` with `ConvexAuthProvider` in the app entry
|
||||
8. Configure the selected auth methods in `convex/auth.ts`
|
||||
9. Run `npx convex dev --once` or the normal dev flow so the updated schema and auth files are pushed
|
||||
9. Run `npx convex dev --once` or the normal dev flow so the updated schema and
|
||||
auth files are pushed
|
||||
10. Verify login locally
|
||||
11. If the user wants production-ready setup, repeat the required auth configuration against the production deployment
|
||||
11. If the user wants production-ready setup, repeat the required auth
|
||||
configuration against the production deployment
|
||||
|
||||
## Expected Files and Decisions
|
||||
|
||||
- `convex/schema.ts`
|
||||
- frontend app entry such as `src/main.tsx` or the framework-equivalent provider file
|
||||
- frontend app entry such as `src/main.tsx` or the framework-equivalent provider
|
||||
file
|
||||
- generated Convex Auth setup produced by `npx @convex-dev/auth`
|
||||
- an existing configured Convex deployment, or the ability to create one with `npx convex dev`
|
||||
- `convex/auth.ts` starts with `providers: []` until the app configures actual sign-in methods
|
||||
- an existing configured Convex deployment, or the ability to create one with
|
||||
`npx convex dev`
|
||||
- `convex/auth.ts` starts with `providers: []` until the app configures actual
|
||||
sign-in methods
|
||||
|
||||
- Decide whether the user is creating a new app or adding auth to an existing app
|
||||
- For a new app, prefer the official starter flow instead of rebuilding setup by hand
|
||||
- Decide whether the user is creating a new app or adding auth to an existing
|
||||
app
|
||||
- For a new app, prefer the official starter flow instead of rebuilding setup by
|
||||
hand
|
||||
- Decide which auth methods the app needs:
|
||||
- magic links or OTPs
|
||||
- OAuth providers
|
||||
- passwords
|
||||
- Decide whether the user wants local-only setup or production-ready setup now
|
||||
- Decide whether the app actually needs a `users` table inside Convex, or whether provider identity alone is enough
|
||||
- Decide whether the app actually needs a `users` table inside Convex, or
|
||||
whether provider identity alone is enough
|
||||
|
||||
## Gotchas
|
||||
|
||||
- Do not assume a specific sign-in method. Ask which methods the app needs before wiring UI and backend behavior.
|
||||
- `npx @convex-dev/auth` is important because it initializes the auth setup, including the key material. Do not skip it when adding Convex Auth to an existing project.
|
||||
- `npx @convex-dev/auth` will fail if the project does not already have a configured `CONVEX_DEPLOYMENT`.
|
||||
- `npx convex dev` may require interactive setup for deployment creation or project selection. If that happens, ask the user explicitly for that human step instead of guessing.
|
||||
- `npx @convex-dev/auth` does not finish the whole integration by itself. You still need to add `authTables`, swap in `ConvexAuthProvider`, and configure at least one auth method.
|
||||
- A project can still build even if `convex/auth.ts` still has `providers: []`, so do not treat a successful build as proof that sign-in is fully configured.
|
||||
- Convex Auth does not mean every app needs a `users` table. If the app only needs authentication gates, `ctx.auth.getUserIdentity()` may be enough.
|
||||
- If the app is greenfield, starting from the official starter flow is usually better than partially recreating it by hand.
|
||||
- Do not stop at local dev setup if the user expects production-ready auth. The production deployment needs the auth setup too.
|
||||
- Keep provider-specific setup and Convex Auth authorization behavior in the official docs instead of inventing shared patterns from memory.
|
||||
- Do not assume a specific sign-in method. Ask which methods the app needs
|
||||
before wiring UI and backend behavior.
|
||||
- `npx @convex-dev/auth` is important because it initializes the auth setup,
|
||||
including the key material. Do not skip it when adding Convex Auth to an
|
||||
existing project.
|
||||
- `npx @convex-dev/auth` will fail if the project does not already have a
|
||||
configured `CONVEX_DEPLOYMENT`.
|
||||
- `npx convex dev` may require interactive setup for deployment creation or
|
||||
project selection. If that happens, ask the user explicitly for that human
|
||||
step instead of guessing.
|
||||
- `npx @convex-dev/auth` does not finish the whole integration by itself. You
|
||||
still need to add `authTables`, swap in `ConvexAuthProvider`, and configure at
|
||||
least one auth method.
|
||||
- A project can still build even if `convex/auth.ts` still has `providers: []`,
|
||||
so do not treat a successful build as proof that sign-in is fully configured.
|
||||
- Convex Auth does not mean every app needs a `users` table. If the app only
|
||||
needs authentication gates, `ctx.auth.getUserIdentity()` may be enough.
|
||||
- If the app is greenfield, starting from the official starter flow is usually
|
||||
better than partially recreating it by hand.
|
||||
- Do not stop at local dev setup if the user expects production-ready auth. The
|
||||
production deployment needs the auth setup too.
|
||||
- Keep provider-specific setup and Convex Auth authorization behavior in the
|
||||
official docs instead of inventing shared patterns from memory.
|
||||
|
||||
## Production
|
||||
|
||||
- Ask whether the user wants dev-only setup or production-ready setup
|
||||
- If the answer is production-ready, make sure the auth configuration is applied to the production deployment, not just the dev deployment
|
||||
- Verify production-specific redirect URLs, auth method configuration, and deployment settings before calling the task complete
|
||||
- Do not silently write a notes file into the repo by default. If the user wants rollout or handoff docs, create one explicitly.
|
||||
- If the answer is production-ready, make sure the auth configuration is applied
|
||||
to the production deployment, not just the dev deployment
|
||||
- Verify production-specific redirect URLs, auth method configuration, and
|
||||
deployment settings before calling the task complete
|
||||
- Do not silently write a notes file into the repo by default. If the user wants
|
||||
rollout or handoff docs, create one explicitly.
|
||||
|
||||
## Human Handoff
|
||||
|
||||
@@ -112,32 +146,43 @@ If `npx convex dev` or deployment setup requires human input:
|
||||
## Validation
|
||||
|
||||
- Verify the user can complete a sign-in flow
|
||||
- Offer to validate sign up, sign out, and sign back in with the configured auth method
|
||||
- If browser automation is available in the environment, you can do this directly
|
||||
- If browser automation is not available, give the user a short manual validation checklist instead
|
||||
- Verify `ctx.auth.getUserIdentity()` returns an identity in protected backend functions
|
||||
- Offer to validate sign up, sign out, and sign back in with the configured auth
|
||||
method
|
||||
- If browser automation is available in the environment, you can do this
|
||||
directly
|
||||
- If browser automation is not available, give the user a short manual
|
||||
validation checklist instead
|
||||
- Verify `ctx.auth.getUserIdentity()` returns an identity in protected backend
|
||||
functions
|
||||
- Verify protected UI only renders after Convex-authenticated state is ready
|
||||
- Verify environment variables and redirect settings match the current app environment
|
||||
- Verify `convex/auth.ts` no longer has an empty `providers: []` configuration once the app is meant to support real sign-in
|
||||
- Run `npx convex dev --once` or the normal dev flow after setup changes and confirm Convex codegen and push succeed
|
||||
- If production-ready setup was requested, verify the production deployment is also configured correctly
|
||||
- Verify environment variables and redirect settings match the current app
|
||||
environment
|
||||
- Verify `convex/auth.ts` no longer has an empty `providers: []` configuration
|
||||
once the app is meant to support real sign-in
|
||||
- Run `npx convex dev --once` or the normal dev flow after setup changes and
|
||||
confirm Convex codegen and push succeed
|
||||
- If production-ready setup was requested, verify the production deployment is
|
||||
also configured correctly
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Confirm the user wants Convex Auth specifically
|
||||
- [ ] Ask whether the user wants local-only setup or production-ready setup
|
||||
- [ ] Ensure a Convex deployment is configured before running auth initialization
|
||||
- [ ] Ensure a Convex deployment is configured before running auth
|
||||
initialization
|
||||
- [ ] Install `@convex-dev/auth` and `@auth/core@0.37.0`
|
||||
- [ ] Run `npx convex dev` first if needed
|
||||
- [ ] Run `npx @convex-dev/auth`
|
||||
- [ ] Confirm `convex/auth.config.ts`, `convex/auth.ts`, and `convex/http.ts` were created
|
||||
- [ ] Confirm `convex/auth.config.ts`, `convex/auth.ts`, and `convex/http.ts`
|
||||
were created
|
||||
- [ ] Follow the setup guide for package install and wiring
|
||||
- [ ] Add `authTables` to `convex/schema.ts`
|
||||
- [ ] Replace `ConvexProvider` with `ConvexAuthProvider`
|
||||
- [ ] Configure at least one auth method in `convex/auth.ts`
|
||||
- [ ] Run `npx convex dev --once` or the normal dev flow after setup changes
|
||||
- [ ] Confirm which sign-in methods the app needs
|
||||
- [ ] Verify the client can sign in and the backend receives authenticated identity
|
||||
- [ ] Verify the client can sign in and the backend receives authenticated
|
||||
identity
|
||||
- [ ] Offer end-to-end validation of sign up, sign out, and sign back in
|
||||
- [ ] If requested, configure the production deployment too
|
||||
- [ ] Only add extra `users` table sync if the app needs app-level user records
|
||||
|
||||
@@ -6,7 +6,8 @@ Official docs:
|
||||
- https://docs.convex.dev/auth/authkit/add-to-app
|
||||
- https://docs.convex.dev/auth/authkit/auto-provision
|
||||
|
||||
Use this when the app already uses WorkOS or the user wants AuthKit specifically.
|
||||
Use this when the app already uses WorkOS or the user wants AuthKit
|
||||
specifically.
|
||||
|
||||
## Workflow
|
||||
|
||||
@@ -22,21 +23,28 @@ Use this when the app already uses WorkOS or the user wants AuthKit specifically
|
||||
8. Configure `convex/auth.config.ts` for WorkOS-issued JWTs
|
||||
9. Wire the client provider and callback flow
|
||||
10. Verify authenticated requests reach Convex
|
||||
11. If the user wants production-ready setup, make sure the production WorkOS configuration is covered too
|
||||
12. Only add `storeUser` or a `users` table if the app needs first-class user rows inside Convex
|
||||
11. If the user wants production-ready setup, make sure the production WorkOS
|
||||
configuration is covered too
|
||||
12. Only add `storeUser` or a `users` table if the app needs first-class user
|
||||
rows inside Convex
|
||||
|
||||
## What To Do
|
||||
|
||||
- Read the official Convex and WorkOS AuthKit guide before writing setup code
|
||||
- Determine whether the user wants a Convex-managed WorkOS team or an existing WorkOS team
|
||||
- Treat `convex.json` as a first-class part of the AuthKit setup, not an optional extra
|
||||
- Follow the current setup flow from the docs instead of relying on older examples
|
||||
- Determine whether the user wants a Convex-managed WorkOS team or an existing
|
||||
WorkOS team
|
||||
- Treat `convex.json` as a first-class part of the AuthKit setup, not an
|
||||
optional extra
|
||||
- Follow the current setup flow from the docs instead of relying on older
|
||||
examples
|
||||
|
||||
## Key Setup Areas
|
||||
|
||||
- package installation for the app's framework
|
||||
- `convex.json` with the `authKit` section for dev, and preview or prod if needed
|
||||
- environment variables such as `WORKOS_CLIENT_ID`, `WORKOS_API_KEY`, and redirect configuration
|
||||
- `convex.json` with the `authKit` section for dev, and preview or prod if
|
||||
needed
|
||||
- environment variables such as `WORKOS_CLIENT_ID`, `WORKOS_API_KEY`, and
|
||||
redirect configuration
|
||||
- `convex/auth.config.ts` wiring for WorkOS-issued JWTs
|
||||
- client provider setup and token flow into Convex
|
||||
- login callback and redirect configuration
|
||||
@@ -55,42 +63,65 @@ Use this when the app already uses WorkOS or the user wants AuthKit specifically
|
||||
- `VITE_WORKOS_REDIRECT_URI`
|
||||
- `NEXT_PUBLIC_WORKOS_REDIRECT_URI`
|
||||
|
||||
For a managed WorkOS team, `convex dev` can provision the AuthKit environment and write local env vars such as `VITE_WORKOS_CLIENT_ID` and `VITE_WORKOS_REDIRECT_URI` into `.env.local` for Vite apps.
|
||||
For a managed WorkOS team, `convex dev` can provision the AuthKit environment
|
||||
and write local env vars such as `VITE_WORKOS_CLIENT_ID` and
|
||||
`VITE_WORKOS_REDIRECT_URI` into `.env.local` for Vite apps.
|
||||
|
||||
## Concrete Steps
|
||||
|
||||
1. Choose Convex-managed or existing WorkOS team
|
||||
2. Create or update `convex.json` with the `authKit` section for the framework in use
|
||||
3. Make sure the dev `redirectUris`, `appHomepageUrl`, `corsOrigins`, and local redirect env vars match the app's actual local port
|
||||
4. For a managed WorkOS team, run `npx convex dev` and follow the interactive onboarding flow
|
||||
5. For an existing WorkOS team, get `WORKOS_CLIENT_ID` and `WORKOS_API_KEY` from the WorkOS dashboard and set them with `npx convex env set`
|
||||
2. Create or update `convex.json` with the `authKit` section for the framework
|
||||
in use
|
||||
3. Make sure the dev `redirectUris`, `appHomepageUrl`, `corsOrigins`, and local
|
||||
redirect env vars match the app's actual local port
|
||||
4. For a managed WorkOS team, run `npx convex dev` and follow the interactive
|
||||
onboarding flow
|
||||
5. For an existing WorkOS team, get `WORKOS_CLIENT_ID` and `WORKOS_API_KEY` from
|
||||
the WorkOS dashboard and set them with `npx convex env set`
|
||||
6. Create or update `convex/auth.config.ts` for WorkOS JWT validation
|
||||
7. Run the normal Convex dev or deploy flow so backend config is synced
|
||||
8. Wire the WorkOS client provider in the app
|
||||
9. Configure callback and redirect handling
|
||||
10. Verify the user can sign in and return to the app
|
||||
11. Verify Convex sees the authenticated user after login
|
||||
12. If the user wants production-ready setup, configure the production client ID, API key, redirect URI, and deployment settings too
|
||||
12. If the user wants production-ready setup, configure the production client
|
||||
ID, API key, redirect URI, and deployment settings too
|
||||
|
||||
## Gotchas
|
||||
|
||||
- The docs split setup between Convex-managed and existing WorkOS teams, so ask which path the user wants if it is not obvious
|
||||
- Keep dev and prod WorkOS configuration separate where the docs call for different client IDs or API keys
|
||||
- Only add `storeUser` or a `users` table if the app needs first-class user rows inside Convex
|
||||
- The docs split setup between Convex-managed and existing WorkOS teams, so ask
|
||||
which path the user wants if it is not obvious
|
||||
- Keep dev and prod WorkOS configuration separate where the docs call for
|
||||
different client IDs or API keys
|
||||
- Only add `storeUser` or a `users` table if the app needs first-class user rows
|
||||
inside Convex
|
||||
- Do not mix dev and prod WorkOS credentials or redirect URIs
|
||||
- If the repo already contains WorkOS setup, preserve the current tenant model unless the user wants to change it
|
||||
- For managed WorkOS setup, `convex dev` is interactive the first time. In non-interactive terminals, stop and ask the user to complete the onboarding prompts.
|
||||
- `convex.json` is not optional for the managed AuthKit flow. It drives redirect URI, homepage URL, CORS configuration, and local env var generation.
|
||||
- If the frontend starts on a different port than the one in `convex.json`, the hosted WorkOS sign-in flow will point to the wrong callback URL. Update `convex.json`, update the local redirect env var, and run `npx convex dev` again.
|
||||
- Vite can fall off `5173` if other apps are already running. Do not assume the default port still matches the generated AuthKit config.
|
||||
- A successful WorkOS sign-in should redirect back to the local callback route and then reach a Convex-authenticated state. Do not stop at "the hosted WorkOS page loaded."
|
||||
- If the repo already contains WorkOS setup, preserve the current tenant model
|
||||
unless the user wants to change it
|
||||
- For managed WorkOS setup, `convex dev` is interactive the first time. In
|
||||
non-interactive terminals, stop and ask the user to complete the onboarding
|
||||
prompts.
|
||||
- `convex.json` is not optional for the managed AuthKit flow. It drives redirect
|
||||
URI, homepage URL, CORS configuration, and local env var generation.
|
||||
- If the frontend starts on a different port than the one in `convex.json`, the
|
||||
hosted WorkOS sign-in flow will point to the wrong callback URL. Update
|
||||
`convex.json`, update the local redirect env var, and run `npx convex dev`
|
||||
again.
|
||||
- Vite can fall off `5173` if other apps are already running. Do not assume the
|
||||
default port still matches the generated AuthKit config.
|
||||
- A successful WorkOS sign-in should redirect back to the local callback route
|
||||
and then reach a Convex-authenticated state. Do not stop at "the hosted WorkOS
|
||||
page loaded."
|
||||
|
||||
## Production
|
||||
|
||||
- Ask whether the user wants dev-only setup or production-ready setup
|
||||
- If the answer is production-ready, make sure the production WorkOS client ID, API key, redirect URI, and Convex deployment config are all covered
|
||||
- Verify the production redirect and callback settings before calling the task complete
|
||||
- Do not silently write a notes file into the repo by default. If the user wants rollout or handoff docs, create one explicitly.
|
||||
- If the answer is production-ready, make sure the production WorkOS client ID,
|
||||
API key, redirect URI, and Convex deployment config are all covered
|
||||
- Verify the production redirect and callback settings before calling the task
|
||||
complete
|
||||
- Do not silently write a notes file into the repo by default. If the user wants
|
||||
rollout or handoff docs, create one explicitly.
|
||||
|
||||
## Validation
|
||||
|
||||
@@ -99,8 +130,10 @@ For a managed WorkOS team, `convex dev` can provision the AuthKit environment an
|
||||
- Verify Convex receives authenticated requests after login
|
||||
- Verify `convex.json` matches the framework and chosen WorkOS setup path
|
||||
- Verify `convex/auth.config.ts` matches the chosen WorkOS setup path
|
||||
- Verify environment variables differ correctly between local and production where needed
|
||||
- If production-ready setup was requested, verify the production WorkOS configuration is also covered
|
||||
- Verify environment variables differ correctly between local and production
|
||||
where needed
|
||||
- If production-ready setup was requested, verify the production WorkOS
|
||||
configuration is also covered
|
||||
|
||||
## Checklist
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: convex
|
||||
description: Routes general Convex requests to the right project skill. Use when the user asks which Convex skill to use or gives an underspecified Convex app task.
|
||||
description: Routes general Convex requests to the right project skill. Use when the user
|
||||
asks which Convex skill to use or gives an underspecified Convex app task.
|
||||
---
|
||||
|
||||
# Convex
|
||||
@@ -11,7 +12,8 @@ If a more specific Convex skill clearly matches the request, use that instead.
|
||||
|
||||
## Start Here
|
||||
|
||||
If the project does not already have Convex AI guidance installed, or the existing guidance looks stale, strongly recommend installing it first.
|
||||
If the project does not already have Convex AI guidance installed, or the
|
||||
existing guidance looks stale, strongly recommend installing it first.
|
||||
|
||||
Preferred:
|
||||
|
||||
@@ -19,7 +21,9 @@ Preferred:
|
||||
npx convex ai-files install
|
||||
```
|
||||
|
||||
This installs or refreshes the managed Convex AI files. It is the recommended starting point for getting the official Convex guidelines in place and following the current Convex AI setup described in the docs:
|
||||
This installs or refreshes the managed Convex AI files. It is the recommended
|
||||
starting point for getting the official Convex guidelines in place and following
|
||||
the current Convex AI setup described in the docs:
|
||||
|
||||
- [Convex AI docs](https://docs.convex.dev/ai)
|
||||
|
||||
@@ -29,6 +33,29 @@ Simple fallback:
|
||||
|
||||
Prefer `npx convex ai-files install` over copying rules by hand when possible.
|
||||
|
||||
## Command Preflight
|
||||
|
||||
Before running any `bunx convex ...` command in ClawHub, explicitly identify:
|
||||
|
||||
- target runtime: `local`, `dev`, or `prod`
|
||||
- deployment: exact name or URL when known, such as `wry-manatee-359` for prod
|
||||
- code state: whether the function/schema changes have already been pushed with
|
||||
`bunx convex dev --once`, `bunx convex deploy`, or the production deploy
|
||||
workflow
|
||||
|
||||
Use the current Convex CLI flag shape:
|
||||
|
||||
- read data: `bunx convex data --deployment <deployment> <table>`
|
||||
- run a function: `bunx convex run --deployment <deployment> <function> '<json>'`
|
||||
- readonly inline query:
|
||||
`bunx convex run --deployment <deployment> --inline-query '<query>'`
|
||||
- single-table import:
|
||||
`bunx convex import --deployment <deployment> --table <table> --replace -y <file>`
|
||||
|
||||
If `--env-file .env.local` produces `401 MissingAccessToken`, omit the env file
|
||||
and target the deployment directly with `--deployment <deployment>` or `--prod`.
|
||||
Do not use stale `--deployment-name` guidance.
|
||||
|
||||
## Route to the Right Skill
|
||||
|
||||
After that, use the most specific Convex skill for the task:
|
||||
@@ -39,7 +66,8 @@ After that, use the most specific Convex skill for the task:
|
||||
- Planning or running a migration: `convex-migration-helper`
|
||||
- Investigating performance issues: `convex-performance-audit`
|
||||
|
||||
If one of those clearly matches the user's goal, switch to it instead of staying in this skill.
|
||||
If one of those clearly matches the user's goal, switch to it instead of staying
|
||||
in this skill.
|
||||
|
||||
## When Not to Use
|
||||
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
---
|
||||
name: create-and-cleanup-migration
|
||||
description: Use for end-to-end ClawHub Convex production migrations, backfills, destructive cleanups, and one-off maintenance functions that must be created, validated, shipped, run, verified, then removed after completion.
|
||||
---
|
||||
|
||||
# Create And Cleanup Migration
|
||||
|
||||
Drive a ClawHub Convex migration from implementation through production cleanup,
|
||||
with explicit operator gates before destructive execution and before removing the
|
||||
temporary migration code.
|
||||
|
||||
## When To Use
|
||||
|
||||
- A Convex production data migration, backfill, destructive cleanup, schema
|
||||
narrowing, table reshaping, or one-off maintenance function is needed.
|
||||
- Temporary Convex code must be created, deployed, run, verified, and then
|
||||
removed after it is no longer useful.
|
||||
- The user asks for the full lifecycle: implement migration, PR, deploy, dry run,
|
||||
apply, verify, cleanup PR, deploy cleanup.
|
||||
|
||||
## Required Companion Guidance
|
||||
|
||||
1. Start with `convex-migration-helper`.
|
||||
2. Read `convex/_generated/ai/guidelines.md` before editing Convex code.
|
||||
3. Default to `@convex-dev/migrations` for production data changes.
|
||||
4. If not using `@convex-dev/migrations`, write down why the component is
|
||||
unnecessary and provide equivalent:
|
||||
- dry-run support
|
||||
- cursor batching
|
||||
- resumable/progress behavior
|
||||
- destructive confirmation token
|
||||
- real Convex runtime validation
|
||||
|
||||
## Safety Rules
|
||||
|
||||
- Never run a destructive production apply step until after presenting dry-run
|
||||
results and receiving explicit user confirmation in the current thread.
|
||||
- Before implementing anything, classify the requested "migration" as one of:
|
||||
code deploy, existing Convex function run, operator import/export command,
|
||||
schema narrowing, data cleanup, or cleanup-code removal. Do not invent a new
|
||||
Convex migration function when the issue or PR specifies an operator command
|
||||
such as `convex import --replace`.
|
||||
- Never remove migration code until after presenting apply/verification results
|
||||
and receiving explicit user confirmation in the current thread.
|
||||
- Keep production commands pointed at the explicit deployment name when known;
|
||||
do not rely on generic `--prod` if this repo's guidance says to verify the
|
||||
actual deployment.
|
||||
- If the migration can affect visibility, moderation, ownership, billing,
|
||||
installability, or public API output, call that out before the apply gate.
|
||||
- Preserve resume cursors, run IDs, PR URLs, deploy URLs, and final stats in the
|
||||
handoff.
|
||||
|
||||
## Phase 1: Design The Migration
|
||||
|
||||
1. Identify the intended data change and whether it is:
|
||||
- schema widen/migrate/narrow
|
||||
- field cleanup
|
||||
- table cleanup
|
||||
- ownership/relationship repair
|
||||
- recurring maintenance
|
||||
2. Choose the implementation:
|
||||
- Prefer `@convex-dev/migrations` for non-trivial production data.
|
||||
- Use a hand-rolled internal function only for a clearly small or special
|
||||
case, and document the exception.
|
||||
3. Define done criteria:
|
||||
- dry-run expected counts
|
||||
- apply expected counts
|
||||
- verification query/result proving no remaining targets
|
||||
- cleanup PR scope
|
||||
|
||||
## Phase 2: Implement
|
||||
|
||||
1. Add or update the Convex migration/maintenance code.
|
||||
2. Include argument validators for every Convex function.
|
||||
3. Include dry-run support.
|
||||
4. Include batching and resume/progress state.
|
||||
5. Include a confirmation token for destructive writes.
|
||||
6. Keep apply logic idempotent where practical.
|
||||
7. Add targeted tests for business logic and safety gates.
|
||||
8. Add real Convex runtime validation for Convex semantics such as pagination,
|
||||
validators, internal/public function boundaries, scheduler behavior, and
|
||||
action/query/mutation interactions.
|
||||
|
||||
## Phase 3: Local Validation
|
||||
|
||||
Run the smallest meaningful set first, then broaden before PR handoff:
|
||||
|
||||
- targeted unit tests for the migration logic
|
||||
- `bunx convex codegen` when Convex API/schema changed
|
||||
- `bunx tsc --noEmit` or the repo's Convex deploy typecheck path
|
||||
- `bun run ci:static`
|
||||
- `bun run ci:unit` for source/test changes unless explicitly waived
|
||||
- a real local Convex validation path, such as `bunx convex dev --once`,
|
||||
`convex run`, HTTP smoke, or local-auth Playwright, covering the changed
|
||||
Convex behavior
|
||||
|
||||
If local real Convex validation is blocked, record the blocker and make the PR
|
||||
or deployment plan explicitly compensate with an equivalent runtime proof.
|
||||
|
||||
## Phase 4: PR, Review, Merge, Deploy
|
||||
|
||||
1. Open a focused PR containing the migration implementation.
|
||||
2. Include:
|
||||
- summary
|
||||
- migration strategy
|
||||
- dry-run/apply safety gates
|
||||
- tests and runtime validation
|
||||
- cleanup plan
|
||||
3. Run the repo's review/CI workflow required by `AGENTS.md`.
|
||||
4. Address actionable review findings.
|
||||
5. Merge only after required checks are green or the user explicitly accepts a
|
||||
documented risk.
|
||||
6. Deploy the relevant production target from `main`.
|
||||
7. Wait for deployment success before running the production dry run.
|
||||
|
||||
## Phase 5: Production Dry Run
|
||||
|
||||
1. Run the production dry run with bounded batch settings.
|
||||
2. Resume until either:
|
||||
- `isDone: true`, or
|
||||
- a clearly documented safety cap is reached.
|
||||
3. Present results to the user before apply:
|
||||
- deployment name
|
||||
- command shape
|
||||
- `dryRun`
|
||||
- `isDone`
|
||||
- done/progress fields
|
||||
- scanned/matched/patched/deleted stats
|
||||
- sample IDs
|
||||
- resume cursors if incomplete
|
||||
- known user-visible or operational implications
|
||||
4. Stop and wait for explicit user confirmation before applying.
|
||||
|
||||
## Phase 6: Production Apply
|
||||
|
||||
1. Run only after explicit user confirmation of the dry-run results.
|
||||
2. Use the destructive confirmation token.
|
||||
3. Resume in bounded batches until complete or until a documented safety cap.
|
||||
4. Present apply results:
|
||||
- patched/deleted counts
|
||||
- skipped/missing counts if tracked
|
||||
- final cursors/progress
|
||||
- any errors or partial completion
|
||||
5. Run verification:
|
||||
- dry run or status command should show zero remaining targets, or
|
||||
- explain why remaining targets are expected.
|
||||
6. Stop and wait for explicit user confirmation before cleanup-code removal.
|
||||
|
||||
## Phase 7: Cleanup PR
|
||||
|
||||
1. Remove temporary migration functions, tests, docs, scripts, and generated API
|
||||
entries that are no longer needed.
|
||||
2. Keep durable specs/docs only if they explain lasting behavior or invariants.
|
||||
3. Run targeted validation plus the repo-required gates for the touched surface.
|
||||
4. Open a cleanup PR with:
|
||||
- apply results
|
||||
- verification proof
|
||||
- explanation of removed temporary code
|
||||
5. Merge after checks/review.
|
||||
6. Deploy the cleanup PR if removing Convex functions or schema/code that affects
|
||||
production.
|
||||
|
||||
## Final Handoff
|
||||
|
||||
Report:
|
||||
|
||||
- implementation PR URL and merge SHA
|
||||
- production deploy run URL and deployed SHA
|
||||
- dry-run result
|
||||
- apply result
|
||||
- verification result
|
||||
- cleanup PR URL, merge SHA, and deploy run URL
|
||||
- any remaining follow-up tasks or intentional retained migration code
|
||||
@@ -0,0 +1,79 @@
|
||||
---
|
||||
name: technical-documentation
|
||||
description: Build and review high-quality technical docs as well as agent instruction files in your repository.
|
||||
license: MIT
|
||||
metadata:
|
||||
source: "https://github.com/vincentkoc/dotskills"
|
||||
---
|
||||
|
||||
# Technical Documentation
|
||||
|
||||
## Purpose
|
||||
|
||||
Produce and review technical documentation that is clear, actionable, and maintainable for both humans and agents, including contributor-governance files and agent instruction files.
|
||||
|
||||
## When to use
|
||||
|
||||
- Creating or overhauling docs in an existing product/codebase (brownfield).
|
||||
- Building evergreen docs meant to stay accurate and reusable over time.
|
||||
- Reviewing doc diffs for structure, clarity, and operational correctness.
|
||||
- Running full-repo documentation audits that must include both governance files and product docs surfaces (`docs/`, `README*`, `.md/.mdx/.mdc`, Fern/Sphinx/Mintlify-style sources).
|
||||
- Updating or reviewing AGENTS.md and/or CONTRIBUTING.md to keep agent and contributor workflows aligned with current repo practices.
|
||||
- Improving repository onboarding/docs that include contribution instructions, issue templates, PR flow, and review gates.
|
||||
- Designing governance documentation strategy for repos with alias instruction files (for example `CLAUDE.md`, `AGENT.md`, `.cursorrules`, `.cursor/rules/*`, `.agent/`, `.agents/`, `.pi/`) where `AGENTS.md` is treated as canonical when present and aliases should be kept as compatibility surfaces.
|
||||
- Diagnosing agent-file drift where teams had to prompt iteratively to surface missing files, broken commands, or policy conflicts.
|
||||
- Applying repository-specific documentation overlays, including OpenClaw page-type, docs IA, preservation, and validation rules when present.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Classify task: `build` or `review`; context: `brownfield` or `evergreen`.
|
||||
2. Inventory full documentation scope early (governance + product docs): AGENTS/CONTRIBUTING/aliases plus docs directories, framework sources, and root/module READMEs.
|
||||
3. Detect multilingual scope (README/docs in multiple languages) and define required parity level.
|
||||
4. Read `references/agent-and-contributing.md` for agent instruction and `CONTRIBUTING.md` workflow rules (inventory, canonical/alias mapping, dual-mode balance, deliverable standards, and precedence/conflict handling).
|
||||
5. Read `references/principles.md` for the governing ruleset (Matt Palmer & OpenAI).
|
||||
6. For OpenClaw docs work, read `references/openclaw.md` before the build/review playbook.
|
||||
7. For build tasks, follow `references/build.md`.
|
||||
8. For review tasks, follow `references/review.md` and proactively detect issues without waiting for repeated prompts.
|
||||
9. For complex or high-risk tasks (build or review), it is acceptable to run longer, deeper, and more exhaustive investigations when needed for confidence.
|
||||
10. When available, use sub-agents for bounded parallel discovery/review work, then merge outputs into one coherent final deliverable.
|
||||
11. Use `references/tooling.md` when platform/tooling choices affect recommendations.
|
||||
12. Run a proactive issue sweep for both governance and docs-content surfaces, and fix high-confidence defects in the same pass unless explicitly asked for report-only mode.
|
||||
13. In brownfield mode, prioritize compatibility with current docs IA, tooling, and release state.
|
||||
14. In evergreen mode, prioritize timeless wording, update strategy, and durable structure.
|
||||
15. Return deliverables plus validation notes, parity status, and remaining gaps.
|
||||
|
||||
## Sub-agent orchestration guidance
|
||||
|
||||
Prefer sub-agents when the repo is large or the requested change set is broad; use them by default for repo-wide, multi-framework, or high-conflict work.
|
||||
|
||||
- `inventory-agent` -> `agents/inventory-agent.md` (`fast` / Claude `haiku`): file/config discovery, coverage map, and missing-path checks.
|
||||
- `governance-agent` -> `agents/governance-agent.md` (`thinking` / Claude `sonnet`): AGENTS/CONTRIBUTING/alias precedence, conflicts, and policy drift.
|
||||
- `docs-framework-agent` -> `agents/docs-framework-agent.md` (`thinking` / Claude `sonnet`): framework config, relative path base, and file-path vs URL-path mapping checks.
|
||||
- `synthesis-agent` -> `agents/synthesis-agent.md` (`long` / Claude `opus`): merge sub-agent outputs into one prioritized fix plan and unified precedence model.
|
||||
|
||||
## Inputs
|
||||
|
||||
- Doc type (tutorial, how-to, reference, explanation) and audience.
|
||||
- File scope or diff scope.
|
||||
- Docs framework/tooling constraints (Fern, Mintlify, Sphinx, etc.).
|
||||
- Build/review mode and brownfield/evergreen intent.
|
||||
- Target agent and human compatibility intent.
|
||||
- Docs framework surfaces in scope (for example Fern, Sphinx, Mintlify, Markdown/MDX/MDC/RST/RSC files).
|
||||
- Desired investigation depth/time budget (quick pass vs exhaustive review).
|
||||
- Execution mode (`single-agent` or `sub-agent-assisted` when available).
|
||||
- Remediation mode (`apply-fixes` by default, or `report-only` when requested).
|
||||
- Multilingual scope: source-of-truth language, target locales, and parity expectations.
|
||||
- Repository-specific overlay constraints, if any.
|
||||
|
||||
## Outputs
|
||||
|
||||
- Updated draft or review findings with clear next actions.
|
||||
- Validation notes (what was checked, what remains).
|
||||
- Navigation/maintenance recommendations for long-term quality.
|
||||
- Governance-doc alignment summary when AGENTS/CONTRIBUTING were touched.
|
||||
- Agent instruction-surface map (primary file, alias files, Codex/Claude/Cursor handling plan).
|
||||
- Documentation-surface coverage map (what was reviewed under `/docs`, README hierarchy, and framework-specific source trees).
|
||||
- Autodetected issue list with applied fixes (or explicit report-only findings).
|
||||
- Delegation notes when sub-agents were used (scope delegated and how findings were merged).
|
||||
- Multilingual parity note (in-sync, partial with rationale, or intentionally divergent).
|
||||
- Repository-specific overlay notes when one was used.
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
name: docs-framework-agent
|
||||
description: Thinking-focused docs framework checker for config-relative paths and route/file mapping consistency.
|
||||
model: sonnet
|
||||
tools:
|
||||
- Read
|
||||
- Glob
|
||||
- Grep
|
||||
permissionMode: default
|
||||
maxTurns: 10
|
||||
---
|
||||
|
||||
You are the docs-framework sub-agent for technical documentation.
|
||||
|
||||
Goals:
|
||||
|
||||
- validate framework config-driven docs behavior
|
||||
- prevent path-mapping drift between source files and published routes
|
||||
|
||||
Tasks:
|
||||
|
||||
- detect and read framework config first (Fern/Sphinx/Mintlify/custom)
|
||||
- resolve paths relative to the declaring file/config
|
||||
- validate both maps:
|
||||
- config -> file exists
|
||||
- config/nav/routing -> URL path is valid and consistent
|
||||
|
||||
Return:
|
||||
|
||||
- config files reviewed
|
||||
- path assumptions made
|
||||
- mismatches (`missing file`, `stale route`, `wrong base path`)
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
name: governance-agent
|
||||
description: Thinking-focused governance reviewer for AGENTS/CONTRIBUTING/alias precedence, conflict detection, and policy drift analysis.
|
||||
model: sonnet
|
||||
tools:
|
||||
- Read
|
||||
- Glob
|
||||
- Grep
|
||||
permissionMode: default
|
||||
maxTurns: 10
|
||||
---
|
||||
|
||||
You are the governance sub-agent for technical documentation.
|
||||
|
||||
Goals:
|
||||
|
||||
- validate AGENTS/CONTRIBUTING/alias alignment and precedence
|
||||
- identify policy drift and conflicting instructions
|
||||
|
||||
Tasks:
|
||||
|
||||
- determine canonical instruction source and alias compatibility mapping
|
||||
- detect conflicts across nested scope files and tool-specific rule consumers
|
||||
- validate command examples against stated governance expectations
|
||||
|
||||
Return:
|
||||
|
||||
- precedence model
|
||||
- conflict list with severity
|
||||
- recommended low-risk remediations
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
name: inventory-agent
|
||||
description: Fast repo-surface discovery for technical documentation audits. Use for coverage mapping and missing-path detection before deeper review.
|
||||
model: haiku
|
||||
tools:
|
||||
- Read
|
||||
- Glob
|
||||
- Grep
|
||||
- LS
|
||||
permissionMode: default
|
||||
maxTurns: 6
|
||||
---
|
||||
|
||||
You are the inventory sub-agent for technical documentation.
|
||||
|
||||
Goals:
|
||||
|
||||
- enumerate governance and docs-content surfaces in scope
|
||||
- detect missing files, broken references, and obvious command/path failures
|
||||
|
||||
Tasks:
|
||||
|
||||
- map `AGENTS.md`/`CONTRIBUTING.md`/aliases and docs surfaces (`docs/**`, README hierarchy, `.md/.mdx/.mdc/.rst/.rsc`)
|
||||
- list framework config files discovered (Fern/Sphinx/Mintlify or equivalent)
|
||||
- report hard failures only, with exact file paths
|
||||
|
||||
Return:
|
||||
|
||||
- coverage map
|
||||
- missing/broken path list
|
||||
- unresolved blockers
|
||||
@@ -0,0 +1,10 @@
|
||||
interface:
|
||||
display_name: "Technical Documentation"
|
||||
short_description: "Build and review technical documentation for brownfield and evergreen systems."
|
||||
icon_small: "./assets/icon.jpg"
|
||||
icon_large: "./assets/icon.jpg"
|
||||
brand_color: "#111827"
|
||||
default_prompt: "Build or review technical documentation with a clear, maintainable, and production-ready workflow."
|
||||
|
||||
policy:
|
||||
allow_implicit_invocation: true
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
name: synthesis-agent
|
||||
description: Long-context synthesis agent that merges sub-agent outputs into one prioritized and deduplicated documentation action plan.
|
||||
model: opus
|
||||
tools:
|
||||
- Read
|
||||
permissionMode: default
|
||||
maxTurns: 12
|
||||
---
|
||||
|
||||
You are the synthesis sub-agent for technical documentation.
|
||||
|
||||
Goal:
|
||||
|
||||
- merge sub-agent outputs into one coherent, non-duplicated action plan
|
||||
|
||||
Tasks:
|
||||
|
||||
- prioritize blockers first, then non-blocking improvements
|
||||
- normalize to one precedence model for governance decisions
|
||||
- remove duplicated recommendations and contradictory fixes
|
||||
- keep final output concise and execution-ready
|
||||
|
||||
Return:
|
||||
|
||||
- prioritized fix plan
|
||||
- validation summary (done vs pending)
|
||||
- explicit remaining gaps/blockers
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
@@ -0,0 +1,145 @@
|
||||
# AGENT and CONTRIBUTING Principles
|
||||
|
||||
This reference consolidates the core rules for agent-policy and contributor-governance docs.
|
||||
|
||||
You must:
|
||||
|
||||
1. Discover repo-level and nested instruction files with:
|
||||
`rg --files -g 'AGENTS.md' -g 'CONTRIBUTING.md' -g 'CLAUDE.md' -g 'AGENT.md' -g '.cursor/rules/*' -g '.cursorrules' -g '.agent/**' -g '.agents/**' -g '.pi/**' -g 'AGENTS.*.md'`
|
||||
2. Read the root and nearest-scope `AGENTS.md`/`CONTRIBUTING.md` pair before editing.
|
||||
3. If alias files exist, normalize to one canonical source (`AGENTS.md` preferred when present; otherwise nearest alias), plus compatibility pointers or explicit symlink notes.
|
||||
4. Document conflicting instructions and precedence decisions.
|
||||
|
||||
## GitHub + AGENTS baseline
|
||||
|
||||
Source: https://docs.github.com/en/communities/setting-up-your-project-for-healthy-contributions/setting-guidelines-for-repository-contributors
|
||||
Source: https://agents.md/
|
||||
Source: https://github.blog/ai-and-ml/github-copilot/how-to-write-a-great-agents-md-lessons-from-over-2500-repositories/
|
||||
Source: https://cobusgreyling.substack.com/p/what-is-agentsmd
|
||||
Source: https://www.infoq.com/news/2025/08/agents-md/
|
||||
|
||||
Use these as default operating principles:
|
||||
|
||||
1. Keep `CONTRIBUTING.md` discoverable and actionable (`.github`, root, or `docs`).
|
||||
2. Keep agent instructions concrete: real commands, real paths, clear boundaries.
|
||||
3. Use explicit behavior boundaries for agents: `Always`, `Ask first`, `Never`.
|
||||
4. Keep contributor and agent rules aligned with actual repository workflows.
|
||||
5. Ensure clear guidance is provided to agents on if, when and how to raise issues and pull requests.
|
||||
|
||||
## Canonical and alias policy
|
||||
|
||||
Source: https://agents.md/
|
||||
Source: https://github.blog/ai-and-ml/github-copilot/how-to-write-a-great-agents-md-lessons-from-over-2500-repositories/
|
||||
|
||||
1. Treat `AGENTS.md` as canonical when present.
|
||||
2. If `AGENTS.md` is absent, treat the nearest alias file as canonical.
|
||||
3. Keep compatibility surfaces explicit: `AGENTS.md`, `AGENT.md`, `.cursorrules`, `.cursor/rules/*`, `.agent/`, `.agents/`, `.pi/`.
|
||||
4. If aliases are used, document how they map back to canonical policy (or symlink when supported).
|
||||
5. When repos use `.agents/` as canonical rule storage, keep `.cursor` as a compatibility symlink to `.agents` for Cursor rule auto-loading.
|
||||
6. Keep policy DRY: store one shared policy core and expose it via aliases/symlinks instead of duplicating rule text.
|
||||
|
||||
## Context-awareness by agent platform
|
||||
|
||||
Source: https://github.com/vercel-labs/agent-skills/blob/main/AGENTS.md
|
||||
Source: https://github.com/openai/codex/blob/main/AGENTS.md
|
||||
|
||||
1. For Cursor and Claude-style glob consumers, keep rule files narrow and bounded.
|
||||
2. Avoid over-referencing large path sets that inflate context for glob-based agents.
|
||||
3. For Codex-style workflows, prefer explicit file references and deterministic commands.
|
||||
4. Keep long runbooks outside top-level policy files; link to scoped docs.
|
||||
5. Ensure all agents have a happy path regardless so ensuring everything works across Codex, Claude and other coding agents.
|
||||
|
||||
## Symlink and compatibility operations
|
||||
|
||||
1. Preferred layout for multi-agent compatibility:
|
||||
- canonical rule directory: `.agents/`
|
||||
- Cursor compatibility path: `.cursor -> .agents` symlink
|
||||
- canonical policy doc: `AGENTS.md` pointing to `.agents` paths where relevant
|
||||
2. Validate symlink state before finalizing changes:
|
||||
- if `.agents/` exists and `.cursor` is missing, create `.cursor` symlink to `.agents`
|
||||
- if `.cursor` is a symlink to another target, fix target or document why it must differ
|
||||
- if `.cursor` is a real directory/file, treat as migration conflict and ask before replacement
|
||||
3. Validate rule payload through the canonical directory:
|
||||
- rules: `.agents/rules/*.mdc` with valid frontmatter (`description`, `globs`, `alwaysApply` as needed)
|
||||
- commands: `.agents/commands/*.md` when command routing is used
|
||||
- MCP config: `.agents/mcp.json` when MCP is in scope
|
||||
4. Keep Codex behavior explicit:
|
||||
- `AGENTS.md` is primary for Codex repository instructions
|
||||
- `.cursor` compatibility is for Cursor auto-loading and does not replace canonical AGENTS policy
|
||||
5. Record applied symlink fixes and unresolved compatibility gaps in validation notes.
|
||||
|
||||
## Dual-mode and deliverable standards
|
||||
|
||||
Source: https://github.blog/ai-and-ml/github-copilot/how-to-write-a-great-agents-md-lessons-from-over-2500-repositories/
|
||||
Source: https://agents.md/
|
||||
Source: https://github.com/openai/codex/blob/main/AGENTS.md
|
||||
Source: https://github.com/vercel-labs/agent-skills/blob/main/AGENTS.md
|
||||
|
||||
1. Author one shared policy core (same commands, boundaries, and precedence) for all agents.
|
||||
2. For Cursor/Claude-style agents, expose that core through glob-driven and bounded files (small `AGENTS.md`/rule surface).
|
||||
3. For Codex, expose that same core through explicit file references with precise scope.
|
||||
4. Where styles diverge, prefer the smallest common structure that satisfies both and avoid duplicating policy text.
|
||||
5. Treat AGENTS/CONTRIBUTING as first-class deliverables when in scope.
|
||||
6. Preserve required structure, constraints, and examples from existing files.
|
||||
7. Align wording and commands with active repository instructions.
|
||||
|
||||
## Proactive issue discovery and remediation
|
||||
|
||||
Source: https://github.blog/ai-and-ml/github-copilot/how-to-write-a-great-agents-md-lessons-from-over-2500-repositories/
|
||||
Source: https://github.com/openai/codex/blob/main/AGENTS.md
|
||||
Source: https://github.com/vercel-labs/agent-skills/blob/main/AGENTS.md
|
||||
|
||||
1. Run a conflict matrix review across AGENTS/aliases/CONTRIBUTING and related command/rule docs before finalizing.
|
||||
2. Treat the following as high-priority defects: missing referenced files, non-existent setup commands, command scope mismatches, and branch/commit policy conflicts.
|
||||
3. Do not stop at caveat-only notes when a low-risk fix is clear; apply the fix in the same pass.
|
||||
4. If a canonical entry file is missing (for example a directory `README.md` that docs depend on), create a minimal actionable file and update references.
|
||||
5. Long-running investigations are acceptable when needed to uncover cross-file drift, especially in agent-instruction ecosystems.
|
||||
|
||||
## Discovery
|
||||
|
||||
1. Agents prefer simple terminal commands so having a well defined `make *` or `npm run *` is ideal
|
||||
2. Agents can discover terminal commands through shell completion so providing shell completion helps
|
||||
|
||||
## CONTRIBUTING size and scope control
|
||||
|
||||
Source: https://contributing.md/how-to-build-contributing-md/
|
||||
Source: https://blog.codacy.com/best-practices-to-manage-an-open-source-project
|
||||
Source: https://mozillascience.github.io/working-open-workshop/contributing/
|
||||
Source: https://github.com/openclaw/openclaw/blob/main/CONTRIBUTING.md
|
||||
|
||||
1. Keep root `CONTRIBUTING.md` focused on setup, issue flow, PR flow, testing, and review gates.
|
||||
2. Use issue/PR template links instead of embedding every process detail inline.
|
||||
3. When the file grows too large, split by domain and link from root.
|
||||
4. Move any large content into docs if avalible (for example Mintlify/Fern/Sphinx workflows) to avoid large contributor guide.
|
||||
5. Optimize for agent/machine readability as well as humans.
|
||||
|
||||
## Example repos to emulate
|
||||
|
||||
Source: https://github.com/openclaw/openclaw/blob/main/AGENTS.md
|
||||
Source: https://github.com/openclaw/openclaw/blob/main/CONTRIBUTING.md
|
||||
Source: https://github.com/openclaw/openclaw/blob/main/VISION.md
|
||||
Source: https://github.com/openai/codex/blob/main/AGENTS.md
|
||||
Source: https://github.com/processing/p5.js/blob/main/AGENTS.md
|
||||
Source: https://github.com/vercel-labs/agent-skills/blob/main/AGENTS.md
|
||||
Source: https://github.com/agentsmd/agents.md/blob/main/AGENTS.md
|
||||
Source: https://github.com/rails/rails/blob/main/CONTRIBUTING.md
|
||||
Source: https://github.com/kubernetes/kubernetes/blob/master/CONTRIBUTING.md
|
||||
Source: https://github.com/atom/atom/blob/master/CONTRIBUTING.md
|
||||
Source: https://github.com/github/docs/blob/main/CONTRIBUTING.md
|
||||
Source: https://github.com/facebook/react/blob/main/CONTRIBUTING.md
|
||||
|
||||
1. OpenClaw: strong real-world alias policy and AGENTS/CONTRIBUTING/VISION cohesion.
|
||||
2. OpenAI Codex: strict command discipline and explicit scope control.
|
||||
3. p5.js: explicit AI-policy guardrails in agent instructions.
|
||||
4. Vercel + agentsmd spec: compact, context-efficient AGENTS patterns.
|
||||
5. Rails/Kubernetes/Atom/GitHub Docs/React: contributor guidance patterns at different project scales.
|
||||
|
||||
## Practical merge policy
|
||||
|
||||
When these rules conflict:
|
||||
|
||||
1. Preserve contributor and reader task success first.
|
||||
2. Preserve instruction clarity and unambiguous boundaries second.
|
||||
3. Preserve long-term maintainability and context-efficiency third.
|
||||
4. Add extra agent optimization only if it does not reduce human clarity or there is explict need.
|
||||
5. Use your judgement as the expert.
|
||||
@@ -0,0 +1,116 @@
|
||||
# Build Docs Playbook
|
||||
|
||||
Read `principles.md` first, then follow this execution flow.
|
||||
|
||||
## 1. Detect and align agent instruction and governance instructions
|
||||
|
||||
- Use `references/agent-and-contributing.md` as the source of truth for inventory, canonical/alias mapping, and precedence/conflict handling.
|
||||
- Apply the symlink compatibility policy when in scope (`.agents` canonical directory with `.cursor` compatibility symlink when required by tooling).
|
||||
- Long-running and extensive build investigations are acceptable when needed to resolve ambiguous or conflicting documentation sources.
|
||||
- When available, use sub-agents for bounded parallel inventory/cross-check tasks and merge results into one canonical decision set.
|
||||
- Capture required constraints before writing:
|
||||
- nested-agent rules, command/test requirements, PR workflow, and style checks.
|
||||
- Use the same command and validation expectations in proposed snippets and examples.
|
||||
|
||||
## 2. Inventory product documentation surfaces (not governance only)
|
||||
|
||||
- For repo-wide builds, include docs content surfaces in addition to AGENTS/CONTRIBUTING.
|
||||
- Inventory docs files and frameworks in scope (examples): `README*.md`, `docs/**`, `**/*.md`, `**/*.mdx`, `**/*.mdc`, `**/*.rst`, `**/*.rsc`, Fern/Mintlify config, Sphinx `conf.py`.
|
||||
- Build a coverage map before drafting so governance and product docs are both represented.
|
||||
- If scope is ambiguous, default to broader docs discovery first, then narrow intentionally.
|
||||
|
||||
## 3. Framework config and path mapping rules
|
||||
|
||||
- Detect framework/config first (for example Fern config, Sphinx `conf.py`, Mintlify config, or equivalent).
|
||||
- Resolve every referenced path relative to the file/config that declares it, not assumed repo root.
|
||||
- Treat filesystem paths and published URL routes as separate mappings; do not infer one from the other without config evidence.
|
||||
- Validate both layers:
|
||||
- config -> file exists on disk
|
||||
- config/nav/routing -> URL path is consistent and reachable
|
||||
- Record path-mapping assumptions and mismatches in handoff (`missing file`, `stale route`, `wrong base path`).
|
||||
|
||||
## 4. Define intent and success
|
||||
|
||||
- Audience, prerequisites, and job-to-be-done.
|
||||
- Expected reader outcome immediately after completion.
|
||||
- Doc type: tutorial, how-to, reference, explanation.
|
||||
- Success criteria: what must be true after publish.
|
||||
|
||||
## 5. Build structure before prose
|
||||
|
||||
- Follow the funnel: what/why, quickstart, next steps.
|
||||
- Keep headings informative and scannable.
|
||||
- Open each section with the takeaway sentence.
|
||||
- Add decision points with concrete branch guidance.
|
||||
- For OpenClaw docs work, choose a page type from `references/openclaw.md` before drafting.
|
||||
- Keep task-critical OpenClaw configuration inline; link exhaustive defaults, enums, schemas, generated references, and rare debugging workflows.
|
||||
|
||||
## 6. Build AGENTS.md and CONTRIBUTING.md intentionally
|
||||
|
||||
- Keep AGENTS.md structure consistent with `agents.md` ecosystem patterns:
|
||||
- include YAML frontmatter when present in repo style (`name`, `description`).
|
||||
- state persona scope and explicit instruction boundaries: `Always`, `Ask first`, `Never`.
|
||||
- include concrete commands and representative code examples.
|
||||
- For CONTRIBUTING.md, prioritize issue triage flow, PR expectations, setup/test commands, and review gates.
|
||||
- Add `Code of Conduct`, `Testing`, `Local checks`, and `PR expectations` sections when missing but required by the repo.
|
||||
- If CONTRIBUTING.md is becoming too large, split by scope into linked docs (for example, framework/tool-specific setup and release workflows) and keep the root file as a concise entry point.
|
||||
- Keep cross-file consistency: links from CONTRIBUTING.md to AGENTS.md (and vice versa) should be accurate and non-circular.
|
||||
- If multiple AGENTS.md files exist, document the directory-level scope and avoid conflicting advice.
|
||||
- If a required canonical entry file is missing (for example referenced `README.md` under a major directory), create the file in the same pass instead of adding a caveat-only note.
|
||||
- For new entry files, keep them minimal and actionable: purpose, prerequisites, concrete run commands, and pointers to deeper docs.
|
||||
|
||||
## 7. Keep agent context tight
|
||||
|
||||
- Author once, expose twice:
|
||||
- keep one shared policy core and avoid duplicating guidance in separate agent-specific files.
|
||||
- publish that core through bounded glob-friendly files for Cursor/Claude plus explicit path references for Codex.
|
||||
- For Cursor and Claude-style agents, avoid broad references. Use minimal globbing and narrow rule files that each serve one concern (for example, repo-wide setup, test rules, security checks).
|
||||
- Keep AGENTS and alias files short-to-medium; move detailed runbooks to linked docs.
|
||||
- For Codex, prefer explicit file references and concrete paths for exact reuse.
|
||||
- Avoid adding unrelated historical or process details to avoid token/context drift during future tool reads.
|
||||
|
||||
## 8. Brownfield build mode
|
||||
|
||||
- Match existing terminology, navigation, and component patterns.
|
||||
- Preserve existing IA unless there is a documented migration plan.
|
||||
- For rewrites, include a migration note from old to new paths.
|
||||
- Prefer smallest safe change set that improves utility.
|
||||
|
||||
## 9. Evergreen build mode
|
||||
|
||||
- Prefer stable concepts over release-tied narrative.
|
||||
- Isolate volatile details under clearly marked version sections.
|
||||
- Include maintenance signals: owners, refresh triggers, stale criteria.
|
||||
- Include lifecycle notes: deprecation and replacement paths.
|
||||
|
||||
## 10. Writing constraints
|
||||
|
||||
- Use precise language and short, imperative instructions.
|
||||
- Keep code examples copy-ready and self-contained.
|
||||
- Include common failure modes and safe defaults.
|
||||
- Avoid placeholder guidance that cannot be executed.
|
||||
|
||||
## 11. Agent and automation readiness
|
||||
|
||||
- Keep key facts in text (not image-only).
|
||||
- Prefer structured lists/tables when choices matter.
|
||||
- Add links and anchors that allow deterministic navigation.
|
||||
- Document what can be checked automatically in CI.
|
||||
|
||||
## 12. Build validation
|
||||
|
||||
- Validate commands and snippets where possible.
|
||||
- Verify links and references in changed sections.
|
||||
- Run a reference existence sweep for every path/command you introduced.
|
||||
- Verify docs-framework consistency when in scope (for example Sphinx/Fern config and referenced doc paths).
|
||||
- For OpenClaw docs work, apply the validation checklist in `references/openclaw.md`.
|
||||
|
||||
## 13. Multilingual parity mode (when applicable)
|
||||
|
||||
- Pick one source-of-truth language for technical accuracy and release timing.
|
||||
- Define parity target: full parity, staged parity, or intentional divergence per section.
|
||||
- Keep structure aligned across locales (headings, anchors, section order) when possible.
|
||||
- Preserve command/code correctness first; localize explanatory text second.
|
||||
- If parity is not feasible, add a visible note with missing scope and expected sync window.
|
||||
- Run a locale parity check for changed sections (added/removed steps, warnings, prerequisites).
|
||||
- Record unresolved checks explicitly in handoff.
|
||||
@@ -0,0 +1,128 @@
|
||||
# OpenClaw Documentation Overlay
|
||||
|
||||
Use this reference only for OpenClaw docs work. It layers OpenClaw-specific page
|
||||
types, navigation, preservation, and validation rules on top of the general
|
||||
technical-documentation skill.
|
||||
|
||||
## Reader Model
|
||||
|
||||
- Lead with the task the reader is trying to complete.
|
||||
- Give one recommended path before alternatives.
|
||||
- Keep main docs focused on the common path; move dense contracts and rare
|
||||
debugging detail to linked reference or troubleshooting pages.
|
||||
- Explain production risks exactly where the reader can make the mistake.
|
||||
- Link concepts, guides, references, CLI pages, SDK docs, testing, and
|
||||
troubleshooting so readers can continue without rereading.
|
||||
|
||||
## Page Types
|
||||
|
||||
Choose the page type before writing or reviewing:
|
||||
|
||||
- Overview: route readers to the right product area, integration path, or guide.
|
||||
- Quickstart: get a new user to a working result with the fewest safe steps.
|
||||
- Topic page: explain a major OpenClaw entity or surface end to end.
|
||||
- Guide: walk through one workflow from prerequisites to production readiness.
|
||||
- API/SDK/CLI reference: define every object, method, command, option, response,
|
||||
error, enum, default, and version rule in scope.
|
||||
- Testing guide: show sandbox setup, fixtures, simulated failures, and live-mode
|
||||
differences.
|
||||
- Troubleshooting guide: map observable symptoms to checks, causes, and fixes.
|
||||
- Governance file: keep agent/contributor policy concrete, scoped, and aligned
|
||||
with current OpenClaw repo behavior.
|
||||
|
||||
## Topic Pages
|
||||
|
||||
Use this shape for major-entity pages:
|
||||
|
||||
1. Title naming the entity or surface.
|
||||
2. Unheaded opening that says what it is, what it owns, and what it does not own.
|
||||
3. Requirements, only when setup needs accounts, versions, permissions, plugins,
|
||||
operating systems, or credentials.
|
||||
4. Quickstart with the recommended path and smallest reliable verification.
|
||||
5. Configuration with task-critical options inline and exhaustive details linked
|
||||
to reference docs.
|
||||
6. Major subtopics organized by reader intent, not under a generic "Subtopics"
|
||||
heading.
|
||||
7. Troubleshooting with observable failures and concrete checks.
|
||||
8. Related links to guides, references, commands, concepts, and adjacent topics.
|
||||
|
||||
## Guides
|
||||
|
||||
Use this shape for workflow pages:
|
||||
|
||||
1. Title naming the outcome, not the implementation detail.
|
||||
2. Opening that states what the reader can accomplish.
|
||||
3. Before you begin: accounts, keys, permissions, versions, tools, and
|
||||
assumptions.
|
||||
4. Choose a path, only when the reader must decide.
|
||||
5. Steps with verb-led headings, commands, expected output, and checks.
|
||||
6. Test with the smallest reliable proof that the workflow works.
|
||||
7. Production readiness: security, retries, limits, observability, migrations,
|
||||
and cleanup.
|
||||
8. Troubleshooting near the workflow that causes the failures.
|
||||
9. See also links to concepts, references, SDK docs, and adjacent guides.
|
||||
|
||||
## Docs IA And Navigation
|
||||
|
||||
- Read `docs/docs.json` before navigation changes.
|
||||
- Keep topic pages and common workflows on the main reader path.
|
||||
- Put exhaustive contracts, generated references, maintainer-only detail, and
|
||||
support material under `Reference` or another clearly scoped support page.
|
||||
- Keep generated `plugins/reference/*` children and redirect-only pages out of
|
||||
visible navigation unless explicitly required.
|
||||
- For moved pages, include a keep/drop/move/destination matrix in the handoff.
|
||||
- Add "Read when" hints for docs-list routing when creating or changing pages
|
||||
that participate in the docs index.
|
||||
|
||||
## Source-Backed Content
|
||||
|
||||
- CLI docs must match current flags, output, errors, and examples.
|
||||
- API/SDK docs must include fields, defaults, enum values, constraints, nullable
|
||||
behavior, lifecycle states, errors, and recovery guidance.
|
||||
- Config docs must align exported types, schema/help output, metadata, baselines,
|
||||
and current docs.
|
||||
- Dependency-backed behavior must be verified from upstream docs, source, or
|
||||
types before documenting defaults, timing, errors, or API behavior.
|
||||
- Separate current behavior, shipped behavior, planned behavior, and maintainer
|
||||
intent.
|
||||
|
||||
## Examples
|
||||
|
||||
- Prefer complete copy-pasteable commands and snippets.
|
||||
- Use realistic variable names and values.
|
||||
- Mark placeholders with angle-bracket names such as `<API_KEY>`.
|
||||
- Show expected success output when it helps verification.
|
||||
- Keep one conceptual unit per code block and use language-specific fences.
|
||||
- Avoid examples that hide setup, auth, error handling, or cleanup.
|
||||
- Never expose real secrets, live config, phone numbers, private videos, or
|
||||
credentials.
|
||||
|
||||
## Preservation Reviews
|
||||
|
||||
For rewrites or splits:
|
||||
|
||||
- Identify source units before rewriting: headings, paragraphs, tables, examples,
|
||||
CLI/API contracts, warnings, and troubleshooting facts.
|
||||
- Map each retained unit to a destination page or section.
|
||||
- Do not treat a broad "covered" row as proof for dense source material; use
|
||||
line- or claim-level evidence when the source unit is dense.
|
||||
- For dropped content, state whether it is obsolete, duplicated elsewhere,
|
||||
unsupported, or moved to a reference/support page.
|
||||
- When a docs-audit artifact is used, verify it is mapped audit data with
|
||||
non-empty `mappings[]`, not only inventory or reindexed JSON.
|
||||
|
||||
## Validation
|
||||
|
||||
Choose the narrowest proof that covers the touched surface:
|
||||
|
||||
- `pnpm docs:list`
|
||||
- `pnpm docs:check-mdx`
|
||||
- `pnpm docs:check-links`
|
||||
- `pnpm docs:check-i18n-glossary`
|
||||
- `pnpm format:docs:check` or `pnpm lint:docs`
|
||||
- `git diff --check`
|
||||
- generated-doc or inventory checks when generated references, plugin catalogs,
|
||||
labeler, or docs scripts changed
|
||||
- behavior tests or command probes when docs claim runtime behavior
|
||||
|
||||
If proof is blocked, say exactly which command was not run and why.
|
||||
@@ -0,0 +1,54 @@
|
||||
# Documentation Principles
|
||||
|
||||
This reference consolidates the core rules used by this skill.
|
||||
|
||||
## Matt Palmer: 8 rules for better docs
|
||||
|
||||
Source: https://mattpalmer.io/posts/2025/10/8-rules-for-better-docs/
|
||||
|
||||
Use these as default operating principles:
|
||||
|
||||
1. Write for humans, optimize for agents.
|
||||
2. Start with a funnel: what/why, quickstart, next steps.
|
||||
3. Use Diataxis to scaffold content.
|
||||
4. Write with AI, but structure for agents.
|
||||
5. Offload routine docs operations to background agents.
|
||||
6. Automate quality with CI.
|
||||
7. Automate scaffolding and repetitive workflow tasks.
|
||||
8. Make contribution easy and visible.
|
||||
|
||||
## OpenAI cookbook: what makes documentation good
|
||||
|
||||
Source: https://cookbook.openai.com/articles/what_makes_documentation_good
|
||||
|
||||
Key quality constraints:
|
||||
|
||||
- Prefer specific and accurate terminology over niche jargon.
|
||||
- Keep examples self-contained and minimize dependencies.
|
||||
- Prioritize high-value topics over edge-case depth.
|
||||
- Do not teach unsafe patterns (for example, exposed secrets).
|
||||
- Open with context that helps readers orient quickly.
|
||||
- Apply empathy and override rigid rules when it clearly improves outcomes.
|
||||
|
||||
## Practical merge policy
|
||||
|
||||
When these rules conflict:
|
||||
|
||||
1. Preserve reader task success first.
|
||||
2. Preserve structural clarity second.
|
||||
3. Preserve long-term maintainability third.
|
||||
4. Add agent optimization only if it does not reduce human clarity.
|
||||
|
||||
For agent-instructions and contributor-governance specifics (AGENTS/aliases/CONTRIBUTING), use `references/agent-and-contributing.md` as the detailed additional source of truth.
|
||||
|
||||
When the target repo or request is OpenClaw-specific, layer `references/openclaw.md` on top of these general rules. Otherwise ignore that repo-specific overlay.
|
||||
|
||||
## Execution policy for this skill
|
||||
|
||||
- Long-running and extensive investigations are allowed for both build and review work when needed to resolve ambiguity or cross-file drift.
|
||||
- Use sub-agents when available for bounded parallel discovery, verification, or cross-source comparison.
|
||||
- Keep one merged outcome: sub-agent outputs must be normalized into a single consistent recommendation/fix set.
|
||||
|
||||
## Multilingual parity rule
|
||||
|
||||
When docs exist in multiple languages, target cross-locale parity for task-critical content (steps, warnings, prerequisites, and limits). If full parity is not possible, publish explicit parity status and sync intent.
|
||||
@@ -0,0 +1,121 @@
|
||||
# Review Docs Playbook
|
||||
|
||||
Read `principles.md` first, then apply this checklist.
|
||||
|
||||
## 1. Scope and classification
|
||||
|
||||
- Identify doc type and target audience.
|
||||
- Confirm brownfield vs evergreen intent.
|
||||
- Confirm expected outcome for the reader.
|
||||
- For full-repo reviews, explicitly include both governance surfaces and product-doc surfaces (`docs/`, README trees, `.md/.mdx/.mdc`, `.rst/.rsc`, framework docs configs).
|
||||
- For OpenClaw docs reviews, apply `references/openclaw.md` for page type, docs IA, preservation, examples, and validation checks.
|
||||
|
||||
## 2. Investigation behavior
|
||||
|
||||
- Proactively find issues and risks without waiting for repeated prompts.
|
||||
- If there are signals of deeper problems, continue investigation beyond the first pass.
|
||||
- Long-running and extensive investigations are acceptable when needed for confidence and correctness.
|
||||
- When available, use sub-agents for bounded parallel discovery (for example file-inventory, command validation, or cross-doc consistency checks), then merge to one final issue set.
|
||||
- When no issues are found, state that explicitly and call out residual risks or validation gaps.
|
||||
- Default to `apply-fixes` for high-confidence documentation defects unless the user explicitly requests `report-only`.
|
||||
- Do not stop at AGENTS/CONTRIBUTING checks when the task is documentation-wide; continue into docs-content and docs-framework surfaces.
|
||||
|
||||
## 3. Governance surface review
|
||||
|
||||
- Use `references/agent-and-contributing.md` as the source of truth for inventory, canonical/alias mapping, and precedence/conflict handling.
|
||||
For AGENTS.md:
|
||||
|
||||
- confirm persona intent, scope, and command/tool boundaries are explicit.
|
||||
- check frontmatter style matches repo conventions when present.
|
||||
- ensure `Always`, `Ask first`, and `Never` boundaries are present when expected.
|
||||
- require concrete command examples and repo-specific paths to avoid ambiguity.
|
||||
|
||||
For CONTRIBUTING.md:
|
||||
|
||||
- verify issue/PR workflow is complete and actionable.
|
||||
- ensure local setup, lint/test commands, and review criteria are accurate.
|
||||
- ensure governance does not conflict with nested AGENTS instructions.
|
||||
- flag oversized files that should be split into linked section docs (for example tool-specific setup and release docs).
|
||||
|
||||
For agent-platform awareness:
|
||||
|
||||
- confirm references are minimal and scoped for Cursor/Claude glob behavior.
|
||||
- confirm Codex-facing guidance uses explicit file references.
|
||||
- confirm both surfaces represent the same shared policy core (commands, boundaries, and precedence), not divergent guidance.
|
||||
- audit `.agents`/`.cursor` compatibility behavior:
|
||||
- verify canonical rule directory and symlink state match repo policy
|
||||
- verify symlink target integrity and platform/tooling expectations
|
||||
- verify AGENTS policy references remain canonical for Codex even when `.cursor` compatibility exists
|
||||
- check for context bloat from duplicated policy statements across agent and contributor files.
|
||||
- check for conflicting rules, skills and agent instructions
|
||||
- check for conflicting information in agent instructions vs codebase
|
||||
- check for broken or missing referenced files (for example README/index files named as canonical entry points).
|
||||
- check for setup/command drift (for example non-existent install commands, root-level commands that should be module-scoped).
|
||||
|
||||
## 4. Product documentation surface review
|
||||
|
||||
- Verify docs IA coverage across root/module `README*` files and `docs/**` trees.
|
||||
- Review framework-native docs sources in scope (for example Fern, Mintlify, Sphinx, MkDocs) and ensure guidance matches actual source-of-truth files.
|
||||
- Check `.md/.mdx/.mdc/.rst/.rsc` for stale commands, missing prerequisites, and broken cross-links.
|
||||
- Confirm referenced doc paths and anchors exist.
|
||||
- Flag docs that should be split/merged to improve discoverability and maintenance.
|
||||
- For OpenClaw docs, check `docs/docs.json`, docs-list routing hints, main path versus `Reference` placement, and generated-reference visibility.
|
||||
- For OpenClaw rewrites or page splits, require source-backed keep/drop/move/destination coverage for important claims, warnings, examples, commands, fields, and troubleshooting facts.
|
||||
|
||||
## 5. Framework config and path mapping checks
|
||||
|
||||
- Detect and read framework config first (for example Fern config, Sphinx `conf.py`, Mintlify config, or equivalent).
|
||||
- Resolve path references relative to the declaring file/config.
|
||||
- Treat filesystem paths and published URL routes as separate maps; verify both.
|
||||
- Flag path-map drift explicitly (`missing file`, `stale route`, `wrong base path`).
|
||||
|
||||
## 6. Structural review
|
||||
|
||||
- Funnel check: what/why, quickstart, next steps.
|
||||
- Validate heading flow and navigation discoverability.
|
||||
- Flag critical content trapped in images or buried sections.
|
||||
- Check Diataxis alignment and split mixed-purpose sections.
|
||||
- For OpenClaw docs, confirm the content matches an explicit page type from `references/openclaw.md`.
|
||||
|
||||
## 7. Writing quality review
|
||||
|
||||
- Check for concise, scannable paragraphs.
|
||||
- Remove ambiguous pronouns and undefined terms.
|
||||
- Verify examples are executable and scoped correctly.
|
||||
- Verify tone is directive, technical, and non-hand-wavy.
|
||||
|
||||
## 8. Brownfield review mode
|
||||
|
||||
- Verify compatibility with existing docs IA and conventions.
|
||||
- Verify anchors, redirects, and cross-doc links remain valid.
|
||||
- Flag regressions in onboarding and task completion paths.
|
||||
- Ensure changed terminology is intentionally propagated.
|
||||
|
||||
## 9. Evergreen review mode
|
||||
|
||||
- Flag date-stamped or brittle wording without version scope.
|
||||
- Check ownership and refresh signals are present.
|
||||
- Ensure recommendations remain valid after routine product evolution.
|
||||
- Flag missing deprecation/migration guidance.
|
||||
|
||||
## 10. Tooling and platform review
|
||||
|
||||
Read `tooling.md` if platform fit is uncertain.
|
||||
|
||||
- Check whether content uses platform primitives effectively.
|
||||
- Flag structure that fights the chosen docs platform.
|
||||
- Recommend targeted platform-aware improvements.
|
||||
|
||||
## 11. Multilingual parity review (when applicable)
|
||||
|
||||
- Confirm declared source-of-truth language and expected parity policy.
|
||||
- Compare changed sections across locales for step/order/warning drift.
|
||||
- Flag missing updates to prerequisites, version notes, limits, and safety guidance.
|
||||
- Allow intentional divergence only when rationale is explicit and user-impact is low.
|
||||
- Require a reader-visible status note when locale parity is partial.
|
||||
|
||||
## 12. Output format
|
||||
|
||||
1. Blocking issues (file + required fix)
|
||||
2. Non-blocking improvements
|
||||
3. Validation notes (done vs pending)
|
||||
@@ -0,0 +1,32 @@
|
||||
# Documentation Tooling Guide
|
||||
|
||||
Source: https://www.mintlify.com/blog/top-7-api-documentation-tools-of-2025
|
||||
|
||||
Use this file when deciding build/review expectations for doc platforms.
|
||||
|
||||
## Tool-selection checkpoints
|
||||
|
||||
- Existing stack lock-in: do not force migration for minor gains.
|
||||
- API workflow depth: generated references, OpenAPI support, testability.
|
||||
- Collaboration model: docs-as-code, review workflow, versioning.
|
||||
- Runtime quality: search, navigation, and copy-ready code snippets.
|
||||
- AI readiness: structured content, stable URLs, machine-friendly layout yet human readable.
|
||||
- Human readiness: reading complexity, reading UX, navigation depth, minimize jargon.
|
||||
|
||||
## Apply in brownfield mode
|
||||
|
||||
- Prioritize compatibility with the current platform.
|
||||
- Use available components and style conventions before introducing new patterns.
|
||||
- Propose migration only when current constraints block critical outcomes.
|
||||
|
||||
## Apply in evergreen mode
|
||||
|
||||
- Favor platforms and templates that make routine updates low-friction.
|
||||
- Standardize section templates to reduce drift.
|
||||
- Capture ownership, update cadence, and stale-content detection rules.
|
||||
|
||||
## Review implications
|
||||
|
||||
- Check whether content uses platform primitives correctly (tabs, callouts, endpoint blocks).
|
||||
- Flag docs that are technically correct but hard to scan in the chosen platform.
|
||||
- Recommend platform-specific improvements only when they reduce cognitive load.
|
||||
@@ -0,0 +1,21 @@
|
||||
# THIS IS AUTOGENERATED. DO NOT EDIT MANUALLY
|
||||
version = 1
|
||||
name = "ClawHub"
|
||||
|
||||
[setup]
|
||||
script = "bun run setup:worktree -- --quiet && bun scripts/dev-worktree.ts --detach"
|
||||
|
||||
[[actions]]
|
||||
name = "Run"
|
||||
icon = "run"
|
||||
command = "bun run dev:worktree"
|
||||
|
||||
[[actions]]
|
||||
name = "Convex Dev"
|
||||
icon = "tool"
|
||||
command = "bun run setup:worktree -- --quiet && bunx convex dev --typecheck=disable"
|
||||
|
||||
[[actions]]
|
||||
name = "Seed Dev DB"
|
||||
icon = "tool"
|
||||
command = "bun run seed:dev"
|
||||
@@ -0,0 +1,20 @@
|
||||
[list]
|
||||
url = "http://127.0.0.1:{{ (repo ~ '-' ~ branch) | hash_port }}"
|
||||
|
||||
[[pre-start]]
|
||||
env = "wt step copy-ignored || true; bun run setup:worktree -- --quiet --force --prefer-fallback"
|
||||
|
||||
[[pre-start]]
|
||||
deps = "test -x node_modules/.bin/vite || bun install"
|
||||
|
||||
[post-start]
|
||||
dev = "bun scripts/dev-worktree.ts --detach --seed --port {{ (repo ~ '-' ~ branch) | hash_port }}"
|
||||
|
||||
[pre-remove]
|
||||
dev = "if test -f .codex/runtime/dev-worktree.pid; then pid=$(cat .codex/runtime/dev-worktree.pid); kill -TERM -$pid 2>/dev/null || kill $pid 2>/dev/null || true; rm -f .codex/runtime/dev-worktree.pid; fi"
|
||||
|
||||
[aliases]
|
||||
dev = "wt --yes hook pre-start && bun scripts/dev-worktree.ts --detach --seed --port {{ (repo ~ '-' ~ branch) | hash_port }}"
|
||||
setup = "wt --yes hook pre-start"
|
||||
stop = "if test -f .codex/runtime/dev-worktree.pid; then pid=$(cat .codex/runtime/dev-worktree.pid); kill -TERM -$pid 2>/dev/null || kill $pid 2>/dev/null || true; rm -f .codex/runtime/dev-worktree.pid; fi"
|
||||
url = "echo http://127.0.0.1:{{ (repo ~ '-' ~ branch) | hash_port }}"
|
||||
@@ -0,0 +1,33 @@
|
||||
profile: clawhub-check
|
||||
provider: blacksmith-testbox
|
||||
blacksmith:
|
||||
org: openclaw
|
||||
workflow: .github/workflows/ci-check-testbox.yml
|
||||
job: check
|
||||
ref: main
|
||||
idleTimeout: 90m
|
||||
debug: false
|
||||
sync:
|
||||
delete: true
|
||||
checksum: false
|
||||
gitSeed: true
|
||||
fingerprint: true
|
||||
baseRef: main
|
||||
exclude:
|
||||
- .artifacts
|
||||
- .codex
|
||||
- .DS_Store
|
||||
- coverage
|
||||
- dist
|
||||
- dist-ssr
|
||||
- node_modules
|
||||
- .output
|
||||
- playwright-report
|
||||
- test-results
|
||||
env:
|
||||
allow:
|
||||
- CI
|
||||
- NODE_OPTIONS
|
||||
- CLAWHUB_*
|
||||
- VITE_CONVEX_URL
|
||||
- VITE_CONVEX_SITE_URL
|
||||
+12
-3
@@ -1,9 +1,7 @@
|
||||
# Frontend
|
||||
VITE_CONVEX_URL=
|
||||
VITE_CONVEX_SITE_URL=
|
||||
VITE_SOULHUB_SITE_URL=
|
||||
VITE_SOULHUB_HOST=
|
||||
VITE_SITE_MODE=
|
||||
VITE_ENABLE_DEV_AUTH=
|
||||
SITE_URL=http://localhost:3000
|
||||
CONVEX_SITE_URL=
|
||||
|
||||
@@ -15,5 +13,16 @@ AUTH_GITHUB_SECRET=
|
||||
JWT_PRIVATE_KEY=
|
||||
JWKS=
|
||||
|
||||
# Local dev personas
|
||||
DEV_AUTH_ENABLED=
|
||||
DEV_AUTH_CONVEX_DEPLOYMENT=
|
||||
DEV_AUTH_SITE_URL=
|
||||
DEV_AUTH_SECRET=
|
||||
|
||||
# Embeddings
|
||||
OPENAI_API_KEY=
|
||||
|
||||
# Transactional email
|
||||
RESEND_API_KEY=
|
||||
CLAWHUB_SECURITY_EMAIL_FROM=ClawHub Security <noreply@notifications.openclaw.ai>
|
||||
CLAWHUB_NOREPLY_FROM=ClawHub <noreply@notifications.openclaw.ai>
|
||||
|
||||
+32
-26
@@ -5,9 +5,11 @@
|
||||
# If you add overlapping rules below the secops block, include @openclaw/openclaw-secops
|
||||
# on those entries too or you can silently remove required secops review.
|
||||
# Security-sensitive code, config, workflows, and docs require secops review.
|
||||
/.github/actions/ @openclaw/openclaw-secops
|
||||
/.github/actionlint.yaml @openclaw/openclaw-secops
|
||||
/.github/codeql/ @openclaw/openclaw-secops
|
||||
/.github/dependabot.yml @openclaw/openclaw-secops
|
||||
/.github/workflows/ @openclaw/openclaw-secops
|
||||
/scripts/check-staged-secrets.mjs @openclaw/openclaw-secops
|
||||
/scripts/clawhub-cli-npm-publish.sh @openclaw/openclaw-secops
|
||||
/scripts/clawhub-cli-npm-release-check.mjs @openclaw/openclaw-secops
|
||||
/scripts/github/clawhub-rescan-auto-response.mjs @openclaw/openclaw-secops
|
||||
@@ -16,15 +18,14 @@
|
||||
/convex/schema.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/convex/auth.config.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/convex/auth.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/convex/commentModeration.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/convex/http.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/convex/httpApi.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/convex/httpApiV1/ @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/convex/packagePublishTokens.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/convex/packages.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/convex/maintenance.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/convex/publishers.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/convex/rateLimits.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/convex/rescanRequests.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/convex/skills.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/convex/skillTransfers.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/convex/tokens.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
@@ -33,7 +34,6 @@
|
||||
/convex/webhooks.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/convex/lib/access.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/convex/lib/apiTokenAuth.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/convex/lib/commentScamPrompt.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/convex/lib/githubActionsOidc.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/convex/lib/httpHeaders.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/convex/lib/httpRateLimit.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
@@ -42,6 +42,7 @@
|
||||
/convex/lib/moderationEngine.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/convex/lib/moderationReasonCodes.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/convex/lib/packageRegistry.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/convex/lib/packageSearchDigest.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/convex/lib/packageSecurity.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/convex/lib/publishers.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/convex/lib/publishLimits.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
@@ -53,25 +54,24 @@
|
||||
/convex/lib/staticPublishScan.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/convex/lib/tokens.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/convex/lib/webhooks.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/convex/model/packages/rescans.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/convex/model/rescans/policy.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/convex/model/skills/rescans.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
|
||||
# Frontend auth, admin, publish, upload, and security-review surfaces.
|
||||
/src/lib/packageApi.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/src/lib/packageUpload.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/src/lib/roles.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/src/lib/uploadFiles.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/src/lib/uploadUtils.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/src/routes/admin.tsx @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/src/routes/cli/auth.tsx @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/src/routes/packages/new.tsx @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/src/routes/publish-plugin.tsx @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/src/routes/publish-skill.tsx @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/src/routes/upload.tsx @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/src/routes/upload/ @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/src/routes/$owner/$slug/security/ @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/src/routes/plugins/$name/security/ @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/src/lib/packageApi.ts @openclaw/openclaw-secops @BunsDev
|
||||
/src/lib/packageUpload.ts @openclaw/openclaw-secops @BunsDev
|
||||
/src/lib/roles.ts @openclaw/openclaw-secops @BunsDev
|
||||
/src/lib/uploadFiles.ts @openclaw/openclaw-secops @BunsDev
|
||||
/src/lib/uploadUtils.ts @openclaw/openclaw-secops @BunsDev
|
||||
/src/routes/admin.tsx @openclaw/openclaw-secops @BunsDev
|
||||
/src/routes/cli/auth.tsx @openclaw/openclaw-secops @BunsDev
|
||||
/src/routes/packages/new.tsx @openclaw/openclaw-secops @BunsDev
|
||||
/src/routes/plugins/publish.tsx @openclaw/openclaw-secops @BunsDev
|
||||
/src/routes/publish-plugin.tsx @openclaw/openclaw-secops @BunsDev
|
||||
/src/routes/publish-skill.tsx @openclaw/openclaw-secops @BunsDev
|
||||
/src/routes/skills/publish.tsx @openclaw/openclaw-secops @BunsDev
|
||||
/src/routes/upload.tsx @openclaw/openclaw-secops @BunsDev
|
||||
/src/routes/upload/ @openclaw/openclaw-secops @BunsDev
|
||||
/src/routes/$owner/$slug/security/ @openclaw/openclaw-secops @BunsDev
|
||||
/src/routes/plugins/$name/security/ @openclaw/openclaw-secops @BunsDev
|
||||
|
||||
# CLI auth, admin, publishing, ownership, and package-contract surfaces.
|
||||
/packages/clawhub/src/browserAuth.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
@@ -86,28 +86,34 @@
|
||||
/packages/clawhub/src/cli/commands/ownership.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/packages/clawhub/src/cli/commands/packages.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/packages/clawhub/src/cli/commands/publish.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/packages/clawhub/src/cli/commands/rescan.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/packages/clawhub/src/cli/commands/transfer.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/packages/clawhub/src/cli/commands/sync.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/packages/clawhub/src/cli/scanSkills.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/packages/clawhub/src/schema/openclawContract.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/packages/clawhub/src/schema/packages.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/packages/clawhub/src/schema/routes.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/packages/clawhub/src/schema/schemas.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/packages/clawhub/src/schema/textFiles.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/packages/schema/src/openclawContract.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/packages/schema/src/index.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/packages/schema/src/packages.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/packages/schema/src/pluginCategories.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/packages/schema/src/routes.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/packages/schema/src/schemas.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/packages/schema/src/textFiles.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/packages/schema/dist/index.d.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/packages/schema/dist/index.js @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/packages/schema/dist/index.js.map @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/packages/schema/dist/pluginCategories.d.ts @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/packages/schema/dist/pluginCategories.js @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/packages/schema/dist/pluginCategories.js.map @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
|
||||
# Security, auth, API, webhook, and deployment documentation.
|
||||
/docs/acceptable-usage.md @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/docs/api.md @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/docs/auth.md @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/docs/deploy.md @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/docs/github-import.md @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/docs/http-api.md @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/docs/namespace-claims.md @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/docs/security.md @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/docs/webhook.md @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/specs/deploy.md @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/specs/github-import.md @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
/public/api/v1/openapi.json @openclaw/openclaw-secops @Patrick-Erichsen
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
name: Org / Namespace Claim
|
||||
description: Request review for an org, brand, package scope, or namespace ownership dispute.
|
||||
title: "Org claim: "
|
||||
labels:
|
||||
- "area: moderation"
|
||||
- "area: security"
|
||||
- "status: review"
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Use this form when you believe a ClawHub org, owner handle, package scope, skill slug, plugin package, or related namespace should be reserved, transferred, renamed, hidden, quarantined, aliased, or reviewed because of real-world project, brand, or organizational ownership.
|
||||
|
||||
Public GitHub issues must not include secrets, private documents, private legal files, personal identity documents, API tokens, DNS control tokens, or other sensitive material. Share public, non-sensitive proof here and tell us below if staff needs to arrange a private channel for sensitive evidence.
|
||||
|
||||
This is not the ban/account appeal flow. If your ClawHub account was banned, disabled, or cannot sign in because of account standing, use the ClawHub appeal form instead: https://appeals.openclaw.ai/
|
||||
|
||||
Related policy discussion: https://github.com/openclaw/clawhub/issues/2320
|
||||
- type: input
|
||||
id: claimed_namespace
|
||||
attributes:
|
||||
label: Claimed owner, org, scope, or namespace
|
||||
description: Which ClawHub owner handle, org handle, package scope, skill slug, or package namespace are you claiming?
|
||||
placeholder: "@example-org, example-org, @example-org/example-plugin, or example-skill"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: disputed_resources
|
||||
attributes:
|
||||
label: Disputed ClawHub resources
|
||||
description: Link every relevant ClawHub URL, package name, skill slug, owner page, or related GitHub issue.
|
||||
placeholder: |
|
||||
- https://clawhub.ai/example-org/example-skill
|
||||
- https://clawhub.ai/plugins/@example-org/example-plugin
|
||||
- Package or skill names involved:
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: claimant_relationship
|
||||
attributes:
|
||||
label: Claimant identity and relationship
|
||||
description: Explain who is making the request and how they are connected to the org, project, package, or brand. Keep it public-safe.
|
||||
placeholder: |
|
||||
I maintain the upstream project at...
|
||||
I am an admin/owner/member of...
|
||||
Public profile or docs showing that relationship:
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: requested_outcome
|
||||
attributes:
|
||||
label: Requested outcome
|
||||
description: Pick every outcome that would resolve the claim.
|
||||
multiple: true
|
||||
options:
|
||||
- Reserve namespace or package
|
||||
- Transfer ownership
|
||||
- Rename existing resource
|
||||
- Hide or quarantine current resource
|
||||
- Add alias or redirect
|
||||
- Review only / need staff guidance
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: public_proof
|
||||
attributes:
|
||||
label: Public proof links and explanation
|
||||
description: Add public links and explain what each proves. Useful proof includes GitHub org/repo control, domain or official email-domain proof, package-registry scope control, trademark or brand evidence, source repo history, package history, and public project docs. Do not paste secrets, tokens, private documents, or private legal files.
|
||||
placeholder: |
|
||||
- https://github.com/example-org/example-project proves...
|
||||
- https://example.org/docs/clawhub proves...
|
||||
- https://www.npmjs.com/org/example-org proves...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: current_owner_context
|
||||
attributes:
|
||||
label: Current owner, history, or context
|
||||
description: Share what you know about the current ClawHub owner, prior transfers, project rename history, or attempted contact.
|
||||
placeholder: |
|
||||
The current listing appears to be owned by...
|
||||
We contacted...
|
||||
The project was renamed from...
|
||||
- type: textarea
|
||||
id: urgency_context
|
||||
attributes:
|
||||
label: User harm or urgency
|
||||
description: Explain impact, affected users, install paths, or other facts that should affect triage priority. Say "No urgent user harm known" if this is not urgent.
|
||||
placeholder: |
|
||||
Users are being directed from...
|
||||
The package is referenced by...
|
||||
We believe this is urgent because...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: sensitive_evidence
|
||||
attributes:
|
||||
label: Sensitive evidence or private staff channel
|
||||
description: Say whether public evidence is enough or whether staff needs to arrange a private channel. Summarize the kind of private evidence without including the sensitive material itself.
|
||||
placeholder: |
|
||||
Public evidence is enough.
|
||||
|
||||
Or:
|
||||
|
||||
We need a private staff channel for DNS challenge proof, private legal documents, or other sensitive evidence.
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: acknowledgements
|
||||
attributes:
|
||||
label: Acknowledgements
|
||||
options:
|
||||
- label: I have not included secrets, API tokens, private documents, private legal files, personal identity documents, or other sensitive material in this public issue.
|
||||
required: true
|
||||
- label: I understand this public issue may be linked from ClawHub moderation or namespace policy discussions.
|
||||
required: true
|
||||
@@ -0,0 +1,104 @@
|
||||
name: RFC
|
||||
description: Propose a ClawHub policy, product, trust, or interface decision for feedback.
|
||||
title: "RFC: "
|
||||
labels:
|
||||
- "type: rfc"
|
||||
- "status: review"
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Use RFCs for decisions that need visible feedback before they become policy, product behavior, or public API contract. Accepted repo RFC files live under `rfcs/`, not `docs/`, so draft/decision records do not publish to the docs site. Keep sensitive enforcement details, private reports, exploit specifics, and scanner thresholds out of the public issue.
|
||||
- type: dropdown
|
||||
id: area
|
||||
attributes:
|
||||
label: Area
|
||||
description: Pick the primary area this RFC affects.
|
||||
options:
|
||||
- Moderation / policy
|
||||
- Security / trust
|
||||
- Product / UX
|
||||
- API / CLI
|
||||
- Documentation
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: context
|
||||
attributes:
|
||||
label: Context
|
||||
description: What problem, decision, or ambiguity does this RFC address?
|
||||
placeholder: |
|
||||
ClawHub needs a clearer policy for...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: goals
|
||||
attributes:
|
||||
label: Goals
|
||||
description: What should this RFC achieve?
|
||||
placeholder: |
|
||||
- Make enforcement expectations understandable to users.
|
||||
- Give moderators a consistent decision boundary.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: non_goals
|
||||
attributes:
|
||||
label: Non-goals
|
||||
description: What is intentionally out of scope?
|
||||
placeholder: |
|
||||
- This RFC does not expose internal scanner thresholds.
|
||||
- This RFC does not decide implementation details for every moderation tool.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: proposal
|
||||
attributes:
|
||||
label: Proposal
|
||||
description: Describe the proposed policy, behavior, or decision.
|
||||
placeholder: |
|
||||
ClawHub should...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: examples
|
||||
attributes:
|
||||
label: Examples
|
||||
description: Give concrete allowed, not allowed, or edge-case examples.
|
||||
placeholder: |
|
||||
Allowed:
|
||||
- Defensive security review with explicit scope and evidence.
|
||||
|
||||
Not allowed:
|
||||
- Account takeover, evasion, or non-consensual surveillance workflows.
|
||||
|
||||
Edge cases:
|
||||
- ...
|
||||
- type: textarea
|
||||
id: user_impact
|
||||
attributes:
|
||||
label: User impact
|
||||
description: How does this affect authors, users, moderators, API consumers, or external contributors?
|
||||
placeholder: |
|
||||
Authors will...
|
||||
Users will...
|
||||
Moderators will...
|
||||
- type: textarea
|
||||
id: open_questions
|
||||
attributes:
|
||||
label: Open questions
|
||||
description: What feedback would be most useful before a decision?
|
||||
placeholder: |
|
||||
- Should appeals be handled in-product, through GitHub, or both?
|
||||
- What examples would make this clearer?
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: feedback_deadline
|
||||
attributes:
|
||||
label: Feedback deadline
|
||||
description: Use an absolute date. Normal RFCs should stay open for 7-14 days unless urgent.
|
||||
placeholder: "YYYY-MM-DD"
|
||||
validations:
|
||||
required: true
|
||||
@@ -0,0 +1,13 @@
|
||||
name: Setup Bun
|
||||
description: Install the pinned Bun runtime and workspace dependencies.
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
|
||||
with:
|
||||
bun-version: 1.3.10
|
||||
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: bun install --frozen-lockfile
|
||||
@@ -16,7 +16,6 @@ query-filters:
|
||||
paths:
|
||||
- convex/auth.config.ts
|
||||
- convex/auth.ts
|
||||
- convex/commentModeration.ts
|
||||
- convex/http.ts
|
||||
- convex/httpApi.ts
|
||||
- convex/httpApiV1
|
||||
@@ -24,7 +23,6 @@ paths:
|
||||
- convex/packages.ts
|
||||
- convex/publishers.ts
|
||||
- convex/rateLimits.ts
|
||||
- convex/rescanRequests.ts
|
||||
- convex/skills.ts
|
||||
- convex/skillTransfers.ts
|
||||
- convex/tokens.ts
|
||||
@@ -33,7 +31,6 @@ paths:
|
||||
- convex/webhooks.ts
|
||||
- convex/lib/access.ts
|
||||
- convex/lib/apiTokenAuth.ts
|
||||
- convex/lib/commentScamPrompt.ts
|
||||
- convex/lib/githubActionsOidc.ts
|
||||
- convex/lib/httpHeaders.ts
|
||||
- convex/lib/httpRateLimit.ts
|
||||
@@ -53,9 +50,6 @@ paths:
|
||||
- convex/lib/staticPublishScan.ts
|
||||
- convex/lib/tokens.ts
|
||||
- convex/lib/webhooks.ts
|
||||
- convex/model/packages/rescans.ts
|
||||
- convex/model/rescans/policy.ts
|
||||
- convex/model/skills/rescans.ts
|
||||
|
||||
paths-ignore:
|
||||
- "**/node_modules"
|
||||
|
||||
@@ -26,10 +26,7 @@ paths:
|
||||
- packages/clawhub/src/cli/commands/ownership.ts
|
||||
- packages/clawhub/src/cli/commands/packages.ts
|
||||
- packages/clawhub/src/cli/commands/publish.ts
|
||||
- packages/clawhub/src/cli/commands/rescan.ts
|
||||
- packages/clawhub/src/cli/commands/sync.ts
|
||||
- packages/clawhub/src/cli/commands/transfer.ts
|
||||
- packages/clawhub/src/cli/scanSkills.ts
|
||||
- packages/clawhub/src/schema/openclawContract.ts
|
||||
- packages/clawhub/src/schema/packages.ts
|
||||
- packages/clawhub/src/schema/routes.ts
|
||||
|
||||
@@ -17,8 +17,9 @@ paths:
|
||||
- src/components/DetailSecuritySummary.tsx
|
||||
- src/components/MarkdownPreview.tsx
|
||||
- src/components/PackageSourceChooser.tsx
|
||||
- src/components/SecurityScannerPage.tsx
|
||||
- src/components/SecurityAuditPage.tsx
|
||||
- src/components/SkillSecurityScanResults.tsx
|
||||
- src/components/securityAuditModel.ts
|
||||
- src/lib/authErrorMessage.ts
|
||||
- src/lib/packageApi.ts
|
||||
- src/lib/packageUpload.ts
|
||||
@@ -32,12 +33,18 @@ paths:
|
||||
- src/routes/admin.tsx
|
||||
- src/routes/cli/auth.tsx
|
||||
- src/routes/packages/new.tsx
|
||||
- src/routes/plugins/publish.tsx
|
||||
- src/routes/publish-plugin.tsx
|
||||
- src/routes/publish-skill.tsx
|
||||
- src/routes/skills/publish.tsx
|
||||
- src/routes/upload.tsx
|
||||
- src/routes/upload
|
||||
- src/routes/$owner/$slug/security-audit.tsx
|
||||
- src/routes/$owner/$slug/security
|
||||
- src/routes/plugins/$name/security-audit.tsx
|
||||
- src/routes/plugins/$name/security
|
||||
- src/routes/plugins/$scope/$name/security-audit.tsx
|
||||
- src/routes/plugins/$scope/$name/security
|
||||
|
||||
paths-ignore:
|
||||
- "**/node_modules"
|
||||
|
||||
@@ -14,7 +14,6 @@ query-filters:
|
||||
security-severity: /([7-9]|10)\.(\d)+/
|
||||
|
||||
paths:
|
||||
- scripts/check-staged-secrets.mjs
|
||||
- scripts/clawhub-cli-npm-release-check.mjs
|
||||
- scripts/github
|
||||
- scripts/verify-convex-contract.ts
|
||||
|
||||
@@ -7,7 +7,10 @@ updates:
|
||||
day: "monday"
|
||||
time: "09:00"
|
||||
timezone: "America/Los_Angeles"
|
||||
open-pull-requests-limit: 10
|
||||
# Preserve the old total Bun capacity: 10 general updates plus the
|
||||
# dedicated 3-PR Plugin Inspector queue that cannot remain as a duplicate
|
||||
# root Bun config.
|
||||
open-pull-requests-limit: 13
|
||||
ignore:
|
||||
- dependency-name: "@auth/core"
|
||||
update-types:
|
||||
@@ -17,6 +20,9 @@ updates:
|
||||
update-types:
|
||||
- "version-update:semver-major"
|
||||
groups:
|
||||
plugin-inspector:
|
||||
patterns:
|
||||
- "@openclaw/plugin-inspector"
|
||||
production-minor-and-patch:
|
||||
dependency-type: "production"
|
||||
update-types:
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
## Summary
|
||||
|
||||
- What changed:
|
||||
- Why:
|
||||
|
||||
## Linked Issue
|
||||
|
||||
- Closes #
|
||||
- Related #
|
||||
|
||||
## Screenshots
|
||||
|
||||
For website/UI changes, attach screenshots or recordings from the real app. Include mobile/narrow views when layout changes.
|
||||
|
||||
- [ ] Screenshots/recordings attached, or `N/A`
|
||||
|
||||
## Behavioural Proof
|
||||
|
||||
Describe how you verified the user-facing behavior. For UI changes, include the path tested and what changed on screen. For backend/API changes, include the request, command, or scenario that proves the behavior.
|
||||
|
||||
- [ ] Behavioural proof included, or `N/A`
|
||||
|
||||
## Security / Trust Impact
|
||||
|
||||
- [ ] No security/trust impact
|
||||
- [ ] Security/trust impact explained
|
||||
|
||||
## Data / Deploy Impact
|
||||
|
||||
- [ ] No data/deploy impact
|
||||
- [ ] Data/deploy impact explained
|
||||
|
||||
## Verification
|
||||
|
||||
- [ ] `bun run ci:static`
|
||||
- [ ] Focused tests for touched behavior:
|
||||
- [ ] `bun run ci:unit` or `N/A` for docs/config-only:
|
||||
- [ ] Broader gate when required (`ci:types-build`, `ci:packages`, `ci:e2e-http`, `ci:playwright-smoke`, `test:pw:local-auth`, `proof:ui`):
|
||||
- [ ] Other:
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
ref: ${{ github.sha }}
|
||||
persist-credentials: false
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Blacksmith Testbox
|
||||
name: Crabbox Testbox Backend
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -11,6 +11,10 @@ on:
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: clawhub-testbox-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
BUN_VERSION: "1.3.10"
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
@@ -22,11 +26,11 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Begin Testbox
|
||||
uses: useblacksmith/begin-testbox@d0e04585c26905fdd92c94a09c159544c7ee1b67
|
||||
uses: useblacksmith/begin-testbox@233448af4bfdc6fca509a7f0974411ac6d8a8043
|
||||
with:
|
||||
testbox_id: ${{ inputs.testbox_id }}
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 50
|
||||
|
||||
@@ -36,7 +40,7 @@ jobs:
|
||||
|
||||
- name: Restore Bun install cache
|
||||
id: bun-cache
|
||||
uses: actions/cache/restore@v5
|
||||
uses: actions/cache/restore@v6
|
||||
with:
|
||||
path: ~/.bun/install/cache
|
||||
key: ${{ runner.os }}-bun-${{ env.BUN_VERSION }}-${{ hashFiles('bun.lock') }}
|
||||
@@ -48,7 +52,7 @@ jobs:
|
||||
|
||||
- name: Save Bun install cache
|
||||
if: steps.bun-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/save@v5
|
||||
uses: actions/cache/save@v6
|
||||
continue-on-error: true
|
||||
with:
|
||||
path: ~/.bun/install/cache
|
||||
@@ -61,20 +65,29 @@ jobs:
|
||||
|
||||
git fetch --no-tags --depth=50 origin "+refs/heads/main:refs/remotes/origin/main"
|
||||
|
||||
link_tool() {
|
||||
local src="$1"
|
||||
local dest="$2"
|
||||
if [ "$src" = "$dest" ]; then
|
||||
return 0
|
||||
fi
|
||||
sudo ln -sf "$src" "$dest"
|
||||
}
|
||||
|
||||
bun_bin="$(command -v bun)"
|
||||
sudo ln -sf "$bun_bin" /usr/local/bin/bun
|
||||
link_tool "$bun_bin" /usr/local/bin/bun
|
||||
|
||||
if command -v bunx >/dev/null 2>&1; then
|
||||
sudo ln -sf "$(command -v bunx)" /usr/local/bin/bunx
|
||||
link_tool "$(command -v bunx)" /usr/local/bin/bunx
|
||||
fi
|
||||
|
||||
node_bin="$(dirname "$(node -p 'process.execPath')")"
|
||||
sudo ln -sf "$node_bin/node" /usr/local/bin/node
|
||||
sudo ln -sf "$node_bin/npm" /usr/local/bin/npm
|
||||
sudo ln -sf "$node_bin/npx" /usr/local/bin/npx
|
||||
link_tool "$node_bin/node" /usr/local/bin/node
|
||||
link_tool "$node_bin/npm" /usr/local/bin/npm
|
||||
link_tool "$node_bin/npx" /usr/local/bin/npx
|
||||
|
||||
- name: Run Testbox
|
||||
uses: useblacksmith/run-testbox@5ca05834db1d3813554d1dd109e5f2087a8d7cbc
|
||||
uses: useblacksmith/run-testbox@3f60ff9ceb2c10c3feefa87dc0c6490cffae059d
|
||||
if: always()
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
|
||||
+208
-47
@@ -4,70 +4,231 @@ on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
VITE_CONVEX_URL: https://example.invalid
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
pr-gates:
|
||||
name: pr-gates
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||
timeout-minutes: 45
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Static checks
|
||||
run: bun run ci:static
|
||||
|
||||
- name: Unit coverage
|
||||
run: bun run ci:unit
|
||||
|
||||
- name: Package checks
|
||||
run: bun run ci:packages
|
||||
|
||||
- name: Typecheck and build
|
||||
run: bun run ci:types-build
|
||||
|
||||
- name: HTTP e2e
|
||||
run: bun run ci:e2e-http
|
||||
|
||||
static:
|
||||
name: static
|
||||
runs-on: ubuntu-latest
|
||||
needs: pr-gates
|
||||
if: ${{ always() }}
|
||||
timeout-minutes: 5
|
||||
|
||||
steps:
|
||||
- name: Mirror pr-gates result
|
||||
env:
|
||||
PR_GATES_RESULT: ${{ needs.pr-gates.result }}
|
||||
run: |
|
||||
test "$PR_GATES_RESULT" = "success"
|
||||
|
||||
unit:
|
||||
name: unit
|
||||
runs-on: ubuntu-latest
|
||||
needs: pr-gates
|
||||
if: ${{ always() }}
|
||||
timeout-minutes: 5
|
||||
|
||||
steps:
|
||||
- name: Mirror pr-gates result
|
||||
env:
|
||||
PR_GATES_RESULT: ${{ needs.pr-gates.result }}
|
||||
run: |
|
||||
test "$PR_GATES_RESULT" = "success"
|
||||
|
||||
packages:
|
||||
name: packages
|
||||
runs-on: ubuntu-latest
|
||||
needs: pr-gates
|
||||
if: ${{ always() }}
|
||||
timeout-minutes: 5
|
||||
|
||||
steps:
|
||||
- name: Mirror pr-gates result
|
||||
env:
|
||||
PR_GATES_RESULT: ${{ needs.pr-gates.result }}
|
||||
run: |
|
||||
test "$PR_GATES_RESULT" = "success"
|
||||
|
||||
types-build:
|
||||
name: types-build
|
||||
runs-on: ubuntu-latest
|
||||
needs: pr-gates
|
||||
if: ${{ always() }}
|
||||
timeout-minutes: 5
|
||||
|
||||
steps:
|
||||
- name: Mirror pr-gates result
|
||||
env:
|
||||
PR_GATES_RESULT: ${{ needs.pr-gates.result }}
|
||||
run: |
|
||||
test "$PR_GATES_RESULT" = "success"
|
||||
|
||||
e2e-http:
|
||||
name: e2e-http
|
||||
runs-on: ubuntu-latest
|
||||
needs: pr-gates
|
||||
if: ${{ always() }}
|
||||
timeout-minutes: 5
|
||||
|
||||
steps:
|
||||
- name: Mirror pr-gates result
|
||||
env:
|
||||
PR_GATES_RESULT: ${{ needs.pr-gates.result }}
|
||||
run: |
|
||||
test "$PR_GATES_RESULT" = "success"
|
||||
|
||||
playwright-smoke:
|
||||
name: playwright-smoke
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||
timeout-minutes: 25
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Cache Playwright browsers
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
bun-version: 1.3.10
|
||||
path: ~/.cache/ms-playwright
|
||||
key: ${{ runner.os }}-playwright-${{ hashFiles('bun.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-playwright-
|
||||
|
||||
- name: Install
|
||||
run: bun install --frozen-lockfile
|
||||
- name: Peer deps
|
||||
run: bun run check:peers
|
||||
- name: Audit dependencies
|
||||
run: bun audit
|
||||
- name: Install Playwright browsers
|
||||
run: bunx playwright install chromium
|
||||
|
||||
- name: Format
|
||||
if: github.event_name == 'pull_request'
|
||||
- name: Browser e2e
|
||||
run: bun run ci:playwright-smoke
|
||||
|
||||
- name: Upload Playwright report
|
||||
if: ${{ !cancelled() }}
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: playwright-report
|
||||
path: playwright-report/
|
||||
if-no-files-found: ignore
|
||||
|
||||
playwright-local-auth-shard:
|
||||
name: playwright-local-auth / ${{ matrix.name }}
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 3
|
||||
matrix:
|
||||
include:
|
||||
- name: account-cleanup
|
||||
specs: |
|
||||
e2e/local-auth/delete-account-resources.pw.test.ts
|
||||
e2e/local-auth/delete-org-resources.pw.test.ts
|
||||
- name: profile-context
|
||||
specs: |
|
||||
e2e/local-auth/header-profile-link.pw.test.ts
|
||||
e2e/local-auth/manage-context-proof.pw.test.ts
|
||||
- name: moderation-star
|
||||
specs: |
|
||||
e2e/local-auth/malicious-skill-ban-flow.pw.test.ts
|
||||
e2e/local-auth/skill-star-sync.pw.test.ts
|
||||
- name: inspector-version
|
||||
specs: |
|
||||
e2e/local-auth/plugin-inspector-findings.pw.test.ts
|
||||
e2e/local-auth/version-delete.pw.test.ts
|
||||
- name: publish-generated-card
|
||||
specs: e2e/local-auth/publish-skill-lifecycle.pw.test.ts
|
||||
grep: publishing a skill queues scan
|
||||
- name: publish-new-version
|
||||
specs: e2e/local-auth/publish-skill-lifecycle.pw.test.ts
|
||||
grep: skill publishers can create a skill
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Cache Playwright browsers
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
key: ${{ runner.os }}-playwright-${{ hashFiles('bun.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-playwright-
|
||||
|
||||
- name: Install Playwright browsers
|
||||
run: bunx playwright install chromium
|
||||
|
||||
- name: Local-auth browser e2e
|
||||
env:
|
||||
PLAYWRIGHT_GREP: ${{ matrix.grep || '' }}
|
||||
PLAYWRIGHT_SPECS: ${{ matrix.specs }}
|
||||
run: |
|
||||
mapfile -d '' changed_files < <(
|
||||
git diff --name-only --diff-filter=ACMR -z \
|
||||
"${{ github.event.pull_request.base.sha }}" \
|
||||
"${{ github.event.pull_request.head.sha }}" \
|
||||
-- \
|
||||
'*.css' '*.js' '*.jsx' '*.json' '*.md' '*.mjs' '*.ts' '*.tsx' '*.yaml' '*.yml'
|
||||
)
|
||||
|
||||
if (( ${#changed_files[@]} == 0 )); then
|
||||
echo "No changed files supported by oxfmt."
|
||||
exit 0
|
||||
set -euo pipefail
|
||||
mapfile -t specs < <(printf '%s\n' "$PLAYWRIGHT_SPECS" | sed '/^[[:space:]]*$/d')
|
||||
args=(--project=chromium "${specs[@]}")
|
||||
if [[ -n "$PLAYWRIGHT_GREP" ]]; then
|
||||
args+=(--grep "$PLAYWRIGHT_GREP")
|
||||
fi
|
||||
bun run test:pw:local-auth -- "${args[@]}"
|
||||
|
||||
bun run format:check -- "${changed_files[@]}"
|
||||
- name: Upload Playwright report
|
||||
if: ${{ !cancelled() }}
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: playwright-local-auth-report-${{ matrix.name }}
|
||||
path: playwright-report/
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Lint
|
||||
run: bun run lint
|
||||
playwright-local-auth:
|
||||
name: playwright-local-auth
|
||||
runs-on: ubuntu-latest
|
||||
needs: playwright-local-auth-shard
|
||||
if: ${{ always() }}
|
||||
timeout-minutes: 5
|
||||
|
||||
- name: Test
|
||||
run: bun run test
|
||||
steps:
|
||||
- name: Check local-auth shards
|
||||
env:
|
||||
VITE_CONVEX_URL: https://example.invalid
|
||||
|
||||
- name: Coverage
|
||||
run: bun run coverage
|
||||
env:
|
||||
VITE_CONVEX_URL: https://example.invalid
|
||||
|
||||
- name: ClawHub CLI Verify
|
||||
run: bun run --cwd packages/clawhub verify
|
||||
|
||||
- name: Typecheck
|
||||
LOCAL_AUTH_RESULT: ${{ needs.playwright-local-auth-shard.result }}
|
||||
run: |
|
||||
bunx tsc --noEmit
|
||||
bunx tsc -p packages/schema/tsconfig.json --noEmit
|
||||
bunx tsc -p packages/clawhub/tsconfig.json --noEmit
|
||||
|
||||
- name: Build
|
||||
run: bun run build
|
||||
if [[ "$LOCAL_AUTH_RESULT" != "success" ]]; then
|
||||
echo "playwright-local-auth shards finished with result: $LOCAL_AUTH_RESULT"
|
||||
exit 1
|
||||
fi
|
||||
echo "playwright-local-auth shards passed."
|
||||
|
||||
@@ -0,0 +1,326 @@
|
||||
name: ClawHub CLI GitHub Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: Release tag to create or repair, for example v0.17.0
|
||||
required: true
|
||||
type: string
|
||||
main_run_id:
|
||||
description: Optional successful main CI run id to include in release proof
|
||||
required: false
|
||||
type: string
|
||||
preflight_run_id:
|
||||
description: Optional successful CLI npm preflight run id to include in release proof
|
||||
required: false
|
||||
type: string
|
||||
publish_run_id:
|
||||
description: Optional successful CLI npm publish run id to include in release proof
|
||||
required: false
|
||||
type: string
|
||||
update_existing:
|
||||
description: Update an existing GitHub Release instead of failing
|
||||
required: true
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
concurrency:
|
||||
group: clawhub-cli-github-release-${{ inputs.tag }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions: {}
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
NODE_VERSION: "24.x"
|
||||
|
||||
jobs:
|
||||
create_or_update_github_release:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout release tooling
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
path: release-tools
|
||||
|
||||
- name: Checkout release tag
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
ref: refs/tags/${{ inputs.tag }}
|
||||
fetch-depth: 0
|
||||
path: release
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
registry-url: https://registry.npmjs.org
|
||||
|
||||
- name: Validate release tag and package metadata
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
RELEASE_MAIN_REF: origin/main
|
||||
run: |
|
||||
set -euo pipefail
|
||||
RELEASE_SHA="$(git rev-parse HEAD)"
|
||||
echo "RELEASE_SHA=$RELEASE_SHA" >> "$GITHUB_ENV"
|
||||
git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
|
||||
if ! git merge-base --is-ancestor "$RELEASE_SHA" "$RELEASE_MAIN_REF"; then
|
||||
echo "Tagged commit ${RELEASE_SHA} is not contained in ${RELEASE_MAIN_REF}." >&2
|
||||
exit 1
|
||||
fi
|
||||
node --input-type=module <<'EOF'
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
const releaseTag = process.env.RELEASE_TAG ?? "";
|
||||
const pkg = JSON.parse(readFileSync("./packages/clawhub/package.json", "utf8"));
|
||||
const version = String(pkg.version ?? "").trim();
|
||||
const errors = [];
|
||||
|
||||
if (pkg.name !== "clawhub") {
|
||||
errors.push(`packages/clawhub/package.json name must be "clawhub"; found "${pkg.name ?? ""}".`);
|
||||
}
|
||||
if (!/^\d+\.\d+\.\d+$/.test(version)) {
|
||||
errors.push(`packages/clawhub/package.json version must be stable semver (X.Y.Z); found "${version || "<missing>"}".`);
|
||||
}
|
||||
if (!/^v\d+\.\d+\.\d+$/.test(releaseTag)) {
|
||||
errors.push(`Release tag must match vX.Y.Z; found "${releaseTag || "<missing>"}".`);
|
||||
}
|
||||
if (releaseTag !== `v${version}`) {
|
||||
errors.push(`Release tag ${releaseTag} does not match packages/clawhub/package.json version ${version}; expected v${version}.`);
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
for (const error of errors) console.error(error);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`Release metadata OK for clawhub@${version} (${releaseTag}).`);
|
||||
EOF
|
||||
working-directory: release
|
||||
|
||||
- name: Resolve release metadata
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
PACKAGE_VERSION="$(node --input-type=module <<'EOF'
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
const pkg = JSON.parse(readFileSync("./packages/clawhub/package.json", "utf8"));
|
||||
process.stdout.write(String(pkg.version ?? "").trim());
|
||||
EOF
|
||||
)"
|
||||
NPM_DIST_JSON=""
|
||||
for attempt in {1..12}; do
|
||||
if NPM_DIST_JSON="$(npm view "clawhub@${PACKAGE_VERSION}" dist.tarball dist.integrity --json 2>/tmp/npm-view-error)" && [[ -n "$NPM_DIST_JSON" ]]; then
|
||||
break
|
||||
fi
|
||||
if [[ "$attempt" == "12" ]]; then
|
||||
cat /tmp/npm-view-error >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
NPM_TARBALL="$(NPM_DIST_JSON="$NPM_DIST_JSON" node --input-type=module <<'EOF'
|
||||
const dist = JSON.parse(process.env.NPM_DIST_JSON ?? "{}");
|
||||
process.stdout.write(String(dist["dist.tarball"] ?? ""));
|
||||
EOF
|
||||
)"
|
||||
NPM_INTEGRITY="$(NPM_DIST_JSON="$NPM_DIST_JSON" node --input-type=module <<'EOF'
|
||||
const dist = JSON.parse(process.env.NPM_DIST_JSON ?? "{}");
|
||||
process.stdout.write(String(dist["dist.integrity"] ?? ""));
|
||||
EOF
|
||||
)"
|
||||
if [[ -z "$NPM_TARBALL" || -z "$NPM_INTEGRITY" ]]; then
|
||||
echo "npm dist metadata for clawhub@${PACKAGE_VERSION} is incomplete." >&2
|
||||
exit 1
|
||||
fi
|
||||
{
|
||||
echo "PACKAGE_VERSION=$PACKAGE_VERSION"
|
||||
echo "NPM_PACKAGE_URL=https://www.npmjs.com/package/clawhub/v/${PACKAGE_VERSION}"
|
||||
echo "NPM_TARBALL_URL=$NPM_TARBALL"
|
||||
echo "NPM_INTEGRITY=$NPM_INTEGRITY"
|
||||
echo "RELEASE_TITLE=clawhub ${PACKAGE_VERSION}"
|
||||
} >> "$GITHUB_ENV"
|
||||
working-directory: release
|
||||
|
||||
- name: Resolve proof workflow run URLs
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
MAIN_RUN_ID: ${{ inputs.main_run_id }}
|
||||
PREFLIGHT_RUN_ID: ${{ inputs.preflight_run_id }}
|
||||
PUBLISH_RUN_ID: ${{ inputs.publish_run_id }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
resolve_run_url() {
|
||||
local env_name="$1"
|
||||
local run_id="$2"
|
||||
local expected_workflow="$3"
|
||||
local expected_event="$4"
|
||||
local expected_branch="$5"
|
||||
|
||||
if [[ -z "$run_id" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local run_json
|
||||
run_json="$(gh run view "$run_id" --repo "$GITHUB_REPOSITORY" --json conclusion,event,headBranch,headSha,url,workflowName)"
|
||||
RUN_JSON="$run_json" RUN_ID="$run_id" EXPECTED_WORKFLOW="$expected_workflow" EXPECTED_EVENT="$expected_event" EXPECTED_BRANCH="$expected_branch" node --input-type=module <<'EOF'
|
||||
const run = JSON.parse(process.env.RUN_JSON);
|
||||
const expectedWorkflow = process.env.EXPECTED_WORKFLOW;
|
||||
if (expectedWorkflow && run.workflowName !== expectedWorkflow) {
|
||||
console.error(`Run ${process.env.RUN_ID} must be ${expectedWorkflow}; got ${run.workflowName ?? "<missing>"}.`);
|
||||
process.exit(1);
|
||||
}
|
||||
if (run.conclusion !== "success") {
|
||||
console.error(`Run ${process.env.RUN_ID} must have conclusion=success; got ${run.conclusion ?? "<missing>"}.`);
|
||||
process.exit(1);
|
||||
}
|
||||
if (run.headSha !== process.env.RELEASE_SHA) {
|
||||
console.error(`Run ${process.env.RUN_ID} must use release SHA ${process.env.RELEASE_SHA}; got ${run.headSha ?? "<missing>"}.`);
|
||||
process.exit(1);
|
||||
}
|
||||
if (process.env.EXPECTED_EVENT && run.event !== process.env.EXPECTED_EVENT) {
|
||||
console.error(`Run ${process.env.RUN_ID} must have event=${process.env.EXPECTED_EVENT}; got ${run.event ?? "<missing>"}.`);
|
||||
process.exit(1);
|
||||
}
|
||||
if (process.env.EXPECTED_BRANCH && run.headBranch !== process.env.EXPECTED_BRANCH) {
|
||||
console.error(`Run ${process.env.RUN_ID} must have headBranch=${process.env.EXPECTED_BRANCH}; got ${run.headBranch ?? "<missing>"}.`);
|
||||
process.exit(1);
|
||||
}
|
||||
process.stdout.write(run.url);
|
||||
EOF
|
||||
echo "${env_name}=$(RUN_JSON="$run_json" RUN_ID="$run_id" EXPECTED_WORKFLOW="$expected_workflow" node --input-type=module <<'EOF'
|
||||
const run = JSON.parse(process.env.RUN_JSON);
|
||||
process.stdout.write(run.url);
|
||||
EOF
|
||||
)" >> "$GITHUB_ENV"
|
||||
}
|
||||
|
||||
resolve_run_url MAIN_RUN_URL "$MAIN_RUN_ID" "CI" "" ""
|
||||
resolve_run_url PREFLIGHT_RUN_URL "$PREFLIGHT_RUN_ID" "ClawHub CLI NPM Release" "workflow_dispatch" "main"
|
||||
resolve_run_url PUBLISH_RUN_URL "$PUBLISH_RUN_ID" "ClawHub CLI NPM Release" "workflow_dispatch" "main"
|
||||
|
||||
- name: Verify preflight proof artifact
|
||||
if: ${{ inputs.preflight_run_id != '' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PREFLIGHT_RUN_ID: ${{ inputs.preflight_run_id }}
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
PROOF_DIR="$RUNNER_TEMP/clawhub-cli-github-release-preflight-proof"
|
||||
rm -rf "$PROOF_DIR"
|
||||
mkdir -p "$PROOF_DIR"
|
||||
gh run download "$PREFLIGHT_RUN_ID" \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--name "clawhub-cli-npm-preflight-${RELEASE_TAG}" \
|
||||
--dir "$PROOF_DIR"
|
||||
|
||||
if [[ "$(tr -d '\r\n' < "$PROOF_DIR/release-tag.txt")" != "$RELEASE_TAG" ]]; then
|
||||
echo "Preflight artifact tag does not match ${RELEASE_TAG}." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$(tr -d '\r\n' < "$PROOF_DIR/release-sha.txt")" != "$RELEASE_SHA" ]]; then
|
||||
echo "Preflight artifact SHA does not match ${RELEASE_SHA}." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$(tr -d '\r\n' < "$PROOF_DIR/package-version.txt")" != "$PACKAGE_VERSION" ]]; then
|
||||
echo "Preflight artifact version does not match ${PACKAGE_VERSION}." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Verify publish proof artifact
|
||||
if: ${{ inputs.publish_run_id != '' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PUBLISH_RUN_ID: ${{ inputs.publish_run_id }}
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
PROOF_DIR="$RUNNER_TEMP/clawhub-cli-github-release-publish-proof"
|
||||
rm -rf "$PROOF_DIR"
|
||||
mkdir -p "$PROOF_DIR"
|
||||
gh run download "$PUBLISH_RUN_ID" \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--name "clawhub-cli-npm-publish-${RELEASE_TAG}" \
|
||||
--dir "$PROOF_DIR"
|
||||
|
||||
if [[ "$(tr -d '\r\n' < "$PROOF_DIR/release-tag.txt")" != "$RELEASE_TAG" ]]; then
|
||||
echo "Publish artifact tag does not match ${RELEASE_TAG}." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$(tr -d '\r\n' < "$PROOF_DIR/release-sha.txt")" != "$RELEASE_SHA" ]]; then
|
||||
echo "Publish artifact SHA does not match ${RELEASE_SHA}." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$(tr -d '\r\n' < "$PROOF_DIR/package-version.txt")" != "$PACKAGE_VERSION" ]]; then
|
||||
echo "Publish artifact version does not match ${PACKAGE_VERSION}." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$(tr -d '\r\n' < "$PROOF_DIR/preflight-only.txt")" != "false" ]]; then
|
||||
echo "Publish artifact must come from a real publish run." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$(tr -d '\r\n' < "$PROOF_DIR/npm-tarball-url.txt")" != "$NPM_TARBALL_URL" ]]; then
|
||||
echo "Publish artifact tarball URL does not match npm metadata." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$(tr -d '\r\n' < "$PROOF_DIR/npm-integrity.txt")" != "$NPM_INTEGRITY" ]]; then
|
||||
echo "Publish artifact integrity does not match npm metadata." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Build release notes
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
node ../release-tools/scripts/extract-changelog-release.mjs --tag "$RELEASE_TAG" --changelog CHANGELOG.md > ../release-body.md
|
||||
{
|
||||
echo
|
||||
echo "### Release Proof"
|
||||
echo
|
||||
echo "- npm: ${NPM_PACKAGE_URL}"
|
||||
echo "- tarball: ${NPM_TARBALL_URL}"
|
||||
echo "- integrity: ${NPM_INTEGRITY}"
|
||||
if [[ -n "${MAIN_RUN_URL:-}" ]]; then
|
||||
echo "- main CI: ${MAIN_RUN_URL}"
|
||||
fi
|
||||
if [[ -n "${PREFLIGHT_RUN_URL:-}" ]]; then
|
||||
echo "- npm preflight: ${PREFLIGHT_RUN_URL}"
|
||||
fi
|
||||
if [[ -n "${PUBLISH_RUN_URL:-}" ]]; then
|
||||
echo "- npm publish: ${PUBLISH_RUN_URL}"
|
||||
fi
|
||||
} >> ../release-body.md
|
||||
working-directory: release
|
||||
|
||||
- name: Create or update GitHub Release
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
UPDATE_EXISTING: ${{ inputs.update_existing }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
|
||||
if [[ "$UPDATE_EXISTING" != "true" ]]; then
|
||||
echo "GitHub Release ${RELEASE_TAG} already exists. Rerun with update_existing=true to repair it." >&2
|
||||
exit 1
|
||||
fi
|
||||
gh release edit "$RELEASE_TAG" \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--title "$RELEASE_TITLE" \
|
||||
--notes-file release-body.md
|
||||
else
|
||||
gh release create "$RELEASE_TAG" \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--title "$RELEASE_TITLE" \
|
||||
--notes-file release-body.md
|
||||
fi
|
||||
@@ -21,6 +21,8 @@ concurrency:
|
||||
group: clawhub-cli-npm-release-${{ inputs.tag }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions: {}
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
NODE_VERSION: "24.x"
|
||||
@@ -40,11 +42,17 @@ jobs:
|
||||
exit 1
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
ref: refs/tags/${{ inputs.tag }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Checkout release tooling
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
path: release-tools
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
@@ -106,6 +114,11 @@ jobs:
|
||||
git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
|
||||
node scripts/clawhub-cli-npm-release-check.mjs
|
||||
|
||||
- name: Validate GitHub Release notes
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
run: node release-tools/scripts/extract-changelog-release.mjs --tag "$RELEASE_TAG" >/tmp/clawhub-cli-release-notes.md
|
||||
|
||||
- name: Verify CLI package
|
||||
run: bun run --cwd "$PACKAGE_DIR" verify
|
||||
|
||||
@@ -181,15 +194,21 @@ jobs:
|
||||
environment: npm-release
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
contents: write
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
ref: refs/tags/${{ inputs.tag }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Checkout release tooling
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
path: release-tools
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
@@ -256,6 +275,11 @@ jobs:
|
||||
git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
|
||||
node scripts/clawhub-cli-npm-release-check.mjs
|
||||
|
||||
- name: Validate GitHub Release notes
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
run: node release-tools/scripts/extract-changelog-release.mjs --tag "$RELEASE_TAG" >/tmp/clawhub-cli-release-notes.md
|
||||
|
||||
- name: Verify prepared tarball provenance
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
@@ -313,3 +337,106 @@ jobs:
|
||||
publish_target="./${publish_target}"
|
||||
fi
|
||||
bash scripts/clawhub-cli-npm-publish.sh --publish "${publish_target}"
|
||||
|
||||
- name: Resolve npm release metadata
|
||||
run: |
|
||||
set -euo pipefail
|
||||
PACKAGE_VERSION="$(node --input-type=module <<'EOF'
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
const pkg = JSON.parse(readFileSync(`./${process.env.PACKAGE_DIR}/package.json`, "utf8"));
|
||||
process.stdout.write(String(pkg.version ?? "").trim());
|
||||
EOF
|
||||
)"
|
||||
NPM_DIST_JSON=""
|
||||
for attempt in {1..12}; do
|
||||
if NPM_DIST_JSON="$(npm view "clawhub@${PACKAGE_VERSION}" dist.tarball dist.integrity --json 2>/tmp/npm-view-error)" && [[ -n "$NPM_DIST_JSON" ]]; then
|
||||
break
|
||||
fi
|
||||
if [[ "$attempt" == "12" ]]; then
|
||||
cat /tmp/npm-view-error >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
NPM_TARBALL="$(NPM_DIST_JSON="$NPM_DIST_JSON" node --input-type=module <<'EOF'
|
||||
const dist = JSON.parse(process.env.NPM_DIST_JSON ?? "{}");
|
||||
process.stdout.write(String(dist["dist.tarball"] ?? ""));
|
||||
EOF
|
||||
)"
|
||||
NPM_INTEGRITY="$(NPM_DIST_JSON="$NPM_DIST_JSON" node --input-type=module <<'EOF'
|
||||
const dist = JSON.parse(process.env.NPM_DIST_JSON ?? "{}");
|
||||
process.stdout.write(String(dist["dist.integrity"] ?? ""));
|
||||
EOF
|
||||
)"
|
||||
if [[ -z "$NPM_TARBALL" || -z "$NPM_INTEGRITY" ]]; then
|
||||
echo "npm dist metadata for clawhub@${PACKAGE_VERSION} is incomplete." >&2
|
||||
exit 1
|
||||
fi
|
||||
{
|
||||
echo "PACKAGE_VERSION=$PACKAGE_VERSION"
|
||||
echo "NPM_PACKAGE_URL=https://www.npmjs.com/package/clawhub/v/${PACKAGE_VERSION}"
|
||||
echo "NPM_TARBALL_URL=$NPM_TARBALL"
|
||||
echo "NPM_INTEGRITY=$NPM_INTEGRITY"
|
||||
echo "RELEASE_TITLE=clawhub ${PACKAGE_VERSION}"
|
||||
} >> "$GITHUB_ENV"
|
||||
|
||||
- name: Write npm publish proof artifact
|
||||
id: publish_proof
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
PUBLISH_PROOF_DIR="$RUNNER_TEMP/clawhub-cli-npm-publish-proof"
|
||||
rm -rf "$PUBLISH_PROOF_DIR"
|
||||
mkdir -p "$PUBLISH_PROOF_DIR"
|
||||
printf '%s\n' "$RELEASE_TAG" > "$PUBLISH_PROOF_DIR/release-tag.txt"
|
||||
git rev-parse HEAD > "$PUBLISH_PROOF_DIR/release-sha.txt"
|
||||
printf '%s\n' "$PACKAGE_VERSION" > "$PUBLISH_PROOF_DIR/package-version.txt"
|
||||
printf '%s\n' "$NPM_TARBALL_URL" > "$PUBLISH_PROOF_DIR/npm-tarball-url.txt"
|
||||
printf '%s\n' "$NPM_INTEGRITY" > "$PUBLISH_PROOF_DIR/npm-integrity.txt"
|
||||
printf '%s\n' "$GITHUB_RUN_ID" > "$PUBLISH_PROOF_DIR/publish-run-id.txt"
|
||||
printf '%s\n' "false" > "$PUBLISH_PROOF_DIR/preflight-only.txt"
|
||||
echo "dir=$PUBLISH_PROOF_DIR" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Upload npm publish proof artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: clawhub-cli-npm-publish-${{ inputs.tag }}
|
||||
path: ${{ steps.publish_proof.outputs.dir }}
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Build GitHub Release notes
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
node release-tools/scripts/extract-changelog-release.mjs --tag "$RELEASE_TAG" > release-body.md
|
||||
{
|
||||
echo
|
||||
echo "### Release Proof"
|
||||
echo
|
||||
echo "- npm: ${NPM_PACKAGE_URL}"
|
||||
echo "- tarball: ${NPM_TARBALL_URL}"
|
||||
echo "- integrity: ${NPM_INTEGRITY}"
|
||||
echo "- npm preflight: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${{ inputs.preflight_run_id }}"
|
||||
echo "- npm publish: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
|
||||
} >> release-body.md
|
||||
|
||||
- name: Create or update GitHub Release
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
|
||||
gh release edit "$RELEASE_TAG" \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--title "$RELEASE_TITLE" \
|
||||
--notes-file release-body.md
|
||||
else
|
||||
gh release create "$RELEASE_TAG" \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--title "$RELEASE_TITLE" \
|
||||
--notes-file release-body.md
|
||||
fi
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
CLAWHUB_RESCAN_GUIDANCE_APPLY: "1"
|
||||
ISSUE_NUMBER: ${{ github.event.issue.number || github.event.inputs.issue }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
ref: ${{ github.sha }}
|
||||
persist-credentials: false
|
||||
|
||||
@@ -3,6 +3,8 @@ name: ClawSweeper Dispatch
|
||||
on:
|
||||
issues:
|
||||
types: [opened, reopened, edited, labeled, unlabeled]
|
||||
issue_comment:
|
||||
types: [created, edited]
|
||||
pull_request_target: # zizmor: ignore[dangerous-triggers] maintainer-owned external dispatch; no checkout or untrusted PR code execution
|
||||
types: [opened, reopened, synchronize, ready_for_review, edited, labeled, unlabeled]
|
||||
|
||||
@@ -16,7 +18,7 @@ concurrency:
|
||||
jobs:
|
||||
dispatch:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ !(endsWith(github.actor, '[bot]') && (github.event.action == 'labeled' || github.event.action == 'unlabeled')) }}
|
||||
if: ${{ github.event_name == 'issue_comment' || !(endsWith(github.actor, '[bot]') && (github.event.action == 'labeled' || github.event.action == 'unlabeled')) }}
|
||||
env:
|
||||
HAS_CLAWSWEEPER_APP_PRIVATE_KEY: ${{ secrets.CLAWSWEEPER_APP_PRIVATE_KEY != '' }}
|
||||
CLAWSWEEPER_APP_CLIENT_ID: Iv23liOECG0slfuhz093
|
||||
@@ -35,10 +37,24 @@ jobs:
|
||||
private-key: ${{ secrets.CLAWSWEEPER_APP_PRIVATE_KEY }}
|
||||
owner: openclaw
|
||||
repositories: clawsweeper
|
||||
permission-contents: write
|
||||
|
||||
- name: Create target comment token
|
||||
id: target_token
|
||||
if: ${{ github.event_name == 'issue_comment' && env.HAS_CLAWSWEEPER_APP_PRIVATE_KEY == 'true' }}
|
||||
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
|
||||
with:
|
||||
client-id: ${{ env.CLAWSWEEPER_APP_CLIENT_ID }}
|
||||
private-key: ${{ secrets.CLAWSWEEPER_APP_PRIVATE_KEY }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
repositories: ${{ github.event.repository.name }}
|
||||
permission-issues: write
|
||||
permission-pull-requests: read
|
||||
|
||||
- name: Dispatch exact ClawSweeper review
|
||||
if: ${{ github.event_name == 'issues' || github.event_name == 'pull_request_target' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.token.outputs.token || secrets.OPENCLAW_GH_TOKEN }}
|
||||
GH_TOKEN: ${{ steps.token.outputs.token }}
|
||||
TARGET_REPO: ${{ github.repository }}
|
||||
ITEM_NUMBER: ${{ github.event.issue.number || github.event.pull_request.number }}
|
||||
ITEM_KIND: ${{ github.event_name == 'pull_request_target' && 'pull_request' || 'issue' }}
|
||||
@@ -60,3 +76,83 @@ jobs:
|
||||
gh api repos/openclaw/clawsweeper/dispatches \
|
||||
--method POST \
|
||||
--input - <<< "$payload"
|
||||
|
||||
- name: Acknowledge and dispatch ClawSweeper comment
|
||||
if: ${{ github.event_name == 'issue_comment' }}
|
||||
env:
|
||||
DISPATCH_TOKEN: ${{ steps.token.outputs.token }}
|
||||
TARGET_TOKEN: ${{ steps.target_token.outputs.token }}
|
||||
TARGET_REPO: ${{ github.repository }}
|
||||
ITEM_NUMBER: ${{ github.event.issue.number }}
|
||||
COMMENT_ID: ${{ github.event.comment.id }}
|
||||
COMMENT_BODY: ${{ github.event.comment.body }}
|
||||
AUTHOR_ASSOCIATION: ${{ github.event.comment.author_association }}
|
||||
SOURCE_ACTION: ${{ github.event.action }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -z "$DISPATCH_TOKEN" ]; then
|
||||
echo "::notice::Skipping ClawSweeper comment dispatch because no ClawSweeper app token is configured."
|
||||
exit 0
|
||||
fi
|
||||
body_file="$RUNNER_TEMP/clawsweeper-comment-body.txt"
|
||||
printf '%s\n' "$COMMENT_BODY" > "$body_file"
|
||||
if ! grep -Eiq '(^|[[:space:]])@(clawsweeper|openclaw-clawsweeper)\b(\[bot\])?|(^|[[:space:]])/(clawsweeper|review|automerge|autoclose)\b' "$body_file"; then
|
||||
echo "No ClawSweeper command found in comment."
|
||||
exit 0
|
||||
fi
|
||||
if [ -n "$TARGET_TOKEN" ]; then
|
||||
err="$(mktemp)"
|
||||
if GH_TOKEN="$TARGET_TOKEN" gh api -X POST \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
"repos/$TARGET_REPO/issues/comments/$COMMENT_ID/reactions" \
|
||||
-f content="eyes" 2>"$err" >/dev/null; then
|
||||
echo "Acknowledged ClawSweeper command comment."
|
||||
elif grep -qi "HTTP 422\\|already exists" "$err"; then
|
||||
echo "ClawSweeper command comment already acknowledged."
|
||||
else
|
||||
cat "$err" >&2
|
||||
echo "::warning::Could not acknowledge ClawSweeper command comment."
|
||||
fi
|
||||
rm -f "$err"
|
||||
else
|
||||
echo "::notice::Skipping ClawSweeper comment acknowledgement because no target token is configured."
|
||||
fi
|
||||
status_comment_id=""
|
||||
if [ -n "$TARGET_TOKEN" ]; then
|
||||
case "$AUTHOR_ASSOCIATION" in
|
||||
OWNER|MEMBER|COLLABORATOR)
|
||||
status_body="$(printf '%s\n' \
|
||||
"<!-- clawsweeper-command-ack:$COMMENT_ID -->" \
|
||||
"ClawSweeper picked this up." \
|
||||
"" \
|
||||
"Command router queued. I will update this comment with the next step.")"
|
||||
status_payload="$(jq -nc --arg body "$status_body" '{body:$body}')"
|
||||
status_err="$(mktemp)"
|
||||
if status_response="$(GH_TOKEN="$TARGET_TOKEN" gh api \
|
||||
"repos/$TARGET_REPO/issues/$ITEM_NUMBER/comments" \
|
||||
--method POST \
|
||||
--input - <<< "$status_payload" 2>"$status_err")"; then
|
||||
status_comment_id="$(jq -r '.id // empty' <<< "$status_response")"
|
||||
else
|
||||
cat "$status_err" >&2
|
||||
echo "::warning::Could not create ClawSweeper queued status comment; dispatching command router without one."
|
||||
fi
|
||||
rm -f "$status_err"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
payload="$(jq -nc \
|
||||
--arg target_repo "$TARGET_REPO" \
|
||||
--argjson item_number "$ITEM_NUMBER" \
|
||||
--argjson comment_id "$COMMENT_ID" \
|
||||
--arg status_comment_id "$status_comment_id" \
|
||||
--arg source_event "issue_comment" \
|
||||
--arg source_action "$SOURCE_ACTION" \
|
||||
'{event_type:"clawsweeper_comment",client_payload:({target_repo:$target_repo,item_number:$item_number,comment_id:$comment_id,source_event:$source_event,source_action:$source_action,max_comments:"1"} + (if $status_comment_id != "" then {status_comment_id:($status_comment_id|tonumber)} else {} end))}')"
|
||||
if GH_TOKEN="$DISPATCH_TOKEN" gh api repos/openclaw/clawsweeper/dispatches \
|
||||
--method POST \
|
||||
--input - <<< "$payload"; then
|
||||
echo "Dispatched ClawSweeper comment router."
|
||||
else
|
||||
echo "::warning::Skipping ClawSweeper comment dispatch because the configured credential could not dispatch to openclaw/clawsweeper."
|
||||
fi
|
||||
|
||||
@@ -82,19 +82,19 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.profile == 'all' || inputs.profile == matrix.category }}
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
submodules: false
|
||||
|
||||
- name: Initialize CodeQL
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.profile == 'all' || inputs.profile == matrix.category }}
|
||||
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
|
||||
uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
config-file: ${{ matrix.config_file }}
|
||||
|
||||
- name: Analyze
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.profile == 'all' || inputs.profile == matrix.category }}
|
||||
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
|
||||
uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
|
||||
with:
|
||||
category: "/codeql-light/${{ matrix.category }}"
|
||||
|
||||
@@ -12,13 +12,18 @@ on:
|
||||
- full
|
||||
- backend
|
||||
- frontend
|
||||
allow_deleting_large_indexes:
|
||||
description: "Allow Convex to delete large indexes"
|
||||
required: true
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
concurrency:
|
||||
group: deploy-production
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
contents: write
|
||||
statuses: read
|
||||
|
||||
jobs:
|
||||
@@ -94,12 +99,13 @@ jobs:
|
||||
fi
|
||||
|
||||
echo "Deploy target: ${{ needs.validate-deploy-request.outputs.target }}"
|
||||
echo "Allow deleting large Convex indexes: ${{ inputs.allow_deleting_large_indexes }}"
|
||||
|
||||
if [[ -z "$PLAYWRIGHT_AUTH_STORAGE_STATE_JSON" ]]; then
|
||||
echo "PLAYWRIGHT_AUTH_STORAGE_STATE_JSON not set; authenticated smoke will be skipped."
|
||||
fi
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
|
||||
with:
|
||||
@@ -118,13 +124,20 @@ jobs:
|
||||
|
||||
- name: Deploy Convex
|
||||
if: needs.validate-deploy-request.outputs.deploy_backend == 'true'
|
||||
run: bun run convex:deploy
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ "${{ inputs.allow_deleting_large_indexes }}" == "true" ]]; then
|
||||
bunx convex deploy --typecheck=disable --yes --allow-deleting-large-indexes
|
||||
else
|
||||
bun run convex:deploy
|
||||
fi
|
||||
|
||||
- name: Verify Convex contract
|
||||
if: needs.validate-deploy-request.outputs.deploy_backend == 'true'
|
||||
run: bun run verify:convex-contract -- --prod
|
||||
|
||||
- name: Wait for Vercel production deployment
|
||||
id: vercel
|
||||
if: needs.validate-deploy-request.outputs.deploy_frontend == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
@@ -134,17 +147,26 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
for attempt in {1..90}; do
|
||||
if ! state="$(gh api "repos/$GITHUB_REPOSITORY/commits/$GITHUB_SHA/status" \
|
||||
--jq '.statuses[] | select(.context == env.VERCEL_STATUS_CONTEXT) | .state' \
|
||||
if ! status_json="$(gh api "repos/$GITHUB_REPOSITORY/commits/$GITHUB_SHA/status" \
|
||||
--jq '.statuses[] | select(.context == env.VERCEL_STATUS_CONTEXT) | {state, target_url, description} | @base64' \
|
||||
2>/dev/null | head -n1)"; then
|
||||
echo "GitHub status check failed for $GITHUB_SHA on attempt $attempt; retrying..."
|
||||
sleep 10
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ -z "$status_json" ]]; then
|
||||
state=""
|
||||
target_url=""
|
||||
else
|
||||
state="$(printf '%s' "$status_json" | base64 -d | jq -r '.state // ""')"
|
||||
target_url="$(printf '%s' "$status_json" | base64 -d | jq -r '.target_url // ""')"
|
||||
fi
|
||||
|
||||
case "$state" in
|
||||
success)
|
||||
echo "Vercel production deployment ready for $GITHUB_SHA"
|
||||
echo "deployment_url=$target_url" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
;;
|
||||
failure|error)
|
||||
@@ -166,7 +188,7 @@ jobs:
|
||||
exit 1
|
||||
|
||||
- name: Install Playwright browser
|
||||
if: needs.validate-deploy-request.outputs.run_smoke == 'true'
|
||||
if: needs.validate-deploy-request.outputs.run_smoke == 'true' && needs.validate-deploy-request.outputs.deploy_frontend == 'true'
|
||||
run: bunx playwright install --with-deps chromium webkit
|
||||
|
||||
- name: Smoke test production HTTP
|
||||
@@ -174,11 +196,55 @@ jobs:
|
||||
run: bun run test:e2e:prod-http
|
||||
|
||||
- name: Write authenticated storage state
|
||||
if: needs.validate-deploy-request.outputs.run_smoke == 'true' && env.PLAYWRIGHT_AUTH_STORAGE_STATE_JSON != ''
|
||||
if: needs.validate-deploy-request.outputs.run_smoke == 'true' && needs.validate-deploy-request.outputs.deploy_frontend == 'true' && env.PLAYWRIGHT_AUTH_STORAGE_STATE_JSON != ''
|
||||
run: |
|
||||
echo "$PLAYWRIGHT_AUTH_STORAGE_STATE_JSON" > "$RUNNER_TEMP/playwright-auth.json"
|
||||
echo "PLAYWRIGHT_AUTH_STORAGE_STATE=$RUNNER_TEMP/playwright-auth.json" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Smoke test production UI
|
||||
if: needs.validate-deploy-request.outputs.run_smoke == 'true'
|
||||
run: bunx playwright test e2e/menu-smoke.pw.test.ts e2e/publish-entry-workflows.pw.test.ts e2e/upload-auth-smoke.pw.test.ts
|
||||
if: needs.validate-deploy-request.outputs.run_smoke == 'true' && needs.validate-deploy-request.outputs.deploy_frontend == 'true'
|
||||
run: bunx playwright test --workers=1 e2e/menu-smoke.pw.test.ts e2e/publish-entry-workflows.pw.test.ts e2e/upload-auth-smoke.pw.test.ts
|
||||
|
||||
- name: Tag production frontend deployment
|
||||
if: needs.validate-deploy-request.outputs.deploy_frontend == 'true'
|
||||
env:
|
||||
DEPLOY_TARGET: ${{ needs.validate-deploy-request.outputs.target }}
|
||||
DEPLOYMENT_URL: ${{ steps.vercel.outputs.deployment_url }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
deployed_at="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
||||
tag_name="deploy/prod/$(date -u +"%Y%m%d-%H%M%SZ")-${GITHUB_SHA::7}"
|
||||
version_prefix="prod/v$(date -u +"%Y.%m.%d")."
|
||||
run_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
|
||||
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
next_version=1
|
||||
while IFS= read -r existing_tag; do
|
||||
existing_tag="${existing_tag#refs/tags/}"
|
||||
existing_tag="${existing_tag%\^\{\}}"
|
||||
suffix="${existing_tag##*.}"
|
||||
if [[ "$existing_tag" == "$version_prefix"* && "$suffix" =~ ^[0-9]+$ && "$suffix" -ge "$next_version" ]]; then
|
||||
next_version=$((suffix + 1))
|
||||
fi
|
||||
done < <(git ls-remote --tags origin "refs/tags/${version_prefix}*" | awk '{print $2}' | sort -u)
|
||||
version_tag="${version_prefix}${next_version}"
|
||||
|
||||
git tag -a "$tag_name" "$GITHUB_SHA" \
|
||||
-m "Production frontend deploy $tag_name" \
|
||||
-m "SHA: $GITHUB_SHA" \
|
||||
-m "Version: $version_tag" \
|
||||
-m "Deployed at: $deployed_at" \
|
||||
-m "Target: $DEPLOY_TARGET" \
|
||||
-m "Vercel: ${DEPLOYMENT_URL:-unknown}" \
|
||||
-m "Run: $run_url"
|
||||
git tag -a "$version_tag" "$GITHUB_SHA" \
|
||||
-m "Production frontend deploy $version_tag" \
|
||||
-m "SHA: $GITHUB_SHA" \
|
||||
-m "Timestamp tag: $tag_name" \
|
||||
-m "Deployed at: $deployed_at" \
|
||||
-m "Target: $DEPLOY_TARGET" \
|
||||
-m "Vercel: ${DEPLOYMENT_URL:-unknown}" \
|
||||
-m "Run: $run_url"
|
||||
git push origin "refs/tags/$tag_name" "refs/tags/$version_tag"
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
name: OpenClaw Docs Sync Dispatch
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- docs/**
|
||||
- .github/workflows/openclaw-docs-sync-dispatch.yml
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
dispatch-openclaw-docs-sync:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Dispatch OpenClaw docs sync
|
||||
env:
|
||||
OPENCLAW_GH_TOKEN: ${{ secrets.OPENCLAW_GH_TOKEN }}
|
||||
LEGACY_TOKEN: ${{ secrets.OPENCLAW_DOCS_SYNC_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
OPENCLAW_DOCS_SYNC_TOKEN="${OPENCLAW_GH_TOKEN:-${LEGACY_TOKEN:-}}"
|
||||
|
||||
if [ -z "${OPENCLAW_DOCS_SYNC_TOKEN:-}" ]; then
|
||||
echo "::error::OPENCLAW_GH_TOKEN or legacy token is required"
|
||||
echo "::error::to dispatch openclaw/openclaw docs sync."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
dispatch_url="https://api.github.com/repos/openclaw/openclaw"
|
||||
dispatch_url+="/actions/workflows/docs-sync-publish.yml/dispatches"
|
||||
|
||||
curl --fail-with-body --silent --show-error \
|
||||
--request POST \
|
||||
--header "Authorization: Bearer ${OPENCLAW_DOCS_SYNC_TOKEN}" \
|
||||
--header "Accept: application/vnd.github+json" \
|
||||
--header "X-GitHub-Api-Version: 2022-11-28" \
|
||||
"${dispatch_url}" \
|
||||
--data '{"ref":"main"}'
|
||||
@@ -57,11 +57,28 @@ on:
|
||||
description: Optional source ref override for local-folder publishes.
|
||||
required: false
|
||||
type: string
|
||||
clawhub_version:
|
||||
description: Legacy npm CLI version input. Kept for compatibility; the workflow now runs the checked-out source.
|
||||
source_path:
|
||||
description: Optional source path inside the repository for monorepo package publishes.
|
||||
required: false
|
||||
type: string
|
||||
default: latest
|
||||
package_artifact_name:
|
||||
description: Optional Actions artifact name containing a prebuilt ClawPack .tgz to publish.
|
||||
required: false
|
||||
type: string
|
||||
package_artifact_path:
|
||||
description: Optional path to the .tgz inside package_artifact_name. Defaults to the only .tgz in the artifact.
|
||||
required: false
|
||||
type: string
|
||||
inspector_artifact_name:
|
||||
description: Artifact name for plugin inspector reports. Set a unique value when calling this workflow from a matrix.
|
||||
required: false
|
||||
type: string
|
||||
default: plugin-inspector-report
|
||||
publish_json_artifact_name:
|
||||
description: Artifact name for the package publish JSON output. Set a unique value when calling this workflow from a matrix.
|
||||
required: false
|
||||
type: string
|
||||
default: clawhub-package-publish-json
|
||||
secrets:
|
||||
clawhub_token:
|
||||
required: false
|
||||
@@ -76,18 +93,21 @@ on:
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
id-token: write
|
||||
outputs:
|
||||
publish_json: ${{ steps.capture.outputs.publish_json }}
|
||||
release_id: ${{ steps.capture.outputs.release_id }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
ref: ${{ github.sha }}
|
||||
|
||||
@@ -148,7 +168,7 @@ jobs:
|
||||
fh.write(f"ref={workflow_sha}\n")
|
||||
PY
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
repository: ${{ steps.clawhub_source.outputs.repository }}
|
||||
ref: ${{ steps.clawhub_source.outputs.ref }}
|
||||
@@ -212,7 +232,56 @@ jobs:
|
||||
PY
|
||||
echo "CLAWHUB_CONFIG_PATH=$RUNNER_TEMP/clawhub-config.json" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Download prebuilt package artifact
|
||||
if: inputs.package_artifact_name != ''
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: ${{ inputs.package_artifact_name }}
|
||||
path: ${{ runner.temp }}/prebuilt-package-artifact
|
||||
|
||||
- name: Resolve prebuilt package artifact
|
||||
id: resolve_artifact
|
||||
env:
|
||||
INPUT_PACKAGE_ARTIFACT_NAME: ${{ inputs.package_artifact_name }}
|
||||
INPUT_PACKAGE_ARTIFACT_PATH: ${{ inputs.package_artifact_path }}
|
||||
run: |
|
||||
python3 - <<'PY'
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
artifact_name = os.environ["INPUT_PACKAGE_ARTIFACT_NAME"].strip()
|
||||
output_path = Path(os.environ["GITHUB_OUTPUT"])
|
||||
if not artifact_name:
|
||||
with output_path.open("a", encoding="utf-8") as fh:
|
||||
fh.write("package_artifact_path=\n")
|
||||
raise SystemExit(0)
|
||||
|
||||
artifact_root = Path(os.environ["RUNNER_TEMP"]) / "prebuilt-package-artifact"
|
||||
requested_path = os.environ["INPUT_PACKAGE_ARTIFACT_PATH"].strip()
|
||||
if requested_path:
|
||||
candidate = (artifact_root / requested_path).resolve()
|
||||
if artifact_root.resolve() not in candidate.parents and candidate != artifact_root.resolve():
|
||||
raise SystemExit(f"Prebuilt artifact path escapes downloaded artifact: {requested_path}")
|
||||
if not candidate.is_file():
|
||||
raise SystemExit(f"Prebuilt package artifact path not found: {requested_path}")
|
||||
else:
|
||||
candidates = sorted(path for path in artifact_root.rglob("*.tgz") if path.is_file())
|
||||
if not candidates:
|
||||
raise SystemExit(f"Prebuilt package artifact {artifact_name!r} did not contain a .tgz file.")
|
||||
if len(candidates) > 1:
|
||||
joined = ", ".join(str(path.relative_to(artifact_root)) for path in candidates)
|
||||
raise SystemExit(
|
||||
"Prebuilt package artifact contains multiple .tgz files; set package_artifact_path. "
|
||||
f"Found: {joined}"
|
||||
)
|
||||
candidate = candidates[0]
|
||||
|
||||
with output_path.open("a", encoding="utf-8") as fh:
|
||||
fh.write(f"package_artifact_path={candidate}\n")
|
||||
PY
|
||||
|
||||
- name: Resolve publish command
|
||||
id: resolve_publish
|
||||
env:
|
||||
INPUT_SOURCE: ${{ inputs.source }}
|
||||
INPUT_REF: ${{ inputs.ref }}
|
||||
@@ -223,9 +292,12 @@ jobs:
|
||||
INPUT_SOURCE_REPO: ${{ inputs.source_repo }}
|
||||
INPUT_SOURCE_COMMIT: ${{ inputs.source_commit }}
|
||||
INPUT_SOURCE_REF: ${{ inputs.source_ref }}
|
||||
INPUT_SOURCE_PATH: ${{ inputs.source_path }}
|
||||
PREBUILT_PACKAGE_ARTIFACT_PATH: ${{ steps.resolve_artifact.outputs.package_artifact_path }}
|
||||
INPUT_SITE: ${{ inputs.site }}
|
||||
INPUT_REGISTRY: ${{ inputs.registry }}
|
||||
CLAWHUB_TOKEN: ${{ secrets.clawhub_token }}
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
GITHUB_EVENT_NAME: ${{ github.event_name }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
@@ -236,6 +308,78 @@ jobs:
|
||||
import os
|
||||
import shlex
|
||||
from pathlib import Path
|
||||
from urllib.error import HTTPError
|
||||
from urllib.parse import quote, urlparse
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
def split_ref_path(value):
|
||||
if not value:
|
||||
return "", ""
|
||||
if ":" not in value:
|
||||
return value, ""
|
||||
ref, path = value.split(":", 1)
|
||||
return ref, path.strip("/")
|
||||
|
||||
def github_commit_exists(repo, ref):
|
||||
token = os.environ.get("GITHUB_TOKEN", "").strip()
|
||||
headers = {
|
||||
"Accept": "application/vnd.github+json",
|
||||
"User-Agent": "clawhub-package-publish",
|
||||
}
|
||||
if token:
|
||||
headers["Authorization"] = f"Bearer {token}"
|
||||
request = Request(
|
||||
f"https://api.github.com/repos/{repo}/commits/{quote(ref, safe='')}",
|
||||
headers=headers,
|
||||
)
|
||||
try:
|
||||
with urlopen(request, timeout=10) as response:
|
||||
return 200 <= response.status < 300
|
||||
except HTTPError as error:
|
||||
if error.code in (404, 422):
|
||||
return False
|
||||
raise
|
||||
|
||||
def resolve_github_url_ref_and_path(repo, kind, segments):
|
||||
min_path_segments = 1 if kind == "blob" else 0
|
||||
max_ref_segments = len(segments) - min_path_segments
|
||||
for ref_segment_count in range(max_ref_segments, 0, -1):
|
||||
ref = "/".join(segments[:ref_segment_count])
|
||||
path = "/".join(segments[ref_segment_count:]).strip("/")
|
||||
if kind == "blob" and not path:
|
||||
continue
|
||||
if not github_commit_exists(repo, ref):
|
||||
continue
|
||||
if kind == "blob":
|
||||
path = "/".join(path.split("/")[:-1]).strip("/")
|
||||
return ref, path
|
||||
raise SystemExit(f"GitHub ref not found in source URL for {repo}")
|
||||
|
||||
def parse_github_source(value):
|
||||
raw = value.strip()
|
||||
if raw.startswith("github:"):
|
||||
raw = raw[len("github:"):]
|
||||
if raw.startswith("https://") or raw.startswith("http://"):
|
||||
parsed = urlparse(raw)
|
||||
if parsed.netloc.lower() != "github.com":
|
||||
return None
|
||||
parts = [part for part in parsed.path.strip("/").split("/") if part]
|
||||
if len(parts) < 2:
|
||||
return None
|
||||
repo_name = parts[1][:-4] if parts[1].endswith(".git") else parts[1]
|
||||
repo = f"{parts[0]}/{repo_name}"
|
||||
if len(parts) >= 4 and parts[2] in {"tree", "blob"}:
|
||||
ref, path = resolve_github_url_ref_and_path(repo, parts[2], parts[3:])
|
||||
return {"repo": repo, "ref": ref, "path": path}
|
||||
return {"repo": repo, "ref": "", "path": ""}
|
||||
|
||||
source_part, at, ref_part = raw.partition("@")
|
||||
repo_parts = source_part.split("/")
|
||||
if len(repo_parts) != 2 or not repo_parts[0] or not repo_parts[1]:
|
||||
return None
|
||||
repo_name = repo_parts[1][:-4] if repo_parts[1].endswith(".git") else repo_parts[1]
|
||||
ref, path = split_ref_path(ref_part if at else "")
|
||||
return {"repo": f"{repo_parts[0]}/{repo_name}", "ref": ref, "path": path}
|
||||
|
||||
source = os.environ["INPUT_SOURCE"].strip()
|
||||
if not source:
|
||||
@@ -247,6 +391,33 @@ jobs:
|
||||
is_local_source = source.startswith(".") or source.startswith("/") or Path(source).exists()
|
||||
if ref and "@" not in source and not source.startswith("http") and not is_local_source:
|
||||
source = f"{source}@{ref}"
|
||||
source_path = os.environ["INPUT_SOURCE_PATH"].strip()
|
||||
prebuilt_artifact_path = os.environ["PREBUILT_PACKAGE_ARTIFACT_PATH"].strip()
|
||||
inspect_checkout_repository = ""
|
||||
inspect_checkout_ref = ""
|
||||
inspect_local_root = str(Path(os.environ["GITHUB_WORKSPACE"]).resolve())
|
||||
inspect_subdir = source_path
|
||||
if prebuilt_artifact_path:
|
||||
inspect_local_root = str((Path(os.environ["RUNNER_TEMP"]) / "prebuilt-package-inspect").resolve())
|
||||
inspect_subdir = ""
|
||||
elif is_local_source:
|
||||
inspect_local_root = str(Path(source).resolve())
|
||||
else:
|
||||
github_source = parse_github_source(source)
|
||||
source_ref_differs_from_checkout = (
|
||||
bool(github_source and github_source["ref"])
|
||||
and github_source["ref"] != os.environ["GITHUB_SHA"]
|
||||
)
|
||||
if github_source and (
|
||||
github_source["repo"] != os.environ["GITHUB_REPOSITORY"]
|
||||
or source_ref_differs_from_checkout
|
||||
):
|
||||
inspect_checkout_repository = github_source["repo"]
|
||||
inspect_checkout_ref = github_source["ref"]
|
||||
inspect_local_root = str((Path(os.environ["GITHUB_WORKSPACE"]) / "clawhub-publish-source").resolve())
|
||||
inspect_subdir = source_path or github_source["path"]
|
||||
elif github_source:
|
||||
inspect_subdir = source_path or github_source["path"]
|
||||
|
||||
cli_entry = (
|
||||
Path(os.environ["GITHUB_WORKSPACE"])
|
||||
@@ -259,12 +430,13 @@ jobs:
|
||||
if not cli_entry.exists():
|
||||
raise SystemExit(f"Missing ClawHub CLI entrypoint at {cli_entry}")
|
||||
|
||||
cmd_source = prebuilt_artifact_path or source
|
||||
cmd = [
|
||||
"bun",
|
||||
str(cli_entry),
|
||||
"package",
|
||||
"publish",
|
||||
source,
|
||||
cmd_source,
|
||||
"--site",
|
||||
os.environ["INPUT_SITE"],
|
||||
"--registry",
|
||||
@@ -287,6 +459,16 @@ jobs:
|
||||
source_repo = os.environ["INPUT_SOURCE_REPO"].strip()
|
||||
source_commit = os.environ["INPUT_SOURCE_COMMIT"].strip()
|
||||
source_ref = os.environ["INPUT_SOURCE_REF"].strip()
|
||||
if prebuilt_artifact_path:
|
||||
if not source_repo and not source_commit:
|
||||
source_repo = os.environ["GITHUB_REPOSITORY"].strip()
|
||||
source_commit = os.environ["GITHUB_SHA"].strip()
|
||||
elif not source_repo or not source_commit:
|
||||
raise SystemExit(
|
||||
"Prebuilt artifact mode requires source_repo and source_commit together when overriding source attribution."
|
||||
)
|
||||
if not source_ref:
|
||||
source_ref = os.environ["GITHUB_REF"].strip()
|
||||
if source_repo:
|
||||
cmd += ["--source-repo", source_repo]
|
||||
if source_commit:
|
||||
@@ -297,6 +479,8 @@ jobs:
|
||||
github_ref = os.environ["GITHUB_REF"].strip()
|
||||
if github_ref:
|
||||
cmd += ["--source-ref", github_ref]
|
||||
if source_path:
|
||||
cmd += ["--source-path", source_path]
|
||||
if os.environ["INPUT_DRY_RUN"] != "true" and os.environ["CLAWHUB_TOKEN"].strip():
|
||||
cmd += [
|
||||
"--manual-override-reason",
|
||||
@@ -308,8 +492,65 @@ jobs:
|
||||
path.write_text("#!/usr/bin/env bash\nset -euo pipefail\n" + shell_line + "\n", encoding="utf-8")
|
||||
path.chmod(0o755)
|
||||
print(shell_line)
|
||||
|
||||
output_path = Path(os.environ["GITHUB_OUTPUT"])
|
||||
with output_path.open("a", encoding="utf-8") as fh:
|
||||
fh.write(f"inspect_checkout_repository={inspect_checkout_repository}\n")
|
||||
fh.write(f"inspect_checkout_ref={inspect_checkout_ref}\n")
|
||||
fh.write(f"inspect_local_root={inspect_local_root}\n")
|
||||
fh.write(f"inspect_subdir={inspect_subdir}\n")
|
||||
PY
|
||||
|
||||
- name: Extract prebuilt package artifact for plugin validation
|
||||
if: steps.resolve_artifact.outputs.package_artifact_path != ''
|
||||
env:
|
||||
PREBUILT_PACKAGE_ARTIFACT_PATH: ${{ steps.resolve_artifact.outputs.package_artifact_path }}
|
||||
INSPECT_LOCAL_ROOT: ${{ steps.resolve_publish.outputs.inspect_local_root }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p "$INSPECT_LOCAL_ROOT"
|
||||
tar -xzf "$PREBUILT_PACKAGE_ARTIFACT_PATH" -C "$INSPECT_LOCAL_ROOT" --strip-components=1
|
||||
|
||||
- name: Checkout publish source for plugin inspector
|
||||
if: steps.resolve_publish.outputs.inspect_checkout_repository != ''
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
repository: ${{ steps.resolve_publish.outputs.inspect_checkout_repository }}
|
||||
ref: ${{ steps.resolve_publish.outputs.inspect_checkout_ref }}
|
||||
path: clawhub-publish-source
|
||||
|
||||
- name: Run plugin validation
|
||||
env:
|
||||
INSPECT_LOCAL_ROOT: ${{ steps.resolve_publish.outputs.inspect_local_root }}
|
||||
INSPECT_SUBDIR: ${{ steps.resolve_publish.outputs.inspect_subdir }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
inspect_root="$(python3 - <<'PY'
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
root = Path(os.environ["INSPECT_LOCAL_ROOT"]).resolve()
|
||||
subdir = os.environ["INSPECT_SUBDIR"].strip()
|
||||
inspect_root = (root / subdir).resolve() if subdir else root
|
||||
if inspect_root != root and root not in inspect_root.parents:
|
||||
raise SystemExit(f"Inspector source path escapes publish source: {subdir}")
|
||||
print(inspect_root)
|
||||
PY
|
||||
)"
|
||||
if [ ! -f "$inspect_root/package.json" ] && [ ! -f "$inspect_root/openclaw.plugin.json" ]; then
|
||||
echo "::warning::Plugin Inspector skipped because $inspect_root is not a plugin root."
|
||||
exit 0
|
||||
fi
|
||||
bun "$GITHUB_WORKSPACE/clawhub-source/packages/clawhub/src/cli.ts" package validate "$inspect_root" --out "$RUNNER_TEMP/plugin-inspector"
|
||||
|
||||
- name: Upload plugin inspector reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ inputs.inspector_artifact_name }}
|
||||
path: ${{ runner.temp }}/plugin-inspector
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Run package publish
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -339,6 +580,6 @@ jobs:
|
||||
- name: Upload publish JSON artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: clawhub-package-publish-json
|
||||
name: ${{ inputs.publish_json_artifact_name }}
|
||||
path: ${{ runner.temp }}/package-publish.json
|
||||
if-no-files-found: error
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
name: Plugin Inspector Bulk Scan
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
batch_size:
|
||||
description: "Maximum plugin releases to scan"
|
||||
required: false
|
||||
default: "25"
|
||||
dry_run:
|
||||
description: "Preview impact without persisting findings or sending emails"
|
||||
required: false
|
||||
default: "false"
|
||||
type: boolean
|
||||
dry_run_max_batches:
|
||||
description: "Maximum preview batches to scan when dry_run is enabled"
|
||||
required: false
|
||||
default: "20"
|
||||
source_pr:
|
||||
description: "Merged PR number that triggered this scan, when dispatched automatically"
|
||||
required: false
|
||||
default: ""
|
||||
source_sha:
|
||||
description: "Merged commit SHA that triggered this scan, when dispatched automatically"
|
||||
required: false
|
||||
default: ""
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
scan:
|
||||
name: Scan published plugins
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Run plugin inspector bulk scan
|
||||
env:
|
||||
CLAWHUB_SITE_URL: ${{ vars.CLAWHUB_SITE_URL || 'https://clawhub.ai' }}
|
||||
CLAWHUB_PLUGIN_INSPECTOR_WORKER_TOKEN: ${{ secrets.CLAWHUB_PLUGIN_INSPECTOR_WORKER_TOKEN }}
|
||||
PLUGIN_INSPECTOR_BATCH_SIZE: ${{ inputs.batch_size || '25' }}
|
||||
PLUGIN_INSPECTOR_DRY_RUN: ${{ inputs.dry_run && '1' || '0' }}
|
||||
PLUGIN_INSPECTOR_DRY_RUN_MAX_BATCHES: ${{ inputs.dry_run_max_batches || '20' }}
|
||||
PLUGIN_INSPECTOR_SOURCE_PR: ${{ inputs.source_pr || '' }}
|
||||
PLUGIN_INSPECTOR_SOURCE_SHA: ${{ inputs.source_sha || '' }}
|
||||
PLUGIN_INSPECTOR_ARTIFACT_DIR: plugin-inspector-bulk-scan-reports
|
||||
run: bun scripts/package-inspector-nightly-scan.ts
|
||||
|
||||
- name: Upload inspector reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: plugin-inspector-bulk-scan-reports
|
||||
path: plugin-inspector-bulk-scan-reports
|
||||
if-no-files-found: warn
|
||||
@@ -0,0 +1,55 @@
|
||||
name: Plugin Inspector Pin Bump Dispatch
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- package.json
|
||||
- packages/clawhub/package.json
|
||||
- bun.lock
|
||||
|
||||
permissions:
|
||||
actions: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
dispatch-plugin-inspector-bulk-scan:
|
||||
name: Dispatch Plugin Inspector bulk scan
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout main commit
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
ref: ${{ github.sha }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Fetch previous main commit
|
||||
env:
|
||||
BASE_SHA: ${{ github.event.before }}
|
||||
run: git fetch --no-tags --depth=1 origin "$BASE_SHA"
|
||||
|
||||
- name: Detect pinned Plugin Inspector change
|
||||
id: detect
|
||||
env:
|
||||
BASE_SHA: ${{ github.event.before }}
|
||||
HEAD_SHA: ${{ github.sha }}
|
||||
run: node scripts/github/plugin-inspector-pin-change.mjs --base "$BASE_SHA" --head "$HEAD_SHA"
|
||||
|
||||
- name: Dispatch Plugin Inspector bulk scan
|
||||
if: ${{ steps.detect.outputs.changed == 'true' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
gh workflow run plugin-inspector-bulk-scan.yml \
|
||||
--ref main \
|
||||
-f batch_size=25 \
|
||||
-f dry_run=false \
|
||||
-f dry_run_max_batches=20 \
|
||||
-f source_sha=${{ github.sha }}
|
||||
|
||||
- name: Explain skipped dispatch
|
||||
if: ${{ steps.detect.outputs.changed != 'true' }}
|
||||
env:
|
||||
DISPATCH_SKIP_REASON: ${{ steps.detect.outputs.reason }}
|
||||
run: printf '%s\n' "$DISPATCH_SKIP_REASON"
|
||||
@@ -0,0 +1,67 @@
|
||||
name: Publish Hosted Catalog Feed
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "17 */6 * * *"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
expires_in_days:
|
||||
description: "How long the published feed remains fresh"
|
||||
required: true
|
||||
default: "7"
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: publish-catalog-feed
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
validate-ref:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Require main ref for production publication
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ "${GITHUB_REF}" != "refs/heads/main" ]]; then
|
||||
echo "Production catalog publications must run from main."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
needs: validate-ref
|
||||
environment:
|
||||
name: Production
|
||||
url: https://registry.openclaw.ai/v1/feeds/plugins
|
||||
env:
|
||||
EXPIRES_IN_DAYS: ${{ inputs.expires_in_days || '7' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
|
||||
with:
|
||||
bun-version: 1.3.10
|
||||
|
||||
- name: Install
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Publish current production catalog
|
||||
env:
|
||||
CONVEX_DEPLOY_KEY: ${{ secrets.CONVEX_DEPLOY_KEY }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -z "$CONVEX_DEPLOY_KEY" ]]; then
|
||||
echo "::error::Missing Production environment secret CONVEX_DEPLOY_KEY"
|
||||
exit 1
|
||||
fi
|
||||
if ! [[ "$EXPIRES_IN_DAYS" =~ ^[1-9][0-9]*$ ]]; then
|
||||
echo "::error::expires_in_days must be a positive integer"
|
||||
exit 1
|
||||
fi
|
||||
expires_at="$(node -e 'const days = Number(process.env.EXPIRES_IN_DAYS); console.log(new Date(Date.now() + days * 86400000).toISOString())')"
|
||||
bunx convex run catalogFeed:publish "{\"expiresAt\":\"$expires_at\"}" --prod
|
||||
@@ -0,0 +1,84 @@
|
||||
name: ClawHub Scheduled Live Checks
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "17 5 * * *"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
github-repo:
|
||||
description: GitHub skills repo to use for the source-backed canary
|
||||
required: false
|
||||
default: openclaw/agent-skills
|
||||
github-skill:
|
||||
description: Skill slug to verify from the GitHub skills repo
|
||||
required: false
|
||||
default: handoff
|
||||
|
||||
concurrency:
|
||||
group: clawhub-scheduled-live-checks-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
VITE_CONVEX_URL: https://example.invalid
|
||||
|
||||
jobs:
|
||||
github-backed-skills:
|
||||
name: GitHub-backed skills canary
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Run GitHub-backed skills live canary
|
||||
env:
|
||||
CLAWHUB_LIVE_GITHUB_CANARY: "1"
|
||||
CLAWHUB_LIVE_GITHUB_REPO: ${{ inputs.github-repo || 'openclaw/agent-skills' }}
|
||||
CLAWHUB_LIVE_GITHUB_SKILL: ${{ inputs.github-skill || 'handoff' }}
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
run: bunx vitest run convex/githubSkillSync.live.test.ts
|
||||
|
||||
open-failure-issue:
|
||||
name: Open failure issue
|
||||
needs: github-backed-skills
|
||||
if: ${{ always() && needs.github-backed-skills.result == 'failure' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- name: Open or update failure issue
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
WORKFLOW_NAME: ${{ github.workflow }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
marker_token="clawhub-scheduled-live-checks-failure"
|
||||
marker="<!-- $marker_token -->"
|
||||
title="ClawHub scheduled live checks failing"
|
||||
issue_number="$(gh issue list \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--state open \
|
||||
--search "$marker_token in:body" \
|
||||
--json number \
|
||||
--jq '.[0].number // empty')"
|
||||
|
||||
body_file="$(mktemp)"
|
||||
cat > "$body_file" <<EOF
|
||||
$marker
|
||||
The scheduled ClawHub live checks failed.
|
||||
|
||||
Workflow: $WORKFLOW_NAME
|
||||
Run: $RUN_URL
|
||||
EOF
|
||||
|
||||
if [[ -n "$issue_number" ]]; then
|
||||
gh issue comment "$issue_number" --repo "$GITHUB_REPOSITORY" --body-file "$body_file"
|
||||
else
|
||||
gh issue create --repo "$GITHUB_REPOSITORY" --title "$title" --body-file "$body_file"
|
||||
fi
|
||||
@@ -6,6 +6,8 @@ on:
|
||||
pull_request:
|
||||
branches: [main, master]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
trufflehog:
|
||||
name: Scan for Verified Secrets
|
||||
@@ -14,7 +16,7 @@ jobs:
|
||||
contents: read # Required to scan the code in the PR
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0 # necessary to support the scoping requirements below
|
||||
|
||||
@@ -49,7 +51,7 @@ jobs:
|
||||
id: trufflehog
|
||||
# Use a concrete released ref that resolves in upstream action registry.
|
||||
# v3 (major tag) is not published by trufflesecurity/trufflehog.
|
||||
uses: trufflesecurity/trufflehog@v3.95.2
|
||||
uses: trufflesecurity/trufflehog@v3.95.6
|
||||
with:
|
||||
path: ./
|
||||
base: ${{ steps.scan_range.outputs.base }}
|
||||
|
||||
@@ -0,0 +1,336 @@
|
||||
name: Security Dataset Snapshot
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
upload:
|
||||
description: "Upload sanitized dataset files to Hugging Face"
|
||||
required: true
|
||||
default: "false"
|
||||
type: choice
|
||||
options:
|
||||
- "false"
|
||||
- "true"
|
||||
limit:
|
||||
description: "Optional source artifact cap for validation runs"
|
||||
required: false
|
||||
default: ""
|
||||
hf-revision:
|
||||
description: "Hugging Face branch/revision to upload to"
|
||||
required: true
|
||||
default: "main"
|
||||
page-size:
|
||||
description: "Live export page size per Convex request"
|
||||
required: true
|
||||
default: "25"
|
||||
min-page-size:
|
||||
description: "Smallest page size to retry after Convex timeouts"
|
||||
required: true
|
||||
default: "1"
|
||||
batch-pages:
|
||||
description: "Live export pages per Convex request"
|
||||
required: true
|
||||
default: "1"
|
||||
concurrency:
|
||||
description: "Concurrent live export shards"
|
||||
required: true
|
||||
default: "2"
|
||||
shards:
|
||||
description: "Created-at shards per source kind"
|
||||
required: true
|
||||
default: "128"
|
||||
schedule:
|
||||
- cron: "17 9 * * *"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: clawhub-security-dataset-snapshot
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
plan-security-dataset:
|
||||
name: Plan sanitized security dataset shards
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 15
|
||||
environment: Production
|
||||
outputs:
|
||||
matrix: ${{ steps.plan.outputs.matrix }}
|
||||
source_snapshot_id: ${{ steps.plan.outputs.source_snapshot_id }}
|
||||
env:
|
||||
CONVEX_URL: ${{ vars.CONVEX_URL || vars.VITE_CONVEX_URL || 'https://wry-manatee-359.convex.cloud' }}
|
||||
SECURITY_SCAN_WORKER_TOKEN: ${{ secrets.SECURITY_SCAN_WORKER_TOKEN }}
|
||||
HF_DATASET_REPO: OpenClaw/clawhub-security-signals
|
||||
HF_REVISION: ${{ inputs['hf-revision'] || 'main' }}
|
||||
HF_UPLOAD: ${{ github.event_name == 'schedule' || inputs.upload == 'true' }}
|
||||
SNAPSHOT_LIMIT: ${{ inputs.limit || '' }}
|
||||
SNAPSHOT_PAGE_SIZE: ${{ inputs['page-size'] || '25' }}
|
||||
SNAPSHOT_MIN_PAGE_SIZE: ${{ inputs['min-page-size'] || '1' }}
|
||||
SNAPSHOT_BATCH_PAGES: ${{ inputs['batch-pages'] || '1' }}
|
||||
SNAPSHOT_CONCURRENCY: ${{ inputs.concurrency || '1' }}
|
||||
SNAPSHOT_SHARDS: ${{ inputs.shards || '128' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Check configuration
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -z "$SECURITY_SCAN_WORKER_TOKEN" ]]; then
|
||||
echo "::error::SECURITY_SCAN_WORKER_TOKEN is required"
|
||||
exit 1
|
||||
fi
|
||||
echo "Upload enabled: $HF_UPLOAD"
|
||||
echo "Convex URL: $CONVEX_URL"
|
||||
echo "Hugging Face repo: $HF_DATASET_REPO"
|
||||
echo "Hugging Face revision: $HF_REVISION"
|
||||
echo "Shard count per source kind: $SNAPSHOT_SHARDS"
|
||||
|
||||
- name: Plan live export shards
|
||||
id: plan
|
||||
run: |
|
||||
set -euo pipefail
|
||||
source_snapshot_id="live-convex-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
|
||||
matrix_path="$RUNNER_TEMP/security-dataset-shards.json"
|
||||
bun scripts/security-dataset/export-snapshot.ts \
|
||||
--convex-url "$CONVEX_URL" \
|
||||
--worker-token "$SECURITY_SCAN_WORKER_TOKEN" \
|
||||
--source-snapshot-id "$source_snapshot_id" \
|
||||
--hf-repo "$HF_DATASET_REPO" \
|
||||
--hf-revision "$HF_REVISION" \
|
||||
--page-size "$SNAPSHOT_PAGE_SIZE" \
|
||||
--min-page-size "$SNAPSHOT_MIN_PAGE_SIZE" \
|
||||
--batch-pages "$SNAPSHOT_BATCH_PAGES" \
|
||||
--concurrency "$SNAPSHOT_CONCURRENCY" \
|
||||
--shards "$SNAPSHOT_SHARDS" \
|
||||
--write-shard-matrix "$matrix_path"
|
||||
echo "source_snapshot_id=$source_snapshot_id" >> "$GITHUB_OUTPUT"
|
||||
echo "matrix=$(jq -c . "$matrix_path")" >> "$GITHUB_OUTPUT"
|
||||
jq '{shard_count: (.include | length), first: .include[0], last: .include[-1]}' "$matrix_path"
|
||||
|
||||
export-security-dataset-shards:
|
||||
name: Export sanitized shard ${{ matrix.index }}
|
||||
needs: plan-security-dataset
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 120
|
||||
environment: Production
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 12
|
||||
matrix: ${{ fromJson(needs.plan-security-dataset.outputs.matrix) }}
|
||||
env:
|
||||
CONVEX_URL: ${{ vars.CONVEX_URL || vars.VITE_CONVEX_URL || 'https://wry-manatee-359.convex.cloud' }}
|
||||
SECURITY_SCAN_WORKER_TOKEN: ${{ secrets.SECURITY_SCAN_WORKER_TOKEN }}
|
||||
HF_DATASET_REPO: OpenClaw/clawhub-security-signals
|
||||
HF_REVISION: ${{ inputs['hf-revision'] || 'main' }}
|
||||
SNAPSHOT_PAGE_SIZE: ${{ inputs['page-size'] || '25' }}
|
||||
SNAPSHOT_MIN_PAGE_SIZE: ${{ inputs['min-page-size'] || '1' }}
|
||||
SNAPSHOT_BATCH_PAGES: ${{ inputs['batch-pages'] || '1' }}
|
||||
SANITIZED_OUT_DIR: /tmp/clawhub-security-dataset/shard
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Export sanitized shard
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p "$SANITIZED_OUT_DIR"
|
||||
args=(
|
||||
--convex-url "$CONVEX_URL"
|
||||
--worker-token "$SECURITY_SCAN_WORKER_TOKEN"
|
||||
--source-snapshot-id "${{ needs.plan-security-dataset.outputs.source_snapshot_id }}"
|
||||
--out-dir "$SANITIZED_OUT_DIR"
|
||||
--hf-dataset
|
||||
--hf-repo "$HF_DATASET_REPO"
|
||||
--hf-revision "$HF_REVISION"
|
||||
--source-kind "${{ matrix.sourceKind }}"
|
||||
--created-after "${{ matrix.createdAtGte }}"
|
||||
--created-before "${{ matrix.createdAtLt }}"
|
||||
--page-size "$SNAPSHOT_PAGE_SIZE"
|
||||
--min-page-size "$SNAPSHOT_MIN_PAGE_SIZE"
|
||||
--batch-pages "$SNAPSHOT_BATCH_PAGES"
|
||||
--concurrency 1
|
||||
--shards 1
|
||||
)
|
||||
bun scripts/security-dataset/export-snapshot.ts "${args[@]}" | tee "$SANITIZED_OUT_DIR/summary.json"
|
||||
snapshot_dir="$(jq -r '.snapshotDir' "$SANITIZED_OUT_DIR/summary.json")"
|
||||
if [[ -z "$snapshot_dir" || "$snapshot_dir" == "null" ]]; then
|
||||
echo "::error::export summary did not include snapshotDir"
|
||||
exit 1
|
||||
fi
|
||||
echo "SNAPSHOT_DIR=$snapshot_dir" >> "$GITHUB_ENV"
|
||||
jq '.manifest.row_counts, .manifest.huggingface_dataset' "$SANITIZED_OUT_DIR/summary.json"
|
||||
|
||||
- name: Upload shard artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: security-dataset-shard-${{ matrix.index }}
|
||||
path: ${{ env.SNAPSHOT_DIR }}
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
publish-security-dataset:
|
||||
name: Publish sanitized security dataset
|
||||
needs:
|
||||
- plan-security-dataset
|
||||
- export-security-dataset-shards
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 120
|
||||
environment: Production
|
||||
env:
|
||||
HF_DATASET_REPO: OpenClaw/clawhub-security-signals
|
||||
HF_OIDC_RESOURCE: datasets/OpenClaw/clawhub-security-signals
|
||||
HF_REVISION: ${{ inputs['hf-revision'] || 'main' }}
|
||||
HF_UPLOAD: ${{ github.event_name == 'schedule' || inputs.upload == 'true' }}
|
||||
SANITIZED_OUT_DIR: /tmp/clawhub-security-dataset/sanitized
|
||||
WORK_DIR: /tmp/clawhub-security-dataset
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
if: ${{ env.HF_UPLOAD == 'true' }}
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Download sanitized shard artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: security-dataset-shard-*
|
||||
path: ${{ env.WORK_DIR }}/shards
|
||||
|
||||
- name: Merge sanitized shard outputs
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p "$SANITIZED_OUT_DIR"
|
||||
source_snapshot_id="${{ needs.plan-security-dataset.outputs.source_snapshot_id }}"
|
||||
bun scripts/security-dataset/merge-snapshots.ts \
|
||||
--shards-dir "$WORK_DIR/shards" \
|
||||
--out-dir "$SANITIZED_OUT_DIR" \
|
||||
--source-snapshot-id "$source_snapshot_id" \
|
||||
--hf-repo "$HF_DATASET_REPO" \
|
||||
--hf-revision "$HF_REVISION" | tee "$SANITIZED_OUT_DIR/summary.json"
|
||||
snapshot_dir="$(jq -r '.snapshotDir' "$SANITIZED_OUT_DIR/summary.json")"
|
||||
if [[ -z "$snapshot_dir" || "$snapshot_dir" == "null" ]]; then
|
||||
echo "::error::merge summary did not include snapshotDir"
|
||||
exit 1
|
||||
fi
|
||||
echo "SNAPSHOT_DIR=$snapshot_dir" >> "$GITHUB_ENV"
|
||||
jq '.manifest.row_counts, .manifest.huggingface_dataset' "$SANITIZED_OUT_DIR/summary.json"
|
||||
|
||||
- name: Validate sanitized output guardrails
|
||||
run: |
|
||||
set -euo pipefail
|
||||
bun scripts/security-dataset/validate-guardrails.ts --snapshot-dir "$SNAPSHOT_DIR"
|
||||
|
||||
- name: Install Hugging Face uploader
|
||||
if: ${{ env.HF_UPLOAD == 'true' }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install 'huggingface_hub[hf_xet]'
|
||||
|
||||
- name: Upload sanitized dataset to Hugging Face
|
||||
if: ${{ env.HF_UPLOAD == 'true' }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
python - <<'PY'
|
||||
import json
|
||||
import os
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
from huggingface_hub import HfApi
|
||||
|
||||
def get_github_oidc_token() -> str:
|
||||
request_url = os.environ["ACTIONS_ID_TOKEN_REQUEST_URL"]
|
||||
separator = "&" if "?" in request_url else "?"
|
||||
request = urllib.request.Request(
|
||||
f"{request_url}{separator}audience=https://huggingface.co",
|
||||
headers={
|
||||
"Authorization": f"bearer {os.environ['ACTIONS_ID_TOKEN_REQUEST_TOKEN']}",
|
||||
"Accept": "application/json",
|
||||
},
|
||||
)
|
||||
with urllib.request.urlopen(request) as response:
|
||||
payload = json.loads(response.read().decode("utf-8"))
|
||||
return payload["value"]
|
||||
|
||||
def exchange_hugging_face_token(oidc_token: str, resource: str) -> str:
|
||||
body = json.dumps({
|
||||
"grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
|
||||
"subject_token_type": "urn:ietf:params:oauth:token-type:id_token",
|
||||
"subject_token": oidc_token,
|
||||
"resource": resource,
|
||||
}).encode("utf-8")
|
||||
request = urllib.request.Request(
|
||||
"https://huggingface.co/oauth/token",
|
||||
data=body,
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
},
|
||||
method="POST",
|
||||
)
|
||||
with urllib.request.urlopen(request) as response:
|
||||
payload = json.loads(response.read().decode("utf-8"))
|
||||
return payload["access_token"]
|
||||
|
||||
snapshot_dir = Path(os.environ["SNAPSHOT_DIR"])
|
||||
repo_id = os.environ["HF_DATASET_REPO"]
|
||||
revision = os.environ["HF_REVISION"]
|
||||
token = exchange_hugging_face_token(
|
||||
get_github_oidc_token(),
|
||||
os.environ["HF_OIDC_RESOURCE"],
|
||||
)
|
||||
api = HfApi(token=token)
|
||||
|
||||
data_commit = api.upload_folder(
|
||||
folder_path=str(snapshot_dir / "hf-dataset" / "data"),
|
||||
path_in_repo="data",
|
||||
repo_id=repo_id,
|
||||
repo_type="dataset",
|
||||
revision=revision,
|
||||
delete_patterns="data/*.jsonl",
|
||||
commit_message="Update nightly ClawHub security dataset splits",
|
||||
)
|
||||
|
||||
manifest_path = snapshot_dir / "manifest.json"
|
||||
manifest = json.loads(manifest_path.read_text())
|
||||
manifest["huggingface_dataset"]["commit"] = data_commit.oid
|
||||
manifest["huggingface_dataset"]["revision"] = revision
|
||||
manifest_path.write_text(json.dumps(manifest, indent=2) + "\n")
|
||||
|
||||
manifest_commit = api.upload_file(
|
||||
path_or_fileobj=str(manifest_path),
|
||||
path_in_repo="metadata/latest-manifest.json",
|
||||
repo_id=repo_id,
|
||||
repo_type="dataset",
|
||||
revision=revision,
|
||||
commit_message="Update nightly ClawHub security dataset manifest",
|
||||
)
|
||||
print(json.dumps({
|
||||
"data_commit": data_commit.oid,
|
||||
"manifest_commit": manifest_commit.oid,
|
||||
"repo": repo_id,
|
||||
"revision": revision,
|
||||
}, indent=2))
|
||||
PY
|
||||
|
||||
- name: Upload sanitized summary artifact
|
||||
if: ${{ !cancelled() }}
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: security-dataset-summary-${{ github.run_id }}
|
||||
path: |
|
||||
${{ env.SANITIZED_OUT_DIR }}/summary.json
|
||||
${{ env.SNAPSHOT_DIR }}/manifest.json
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Cleanup transient dataset files
|
||||
if: ${{ always() }}
|
||||
run: rm -rf "$WORK_DIR"
|
||||
@@ -0,0 +1,119 @@
|
||||
name: Security Scan Codex Worker
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
limit:
|
||||
description: "Deprecated alias for batch-limit"
|
||||
required: false
|
||||
default: ""
|
||||
batch-limit:
|
||||
description: "Maximum Codex scans to run in parallel per worker shard"
|
||||
required: true
|
||||
default: "4"
|
||||
max-jobs:
|
||||
description: "Optional total jobs cap per worker shard"
|
||||
required: false
|
||||
default: ""
|
||||
max-runtime-minutes:
|
||||
description: "Stop claiming new batches after this many minutes"
|
||||
required: true
|
||||
default: "40"
|
||||
schedule:
|
||||
- cron: "*/5 * * * *"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: clawhub-security-scan
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
codex-security-scan:
|
||||
name: Codex security scan shard ${{ matrix.shard }}
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||
timeout-minutes: 60
|
||||
environment: Production
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 2
|
||||
matrix:
|
||||
shard: [0, 1, 2, 3]
|
||||
env:
|
||||
CONVEX_URL: ${{ vars.CONVEX_URL || vars.VITE_CONVEX_URL || 'https://wry-manatee-359.convex.cloud' }}
|
||||
CODEX_SECURITY_SCAN_LIMIT: ${{ inputs.limit || inputs['batch-limit'] || '4' }}
|
||||
CODEX_SECURITY_SCAN_MAX_JOBS: ${{ inputs['max-jobs'] || '' }}
|
||||
CODEX_SECURITY_SCAN_MAX_RUNTIME_MINUTES: ${{ inputs['max-runtime-minutes'] || '40' }}
|
||||
CODEX_SECURITY_SCAN_LEASE_MINUTES: "60"
|
||||
CODEX_SECURITY_SCAN_DIAGNOSTICS_DIR: codex-security-scan-diagnostics-${{ matrix.shard }}
|
||||
CODEX_SECURITY_SCAN_SHARD: ${{ matrix.shard }}
|
||||
CODEX_SECURITY_SCAN_WORKER_ID: "github-actions:${{ github.run_id }}:${{ github.run_attempt }}:${{ matrix.shard }}"
|
||||
SKILLSPECTOR_PROVIDER: openai
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install Codex CLI
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if ! command -v codex >/dev/null 2>&1; then
|
||||
npm install -g @openai/codex@latest
|
||||
fi
|
||||
codex --version
|
||||
|
||||
- name: Install SkillSpector
|
||||
run: |
|
||||
set -euo pipefail
|
||||
python -m venv "$RUNNER_TEMP/skillspector-venv"
|
||||
source "$RUNNER_TEMP/skillspector-venv/bin/activate"
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install 'git+https://github.com/NVIDIA/skillspector.git'
|
||||
echo "$RUNNER_TEMP/skillspector-venv/bin" >> "$GITHUB_PATH"
|
||||
skillspector --help >/dev/null
|
||||
|
||||
- name: Authenticate Codex CLI
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
run: printf '%s' "$OPENAI_API_KEY" | codex login --with-api-key
|
||||
|
||||
- name: Run Codex security worker
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
SECURITY_SCAN_WORKER_TOKEN: ${{ secrets.SECURITY_SCAN_WORKER_TOKEN }}
|
||||
run: |
|
||||
bun scripts/security/run-codex-scan-worker.ts \
|
||||
--batch-limit "$CODEX_SECURITY_SCAN_LIMIT" \
|
||||
--max-jobs "$CODEX_SECURITY_SCAN_MAX_JOBS" \
|
||||
--max-runtime-minutes "$CODEX_SECURITY_SCAN_MAX_RUNTIME_MINUTES" \
|
||||
--lease-minutes "$CODEX_SECURITY_SCAN_LEASE_MINUTES"
|
||||
|
||||
- name: Prepare Codex security diagnostics scan
|
||||
if: ${{ !cancelled() }}
|
||||
run: mkdir -p "$CODEX_SECURITY_SCAN_DIAGNOSTICS_DIR"
|
||||
|
||||
- name: Scan Codex security diagnostics for verified secrets
|
||||
id: diagnostics_secret_scan
|
||||
if: ${{ !cancelled() }}
|
||||
run: |
|
||||
docker run --rm \
|
||||
-v "$PWD/$CODEX_SECURITY_SCAN_DIAGNOSTICS_DIR:/scan:ro" \
|
||||
ghcr.io/trufflesecurity/trufflehog:3.95.5@sha256:56c25710275c4b8d74c4f1346a5e7c606fa7ff4afe996f680b288d0fae3fcd9c \
|
||||
filesystem /scan \
|
||||
--only-verified \
|
||||
--fail \
|
||||
--no-update \
|
||||
--github-actions
|
||||
|
||||
- name: Upload Codex security diagnostics
|
||||
if: ${{ !cancelled() && steps.diagnostics_secret_scan.outcome == 'success' }}
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: codex-security-scan-diagnostics-${{ github.run_id }}-${{ matrix.shard }}
|
||||
path: ${{ env.CODEX_SECURITY_SCAN_DIAGNOSTICS_DIR }}
|
||||
if-no-files-found: ignore
|
||||
@@ -0,0 +1,86 @@
|
||||
name: Skill Card Worker
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Security Scan Codex Worker"]
|
||||
types: [completed]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
batch-limit:
|
||||
description: "Maximum Skill Card jobs to run in parallel per worker shard"
|
||||
required: true
|
||||
default: "4"
|
||||
max-jobs:
|
||||
description: "Optional total jobs cap per worker shard"
|
||||
required: false
|
||||
default: ""
|
||||
max-runtime-minutes:
|
||||
description: "Stop claiming new batches after this many minutes"
|
||||
required: true
|
||||
default: "40"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: clawhub-skill-card-worker
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
skill-card-worker:
|
||||
name: Skill Card worker shard ${{ matrix.shard }}
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||
timeout-minutes: 60
|
||||
environment: Production
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 2
|
||||
matrix:
|
||||
shard: [0, 1, 2, 3]
|
||||
env:
|
||||
CONVEX_URL: ${{ vars.CONVEX_URL || vars.VITE_CONVEX_URL || 'https://wry-manatee-359.convex.cloud' }}
|
||||
SKILL_CARD_WORKER_LIMIT: ${{ github.event.inputs['batch-limit'] || '4' }}
|
||||
SKILL_CARD_WORKER_MAX_JOBS: ${{ github.event.inputs['max-jobs'] || '' }}
|
||||
SKILL_CARD_WORKER_MAX_RUNTIME_MINUTES: ${{ github.event.inputs['max-runtime-minutes'] || '40' }}
|
||||
SKILL_CARD_WORKER_LEASE_MINUTES: "60"
|
||||
SKILL_CARD_WORKER_SHARD: ${{ matrix.shard }}
|
||||
SKILL_CARD_WORKER_ID: "github-actions:${{ github.run_id }}:${{ github.run_attempt }}:${{ matrix.shard }}"
|
||||
NVIDIA_TRUSTWORTHY_AI_DIR: ${{ github.workspace }}/.artifacts/nvidia-trustworthy-ai
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
repository: NVIDIA/Trustworthy-AI
|
||||
ref: fb5867e9070b4080d28818242e20334e10ac55fc
|
||||
path: .artifacts/nvidia-trustworthy-ai
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
- name: Install Codex CLI and renderer dependencies
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if ! command -v codex >/dev/null 2>&1; then
|
||||
npm install -g @openai/codex@latest
|
||||
fi
|
||||
python3 -m pip install --user jinja2
|
||||
codex --version
|
||||
|
||||
- name: Authenticate Codex CLI
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
run: printf '%s' "$OPENAI_API_KEY" | codex login --with-api-key
|
||||
|
||||
- name: Run Skill Card worker
|
||||
env:
|
||||
# Shared Convex worker credential used by security and Skill Card workers.
|
||||
SECURITY_SCAN_WORKER_TOKEN: ${{ secrets.SECURITY_SCAN_WORKER_TOKEN }}
|
||||
run: |
|
||||
args=(
|
||||
--batch-limit "$SKILL_CARD_WORKER_LIMIT"
|
||||
--max-jobs "$SKILL_CARD_WORKER_MAX_JOBS"
|
||||
--max-runtime-minutes "$SKILL_CARD_WORKER_MAX_RUNTIME_MINUTES"
|
||||
--lease-minutes "$SKILL_CARD_WORKER_LEASE_MINUTES"
|
||||
--nvidia-tool-dir "$NVIDIA_TRUSTWORTHY_AI_DIR"
|
||||
)
|
||||
bun scripts/skill-cards/run-skill-card-worker.ts "${args[@]}"
|
||||
@@ -0,0 +1,312 @@
|
||||
name: Skill Publish
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
skill_path:
|
||||
description: Optional path to one skill folder. When set, only this skill is processed.
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
root:
|
||||
description: Directory containing skill folders for catalog publishing.
|
||||
required: false
|
||||
type: string
|
||||
default: skills
|
||||
dry_run:
|
||||
description: Preview only. When true, no publish mutation is performed.
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
owner:
|
||||
description: Optional owner/publisher handle for org publishing.
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
tags:
|
||||
description: Optional comma-separated tags override.
|
||||
required: false
|
||||
type: string
|
||||
default: latest
|
||||
registry:
|
||||
description: ClawHub registry URL.
|
||||
required: false
|
||||
type: string
|
||||
default: https://clawhub.ai
|
||||
site:
|
||||
description: ClawHub site URL.
|
||||
required: false
|
||||
type: string
|
||||
default: https://clawhub.ai
|
||||
ref:
|
||||
description: Optional caller repository ref to check out.
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
secrets:
|
||||
clawhub_token:
|
||||
required: false
|
||||
outputs:
|
||||
publish_json:
|
||||
description: Structured JSON output from skill publishing.
|
||||
value: ${{ jobs.publish.outputs.publish_json }}
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
outputs:
|
||||
publish_json: ${{ steps.capture.outputs.publish_json }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
ref: ${{ inputs.ref || github.sha }}
|
||||
|
||||
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
|
||||
with:
|
||||
bun-version: 1.3.10
|
||||
|
||||
- name: Resolve ClawHub workflow source
|
||||
id: clawhub_source
|
||||
run: |
|
||||
python3 - <<'PY'
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
request_token = os.environ.get("ACTIONS_ID_TOKEN_REQUEST_TOKEN", "").strip()
|
||||
request_url = os.environ.get("ACTIONS_ID_TOKEN_REQUEST_URL", "").strip()
|
||||
if not request_token or not request_url:
|
||||
raise SystemExit("GitHub OIDC token request env vars are missing; id-token: write is required.")
|
||||
|
||||
audience = "clawhub-workflow-source"
|
||||
joiner = "&" if "?" in request_url else "?"
|
||||
request = Request(
|
||||
f"{request_url}{joiner}audience={audience}",
|
||||
headers={"Authorization": f"Bearer {request_token}"},
|
||||
)
|
||||
with urlopen(request) as response:
|
||||
payload = json.load(response)
|
||||
|
||||
token = str(payload.get("value", "")).strip()
|
||||
if not token:
|
||||
raise SystemExit("GitHub OIDC token response did not include a token value.")
|
||||
|
||||
try:
|
||||
encoded_payload = token.split(".")[1]
|
||||
except IndexError as exc:
|
||||
raise SystemExit("GitHub OIDC token was not a valid JWT.") from exc
|
||||
padding = "=" * (-len(encoded_payload) % 4)
|
||||
claims = json.loads(base64.urlsafe_b64decode(encoded_payload + padding).decode("utf-8"))
|
||||
|
||||
workflow_ref = str(claims.get("job_workflow_ref", "")).strip()
|
||||
workflow_sha = str(claims.get("job_workflow_sha", "")).strip()
|
||||
repo, marker, _ = workflow_ref.partition("/.github/workflows/")
|
||||
if not marker or not repo or not workflow_sha:
|
||||
raise SystemExit(
|
||||
"Unable to resolve reusable workflow source from GitHub OIDC claims: "
|
||||
f"job_workflow_ref={workflow_ref!r} job_workflow_sha={workflow_sha!r}"
|
||||
)
|
||||
|
||||
with Path(os.environ["GITHUB_OUTPUT"]).open("a", encoding="utf-8") as fh:
|
||||
fh.write(f"repository={repo}\n")
|
||||
fh.write(f"ref={workflow_sha}\n")
|
||||
PY
|
||||
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
repository: ${{ steps.clawhub_source.outputs.repository }}
|
||||
ref: ${{ steps.clawhub_source.outputs.ref }}
|
||||
path: clawhub-source
|
||||
|
||||
- name: Install ClawHub CLI dependencies
|
||||
working-directory: clawhub-source
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Validate publish mode inputs
|
||||
env:
|
||||
DRY_RUN: ${{ inputs.dry_run }}
|
||||
CLAWHUB_TOKEN: ${{ secrets.clawhub_token }}
|
||||
run: |
|
||||
if [[ "$DRY_RUN" == "true" || -n "$CLAWHUB_TOKEN" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
echo "::error::Real skill publishes need secrets.clawhub_token. GitHub OIDC trusted publishing for skills is not supported yet."
|
||||
exit 1
|
||||
|
||||
- name: Write ClawHub config
|
||||
env:
|
||||
CLAWHUB_TOKEN: ${{ secrets.clawhub_token }}
|
||||
CLAWHUB_REGISTRY: ${{ inputs.registry }}
|
||||
run: |
|
||||
if [[ -z "$CLAWHUB_TOKEN" ]]; then
|
||||
echo "No ClawHub token provided, skipping config file creation."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
python3 - <<'PY'
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
path = Path(os.environ["RUNNER_TEMP"]) / "clawhub-config.json"
|
||||
path.write_text(
|
||||
json.dumps({"registry": os.environ["CLAWHUB_REGISTRY"], "token": os.environ["CLAWHUB_TOKEN"]}, indent=2) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
print(path)
|
||||
PY
|
||||
echo "CLAWHUB_CONFIG_PATH=$RUNNER_TEMP/clawhub-config.json" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Run skill publishes
|
||||
env:
|
||||
INPUT_SKILL_PATH: ${{ inputs.skill_path }}
|
||||
INPUT_ROOT: ${{ inputs.root }}
|
||||
INPUT_DRY_RUN: ${{ inputs.dry_run }}
|
||||
INPUT_OWNER: ${{ inputs.owner }}
|
||||
INPUT_TAGS: ${{ inputs.tags }}
|
||||
INPUT_SITE: ${{ inputs.site }}
|
||||
INPUT_REGISTRY: ${{ inputs.registry }}
|
||||
INPUT_REF: ${{ inputs.ref }}
|
||||
SOURCE_REPOSITORY: ${{ github.repository }}
|
||||
SOURCE_REF: ${{ github.ref }}
|
||||
run: |
|
||||
python3 - <<'PY'
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
workspace = Path(os.environ["GITHUB_WORKSPACE"]).resolve()
|
||||
cli_entry = workspace / "clawhub-source" / "packages" / "clawhub" / "src" / "cli.ts"
|
||||
if not cli_entry.is_file():
|
||||
raise SystemExit(f"Missing ClawHub CLI entrypoint at {cli_entry}")
|
||||
|
||||
def resolve_inside_workspace(raw_path):
|
||||
path = (workspace / raw_path).resolve()
|
||||
try:
|
||||
path.relative_to(workspace)
|
||||
except ValueError as exc:
|
||||
raise SystemExit(f"Publish path must be inside the caller repository: {raw_path}") from exc
|
||||
return path
|
||||
|
||||
def is_skill_folder(path):
|
||||
return path.is_dir() and any((path / name).is_file() for name in ("SKILL.md", "skill.md"))
|
||||
|
||||
skill_path = os.environ["INPUT_SKILL_PATH"].strip()
|
||||
root_input = os.environ["INPUT_ROOT"].strip() or "skills"
|
||||
if skill_path:
|
||||
targets = [resolve_inside_workspace(skill_path)]
|
||||
if not is_skill_folder(targets[0]):
|
||||
raise SystemExit(f"skill_path is not a skill folder: {skill_path}")
|
||||
else:
|
||||
root = resolve_inside_workspace(root_input)
|
||||
if is_skill_folder(root):
|
||||
targets = [root]
|
||||
elif root.is_dir():
|
||||
targets = sorted(
|
||||
(child for child in root.iterdir() if is_skill_folder(child)),
|
||||
key=lambda child: child.name.lower(),
|
||||
)
|
||||
else:
|
||||
targets = []
|
||||
if not targets:
|
||||
raise SystemExit(f"No skill folders found under: {root_input}")
|
||||
|
||||
source_commit = subprocess.check_output(
|
||||
["git", "rev-parse", "HEAD"], cwd=workspace, text=True
|
||||
).strip()
|
||||
source_ref = os.environ["INPUT_REF"].strip() or os.environ["SOURCE_REF"].strip()
|
||||
dry_run = os.environ["INPUT_DRY_RUN"] == "true"
|
||||
owner = os.environ["INPUT_OWNER"].strip()
|
||||
tags = os.environ["INPUT_TAGS"].strip()
|
||||
|
||||
results = {"wouldPublish": [], "published": [], "alreadySynced": [], "skipped": [], "failed": []}
|
||||
status_keys = {
|
||||
"would-publish": "wouldPublish",
|
||||
"published": "published",
|
||||
"unchanged": "alreadySynced",
|
||||
}
|
||||
|
||||
for target in targets:
|
||||
relative_path = target.relative_to(workspace).as_posix()
|
||||
command = [
|
||||
"bun", str(cli_entry),
|
||||
"--workdir", str(workspace),
|
||||
"--site", os.environ["INPUT_SITE"],
|
||||
"--registry", os.environ["INPUT_REGISTRY"],
|
||||
"skill", "publish", relative_path,
|
||||
"--json",
|
||||
"--source-repo", os.environ["SOURCE_REPOSITORY"],
|
||||
"--source-commit", source_commit,
|
||||
"--source-path", relative_path,
|
||||
]
|
||||
if dry_run:
|
||||
command.append("--dry-run")
|
||||
if owner:
|
||||
command += ["--owner", owner]
|
||||
if tags:
|
||||
command += ["--tags", tags]
|
||||
if source_ref:
|
||||
command += ["--source-ref", source_ref]
|
||||
|
||||
completed = subprocess.run(command, cwd=workspace, capture_output=True, text=True)
|
||||
if completed.returncode != 0:
|
||||
message = completed.stderr.strip() or completed.stdout.strip() or f"exit {completed.returncode}"
|
||||
results["failed"].append({"slug": target.name, "folder": relative_path, "message": message})
|
||||
continue
|
||||
try:
|
||||
result = json.loads(completed.stdout)
|
||||
results[status_keys[result["status"]]].append(result)
|
||||
except (KeyError, ValueError, json.JSONDecodeError) as exc:
|
||||
results["failed"].append({"slug": target.name, "folder": relative_path, "message": f"Invalid publish output: {exc}"})
|
||||
|
||||
output = {
|
||||
"ok": not results["failed"],
|
||||
"dryRun": dry_run,
|
||||
"registry": os.environ["INPUT_REGISTRY"],
|
||||
"roots": [skill_path or root_input],
|
||||
**({"owner": owner.lstrip("@") } if owner else {}),
|
||||
"summary": {key: len(value) for key, value in results.items()},
|
||||
**results,
|
||||
}
|
||||
output_path = Path(os.environ["RUNNER_TEMP"]) / "skill-publish.json"
|
||||
output_path.write_text(json.dumps(output, indent=2) + "\n", encoding="utf-8")
|
||||
print(json.dumps(output, indent=2))
|
||||
if results["failed"]:
|
||||
sys.exit(1)
|
||||
PY
|
||||
|
||||
- name: Capture workflow outputs
|
||||
id: capture
|
||||
run: |
|
||||
python3 - <<'PY'
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
output_path = Path(os.environ["RUNNER_TEMP"]) / "skill-publish.json"
|
||||
parsed = json.loads(output_path.read_text(encoding="utf-8"))
|
||||
with Path(os.environ["GITHUB_OUTPUT"]).open("a", encoding="utf-8") as fh:
|
||||
fh.write("publish_json<<__CLAWHUB_JSON__\n")
|
||||
fh.write(json.dumps(parsed, indent=2))
|
||||
fh.write("\n__CLAWHUB_JSON__\n")
|
||||
PY
|
||||
|
||||
- name: Upload publish JSON artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: clawhub-skill-publish-json
|
||||
path: ${{ runner.temp }}/skill-publish.json
|
||||
if-no-files-found: error
|
||||
@@ -10,6 +10,8 @@ concurrency:
|
||||
group: update-convex-ai-files
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions: {}
|
||||
|
||||
env:
|
||||
BUN_VERSION: "1.3.10"
|
||||
UPDATE_BRANCH: automation/update-convex-ai-files
|
||||
@@ -23,7 +25,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -32,6 +34,21 @@ jobs:
|
||||
with:
|
||||
bun-version: ${{ env.BUN_VERSION }}
|
||||
|
||||
- uses: actions/create-github-app-token@v3
|
||||
id: app-token
|
||||
continue-on-error: true
|
||||
with:
|
||||
app-id: "2729701"
|
||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||
|
||||
- uses: actions/create-github-app-token@v3
|
||||
id: app-token-fallback
|
||||
continue-on-error: true
|
||||
if: steps.app-token.outcome == 'failure'
|
||||
with:
|
||||
app-id: "2971289"
|
||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY_FALLBACK }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
@@ -71,7 +88,7 @@ jobs:
|
||||
- name: Open or update pull request
|
||||
if: steps.changes.outputs.changed == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token || github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
|
||||
+10
-6
@@ -2,6 +2,10 @@ node_modules
|
||||
.DS_Store
|
||||
.bun-build
|
||||
*.bun-build
|
||||
.artifacts/
|
||||
artifacts/
|
||||
.proof/
|
||||
.cache/
|
||||
.data/
|
||||
bin/docs-list
|
||||
dist
|
||||
@@ -29,17 +33,17 @@ eval/results/
|
||||
playwright-report
|
||||
test-results
|
||||
.playwright
|
||||
/public/llms.txt
|
||||
convex/_generated/*
|
||||
!convex/_generated/ai/
|
||||
convex/_generated/ai/*
|
||||
!convex/_generated/ai/guidelines.md
|
||||
!convex/_generated/ai/ai-files.state.json
|
||||
skills-lock.json
|
||||
*/skills/*
|
||||
!.agents/skills/
|
||||
!.agents/skills/convex*/
|
||||
!.agents/skills/convex*/**
|
||||
!.agents/skills/blacksmith-testbox/
|
||||
!.agents/skills/blacksmith-testbox/**
|
||||
skills/*
|
||||
.codex/*
|
||||
!.codex/environments/
|
||||
!.codex/environments/environment.toml
|
||||
.crabbox/
|
||||
/.comux-hooks
|
||||
/.comux
|
||||
|
||||
+36
-36
@@ -1,38 +1,38 @@
|
||||
{
|
||||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||
"plugins": ["unicorn", "typescript", "oxc"],
|
||||
"categories": {
|
||||
"correctness": "error",
|
||||
"perf": "error",
|
||||
"suspicious": "error"
|
||||
},
|
||||
"rules": {
|
||||
"curly": "off",
|
||||
"eslint-plugin-unicorn/prefer-array-find": "off",
|
||||
"eslint-plugin-unicorn/no-array-sort": "off",
|
||||
"eslint/no-await-in-loop": "off",
|
||||
"eslint/no-underscore-dangle": "off",
|
||||
"eslint/no-new": "off",
|
||||
"oxc/no-accumulating-spread": "off",
|
||||
"oxc/no-async-endpoint-handlers": "off",
|
||||
"oxc/no-map-spread": "off",
|
||||
"typescript/no-explicit-any": "error",
|
||||
"typescript/no-extraneous-class": "off",
|
||||
"typescript/no-unnecessary-boolean-literal-compare": "off",
|
||||
"typescript/no-unnecessary-type-assertion": "off",
|
||||
"typescript/no-unsafe-type-assertion": "off",
|
||||
"unicorn/consistent-function-scoping": "off",
|
||||
"unicorn/require-post-message-target-origin": "off"
|
||||
},
|
||||
"ignorePatterns": [
|
||||
".output/",
|
||||
".tanstack/",
|
||||
"convex/_generated/",
|
||||
"coverage/",
|
||||
"dist/",
|
||||
"node_modules/",
|
||||
"public/",
|
||||
"src/routeTree.gen.ts",
|
||||
"test-results/"
|
||||
]
|
||||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||
"plugins": ["unicorn", "typescript", "oxc"],
|
||||
"categories": {
|
||||
"correctness": "error",
|
||||
"perf": "error",
|
||||
"suspicious": "error"
|
||||
},
|
||||
"rules": {
|
||||
"curly": "off",
|
||||
"eslint-plugin-unicorn/prefer-array-find": "off",
|
||||
"eslint-plugin-unicorn/no-array-sort": "off",
|
||||
"eslint/no-await-in-loop": "off",
|
||||
"eslint/no-underscore-dangle": "off",
|
||||
"eslint/no-new": "off",
|
||||
"oxc/no-accumulating-spread": "off",
|
||||
"oxc/no-async-endpoint-handlers": "off",
|
||||
"oxc/no-map-spread": "off",
|
||||
"typescript/no-explicit-any": "error",
|
||||
"typescript/no-extraneous-class": "off",
|
||||
"typescript/no-unnecessary-boolean-literal-compare": "off",
|
||||
"typescript/no-unnecessary-type-assertion": "off",
|
||||
"typescript/no-unsafe-type-assertion": "off",
|
||||
"unicorn/consistent-function-scoping": "off",
|
||||
"unicorn/require-post-message-target-origin": "off"
|
||||
},
|
||||
"ignorePatterns": [
|
||||
".output/",
|
||||
".tanstack/",
|
||||
"convex/_generated/",
|
||||
"coverage/",
|
||||
"dist/",
|
||||
"node_modules/",
|
||||
"public/",
|
||||
"src/routeTree.gen.ts",
|
||||
"test-results/"
|
||||
]
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 74 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 65 KiB |
@@ -0,0 +1,21 @@
|
||||
# ClawHub publisher search proof
|
||||
|
||||
Status: pass (production bug reproduction before backend deploy)
|
||||
|
||||
## Convex read-only validation (`wry-manatee-359.convex.cloud`)
|
||||
|
||||
| Query | `listPublicPage` handles |
|
||||
| --- | --- |
|
||||
| `vyctor` | `[]` |
|
||||
| `vyctorbrzezowski` | `[]` |
|
||||
| `vincent` | `["vincentchan"]` |
|
||||
| `vincentkoc` | `[]` |
|
||||
|
||||
## Profiles that exist but are missing from search
|
||||
|
||||
- `vyctorbrzezowski` → 5 skills, 1 package, 46 installs
|
||||
- `vincentkoc` → public profile, 0 published skills
|
||||
|
||||
## Unit tests
|
||||
|
||||
`VITE_CONVEX_URL=https://example.invalid bunx vitest run convex/publishers.test.ts`
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"baseline": "production",
|
||||
"candidate": "production-before-fix",
|
||||
"generatedAt": "2026-06-23T02:50:00.000Z",
|
||||
"mode": "feature",
|
||||
"status": "pass",
|
||||
"lanes": [
|
||||
{
|
||||
"name": "candidate",
|
||||
"ref": "https://clawhub.ai",
|
||||
"status": "pass",
|
||||
"steps": [
|
||||
{
|
||||
"lane": "candidate",
|
||||
"name": "publishers?q=vyctorbrzezowski returns no publishers (prod before deploy)",
|
||||
"screenshot": "screenshots/vyctorbrzezowski-empty.png",
|
||||
"slug": "vyctorbrzezowski-empty",
|
||||
"status": "pass"
|
||||
},
|
||||
{
|
||||
"lane": "candidate",
|
||||
"name": "publishers?q=vincent shows vincentchan but not vincentkoc (prod before deploy)",
|
||||
"screenshot": "screenshots/vincent-missing-vincentkoc.png",
|
||||
"slug": "vincent-missing-vincentkoc",
|
||||
"status": "pass"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
.env.local
|
||||
.convex/
|
||||
node_modules/
|
||||
@@ -5,20 +5,38 @@
|
||||
- `src/` — TanStack Start app code (routes, components, styles).
|
||||
- `convex/` — Convex backend (schema, queries/mutations/actions, HTTP routes).
|
||||
- `convex/_generated/` — generated Convex API/types; committed for builds.
|
||||
- `docs/` — product/spec docs (see `docs/spec.md`).
|
||||
- `docs/` — publishable public/operator docs for the ClawHub docs tab.
|
||||
- `specs/` — product specs, plans, regression notes, design history (see `specs/spec.md`).
|
||||
- `public/` — static assets.
|
||||
|
||||
## Durable Intent & Specs
|
||||
|
||||
- Use `specs/` to persist system/subsystem intent, invariants, and design rationale that future agents should preserve.
|
||||
- Keep intended behavior for security-sensitive flows there, especially moderation, upload gating, scanner outcomes, appeals, bans, ownership, package installability, and API trust boundaries.
|
||||
- If code changes reveal or change how a subsystem is supposed to work, update the relevant spec or add a focused spec note instead of burying the intent only in PR text or public docs.
|
||||
- Keep `docs/` user/operator-facing: explain current behavior and commands there, but put internal “why this must work this way” context in `specs/`.
|
||||
|
||||
## Build, Test, and Development Commands
|
||||
|
||||
- `bun run dev` — local app server at `http://localhost:3000`.
|
||||
Keep this section as the command map agents normally need, not a full `package.json` script index.
|
||||
|
||||
- `bun run dev` — foreground local app server at `http://localhost:3000`.
|
||||
- `bunx convex dev --typecheck=disable` — local Convex backend/function watcher for manual setup.
|
||||
- `bunx convex codegen` — regenerate `convex/_generated` after Convex API/schema changes.
|
||||
- `.worktreeinclude` — Codex-managed worktrees copy ignored local state (`.env.local`, `.convex/`, and `node_modules/`) from the local checkout at creation time.
|
||||
- `bun run setup:worktree` — validate copied `.env.local` / `.convex` state, or link missing fallback state from a usable source worktree. Use `-- --from <path>` or `CLAWHUB_WORKTREE_SOURCE=<path>` when auto-discovery picks the wrong source.
|
||||
- `bun run dev:worktree` — Worktrunk-managed detached worktree server that also seeds local fixtures plus the public corpus once before starting the app when `VITE_CONVEX_URL` and `CONVEX_DEPLOYMENT` are local. Requires `wt` on `PATH`; from that worktree use `wt --yes url` to print the branch URL and `wt --yes stop` to stop it.
|
||||
- `bun run seed:dev` — manual reseed path; runs worktree setup, waits for local Convex, seeds local fixtures plus the public corpus, and refreshes stats.
|
||||
- `bun run build` — production build (Vite + Nitro).
|
||||
- `bun run preview` — preview built app.
|
||||
- `bunx convex dev` — Convex dev deployment + function watcher.
|
||||
- `bunx convex codegen` — regenerate `convex/_generated`.
|
||||
- `bun run format:check` — formatting check.
|
||||
- `bun run lint` — Biome + oxlint (type-aware).
|
||||
- `bun run test` — Vitest (unit tests).
|
||||
- `bun run coverage` — coverage run; keep global >= 80%.
|
||||
- `bun run ci:static` — required pre-handoff static gate: peer checks, audit, formatting, lint, and dead-code checks.
|
||||
- `bun run ci:unit` — Vitest coverage gate; required for source/test PRs unless docs/config-only.
|
||||
- `bun run ci:types-build` — full TypeScript/build gate for app, Convex, and packages.
|
||||
- `bun run ci:packages` — schema, CLI, and moderation package verification.
|
||||
- `bun run ci:e2e-http` — secretless HTTP and CLI e2e subset.
|
||||
- `bun run ci:playwright-smoke` — chromium smoke against the public read backend.
|
||||
- `bun run test:pw:local-auth` — local Convex/dev-auth browser gate for signed-in/write flows.
|
||||
|
||||
Specialized corpus, scanner, security-worker, UI proof, proof publishing, Crabbox, docs-authoring, and dataset scripts are real maintenance tools, but they should stay in the relevant specs, skills, or package script lookup unless the task touches that subsystem.
|
||||
|
||||
## Coding Style & Naming Conventions
|
||||
|
||||
@@ -26,6 +44,7 @@
|
||||
- Indentation: 2 spaces, single quotes (Biome).
|
||||
- Lint/format: Biome + oxlint (type-aware).
|
||||
- Convex function names: verb-first (`getBySlug`, `publishVersion`).
|
||||
- Inline code comments: add brief comments for tricky, bug-prone, or previously buggy logic.
|
||||
|
||||
## Testing Guidelines
|
||||
|
||||
@@ -33,17 +52,21 @@
|
||||
- Tests live in `src/**` and `convex/lib/**`.
|
||||
- Coverage threshold: 80% global (lines/functions/branches/statements).
|
||||
- Example: `convex/lib/skills.test.ts`.
|
||||
- When adding or changing Convex functions, do not rely only on mocked `ctx` tests for behavior that depends on Convex runtime semantics such as pagination, indexes, validators, auth identity, internal/public function boundaries, scheduler/cron behavior, actions calling queries/mutations, HTTP actions, storage, or OCC/transaction behavior. Add or run a real Convex validation path, such as `convex dev --once`, `convex run`, an HTTP action smoke, or a local-auth Playwright flow, covering the changed behavior. Mocked `ctx.db` / `ctx.runQuery` tests are still fine for pure business logic, but they do not count as Convex runtime validation.
|
||||
- For local UI state testing, prefer creating realistic backend state through seed logic plus a DevPersonaFab entry for the associated test user. Avoid one-off manual DB edits when the state is likely to be reused, such as org membership, official publisher access, moderation holds, or publishing permissions.
|
||||
|
||||
## Commit & Pull Request Guidelines
|
||||
|
||||
- Commit messages: Conventional Commits (`feat:`, `fix:`, `chore:`, `docs:`…).
|
||||
- Keep changes scoped; avoid repo-wide search/replace.
|
||||
- Before commit/PR handoff, run `bun run format:check` and `bun run lint`; include commands run in the PR summary.
|
||||
- Before commit/PR handoff, run `bun run ci:static` so formatting, linting, audit/peer checks, and dead-code export checks match the CI `static` job. For faster inner loops, targeted `bun run format:check -- <files>` / `bun run lint` are fine, but do not treat them as the final pre-push gate.
|
||||
- Before commit/PR handoff for non-trivial code changes, use `$autoreview` until no accepted/actionable findings remain, unless equivalent manual review already happened, the change is trivial/docs-only, or the user opts out.
|
||||
- Before opening a PR for source or test changes, run the targeted tests for the touched behavior and `bun run ci:unit` (`VITE_CONVEX_URL=https://example.invalid bun run coverage`) unless the change is docs/config-only or the user explicitly asks to rely on CI. For runtime, build, or package changes, also run the matching broader gate when it covers the touched surface: `bun run ci:types-build`, `bun run ci:packages`, `bun run ci:e2e-http`, or `bun run ci:playwright-smoke`.
|
||||
- PRs: include summary + test commands run. Add screenshots for UI changes.
|
||||
- Screenshot proof MUST come from a real running ClawHub instance in a real browser. Do not use generated HTML mockups, synthetic terminal cards, or manually composed images as proof. For route/status/backend visibility bugs, run ClawHub locally with the relevant Convex code and fixture state, capture the actual browser page, and state the local URL and fixture used.
|
||||
- Before merging any PR, verify TypeScript cleanly with `bunx tsc -p packages/schema/tsconfig.json --noEmit` and `bunx tsc -p packages/clawhub/tsconfig.json --noEmit`; if Convex code changed, also run the repo typecheck path used by deploy so `bunx convex deploy` will not fail on `tsc`.
|
||||
- GitHub comments: for multiline `gh` comments/close messages, use `--body-file`, `--input`, or stdin/heredoc with real newlines; never pass literal `\\n` in shell strings.
|
||||
- Reject PRs that add skills into source code/repo content directly (for example under `skills/` or seed-only additions intended as published skills). Skills must be uploaded/published via CLI.
|
||||
- Repo-local Convex developer skills under `.agents/skills/convex*/` are allowed when they support working on this codebase; keep top-level `skills/` reserved for installed/published skill content and ignored by git.
|
||||
- Repo-local developer skills under `.agents/skills/` are allowed only when they are ClawHub-specific, such as Convex, moderation, PR maintainer, or UI proof workflows. Keep generic shared skills such as `crabbox` and `autoreview` in the global `agent-skills` install, not this repo. Keep top-level `skills/` reserved for installed/published skill content and ignored by git.
|
||||
|
||||
## Production Release
|
||||
|
||||
@@ -77,9 +100,17 @@
|
||||
|
||||
## Convex Ops (Gotchas)
|
||||
|
||||
- Before any `bunx convex ...` command, name the target runtime (`local`, `dev`, or `prod`), the exact deployment when known, and whether the current function/schema code has already been pushed or deployed.
|
||||
- New Convex functions must be pushed before `convex run`: use `bunx convex dev --once` (dev) or `bunx convex deploy` (prod).
|
||||
- For non-interactive prod deploys, use `bunx convex deploy -y` to skip confirmation.
|
||||
- If `bunx convex run --env-file .env.local ...` returns `401 MissingAccessToken` despite `bunx convex login`, workaround: omit `--env-file` and use `--deployment-name <name>` / `--prod`.
|
||||
- If `bunx convex run --env-file .env.local ...` returns `401 MissingAccessToken` despite `bunx convex login`, workaround: omit `--env-file` and use `--deployment <name>` / `--prod`.
|
||||
|
||||
## Convex Migrations & Backfills
|
||||
|
||||
- Any Convex production data migration, backfill, destructive cleanup, schema narrowing, or table reshaping must start with the `convex-migration-helper` skill. Default to `@convex-dev/migrations` for production data changes because it provides batching, dry runs, resume/progress tracking, and safer operator UX. Exceptions require an explicit note explaining why the component is unnecessary, plus equivalent dry-run support, cursor batching, resume/progress behavior, confirmation for destructive writes, and real Convex runtime validation.
|
||||
- When adding or changing Convex tables, TTL fields, cleanup crons, retention policy, auth/session cleanup, metric dedupe cleanup, or deprecated table removal, use the repo-local `convex-retention` skill and update `convex/lib/retentionPolicy.ts`.
|
||||
- Use `convex/migrations.ts` for component-backed table-wide backfills; keep custom repairs, admin-gated operations, and incident-specific workflows in `convex/maintenance.ts`.
|
||||
- After a migration or cleanup is verified complete, remove temporary migration functions/code in a follow-up PR unless they are intentionally retained as ongoing maintenance tooling.
|
||||
|
||||
## Convex Query & Bandwidth Rules
|
||||
|
||||
|
||||
+236
@@ -2,9 +2,245 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Changes
|
||||
|
||||
- Web: organization publishers can upload durable PNG, JPEG, or WebP logos from settings instead of relying on hotlinked image URLs.
|
||||
- Web/API: make default skill and plugin discovery freshness-aware, add seven-day trending views for both catalogs, and use verified status plus usage as search tie-breakers within direct matches.
|
||||
|
||||
## 0.23.0 - 2026-06-23
|
||||
|
||||
### Changes
|
||||
|
||||
- CLI: restore `clawhub sync` for scanning local skill folders and publishing new or changed skills in batches, including dry-run, JSON, owner, version-bump, provenance, and non-interactive `--all` options.
|
||||
|
||||
## 0.22.0 - 2026-06-15
|
||||
|
||||
### Changes
|
||||
|
||||
- CLI: remove the `clawhub sync` command. `clawhub skill publish <path>` now skips unchanged content, defaults new skills to `1.0.0`, defaults changed skills to the next patch version, and supports dry-run/JSON output.
|
||||
- GitHub Actions: preserve catalog publishing through the reusable `skill-publish.yml` workflow, which invokes ordinary `skill publish` once per skill folder.
|
||||
|
||||
## 0.21.0 - 2026-06-11
|
||||
|
||||
### Changes
|
||||
|
||||
- CLI/API: add public `clawhub package trusted-publisher set` and `clawhub package trusted-publisher delete` commands so package managers can configure or remove GitHub Actions OIDC trusted publishing for existing packages.
|
||||
|
||||
## 0.20.2 - 2026-06-11
|
||||
|
||||
### Changes
|
||||
|
||||
- CLI packages now require Node.js 22 or newer, dropping the EOL Node 20 runtime floor.
|
||||
- CLI: add `clawhub package validate <source>` for local plugin validation with author-facing Plugin Inspector findings, remediation text, and report artifacts.
|
||||
|
||||
## 0.20.0 - 2026-06-06
|
||||
|
||||
### Changes
|
||||
|
||||
- CLI/API: replace local `clawhub scan` uploads with stored submitted-version scan report downloads, including owner-authorized `clawhub scan download <name> --version <version>` support for blocked skill and plugin submissions.
|
||||
|
||||
## 0.19.2 - 2026-06-05
|
||||
|
||||
### Fixes
|
||||
|
||||
- CLI: accept the legacy `clawhub skill verify --json` flag as a hidden compatibility no-op while continuing to print JSON by default.
|
||||
|
||||
## 0.19.1 - 2026-06-05
|
||||
|
||||
### Fixes
|
||||
|
||||
- CLI: install source-backed GitHub skills from the deployed `/api/v1/skills/:slug/install` resolver so `clawhub install` works for skills without hosted ClawHub versions.
|
||||
|
||||
## 0.19.0 - 2026-06-03
|
||||
|
||||
### Changes
|
||||
|
||||
- CLI/API: add authenticated `clawhub scan` submit/poll support for ephemeral local skill bundles and owner-authorized published skill scans, including JSON output and report ZIP downloads (#2479).
|
||||
|
||||
### Fixes
|
||||
|
||||
- Auth/Ops: keep GitHub account-age lookups on immutable numeric IDs, retry without auth when a configured GitHub token is rejected, and add an operator backfill for missing cached account ages.
|
||||
- API/CLI: report Skill Card verification with flattened skill/version metadata, ClawScan verdict fields at `security.*`, and supporting scanner evidence under `security.signals`.
|
||||
|
||||
## 0.18.0 - 2026-05-25
|
||||
|
||||
### Changes
|
||||
|
||||
- CLI/API: add Skill Card verification surfaces, including `clawhub skill verify <slug>` JSON output and `--card` Markdown retrieval (#2382).
|
||||
|
||||
### Fixes
|
||||
|
||||
- API: fix `GET /api/v1/skills` pagination so `cursor` advances to the next page instead of repeating the first page for supported non-trending sorts (#2275) (thanks @vyctorbrzezowski, @enerj).
|
||||
- Web: block collaborative membership on personal publishers while allowing the linked owner to clean up stale extra membership rows (thanks @vyctorbrzezowski).
|
||||
- Security/API: hide owned package/plugin catalog entries, revoke package publish tokens, and restore only matching ban-hidden packages on user unban (thanks @vyctorbrzezowski).
|
||||
- API: block public raw skill files when moderation already blocks downloads and reject skill tags that point at another skill's version (thanks @vyctorbrzezowski).
|
||||
- Web: stop stale unban restore batches from reactivating skills after the owner is banned again or deactivated (thanks @vyctorbrzezowski).
|
||||
- Security/API: reject direct skill owner transfers when the skill is hidden, suspicious, or malicious (thanks @vyctorbrzezowski).
|
||||
- Security/API: revalidate package publish actor, owner, and owner publisher active state in the final release insert (thanks @vyctorbrzezowski).
|
||||
|
||||
## 0.17.0 - 2026-05-19
|
||||
|
||||
- CLI/API: add self-serve org publisher creation with `clawhub publisher create <handle>` and scoped package publish errors that point to the command.
|
||||
|
||||
## 0.16.0 - 2026-05-18
|
||||
|
||||
### Fixes
|
||||
|
||||
- CLI/API: make package publishes robust under parallel same-publisher release jobs by avoiding unnecessary shared publisher writes, retrying transient Convex contention, and labeling contention separately from package validation failures (#2291).
|
||||
- Security: move upload ClawScan classification to a GitHub Actions Codex worker, treat VirusTotal as telemetry-only signal, and trust verified `@openclaw/*` plugin packages by default.
|
||||
- Security: cancel pending skill ownership transfers before rejecting accept attempts when the requester is inactive or the skill is hidden, removed, or malicious (#2276, #2277) (thanks @vyctorbrzezowski).
|
||||
- API/CLI: fix package delete returning 500 for packages with capability tags when no capability search digest row existed yet (#2212) (thanks @momothemage).
|
||||
- API: return a clear 400 for `/api/v1/packages/search` without a non-empty `q` instead of treating `search` as a package name (thanks @vyctorbrzezowski).
|
||||
- Web/API: keep search results limited to items with match evidence, preserve trust and popularity as tie-breakers, and show `N+` counts without exact count queries (#2206) (thanks @vyctorbrzezowski).
|
||||
- Web: preserve `ownerHandle` through legacy skill publish redirects so org admins land in the correct new-version owner context (#2177).
|
||||
- Settings: save display name/bio changes even when a legacy personal publisher handle conflict prevents publisher profile sync (#1199).
|
||||
- Auth: show a visible error if the GitHub sign-in request fails before the provider redirect starts (#2197).
|
||||
- Schema: include `.tsv`, `.conf`, `.properties`, and `.dat` in the exported text-file allowlist and regenerate the committed schema package runtime (#2172, #874) (thanks @alexuser).
|
||||
- API: return `400` for invalid known public package filters and invalid skill list sort values, while continuing to ignore unknown query parameters (#2184).
|
||||
- API/docs: document v1 plain-text error responses and expose owner metadata in the OpenAPI search result schema (#2187) (thanks @vyctorbrzezowski).
|
||||
- Web: rank publisher card preview items by downloads instead of recent publish order (thanks @vyctorbrzezowski).
|
||||
- Web: remove the desktop Files tab height cap and make mobile truncation explicit (thanks @vyctorbrzezowski).
|
||||
- Web: keep skill/plugin detail tabs at mobile-friendly touch target height.
|
||||
|
||||
### Changes
|
||||
|
||||
- CLI/API: include skill owner handles in search results so duplicate/common slugs are easier to disambiguate (thanks @vyctorbrzezowski).
|
||||
- Web: let skill publishers pick a curated lucide icon for cards and listings (#2174) (thanks @momothemage).
|
||||
- Web/API: add keyword-based plugin categories plus API-backed plugin search sorting for recently updated, newest, and name (#2118) (thanks @vyctorbrzezowski).
|
||||
- Web: polish the starred skills page with grid/list controls, sorting, and optimistic unstar behavior (#2159) (thanks @vyctorbrzezowski).
|
||||
- API/docs: expand the v1 OpenAPI contract with package/plugin catalog endpoints and align documented rate limits with the server constants (#2186) (thanks @vyctorbrzezowski).
|
||||
- Admin/Ops: audit profile syncs, self-service account/profile changes, personal publisher syncs, and org trusted-publisher changes so slug and ownership investigations have a complete ledger.
|
||||
- Dependencies: update production `@clack/prompts`, `tailwind-merge`, and `yaml` dependencies (#2198).
|
||||
|
||||
## 0.15.0 - 2026-05-12
|
||||
|
||||
### Changes
|
||||
|
||||
- Web: polish dashboard artifact cards, loading skeletons, skill summary/detail layout, and adoption metrics after the 0.14 release (#2150, #2153, #2156, #2157, #2158, #2160).
|
||||
- Docs/dev: clarify pre-PR validation gates for local contributors (#2161).
|
||||
|
||||
### Fixes
|
||||
|
||||
- Web: show plugin settings actions to package managers and preserve manager access in dashboard rows (#2163, #2168).
|
||||
- Web: refresh skill star state after mutations and keep skill tabs from causing horizontal scroll (#2154, #2155).
|
||||
- Web: show owner names when handles are hidden, and clarify editable skill summary settings copy (#2151, #2162).
|
||||
- Dashboard: add a publisher switcher so org-owned skills and plugins are visible to org admins after transfer or publish (#2132).
|
||||
- Web: let org publishers/admins republish transferred org-owned skills without the publish form treating the existing slug as taken, including legacy users with synthesized personal publishers (#2171).
|
||||
- CLI: send skill ownership command payloads as JSON objects so rename/merge operations reach the API correctly (#1300).
|
||||
- CLI: keep an install fingerprint in skill origin metadata so `clawhub update <skill>` does not report fresh installs as local changes when the server cannot resolve the current hash (#169).
|
||||
- CLI: migrate cached `registry.clawhub.ai` registries back to `clawhub.ai` so `clawhub explore` no longer talks to the retired Vercel deployment (#1098).
|
||||
- CLI: publish `.tsv`, `.conf`, `.properties`, `.dat`, and safe extensionless text files while excluding dotfiles and sampling extensionless files before full reads (#874).
|
||||
- Tests: remove obsolete rescan e2e probes that no longer match current moderation behavior (#2152).
|
||||
|
||||
## 0.14.0 - 2026-05-11
|
||||
|
||||
### Changes
|
||||
|
||||
- Dev: auto-start services for Codex worktrees and add a local dev persona FAB (#2146, #2147).
|
||||
- Dev: add a local ClawScan dry-run helper script (#2143).
|
||||
|
||||
### Fixes
|
||||
|
||||
- API: return deterministic 403 responses for skill/package rescan and package transfer permission denials, with CI e2e coverage for protected write endpoints.
|
||||
|
||||
## 0.13.0 - 2026-05-11
|
||||
|
||||
### Changes
|
||||
|
||||
- Web: redesign Settings into focused account, organization, API token, and account deletion views with responsive desktop and mobile layouts (#2134) (thanks @vyctorbrzezowski).
|
||||
- Web: replace the Users directory with a Publishers discovery surface covering builders and organizations, add `/publishers` as the canonical route, and keep `/users` compatibility (#2087) (thanks @vyctorbrzezowski).
|
||||
- Web: polish browse/listing surfaces across skills, plugins, and search, including plugin card view parity, clearer search controls, visible safety filtering, and more consistent card metadata treatment (#2084) (thanks @vyctorbrzezowski).
|
||||
- Web: allow skill owners and publisher admins to edit a skill summary from the detail page (#1411) (thanks @SylvanXiao).
|
||||
- CLI/Auth: add device-code login for remote or headless shells, backed by ClawHub device authorization endpoints (#1867) (thanks @LumenFromTheFuture).
|
||||
- CLI: add per-skill pinning so installed skills can be frozen against direct updates, bulk updates, and force reinstalls (#1806) (thanks @deepujain).
|
||||
- Web: rename the skills and plugins browse alternate view from Cards to Grid while keeping legacy `view=cards` URLs compatible (#2119) (thanks @vyctorbrzezowski).
|
||||
- Dev docs: refresh generated Convex AI guidance files (#2000).
|
||||
|
||||
### Fixes
|
||||
|
||||
- Moderation: stop treating VirusTotal Code Insight/Palm verdicts as a hide authority for skills; real AV-engine hits and ClawScan findings still contribute moderation verdicts.
|
||||
- Moderation: stop treating static suspicious-only findings as a verdict; keep file/line evidence for review while VT/LLM decide public suspicious status.
|
||||
- ClawScan: reduce false positives for scoped uninstall cleanup, declared provider login flows, Basic Auth/base64 handling, and user-directed provider uploads while hard-blocking stealth browser abuse patterns.
|
||||
- ClawScan: lower false positives by treating purpose-aligned notes as benign unless structured LLM findings contain a material concern, and add targeted rescan batches for suspicious skills/plugins.
|
||||
- Moderation: split visible ClawScan review guidance from hidden suspicious filtering, and add operator cleanup for stale aggregate rows and obvious test/placeholder suspicious skills.
|
||||
- Security: add an admin-only moderation hold lift path for false-positive publisher holds, with audited skill restoration that preserves independently hidden skills (#1133) (thanks @Justincredible-tech).
|
||||
- Moderation: let platform moderators and admins trigger skill/package security rescans for any owner from the CLI, without consuming the owner recovery cap.
|
||||
- ClawScan: include package `openclaw.environment` env/config declarations in package review prompts so declared plugin runtime requirements are not reported as missing (#2013).
|
||||
- Skills/Packages: let publisher admins manage owned lifecycle operations consistently, including skill rename/delete/restore, direct skill moves into org publishers they administer, package restore from the CLI/API, and direct moves back to their personal publisher.
|
||||
- Skills: repair publisher-owned skill merges, bound historical slug redirects, block protected slug namespaces, and expire owner-unpublished slug reservations after 30 days (#2115) (thanks @fuller-stack-dev).
|
||||
- Skills: allow confirmed owner migration when republishing an existing skill to another publisher, preserving versions, stats, aliases, and audit history (#1998, #2102) (thanks @momothemage).
|
||||
- Security: block owner delete/undelete paths from overriding moderator or scanner hides, and return explicit 403 authz responses for owner restore denials (#2078) (thanks @momothemage).
|
||||
- CLI/API: send skill transfer payloads as JSON objects so transfer requests reach the API correctly.
|
||||
- Packages: keep package search digests schema-safe during delete/restore so package lifecycle CLI calls do not fail after provenance updates.
|
||||
- Search: recall skill matches by non-first slug/display-name tokens while keeping multi-token queries on the direct recall path constrained to all query tokens (#2140) (thanks @momothemage).
|
||||
- Search/Web: disclose when `/search` is hiding suspicious skills and add an explicit opt-out so unified search no longer silently differs from `/skills` for the same query (#2079) (thanks @momothemage).
|
||||
- Uploads: accept PowerShell `.ps1`, `.psm1`, and `.psd1` files as text-based skill files while keeping normal scan coverage (#897) (thanks @cute-omega).
|
||||
- Packages: count package install stat events separately from package downloads and record npm tarball fetches as installs (#1712).
|
||||
- Web: keep the Publishers directory responsive for high-volume publishers by using bounded published-item previews, and abort stale unified-search plugin requests during route changes.
|
||||
- Web: point skill, plugin, and soul owner links directly at canonical `/p/:handle` publisher profiles instead of legacy redirect routes.
|
||||
- Web/API: ignore stale public skill-list cursors from older sort or safety-filter indexes instead of throwing pagination errors.
|
||||
- Web: restore dashboard skill metrics for owned skills and use pointer cursors on dropdown menu items (#2113) (thanks @fuller-stack-dev).
|
||||
- Web: show the skills browse `Hide suspicious` control only when the loaded results include suspicious skills (thanks @vyctorbrzezowski).
|
||||
- Web: align signed-in header avatar controls across desktop and mobile so the menu trigger keeps consistent sizing, truncation, and dropdown styling (#2124) (thanks @vyctorbrzezowski).
|
||||
- Web: constrain settings, profile content, skill detail, and plugin detail pages to the header content width while preserving profile hero bleed (thanks @vyctorbrzezowski).
|
||||
- Web: show publish-page validation next to the relevant fields and upload picker so invalid inputs are not buried below the form (#908) (thanks @AndyZhengyan).
|
||||
- Docs: remove README references to the inactive onlycrabs.ai domain while leaving the internal SoulHub configuration generic (#951) (thanks @muescha).
|
||||
- Docs/dev: document the local Convex site proxy URL and make worktree setup reject misconfigured local site URLs that break HTTP routes (#2060) (thanks @vyctorbrzezowski).
|
||||
- Dev setup: make local seed reset deterministic by cleaning stale seed lookup and badge rows for repeated Convex dev runs (#2057) (thanks @vyctorbrzezowski).
|
||||
|
||||
## 0.12.3 - 2026-05-06
|
||||
|
||||
### Fixes
|
||||
|
||||
- CLI/API: allow skill publishes to target an org/user publisher with `--owner` / `ownerHandle`, and keep root `SKILL.md` publishable even when broad ignore rules match Markdown files (thanks @deepujain).
|
||||
- Packages: expose owned plugin/package soft-delete in the CLI and dashboard, keep moderator takedown access, and remove deleted packages from package search surfaces (thanks @Patrick-Erichsen).
|
||||
- Packages: support monorepo package publishes, infer package owners from scoped names, and keep dry-run publishes metadata-only.
|
||||
- Packages: validate code-plugin runtime entries against extracted files, allow admin plugin release publishes, and raise trusted-publish/admin API rate limits for legitimate publish bursts.
|
||||
- API/Search: return lean skill list payloads, route package search through digest indexes, decode scoped package paths, and bound fallback scans to reduce production read pressure.
|
||||
- Web: restore skill downloads and search paging, canonicalize scoped plugin paths, and improve mobile layout responsiveness.
|
||||
- Security: add scanner checks for confirmation bypasses and Python file upload exfiltration while reducing generic false-positive package tags.
|
||||
|
||||
## 0.12.2 - 2026-05-02
|
||||
|
||||
### Fixes
|
||||
|
||||
- CLI: publish code plugins as clawpacks and allow legacy package downloads to keep older install flows working.
|
||||
- API: resolve scoped package routes and accept scoped npm packuments.
|
||||
- Schema: allow nullable package SHA values in package responses and refresh generated schema artifacts.
|
||||
|
||||
## 0.12.1 - 2026-05-02
|
||||
|
||||
### Added
|
||||
|
||||
- Packages: add clawpack parsing, uploads, mirror artifact routes, artifact downloads, release moderation, reports, appeals, and official migration management across API, dashboard, and CLI.
|
||||
- Security: add ClawScan security surfaces, owner rescan guidance, scanner-specific report pages, security dataset snapshots, and redacted skill-content exports.
|
||||
- CLI: add unban support, moderation diagnostics in `inspect`, manual skill-directory listing, package environment filters, and package migration-status commands.
|
||||
- Web: add skills/plugins search typeahead, featured plugin curation, plugin management tools, skill upload shortcuts, and dashboard pagination.
|
||||
|
||||
### Fixes
|
||||
|
||||
- API: raise public read rate limits to reduce false-positive 429s from browser pages and production smoke tests (thanks @steipete).
|
||||
- CLI/moderation: allow `delete`, `hide`, `undelete`, and `unhide` to record moderation reasons in skill notes and audit logs for legal or policy reviews (thanks @steipete).
|
||||
- Packages: make package publish retries idempotent, constrain catalog queries, keep package list queries single-page, count package archive downloads, and keep beta plugin packages off `latest`.
|
||||
- Search: add soul lexical fallback, non-suspicious digest indexes, normalized skill prefix recall, and more stable relevance recall windows.
|
||||
- Security: broaden static scanner coverage for unsafe credential, subprocess, browser-file, provider-secret, and remote-recipe patterns while hardening prompt-boundary handling.
|
||||
- Deploy/CI: harden production smoke checks, expand PR validation coverage, add dead-code gates, and stabilize CodeQL light coverage.
|
||||
- Dependencies: pin `undici` on the Node 20-compatible line after reverting the incompatible v8 update.
|
||||
|
||||
## 0.12.0 - 2026-04-28
|
||||
|
||||
### Added
|
||||
|
||||
- Security: add owner rescan requests, owner flagged inventory, scanner-specific security pages, and in-progress scan states.
|
||||
- UI: adopt shadcn-managed primitives and polish the rescan/security surfaces for mobile.
|
||||
|
||||
### Fixes
|
||||
|
||||
- Moderation: calibrate VirusTotal Code Insight suspicious verdicts so uncorroborated AI-only findings do not keep otherwise clean skills quarantined (#1830, #1841) (thanks @deepujain).
|
||||
- Security: flag exposed secrets in skill docs and normalize VirusTotal engine stats before caching.
|
||||
- Packages: constrain plugin catalog queries and avoid catalog/package-list query limits.
|
||||
- Auth: tolerate stale auth state when reading star status.
|
||||
- CI: harden and debounce ClawSweeper dispatch workflows and fix production smoke coverage.
|
||||
|
||||
## 0.11.0 - 2026-04-28
|
||||
|
||||
|
||||
@@ -47,9 +47,11 @@
|
||||
- Mock `db` objects MUST include `normalizeId: vi.fn()` for trigger wrapper compatibility.
|
||||
|
||||
<!-- convex-ai-start -->
|
||||
|
||||
This project uses [Convex](https://convex.dev) as its backend.
|
||||
|
||||
When working on Convex code, **always read `convex/_generated/ai/guidelines.md` first** for important guidelines on how to correctly use Convex APIs and patterns. The file contains rules that override what you may have learned about Convex from training data.
|
||||
|
||||
Convex agent skills for common tasks can be installed by running `npx convex ai-files install`.
|
||||
|
||||
<!-- convex-ai-end -->
|
||||
|
||||
+122
-49
@@ -12,6 +12,7 @@ Welcome! ClawHub is the public skill registry for [OpenClaw](https://github.com/
|
||||
|
||||
- [Bun](https://bun.sh/) (Convex CLI runs via `bunx`, no global install needed)
|
||||
- [Node.js](https://nodejs.org/) v18, 20, 22, or 24 (required by the local Convex backend; v25+ is not yet supported)
|
||||
- [Worktrunk](https://github.com/max-sixty/worktrunk) (`wt`) for `bun run dev:worktree` and disposable/Codex worktrees. On macOS, `brew install worktrunk` is the quickest path; shell integration is optional.
|
||||
|
||||
### Install and configure
|
||||
|
||||
@@ -25,18 +26,23 @@ Edit `.env.local` with the following values for **local Convex**:
|
||||
```bash
|
||||
# Frontend
|
||||
VITE_CONVEX_URL=http://127.0.0.1:3210
|
||||
VITE_CONVEX_SITE_URL=http://127.0.0.1:3210
|
||||
VITE_CONVEX_SITE_URL=http://127.0.0.1:3211
|
||||
SITE_URL=http://localhost:3000
|
||||
|
||||
# Convex Auth / HTTP routes
|
||||
CONVEX_SITE_URL=http://127.0.0.1:3211
|
||||
|
||||
# Deployment used by `bunx convex dev`
|
||||
CONVEX_DEPLOYMENT=anonymous:anonymous-clawhub
|
||||
```
|
||||
|
||||
Local Convex serves the function endpoint on port 3210 and HTTP routes (`/api/*` and auth callbacks) through the site proxy on port 3211.
|
||||
|
||||
### GitHub OAuth App (for login)
|
||||
|
||||
1. Go to [github.com/settings/developers](https://github.com/settings/developers) and create a new OAuth App.
|
||||
2. Set **Homepage URL** to `http://localhost:3000`.
|
||||
3. Set **Authorization callback URL** to `http://127.0.0.1:3210/api/auth/callback/github`.
|
||||
3. Set **Authorization callback URL** to `http://127.0.0.1:3211/api/auth/callback/github`.
|
||||
4. Copy the Client ID and generate a Client Secret.
|
||||
|
||||
### Run the Convex backend
|
||||
@@ -75,37 +81,105 @@ bun run dev -- --port 3000
|
||||
|
||||
Change the port if 3000 is already in use, and update `SITE_URL` in both `.env.local` and the Convex backend (`bunx convex env set SITE_URL ...`) to match.
|
||||
|
||||
### Seed the database
|
||||
### Worktree/Codex fast path
|
||||
|
||||
Populate sample data so the UI isn't empty:
|
||||
Use this path for disposable branches, Codex sessions, or parallel worktrees after one source worktree already has a working `.env.local` and `.convex` local Convex setup:
|
||||
|
||||
```bash
|
||||
# 3 sample skills (padel, gohome, xuezh)
|
||||
bunx convex run --no-push devSeed:seedNixSkills
|
||||
bun run setup:worktree
|
||||
bun run dev:worktree
|
||||
wt --yes url
|
||||
wt --yes stop
|
||||
```
|
||||
|
||||
`setup:worktree` finds a usable source worktree and symlinks `.env.local` plus `.convex` into the current checkout. If discovery picks the wrong source, pass one explicitly:
|
||||
|
||||
```bash
|
||||
bun run setup:worktree -- --from /path/to/source/worktree
|
||||
CLAWHUB_WORKTREE_SOURCE=/path/to/source/worktree bun run setup:worktree
|
||||
```
|
||||
|
||||
`dev:worktree` is the Worktrunk entrypoint. It runs the hooks in `.config/wt.toml`, copies ignored dependencies listed in `.worktreeinclude` when possible, falls back to `bun install` if Vite is missing, seeds local fixtures plus the public corpus once when `VITE_CONVEX_URL` and `CONVEX_DEPLOYMENT` are local, refreshes cached global stats, and starts detached services on a branch-hashed loopback port. Use `wt --yes url` from the same worktree to print the URL.
|
||||
|
||||
The detached server writes runtime state under `.codex/runtime/`. Stop it with `wt --yes stop` before removing the worktree.
|
||||
|
||||
### Local Codex workers
|
||||
|
||||
Local dev does not start Codex-backed workers by default, so `dev:worktree` does
|
||||
not spend Codex quota.
|
||||
|
||||
To process local ClawScan or Skill Card jobs, opt in for that shell:
|
||||
|
||||
```bash
|
||||
CLAWHUB_ALLOW_LOCAL_CODEX_SCAN=1 bun run dev:workers -- --workers security-scan --once
|
||||
CLAWHUB_ALLOW_LOCAL_CODEX_SCAN=1 bun run dev:workers -- --workers skill-card --once
|
||||
```
|
||||
|
||||
Opted-in local runs use an ignored worktree-local `CODEX_HOME` unless you provide
|
||||
one.
|
||||
|
||||
Without those workers, local ClawScan and Skill Card jobs stay pending until you
|
||||
opt in, seed/mock results, or use the production workflows.
|
||||
|
||||
### Reseed the database
|
||||
|
||||
`dev:worktree` seeds local QA fixtures and the committed public corpus before starting the app when `VITE_CONVEX_URL` points at local Convex and `CONVEX_DEPLOYMENT` is an anonymous/local deployment marker, then records `.codex/runtime/dev-worktree.seeded` so ordinary restarts skip the expensive corpus pass. Remote-backed previews or mismatched deployment markers skip seeding and keep starting. To force the seed path without restarting the preview:
|
||||
|
||||
```bash
|
||||
bun run seed:dev
|
||||
```
|
||||
|
||||
`seed:dev` runs worktree setup, starts or waits for local Convex, seeds the hand-authored local QA fixtures, imports the committed public corpus, and refreshes cached global stats. It is safe to rerun after fixture or schema changes.
|
||||
|
||||
Lower-level seed commands are available for manual recovery or focused fixture work:
|
||||
|
||||
```bash
|
||||
# local moderation/security fixtures only
|
||||
bunx convex run --no-push devSeed:seedLocalFixtures
|
||||
|
||||
# committed public corpus only
|
||||
bun run seed:public-corpus
|
||||
|
||||
# validate the committed public corpus fixture
|
||||
bun run validate:public-corpus
|
||||
|
||||
# 50 extra skills for pagination testing (optional)
|
||||
bunx convex run --no-push devSeedExtra:seedExtraSkillsInternal
|
||||
|
||||
# Refresh the cached skills count (required after seeding)
|
||||
# Refresh cached global stats after manual seeding
|
||||
bunx convex run --no-push statsMaintenance:updateGlobalStatsAction
|
||||
```
|
||||
|
||||
To reset and re-seed:
|
||||
|
||||
```bash
|
||||
bunx convex run --no-push devSeed:seedNixSkills '{"reset": true}'
|
||||
bunx convex run --no-push devSeed:seedLocalFixtures '{"reset": true}'
|
||||
bun run seed:public-corpus -- --reset
|
||||
bunx convex run --no-push statsMaintenance:updateGlobalStatsAction
|
||||
```
|
||||
|
||||
Without `OPENAI_API_KEY`, public corpus import still works, but semantic search quality degrades because embeddings fall back to zero vectors.
|
||||
|
||||
### Worktree troubleshooting
|
||||
|
||||
- `wt: command not found`: install Worktrunk, then rerun `bun run dev:worktree`. Manual `bun run dev` plus `bunx convex dev --typecheck=disable` still works without Worktrunk.
|
||||
- Missing `.env.local` or `.convex`: run `bun run setup:worktree -- --from /path/to/source/worktree`. The source must contain `.env.local` and, for local Convex deployments, `.convex/local/default/config.json`.
|
||||
- Wrong local Convex deployment: make sure `CONVEX_DEPLOYMENT` in `.env.local` matches the local Convex deployment in `.convex/local/default/config.json` when using a `local:` deployment.
|
||||
- Port mismatch: local Convex normally serves cloud functions at `http://127.0.0.1:3210` and HTTP routes/auth callbacks at `http://127.0.0.1:3211`. Keep `VITE_CONVEX_URL`, `VITE_CONVEX_SITE_URL`, and `CONVEX_SITE_URL` aligned with the local config.
|
||||
- `wt step copy-ignored` reports that `.convex` cannot be copied: this can happen when `.convex` is a symlink to the source worktree. The Worktrunk hook continues; confirm `.env.local`, `.convex`, and `node_modules/.bin/vite` exist before debugging deeper.
|
||||
- Local Convex functions are not queryable yet during seeding: leave `bunx convex dev --typecheck=disable` running or rerun `bun run seed:dev`; the seed runner retries while Convex finishes pushing functions.
|
||||
- Local seeding hits a transient Convex write conflict: `seed:public-corpus` retries retryable batch conflicts. If retries are exhausted, stop other local writers and rerun `bun run seed:dev`.
|
||||
- Stale detached services: run `wt --yes stop`, then inspect `.codex/runtime/dev-worktree.log` if the server still does not restart cleanly.
|
||||
|
||||
### Optional environment variables
|
||||
|
||||
These features degrade gracefully without their keys:
|
||||
|
||||
| Variable | Purpose |
|
||||
| ------------------------------------------------------------------------- | --------------------------------------------------------- |
|
||||
| `OPENAI_API_KEY` | Embeddings and vector search (falls back to zero vectors) |
|
||||
| `VT_API_KEY` | VirusTotal malware scanning |
|
||||
| `DISCORD_WEBHOOK_URL` | Discord notifications |
|
||||
| `GITHUB_APP_ID` / `GITHUB_APP_PRIVATE_KEY` / `GITHUB_APP_INSTALLATION_ID` | GitHub backup sync |
|
||||
| Variable | Purpose |
|
||||
| --------------------- | --------------------------------------------------------- |
|
||||
| `OPENAI_API_KEY` | Embeddings and vector search (falls back to zero vectors) |
|
||||
| `VT_API_KEY` | VirusTotal malware scanning |
|
||||
| `DISCORD_WEBHOOK_URL` | Discord notifications |
|
||||
|
||||
## CLI Development
|
||||
|
||||
@@ -114,7 +188,7 @@ The CLI source lives in [`packages/clawhub/`](packages/clawhub/). Both `clawhub`
|
||||
To test the CLI against your local instance:
|
||||
|
||||
```bash
|
||||
CLAWHUB_REGISTRY=http://127.0.0.1:3210 CLAWHUB_SITE=http://localhost:3000 clawhub search "padel"
|
||||
CLAWHUB_REGISTRY=http://127.0.0.1:3211 CLAWHUB_SITE=http://localhost:3000 clawhub search "padel"
|
||||
```
|
||||
|
||||
Use the package-local verification contract when working on the CLI:
|
||||
@@ -128,13 +202,12 @@ bun run --cwd packages/clawhub verify
|
||||
|
||||
`bun test packages/clawhub/` is not the supported workflow. Source tests and built-artifact smoke tests are intentionally split.
|
||||
|
||||
Manual smoke tests are documented in [`docs/manual-testing.md`](docs/manual-testing.md).
|
||||
Manual smoke tests are documented in [`specs/manual-testing.md`](specs/manual-testing.md).
|
||||
|
||||
## Skill & Soul Publishing
|
||||
## Skill Publishing
|
||||
|
||||
- Skill format reference: [`docs/skill-format.md`](docs/skill-format.md)
|
||||
- Soul format reference: [`docs/soul-format.md`](docs/soul-format.md)
|
||||
- End-to-end walkthrough (search, install, publish, sync): [`docs/quickstart.md`](docs/quickstart.md)
|
||||
- End-to-end walkthrough (search, install, and publish): [`docs/quickstart.md`](docs/quickstart.md)
|
||||
|
||||
Quick publish:
|
||||
|
||||
@@ -144,38 +217,37 @@ clawhub publish <path-to-skill-directory>
|
||||
|
||||
## Before Submitting a PR
|
||||
|
||||
```bash
|
||||
bun run lint # oxlint
|
||||
bun run test # Vitest (80% coverage threshold)
|
||||
bun run build # Vite + Nitro
|
||||
bun run --cwd packages/clawhub verify
|
||||
```
|
||||
Run the narrowest meaningful check while iterating, then run the matching CI aliases before handoff:
|
||||
|
||||
These are the same checks that run in CI (`.github/workflows/ci.yml`).
|
||||
- All PRs: `bun run ci:static`.
|
||||
- Source or test changes: focused tests for the touched behavior plus `bun run ci:unit` unless the change is docs/config-only or a maintainer asks to rely on CI.
|
||||
- App runtime, Convex, or build changes: `bun run ci:types-build`.
|
||||
- Package changes: `bun run ci:packages`.
|
||||
- HTTP/API/CLI integration changes: `bun run ci:e2e-http`.
|
||||
- Browser smoke or visual behavior changes: `bun run ci:playwright-smoke`, `bun run test:pw:local-auth`, and/or `bun run proof:ui` depending on the touched flow.
|
||||
|
||||
### Blacksmith Testbox checks
|
||||
`bun run ci:pr` is the local aggregate for the non-browser PR gates. See [`specs/ci.md`](specs/ci.md) for the full CI contract.
|
||||
|
||||
Maintainers with Blacksmith access can run the same checks in a warmed Testbox
|
||||
instead of spending local CPU:
|
||||
### Crabbox remote checks
|
||||
|
||||
Maintainers can run the same checks in a Crabbox lease instead of spending local
|
||||
CPU. ClawHub uses Crabbox as the agent-facing command surface; the Testbox
|
||||
workflow is only the backend for the default Blacksmith provider.
|
||||
|
||||
```bash
|
||||
export CLAWHUB_TESTBOX=1
|
||||
blacksmith testbox warmup ci-check-testbox.yml --ref main --idle-timeout 90
|
||||
bun run testbox:claim -- --id <tbx_id>
|
||||
bun run testbox:sanity -- --id <tbx_id>
|
||||
bun run testbox:run -- --id <tbx_id> -- bun run lint
|
||||
bun run testbox:run -- --id <tbx_id> -- bun run test
|
||||
bun run testbox:run -- --id <tbx_id> -- bun run build
|
||||
bun run crabbox:warmup -- --provider blacksmith-testbox
|
||||
bun run crabbox:run -- --provider blacksmith-testbox --shell -- "bun run lint"
|
||||
bun run crabbox:run -- --provider blacksmith-testbox --shell -- "bun run test"
|
||||
bun run crabbox:run -- --provider blacksmith-testbox --shell -- "bun run build"
|
||||
```
|
||||
|
||||
Use the `tbx_...` id from the current warmup output. The wrapper refuses ids
|
||||
that are missing the local SSH key or were claimed by a different checkout.
|
||||
Use `--id <id-or-slug>` with `crabbox:run` when reusing an existing warmed lease,
|
||||
and stop disposable leases with `bun run crabbox:stop -- --provider <provider>
|
||||
<id-or-slug>`.
|
||||
Use `CLAWHUB_LOCAL_CHECK_MODE=throttled` or `CLAWHUB_LOCAL_CHECK_MODE=full` as
|
||||
the explicit local escape hatch when you intentionally want laptop-side proof.
|
||||
If Blacksmith auth/org access is missing, report that instead of falling back
|
||||
If Crabbox auth/provider access is missing, report that instead of falling back
|
||||
to a broad local gate that can bog down a dev machine.
|
||||
For the initial bootstrap only, the Testbox workflow must land on `main` before
|
||||
`blacksmith testbox warmup ci-check-testbox.yml --ref <branch>` can dispatch it.
|
||||
|
||||
**PR guidelines:**
|
||||
|
||||
@@ -206,11 +278,12 @@ See [`docs/security.md`](docs/security.md) for moderation and upload gating deta
|
||||
## Reading Order for New Contributors
|
||||
|
||||
1. This file (local setup)
|
||||
2. [`docs/quickstart.md`](docs/quickstart.md) — end-to-end workflows
|
||||
3. [`docs/architecture.md`](docs/architecture.md) — system design
|
||||
4. [`docs/skill-format.md`](docs/skill-format.md) — skill structure
|
||||
5. [`docs/cli.md`](docs/cli.md) — CLI reference
|
||||
6. [`docs/http-api.md`](docs/http-api.md) — HTTP endpoints
|
||||
7. [`docs/auth.md`](docs/auth.md) — authentication
|
||||
8. [`docs/deploy.md`](docs/deploy.md) — deployment
|
||||
9. [`docs/troubleshooting.md`](docs/troubleshooting.md) — common issues
|
||||
2. [`docs/clawhub.md`](docs/clawhub.md) — public registry overview
|
||||
3. [`docs/quickstart.md`](docs/quickstart.md) — end-to-end workflows
|
||||
4. [`docs/how-it-works.md`](docs/how-it-works.md) — registry behavior and system overview
|
||||
5. [`docs/skill-format.md`](docs/skill-format.md) — skill structure
|
||||
6. [`docs/cli.md`](docs/cli.md) — CLI reference
|
||||
7. [`docs/http-api.md`](docs/http-api.md) — HTTP endpoints
|
||||
8. [`docs/auth.md`](docs/auth.md) — authentication
|
||||
9. [`specs/deploy.md`](specs/deploy.md) — deployment
|
||||
10. [`docs/troubleshooting.md`](docs/troubleshooting.md) — common issues
|
||||
|
||||
@@ -10,14 +10,14 @@ This document outlines the design rules, patterns, and guidelines for the ClawHu
|
||||
|
||||
ClawHub uses a strict **3-5 color palette** based on the OpenClaw brand:
|
||||
|
||||
| Token | Light Mode | Dark Mode | Usage |
|
||||
|-------|------------|-----------|-------|
|
||||
| `--accent` | `#dc2626` | `#dc2626` | Primary actions, interactive elements, emphasis |
|
||||
| `--accent-deep` | `#b91c1c` | `#ef4444` | Hover states, secondary emphasis |
|
||||
| `--ink` | `#0a0a0a` | `#fafafa` | Primary text |
|
||||
| `--ink-soft` | `#525252` | `#a1a1a1` | Secondary text, descriptions |
|
||||
| `--surface` | `#ffffff` | `#121212` | Card backgrounds, elevated surfaces |
|
||||
| `--bg` | `#fafafa` | `#0a0a0a` | Page background |
|
||||
| Token | Light Mode | Dark Mode | Usage |
|
||||
| --------------- | ---------- | --------- | ----------------------------------------------- |
|
||||
| `--accent` | `#dc2626` | `#dc2626` | Primary actions, interactive elements, emphasis |
|
||||
| `--accent-deep` | `#b91c1c` | `#ef4444` | Hover states, secondary emphasis |
|
||||
| `--ink` | `#0a0a0a` | `#fafafa` | Primary text |
|
||||
| `--ink-soft` | `#525252` | `#a1a1a1` | Secondary text, descriptions |
|
||||
| `--surface` | `#ffffff` | `#121212` | Card backgrounds, elevated surfaces |
|
||||
| `--bg` | `#fafafa` | `#0a0a0a` | Page background |
|
||||
|
||||
### Rules
|
||||
|
||||
@@ -33,21 +33,21 @@ ClawHub uses a strict **3-5 color palette** based on the OpenClaw brand:
|
||||
### Font Stack
|
||||
|
||||
```css
|
||||
--font-sans: 'Geist', system-ui, sans-serif;
|
||||
--font-mono: 'Geist Mono', monospace;
|
||||
--font-display: 'Geist', system-ui, sans-serif;
|
||||
--font-sans: "Geist", system-ui, sans-serif;
|
||||
--font-mono: "Geist Mono", monospace;
|
||||
--font-display: "Geist", system-ui, sans-serif;
|
||||
```
|
||||
|
||||
### Scale
|
||||
|
||||
| Token | Size | Usage |
|
||||
|-------|------|-------|
|
||||
| `--fs-xs` | 0.75rem (12px) | Labels, badges, metadata |
|
||||
| `--fs-sm` | 0.875rem (14px) | Body text, descriptions |
|
||||
| `--fs-base` | 1rem (16px) | Default body text |
|
||||
| `--fs-md` | 1.125rem (18px) | Subheadings |
|
||||
| `--fs-lg` | 1.25rem (20px) | Section titles |
|
||||
| `--fs-xl` | 1.5rem (24px) | Page headings |
|
||||
| Token | Size | Usage |
|
||||
| ----------- | --------------- | ------------------------ |
|
||||
| `--fs-xs` | 0.75rem (12px) | Labels, badges, metadata |
|
||||
| `--fs-sm` | 0.875rem (14px) | Body text, descriptions |
|
||||
| `--fs-base` | 1rem (16px) | Default body text |
|
||||
| `--fs-md` | 1.125rem (18px) | Subheadings |
|
||||
| `--fs-lg` | 1.25rem (20px) | Section titles |
|
||||
| `--fs-xl` | 1.5rem (24px) | Page headings |
|
||||
|
||||
### Rules
|
||||
|
||||
@@ -72,25 +72,24 @@ Use this hierarchy for layout decisions:
|
||||
### Spacing Scale
|
||||
|
||||
```css
|
||||
--space-1: 0.25rem /* 4px */
|
||||
--space-2: 0.5rem /* 8px */
|
||||
--space-3: 0.75rem /* 12px */
|
||||
--space-4: 1rem /* 16px */
|
||||
--space-5: 1.5rem /* 24px */
|
||||
--space-6: 2rem /* 32px */
|
||||
--space-1: 0.25rem /* 4px */ --space-2: 0.5rem /* 8px */ --space-3: 0.75rem /* 12px */
|
||||
--space-4: 1rem /* 16px */ --space-5: 1.5rem /* 24px */ --space-6: 2rem /* 32px */;
|
||||
```
|
||||
|
||||
### Grid Patterns
|
||||
|
||||
#### Auto-fit Grid (Recommended for Cards)
|
||||
|
||||
```css
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
```
|
||||
|
||||
- Automatically adjusts columns based on container width
|
||||
- Prevents orphan items on partial rows
|
||||
- Maintains consistent card widths
|
||||
|
||||
#### Fixed Grid (When exact columns needed)
|
||||
|
||||
```css
|
||||
/* 3-column at desktop, 2 at tablet, 1 at mobile */
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
@@ -106,11 +105,11 @@ grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
|
||||
### Container Widths
|
||||
|
||||
| Size | Max Width | Usage |
|
||||
|------|-----------|-------|
|
||||
| Default | `--page-max` (1200px) | Standard pages |
|
||||
| Narrow | `--page-narrow` (720px) | Reading content, forms |
|
||||
| Wide | Full width | Dashboards, data tables |
|
||||
| Size | Max Width | Usage |
|
||||
| ------- | ----------------------- | ----------------------- |
|
||||
| Default | `--page-max` (1200px) | Standard pages |
|
||||
| Narrow | `--page-narrow` (720px) | Reading content, forms |
|
||||
| Wide | Full width | Dashboards, data tables |
|
||||
|
||||
---
|
||||
|
||||
@@ -128,20 +127,22 @@ grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
|
||||
- Always use `display: flex; flex-direction: column;` for consistent height
|
||||
- Add `flex: 1` to content area for equal-height cards in grids
|
||||
- Include hover state with `border-color` and subtle `box-shadow`
|
||||
|
||||
### Buttons
|
||||
|
||||
| Variant | Usage |
|
||||
|---------|-------|
|
||||
| `primary` | Main actions (Submit, Save, Download) |
|
||||
| `secondary` | Alternative actions |
|
||||
| `ghost` | Tertiary actions, navigation |
|
||||
| `destructive` | Delete, remove, dangerous actions |
|
||||
| Variant | Usage |
|
||||
| ------------- | ------------------------------------- |
|
||||
| `primary` | Main actions (Submit, Save, Download) |
|
||||
| `secondary` | Alternative actions |
|
||||
| `ghost` | Tertiary actions, navigation |
|
||||
| `destructive` | Delete, remove, dangerous actions |
|
||||
|
||||
**Rules:**
|
||||
|
||||
- Always include visible focus state
|
||||
- Minimum touch target: 44x44px on mobile
|
||||
- Include `aria-label` when icon-only
|
||||
@@ -314,17 +315,22 @@ grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
|
||||
```css
|
||||
/* Component */
|
||||
.component-name { }
|
||||
.component-name {
|
||||
}
|
||||
|
||||
/* Component modifier */
|
||||
.component-name.variant { }
|
||||
.component-name.variant {
|
||||
}
|
||||
|
||||
/* Component child */
|
||||
.component-name-child { }
|
||||
.component-name-child {
|
||||
}
|
||||
|
||||
/* State */
|
||||
.component-name.is-active { }
|
||||
.component-name[data-state="open"] { }
|
||||
.component-name.is-active {
|
||||
}
|
||||
.component-name[data-state="open"] {
|
||||
}
|
||||
```
|
||||
|
||||
### File Organization
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
<img src="public/clawd-logo.png" alt="ClawHub" width="120">
|
||||
</p>
|
||||
|
||||

|
||||
|
||||
<h1 align="center">ClawHub</h1>
|
||||
|
||||
<p align="center">
|
||||
@@ -14,13 +16,10 @@ ClawHub is the **public skill registry for OpenClaw**: publish, version, and sea
|
||||
It's designed for fast browsing + a CLI-friendly API, with moderation hooks and vector search.
|
||||
It also now exposes a native **OpenClaw package catalog** for code plugins and bundle plugins.
|
||||
|
||||
onlycrabs.ai is the **SOUL.md registry**: publish and share system lore the same way you publish skills.
|
||||
|
||||
<p align="center">
|
||||
<a href="https://clawhub.ai">ClawHub</a> ·
|
||||
<a href="https://onlycrabs.ai">onlycrabs.ai</a> ·
|
||||
<a href="VISION.md">Vision</a> ·
|
||||
<a href="docs/README.md">Docs</a> ·
|
||||
<a href="docs/clawhub.md">Docs</a> ·
|
||||
<a href="CONTRIBUTING.md">Contributing</a> ·
|
||||
<a href="https://discord.gg/clawd">Discord</a>
|
||||
</p>
|
||||
@@ -31,20 +30,12 @@ onlycrabs.ai is the **SOUL.md registry**: publish and share system lore the same
|
||||
- Publish new skill versions with changelogs + tags (including `latest`).
|
||||
- Rename an owned skill without breaking old links or installs.
|
||||
- Merge duplicate owned skills into one canonical slug.
|
||||
- Browse souls + render their `SOUL.md`.
|
||||
- Publish new soul versions with changelogs + tags.
|
||||
- Search via embeddings (vector index) instead of brittle keywords.
|
||||
- Star + comment; admins/mods can curate and approve skills.
|
||||
- Pin local skill installs so updates and force reinstalls cannot overwrite frozen copies.
|
||||
- Browse OpenClaw packages with family/trust/capability metadata.
|
||||
- Publish native code plugins and bundle plugins through `/packages` APIs and CLI flows.
|
||||
|
||||
## onlycrabs.ai (SOUL.md registry)
|
||||
|
||||
- Entry point is host-based: `onlycrabs.ai`.
|
||||
- On the onlycrabs.ai host, the home page and nav default to souls.
|
||||
- On ClawHub, souls live under `/souls`.
|
||||
- Soul bundles only accept `SOUL.md` for now (no extra files).
|
||||
|
||||
## How it works (high level)
|
||||
|
||||
- Web app: TanStack Start (React, Vite/Nitro).
|
||||
@@ -57,29 +48,31 @@ onlycrabs.ai is the **SOUL.md registry**: publish and share system lore the same
|
||||
Common CLI flows:
|
||||
|
||||
- Auth: `clawhub login`, `clawhub whoami`
|
||||
- Remote/headless auth: `clawhub login --device`
|
||||
- Discover: `clawhub search ...`, `clawhub explore`
|
||||
- Browse unified catalog (skills + plugins): `clawhub package explore`, `clawhub package inspect <name>`
|
||||
- Manage local installs: `clawhub install <slug>`, `clawhub uninstall <slug>`, `clawhub list`, `clawhub update --all`
|
||||
- Inspect without installing: `clawhub inspect <slug>`
|
||||
- Publish/sync skills: `clawhub skill publish <path>`, `clawhub sync`
|
||||
- Manage local installs: `clawhub install @openclaw/demo`, `clawhub pin <skill>`, `clawhub unpin <skill>`, `clawhub uninstall <skill>`, `clawhub list`, `clawhub update --all`
|
||||
- Inspect without installing: `clawhub inspect @openclaw/demo`
|
||||
- Publish skills: `clawhub skill publish <path>`
|
||||
- Publish plugins: `clawhub package publish <source>`
|
||||
- Code-plugin manifests must include `openclaw.compat.pluginApi` and `openclaw.build.openclawVersion`; see [`docs/cli.md`](docs/cli.md) for a minimal example.
|
||||
- Canonicalize owned skills: `clawhub skill rename <slug> <new-slug>`, `clawhub skill merge <source> <target>`
|
||||
- Canonicalize owned skills: `clawhub skill rename <skill> <new-name>`, `clawhub skill merge <source> <target>`
|
||||
|
||||
Docs: [`docs/quickstart.md`](docs/quickstart.md), [`docs/cli.md`](docs/cli.md).
|
||||
|
||||
### Removal permissions
|
||||
|
||||
- `clawhub uninstall <slug>` only removes a local install on your machine.
|
||||
- Uploaded registry skills use soft-delete/restore (`clawhub delete <slug>` / `clawhub undelete <slug>` or API equivalents).
|
||||
- Soft-delete/restore is allowed for the skill owner, moderators, and admins.
|
||||
- `clawhub uninstall <skill>` only removes a local install on your machine.
|
||||
- Uploaded registry skills use soft-delete/restore (`clawhub delete <skill>` / `clawhub undelete <skill>` or API equivalents).
|
||||
- Soft-delete/restore is allowed for the skill or package owner, publisher owner/admin, moderators, and admins.
|
||||
- Packages use `clawhub package delete <name>` / `clawhub package undelete <name>`.
|
||||
- Hard delete is admin-only (management tools / ban flows).
|
||||
- Owner rename keeps the old slug as a redirect alias.
|
||||
- Owner merge hides the source listing and redirects the old slug to the canonical target.
|
||||
|
||||
## Telemetry
|
||||
|
||||
ClawHub tracks minimal **install telemetry** (to compute install counts) when you run `clawhub sync` while logged in.
|
||||
ClawHub tracks minimal **install telemetry** (to compute install counts) when you run `clawhub install` while logged in.
|
||||
Disable via:
|
||||
|
||||
```bash
|
||||
@@ -93,12 +86,13 @@ Details: [`docs/telemetry.md`](docs/telemetry.md).
|
||||
- `src/` — TanStack Start app (routes, components, styles).
|
||||
- `convex/` — schema + queries/mutations/actions + HTTP API routes.
|
||||
- `packages/schema/` — shared API types/routes for the CLI and app.
|
||||
- [`docs/`](docs/README.md) — project documentation (architecture, CLI, auth, deployment, and more).
|
||||
- [`docs/spec.md`](docs/spec.md) — product + implementation spec (good first read).
|
||||
- [`docs/`](docs/README.md) — publishable ClawHub public/operator docs for users, publishers, API clients, and deploy operators.
|
||||
- [`specs/`](specs/README.md) — product specs, plans, regression notes, and design history.
|
||||
- [`specs/spec.md`](specs/spec.md) — product + implementation spec (good first read for maintainers).
|
||||
|
||||
## Local dev
|
||||
|
||||
Prereqs: [Bun](https://bun.sh/) (Convex runs via `bunx`, no global install needed).
|
||||
Prereqs: [Bun](https://bun.sh/) (Convex runs via `bunx`, no global install needed). The detached worktree path also requires [Worktrunk](https://github.com/max-sixty/worktrunk) (`wt`).
|
||||
|
||||
```bash
|
||||
bun install
|
||||
@@ -111,19 +105,24 @@ bunx convex dev
|
||||
# terminal B: web app (port 3000)
|
||||
bun run dev
|
||||
|
||||
# seed sample data
|
||||
bunx convex run --no-push devSeed:seedNixSkills
|
||||
# detached/Codex worktree preview
|
||||
bun run setup:worktree
|
||||
bun run dev:worktree
|
||||
wt --yes url
|
||||
|
||||
# seed local QA fixtures and the public corpus
|
||||
bun run seed:dev
|
||||
```
|
||||
|
||||
For full setup instructions (env vars, GitHub OAuth, JWT keys, database seeding), see [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
`bun run seed:dev` waits for the local Convex deployment, runs the dev fixture seed, and refreshes
|
||||
global stats. The fixtures are owned by `@local` and are safe to rerun after fixture or schema
|
||||
changes. For reset/manual commands and full setup instructions (env vars, GitHub OAuth, JWT keys,
|
||||
database seeding), see [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
|
||||
## Environment
|
||||
|
||||
- `VITE_CONVEX_URL`: Convex deployment URL (`https://<deployment>.convex.cloud`).
|
||||
- `VITE_CONVEX_SITE_URL`: Convex site URL (`https://<deployment>.convex.site`).
|
||||
- `VITE_SOULHUB_SITE_URL`: onlycrabs.ai site URL (`https://onlycrabs.ai`).
|
||||
- `VITE_SOULHUB_HOST`: onlycrabs.ai host match (`onlycrabs.ai`).
|
||||
- `VITE_SITE_MODE`: Optional override (`skills` or `souls`) for SSR builds.
|
||||
- `CONVEX_SITE_URL`: same as `VITE_CONVEX_SITE_URL` (auth + cookies).
|
||||
- `SITE_URL`: App URL (local: `http://localhost:3000`).
|
||||
- `AUTH_GITHUB_ID` / `AUTH_GITHUB_SECRET`: GitHub OAuth App.
|
||||
@@ -199,7 +198,7 @@ metadata: { "clawdbot": { "cliHelp": "padel --help\\nUsage: padel [command]\\n"
|
||||
|
||||
## Skill metadata
|
||||
|
||||
Skills declare their runtime requirements (env vars, binaries, install specs) in the `SKILL.md` frontmatter. ClawHub's security analysis checks these declarations against actual skill behavior.
|
||||
Skills declare their runtime requirements (env vars, binaries, install specs) in the `SKILL.md` frontmatter. ClawHub's security analysis checks these declarations against actual skill behavior; medium review findings stay visible, and the suspicious filter is reserved for high-impact or malicious concerns.
|
||||
|
||||
Full reference: [`docs/skill-format.md`](docs/skill-format.md#frontmatter-metadata)
|
||||
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
# Security Policy
|
||||
|
||||
Use GitHub Security Advisories for vulnerabilities in ClawHub itself.
|
||||
|
||||
Good ClawHub advisory reports include bugs in:
|
||||
|
||||
- the ClawHub website, API, or CLI
|
||||
- registry publishing, downloads, installs, or artifact integrity
|
||||
- authentication, authorization, or API tokens
|
||||
- scanning, moderation, or report handling
|
||||
|
||||
Because ClawHub is a hosted cloud application, ClawHub service vulnerabilities
|
||||
are not publicly disclosed by default. They are publicly disclosed when there is
|
||||
evidence of real user impact or when users need to take action.
|
||||
|
||||
Examples of real user impact include confirmed exploitation, exposure of user
|
||||
data or secrets, malicious content reaching users because of a platform failure,
|
||||
or any issue that requires users to rotate credentials, update local software, or
|
||||
take other protective action.
|
||||
|
||||
Vulnerabilities in user-installed software are publicly disclosed, such as
|
||||
ClawHub CLI packages, binaries, libraries, or other release artifacts that users
|
||||
need to update locally.
|
||||
|
||||
Do not use ClawHub advisories for vulnerabilities in a third-party skill or
|
||||
plugin's own source code. Report those directly to the publisher or source
|
||||
repository linked from the ClawHub listing.
|
||||
|
||||
Use ClawHub's listing reports for genuinely malicious or deceptive marketplace
|
||||
content, such as malicious listings, misleading metadata, undeclared
|
||||
permissions, suspicious install instructions, scam comments, impersonation,
|
||||
trademark misuse, or policy violations.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user