mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-07-02 16:39:14 +02:00
Compare commits
1133 Commits
user/yeela
...
releaseChe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e2ca69947 | ||
|
|
bdf92319da | ||
|
|
c32bfa8c48 | ||
|
|
fcac6ee941 | ||
|
|
536aba7bbb | ||
|
|
640a235cd2 | ||
|
|
00d580985d | ||
|
|
4a3cc92382 | ||
|
|
fb1886c2c3 | ||
|
|
0982821a82 | ||
|
|
fce5a05e61 | ||
|
|
d2537da103 | ||
|
|
f7a50de1f4 | ||
|
|
1dd2ec27dc | ||
|
|
c8a23a231e | ||
|
|
bb1d9776bc | ||
|
|
7605e8f560 | ||
|
|
4be145d345 | ||
|
|
6154098e32 | ||
|
|
9e8327fa60 | ||
|
|
784591a0a8 | ||
|
|
8d14775e12 | ||
|
|
f22ac8ee4b | ||
|
|
58d5c92863 | ||
|
|
2e6fb8f4db | ||
|
|
628d584e33 | ||
|
|
0c3763365a | ||
|
|
b11880342e | ||
|
|
dc7ea58921 | ||
|
|
00507a0af3 | ||
|
|
3d1567cd07 | ||
|
|
a85e8e8fa2 | ||
|
|
b031b0195f | ||
|
|
5a8d35e76f | ||
|
|
5f9e4ef12c | ||
|
|
d66f712c5d | ||
|
|
dd5dbf219e | ||
|
|
689f055f67 | ||
|
|
94676fd31c | ||
|
|
99256c73bb | ||
|
|
d16f605f0d | ||
|
|
caf7b104dc | ||
|
|
6bf19485c1 | ||
|
|
a0ec2f471a | ||
|
|
47e90cc27f | ||
|
|
14229092e1 | ||
|
|
9dbc355da8 | ||
|
|
9d6b5fae77 | ||
|
|
e7f6172e0b | ||
|
|
560a36ccba | ||
|
|
95fde086cc | ||
|
|
6760964e4e | ||
|
|
8fd21a4862 | ||
|
|
11d872efa2 | ||
|
|
007e83baf9 | ||
|
|
22470d14fa | ||
|
|
f39be85109 | ||
|
|
f75a926fd7 | ||
|
|
812656d4d4 | ||
|
|
42ed1a7aee | ||
|
|
04a1ef4258 | ||
|
|
f81ebe516b | ||
|
|
b9719c19a8 | ||
|
|
7889e1a621 | ||
|
|
b0f656433a | ||
|
|
f2dd2f092e | ||
|
|
2985b4bed4 | ||
|
|
9ee58209b7 | ||
|
|
9e275134c7 | ||
|
|
ce4585c9c1 | ||
|
|
ad6d2686c1 | ||
|
|
13d3cb3539 | ||
|
|
de3f3f6af8 | ||
|
|
a1c6d0c22e | ||
|
|
23360e9652 | ||
|
|
1a2d8869cd | ||
|
|
a3a646e3eb | ||
|
|
f77d450c6d | ||
|
|
71f4c42790 | ||
|
|
1100975dbc | ||
|
|
543f97eb24 | ||
|
|
2a916787d0 | ||
|
|
c7fe0b9839 | ||
|
|
39838ce00f | ||
|
|
0c99d4a17e | ||
|
|
148ee3ff96 | ||
|
|
7e46960d38 | ||
|
|
eccc10bd71 | ||
|
|
7f7825a245 | ||
|
|
ca555a0fc2 | ||
|
|
6bb0f0b84b | ||
|
|
775e0d7b54 | ||
|
|
c2b9c9284f | ||
|
|
ce4ac2ad46 | ||
|
|
4d0df63574 | ||
|
|
55303028a7 | ||
|
|
fc0291a115 | ||
|
|
95d6af8be4 | ||
|
|
37361baac6 | ||
|
|
56626b240f | ||
|
|
161ea58236 | ||
|
|
0cd8269e25 | ||
|
|
7e494db6b6 | ||
|
|
e02f479fe6 | ||
|
|
f9ec916810 | ||
|
|
9a8520a9cc | ||
|
|
019b64dc5c | ||
|
|
1a0c359b97 | ||
|
|
fcfaa0ecca | ||
|
|
e7c586a56f | ||
|
|
57316043db | ||
|
|
da772358e8 | ||
|
|
1f3898e67e | ||
|
|
caece16873 | ||
|
|
8589b7536f | ||
|
|
85963715f1 | ||
|
|
67ce8a415a | ||
|
|
e3a6fb809c | ||
|
|
fdbb52222c | ||
|
|
f26c1d40d4 | ||
|
|
cb552699d1 | ||
|
|
e995e0b453 | ||
|
|
c0cc53880b | ||
|
|
b5ad8bae9c | ||
|
|
0b71d6fad6 | ||
|
|
c32e12e90a | ||
|
|
dcb99989e1 | ||
|
|
42f899afaa | ||
|
|
a0dd09d758 | ||
|
|
d80a20673d | ||
|
|
468462db67 | ||
|
|
8e0e891bc3 | ||
|
|
4d6300442a | ||
|
|
cb0359c7fd | ||
|
|
6e1b5c3a34 | ||
|
|
41664af97b | ||
|
|
0105815166 | ||
|
|
f092553005 | ||
|
|
3810c52956 | ||
|
|
bdbed989ea | ||
|
|
7d03e24ed6 | ||
|
|
77198891fe | ||
|
|
c44f2f036f | ||
|
|
e18d2cc094 | ||
|
|
36a11295b6 | ||
|
|
b0912779a7 | ||
|
|
cadbcde351 | ||
|
|
b90b860d7f | ||
|
|
a1d001a748 | ||
|
|
4b0c8d2bb6 | ||
|
|
a6daf226de | ||
|
|
a5d7aca2a2 | ||
|
|
50eb98142a | ||
|
|
3047416be5 | ||
|
|
d7ed0db164 | ||
|
|
fcf0382075 | ||
|
|
cf005d5cd9 | ||
|
|
46aba3f9ac | ||
|
|
1a987ea95a | ||
|
|
410401d272 | ||
|
|
98b4bcc8c1 | ||
|
|
c7cfac6ff0 | ||
|
|
de082ab9f7 | ||
|
|
d66184afdb | ||
|
|
654b5c44b0 | ||
|
|
b3347f1af4 | ||
|
|
8db5d5c2c4 | ||
|
|
621d174e73 | ||
|
|
fbdc5fca51 | ||
|
|
8d3ba403ac | ||
|
|
e256d2fae7 | ||
|
|
7a302812e0 | ||
|
|
a0c8482f84 | ||
|
|
a5917a7298 | ||
|
|
dd1d774c41 | ||
|
|
c8c7c351e2 | ||
|
|
2b02b0526c | ||
|
|
298c75659b | ||
|
|
bbb9034265 | ||
|
|
53930f1f75 | ||
|
|
448637fb1a | ||
|
|
e221d0becf | ||
|
|
31d8b4e5c7 | ||
|
|
7ad50eb175 | ||
|
|
3215bbdd27 | ||
|
|
a70bbeb9cf | ||
|
|
cc532c9536 | ||
|
|
59e34cfd95 | ||
|
|
71865d39e4 | ||
|
|
17f08fa7b1 | ||
|
|
9e48a1e6be | ||
|
|
fea55bed39 | ||
|
|
65f8c52dc4 | ||
|
|
212cdcc3f2 | ||
|
|
b7815e1d1b | ||
|
|
ffc48fa38c | ||
|
|
e85cd8b12b | ||
|
|
25c4ebaee4 | ||
|
|
6571c977de | ||
|
|
416286b6b1 | ||
|
|
0703f6c5e9 | ||
|
|
0b3b9e8e94 | ||
|
|
5428f18900 | ||
|
|
725730636b | ||
|
|
6d603da6cf | ||
|
|
50761e80fa | ||
|
|
82bb5947fd | ||
|
|
4d754f4816 | ||
|
|
fa17e8a83b | ||
|
|
0241f9235a | ||
|
|
77def69df9 | ||
|
|
e2b07e3058 | ||
|
|
dea5763869 | ||
|
|
ac10cb7a16 | ||
|
|
eb6fb14f0a | ||
|
|
940060c307 | ||
|
|
f833dee2f8 | ||
|
|
28ecf3734e | ||
|
|
7f3e344ddc | ||
|
|
c8caeb7e35 | ||
|
|
dbf804f76f | ||
|
|
d3ae8deab7 | ||
|
|
720228cd3b | ||
|
|
a4c085daa2 | ||
|
|
789bb1dcb9 | ||
|
|
51a922e78d | ||
|
|
a188527aa6 | ||
|
|
a45ad73665 | ||
|
|
6ca2f21e7e | ||
|
|
33ffd1f1e1 | ||
|
|
7025117d4e | ||
|
|
39bda8dc8d | ||
|
|
03e16dcad1 | ||
|
|
ca55f400c8 | ||
|
|
baa6b43bde | ||
|
|
292272fe74 | ||
|
|
fa6a6595c5 | ||
|
|
037dee7d25 | ||
|
|
ec525fff37 | ||
|
|
097cbd031f | ||
|
|
977fe8f829 | ||
|
|
8be33f544b | ||
|
|
86931b3c33 | ||
|
|
117a535893 | ||
|
|
ca3b47de9b | ||
|
|
cf26e9b715 | ||
|
|
a5844473ac | ||
|
|
07788990ee | ||
|
|
9eabcd6b46 | ||
|
|
c076e8432f | ||
|
|
17ed3766da | ||
|
|
283a939616 | ||
|
|
70efb6d496 | ||
|
|
8538cc9b5f | ||
|
|
1e3d7618a2 | ||
|
|
1927682966 | ||
|
|
88791ecd90 | ||
|
|
a2f066380f | ||
|
|
8817a9ac30 | ||
|
|
968f516c5a | ||
|
|
0546cb8829 | ||
|
|
b9a41e328e | ||
|
|
88d7bce75a | ||
|
|
2af85fc44d | ||
|
|
1c4d33c45c | ||
|
|
e5af0f31d6 | ||
|
|
424167911e | ||
|
|
7760e3a90a | ||
|
|
307eb0faa4 | ||
|
|
cb4f008466 | ||
|
|
c1ca9155d3 | ||
|
|
2fa9c32ce6 | ||
|
|
5142bc5168 | ||
|
|
3b70461cfb | ||
|
|
3266fff756 | ||
|
|
a9da27ab9c | ||
|
|
b2cf3c948b | ||
|
|
c67958e717 | ||
|
|
bd36460896 | ||
|
|
249fb23994 | ||
|
|
dfb2f5dfef | ||
|
|
f350ba9879 | ||
|
|
0cc4d92711 | ||
|
|
46c485558e | ||
|
|
4ee5fc6d93 | ||
|
|
c4b4746c48 | ||
|
|
7a07689ff6 | ||
|
|
447ffb967a | ||
|
|
cd5eb4d7a8 | ||
|
|
8b7951cfdc | ||
|
|
b1583252ae | ||
|
|
4c2ad7db42 | ||
|
|
11341e6286 | ||
|
|
bde4d6c512 | ||
|
|
bb908fd5d4 | ||
|
|
d7dff73813 | ||
|
|
2522928fae | ||
|
|
f0124c606d | ||
|
|
f367471f6a | ||
|
|
8982eeaf52 | ||
|
|
3ed97e1c48 | ||
|
|
456ea5a130 | ||
|
|
b35b8b6f09 | ||
|
|
b45b4088b7 | ||
|
|
e6b058c26f | ||
|
|
6547d8ca91 | ||
|
|
a6ddfaff61 | ||
|
|
ff8065a61b | ||
|
|
b5b09f322b | ||
|
|
8aba69e628 | ||
|
|
d5b8c791fa | ||
|
|
f4eaee1c5a | ||
|
|
96a51cd45f | ||
|
|
a45688d744 | ||
|
|
694598132f | ||
|
|
1ec6a5ee91 | ||
|
|
8a3c30a315 | ||
|
|
bf96e2de12 | ||
|
|
ad5733c7f9 | ||
|
|
5ade9d6012 | ||
|
|
5db21606bc | ||
|
|
395d2a5315 | ||
|
|
f5061be3e4 | ||
|
|
dc4a4bdf92 | ||
|
|
04868e1a46 | ||
|
|
f1387ba06d | ||
|
|
f17d5da56e | ||
|
|
7239a60311 | ||
|
|
a9b5015fc2 | ||
|
|
694303eee8 | ||
|
|
e09b694d86 | ||
|
|
27c1ffd7da | ||
|
|
1d4d563f93 | ||
|
|
bf91684f38 | ||
|
|
9c5b23e29d | ||
|
|
19e1eb17a3 | ||
|
|
4d3f6032ba | ||
|
|
b75a0b4461 | ||
|
|
72ff20df24 | ||
|
|
90e5eb5ac5 | ||
|
|
b12fa3f007 | ||
|
|
a9195c06db | ||
|
|
4c34ac2993 | ||
|
|
6c85e72edf | ||
|
|
a6e1d9d6e1 | ||
|
|
9377c4d53c | ||
|
|
b2b018ef0a | ||
|
|
1d7ecd3777 | ||
|
|
f699d33038 | ||
|
|
a1ef553aed | ||
|
|
a096f10b10 | ||
|
|
4f33b2fc29 | ||
|
|
e42bfbb834 | ||
|
|
dd440c1699 | ||
|
|
c0b3517056 | ||
|
|
9e58e328d1 | ||
|
|
6e8665c016 | ||
|
|
ef20eb14d3 | ||
|
|
ad082c722f | ||
|
|
e5f0f88f7f | ||
|
|
0af5f449f7 | ||
|
|
ad0ada8726 | ||
|
|
061884b71b | ||
|
|
cdb3336c97 | ||
|
|
729f98e24f | ||
|
|
99ef48a830 | ||
|
|
34d3e19481 | ||
|
|
8773f1dd70 | ||
|
|
2393711eb8 | ||
|
|
60c3df2d95 | ||
|
|
be3e033326 | ||
|
|
162b361e4b | ||
|
|
9682066cbf | ||
|
|
2d5652acfe | ||
|
|
7c670474eb | ||
|
|
76acc3505e | ||
|
|
d3217c1316 | ||
|
|
28e9a82018 | ||
|
|
a1c99ac579 | ||
|
|
88ccb711b7 | ||
|
|
a51223c6f5 | ||
|
|
42a90a0454 | ||
|
|
a9f792fc6c | ||
|
|
5547461219 | ||
|
|
e53f087bde | ||
|
|
6229777a94 | ||
|
|
1283872e09 | ||
|
|
a2e21acc70 | ||
|
|
7c9bef2fa1 | ||
|
|
d63d858a87 | ||
|
|
87439acd82 | ||
|
|
84d441bd3c | ||
|
|
46fa44889c | ||
|
|
6b34f4bfd7 | ||
|
|
b76c511743 | ||
|
|
b0eaf662d9 | ||
|
|
b0e443bee1 | ||
|
|
9c4b0131bc | ||
|
|
55a32a6d0d | ||
|
|
e055e51c37 | ||
|
|
2744f5e3e9 | ||
|
|
c4873ccf93 | ||
|
|
b8e9c51850 | ||
|
|
cc2c1933c4 | ||
|
|
cabea993cb | ||
|
|
ef51c6e72e | ||
|
|
6bb02e25d1 | ||
|
|
1502f7e605 | ||
|
|
da2d4fb3c4 | ||
|
|
f0e9011339 | ||
|
|
d868a4e1ba | ||
|
|
de2a4a53af | ||
|
|
e56bd4fe71 | ||
|
|
40b818ccc7 | ||
|
|
711c7a77be | ||
|
|
dabbbbccb6 | ||
|
|
5860ad38e8 | ||
|
|
4e28e17ffc | ||
|
|
f9ec7229ea | ||
|
|
64c90ffbc5 | ||
|
|
389b47b7d9 | ||
|
|
24fa059250 | ||
|
|
615999be7c | ||
|
|
7db069fd46 | ||
|
|
6e5729ce87 | ||
|
|
91c49a1023 | ||
|
|
edd30b7098 | ||
|
|
a88b100c12 | ||
|
|
2f8d5734ae | ||
|
|
1859603a18 | ||
|
|
bb174066d6 | ||
|
|
6c4515382f | ||
|
|
50c7aec2b8 | ||
|
|
1e410c21b0 | ||
|
|
b3c4200a1c | ||
|
|
a9a9f39293 | ||
|
|
094ac9a77e | ||
|
|
0c8b4cdc62 | ||
|
|
f328c99c82 | ||
|
|
5df602e3f0 | ||
|
|
036c9baf4d | ||
|
|
c7a35c3b89 | ||
|
|
809ff3621d | ||
|
|
b2dcbc5beb | ||
|
|
bcf5ed2db3 | ||
|
|
860bd1b452 | ||
|
|
760c7be191 | ||
|
|
dcb40e5fbd | ||
|
|
681cc781c8 | ||
|
|
14e08aacbf | ||
|
|
8f5c465cd7 | ||
|
|
fd7de044a9 | ||
|
|
3e94217681 | ||
|
|
112fdb6530 | ||
|
|
21844aa770 | ||
|
|
7ad22ae95c | ||
|
|
75770abd47 | ||
|
|
fd8f298c94 | ||
|
|
adfe14d352 | ||
|
|
9dbf5cf009 | ||
|
|
0b9a76d114 | ||
|
|
a6525c11ed | ||
|
|
8d243d4575 | ||
|
|
8fe38e441b | ||
|
|
eae86d794f | ||
|
|
604b6c0b3c | ||
|
|
27e8c01544 | ||
|
|
0a2b102b8c | ||
|
|
55643f36cb | ||
|
|
d481cd3dd1 | ||
|
|
8188a01ae1 | ||
|
|
0eded24122 | ||
|
|
e73c60b5fa | ||
|
|
08c0b0cd48 | ||
|
|
93216bcc3e | ||
|
|
22f5f62f93 | ||
|
|
045e2e9756 | ||
|
|
f6659dfadc | ||
|
|
860c0a1012 | ||
|
|
da9912a09d | ||
|
|
65488ed3e8 | ||
|
|
5de4599c95 | ||
|
|
1c1c421154 | ||
|
|
3afaddeb96 | ||
|
|
8c10325ce7 | ||
|
|
872b30f41a | ||
|
|
a5569a62cd | ||
|
|
c38ab5adc0 | ||
|
|
2954b3b6ea | ||
|
|
1b26e758a7 | ||
|
|
7f817ce5f7 | ||
|
|
3d3599b88d | ||
|
|
507b8501a2 | ||
|
|
c80aa6bade | ||
|
|
2480b4aa7e | ||
|
|
77ddcf7f97 | ||
|
|
1beeea0f49 | ||
|
|
361ba08b8a | ||
|
|
65887ae26d | ||
|
|
39e8a0409d | ||
|
|
88b305247c | ||
|
|
050c40f0fe | ||
|
|
36ecbde932 | ||
|
|
96fb5865df | ||
|
|
0e42849007 | ||
|
|
f60d64e1f2 | ||
|
|
4908572b8d | ||
|
|
5bc7201ae2 | ||
|
|
a20dd0d8fb | ||
|
|
ebcc3af61b | ||
|
|
f483a44db2 | ||
|
|
ff357c49e2 | ||
|
|
22b8ec9fd8 | ||
|
|
4b3990feaa | ||
|
|
7fee4a20c7 | ||
|
|
8adc4032a0 | ||
|
|
9160c672df | ||
|
|
3e257fafcb | ||
|
|
1e3c0a698d | ||
|
|
f7283d9f1f | ||
|
|
614b64b476 | ||
|
|
8b3bf49880 | ||
|
|
9214fac22b | ||
|
|
d35e5146fe | ||
|
|
9c88c73f60 | ||
|
|
ce2672eebb | ||
|
|
8d70ef2c95 | ||
|
|
1d3acee2a0 | ||
|
|
e666d703fc | ||
|
|
57e507af28 | ||
|
|
b8c744053b | ||
|
|
15f930b35f | ||
|
|
89692fea80 | ||
|
|
9820d22f0e | ||
|
|
0e21214939 | ||
|
|
a6b0152822 | ||
|
|
a39555c9ea | ||
|
|
dd8848cf2f | ||
|
|
4d0925f181 | ||
|
|
02bc8aed18 | ||
|
|
3522553d7c | ||
|
|
bb678282d5 | ||
|
|
13d1c73fd3 | ||
|
|
da4e2c042f | ||
|
|
1282a131ff | ||
|
|
61934d8f6a | ||
|
|
0a31e4a4cb | ||
|
|
4384d6f8ee | ||
|
|
b2c3b4381d | ||
|
|
b976423ffb | ||
|
|
58fd821ce4 | ||
|
|
8aceca56dc | ||
|
|
9c82c62f1b | ||
|
|
21edebb0db | ||
|
|
9f2ef0918f | ||
|
|
05b42c7813 | ||
|
|
2662072af7 | ||
|
|
2fb806a82f | ||
|
|
7214e9c185 | ||
|
|
5325bad7ee | ||
|
|
0123c8e53e | ||
|
|
fb04ddca01 | ||
|
|
5efdf1ec3d | ||
|
|
d7ca2e028f | ||
|
|
b3cc2c68fe | ||
|
|
bcb9f59f41 | ||
|
|
405e3c1412 | ||
|
|
9e4168e4fd | ||
|
|
9f2d089825 | ||
|
|
7ff5de12ec | ||
|
|
6e8a349b2d | ||
|
|
cb2024d110 | ||
|
|
ef2c7b0203 | ||
|
|
392bb727b0 | ||
|
|
d03f4d051e | ||
|
|
7937d27b36 | ||
|
|
d27d0b5461 | ||
|
|
1c4eebf3a7 | ||
|
|
e47445bc55 | ||
|
|
bb4c649954 | ||
|
|
483b276edd | ||
|
|
5f5e2174e5 | ||
|
|
3a90bc71b5 | ||
|
|
ba4ecb3b51 | ||
|
|
62702acb31 | ||
|
|
a091aeff54 | ||
|
|
79f74dae79 | ||
|
|
b3f536564c | ||
|
|
da8d3bdde2 | ||
|
|
2f9ef10aaf | ||
|
|
540b4e05b9 | ||
|
|
f052693e2c | ||
|
|
f914525542 | ||
|
|
14b7836d84 | ||
|
|
78eff3b009 | ||
|
|
e759af66bb | ||
|
|
09f97e3c2b | ||
|
|
a83ba1e8e3 | ||
|
|
375f49bb89 | ||
|
|
a95c6cb8b9 | ||
|
|
7a0bf5eb0b | ||
|
|
ddf770f181 | ||
|
|
94c407f0b2 | ||
|
|
306ca6a5ef | ||
|
|
12af4b1b86 | ||
|
|
302c405dad | ||
|
|
e29fb5b51d | ||
|
|
f4df22834a | ||
|
|
08f29f72b3 | ||
|
|
93b8e6c98c | ||
|
|
2759628e6e | ||
|
|
a4e8aebeea | ||
|
|
0eaffd01e1 | ||
|
|
5d70e3c990 | ||
|
|
a179cd63b9 | ||
|
|
8abd60a7bd | ||
|
|
d131a52b43 | ||
|
|
dc38bca9ff | ||
|
|
e0948e9ea3 | ||
|
|
e5412f9e55 | ||
|
|
6e60571ded | ||
|
|
62bbbf49b2 | ||
|
|
516e5e85a5 | ||
|
|
881ab28a9e | ||
|
|
2d2a692a6a | ||
|
|
d991f07513 | ||
|
|
0b49729849 | ||
|
|
0e4a0e206c | ||
|
|
fa5c523c9c | ||
|
|
9cfcc14023 | ||
|
|
77d262bcc0 | ||
|
|
93383da348 | ||
|
|
7210400d36 | ||
|
|
4815f0dcf5 | ||
|
|
81eb0e27fa | ||
|
|
17593fd9a4 | ||
|
|
8ccc5c2d11 | ||
|
|
8258b1ae93 | ||
|
|
6b6b38891a | ||
|
|
6cf6bbcc42 | ||
|
|
57c5b347da | ||
|
|
1dcd603b51 | ||
|
|
b0da194a2a | ||
|
|
767be1b698 | ||
|
|
df8df4b4d3 | ||
|
|
325179c508 | ||
|
|
2423371db6 | ||
|
|
c7088c784d | ||
|
|
40a3e41587 | ||
|
|
ada9d5cffa | ||
|
|
529dfca120 | ||
|
|
50bd17cef5 | ||
|
|
4cc7a7c070 | ||
|
|
7ab817fe27 | ||
|
|
3bff297517 | ||
|
|
98465b0122 | ||
|
|
65102c1141 | ||
|
|
948278884d | ||
|
|
d94dba3506 | ||
|
|
0b38a69c0b | ||
|
|
695824d3db | ||
|
|
c67fa8396b | ||
|
|
ef95617c03 | ||
|
|
ab60578b4d | ||
|
|
4ebd614a87 | ||
|
|
81583102ba | ||
|
|
b061430c1a | ||
|
|
2aaea3ab46 | ||
|
|
9cd18235da | ||
|
|
816e8d6462 | ||
|
|
f8c420cda5 | ||
|
|
abb3d69c00 | ||
|
|
e02e31ced3 | ||
|
|
2993d91580 | ||
|
|
edffb49c9f | ||
|
|
08df962cd3 | ||
|
|
c696c6480d | ||
|
|
bce91f08cc | ||
|
|
e8b1102c87 | ||
|
|
e76558d555 | ||
|
|
54024a3d0e | ||
|
|
cb766ad0ed | ||
|
|
c7a991aa22 | ||
|
|
bb065937b5 | ||
|
|
53b75f2d12 | ||
|
|
d47a5eaa8b | ||
|
|
4b73e22cf1 | ||
|
|
220f7dc0b6 | ||
|
|
ba74fd3e8d | ||
|
|
92969c23c5 | ||
|
|
7563abace5 | ||
|
|
8882c15372 | ||
|
|
5981d0fa45 | ||
|
|
17940257fb | ||
|
|
9ebee55d3f | ||
|
|
6f869f0bf0 | ||
|
|
6cfd7d72bd | ||
|
|
837dbb5c42 | ||
|
|
d1c2623315 | ||
|
|
1ac933a088 | ||
|
|
d5edc4b507 | ||
|
|
25ca8b9f4b | ||
|
|
4c926aa457 | ||
|
|
a2c358a49c | ||
|
|
3d5a7bf0a3 | ||
|
|
1d520250aa | ||
|
|
c57cf0bc70 | ||
|
|
8d36bbcdb4 | ||
|
|
f4f4e535a0 | ||
|
|
70a89c5888 | ||
|
|
7a595f77cb | ||
|
|
aba0545c9c | ||
|
|
a3ac9cf691 | ||
|
|
eef4929fc0 | ||
|
|
cb8124b7d7 | ||
|
|
b149d2c200 | ||
|
|
a74056cc6c | ||
|
|
149f509817 | ||
|
|
d9a016a8a8 | ||
|
|
29ca344cac | ||
|
|
0833775bec | ||
|
|
ba2a8900c5 | ||
|
|
fb458dfddd | ||
|
|
d4141f98c8 | ||
|
|
d03f1be70f | ||
|
|
d76ce7cccf | ||
|
|
b5875453e3 | ||
|
|
3dd81254dd | ||
|
|
1927eaca97 | ||
|
|
cacc69f175 | ||
|
|
07cbd4c259 | ||
|
|
1587198b6f | ||
|
|
de1c564b37 | ||
|
|
3dab762ecd | ||
|
|
1d16c16785 | ||
|
|
decbb030cb | ||
|
|
466a382eaa | ||
|
|
b5cd846cbd | ||
|
|
72832b5ac2 | ||
|
|
692ed30413 | ||
|
|
043ccd7063 | ||
|
|
47431c27b0 | ||
|
|
906c62038a | ||
|
|
204f8970b2 | ||
|
|
1a97524e20 | ||
|
|
af9d610d42 | ||
|
|
0034db1372 | ||
|
|
d8826366c0 | ||
|
|
17b81df79a | ||
|
|
6e2ba2a937 | ||
|
|
ec806e65d6 | ||
|
|
21fd11d14e | ||
|
|
3b8777bc24 | ||
|
|
47561b2202 | ||
|
|
8df9fd7696 | ||
|
|
17081b92d5 | ||
|
|
1da1321d78 | ||
|
|
f69cf9a0e1 | ||
|
|
c6d1b0f4f1 | ||
|
|
72d1ff6720 | ||
|
|
5467ee7113 | ||
|
|
f52c813097 | ||
|
|
edaeaecabd | ||
|
|
f1e26b5033 | ||
|
|
da4f36b90f | ||
|
|
a0f97a57a0 | ||
|
|
3782acf3df | ||
|
|
b43910ea8b | ||
|
|
ca00b92ca3 | ||
|
|
e382f29775 | ||
|
|
8d210ceaa1 | ||
|
|
4e36a01721 | ||
|
|
e91c3e94a5 | ||
|
|
42434ad040 | ||
|
|
08c1e92690 | ||
|
|
0ca8434392 | ||
|
|
1ca74813a4 | ||
|
|
9ca02b06b3 | ||
|
|
416ca196f9 | ||
|
|
163c2192b8 | ||
|
|
182a4facdc | ||
|
|
6851433233 | ||
|
|
c8050dcad4 | ||
|
|
b4b553fc29 | ||
|
|
3a26850a2e | ||
|
|
51193c0f93 | ||
|
|
75a5eff759 | ||
|
|
f3e8211d22 | ||
|
|
2b19ce0a0d | ||
|
|
9b041cc521 | ||
|
|
3b26f532df | ||
|
|
c5e8bfeea8 | ||
|
|
79c260ab1b | ||
|
|
0ce66df5eb | ||
|
|
ea058cfca5 | ||
|
|
6a4109f2f7 | ||
|
|
243b805891 | ||
|
|
d88d554af8 | ||
|
|
99533b4bc9 | ||
|
|
61a8f04bc7 | ||
|
|
40694cb5d3 | ||
|
|
8d5656a074 | ||
|
|
72f1b35fe7 | ||
|
|
46897d3527 | ||
|
|
1b2c93e616 | ||
|
|
1f7377da01 | ||
|
|
76ee54a3ff | ||
|
|
d3c9837581 | ||
|
|
bd5c924983 | ||
|
|
4dda6b1294 | ||
|
|
5971d36d62 | ||
|
|
a5f023145f | ||
|
|
9d7897852e | ||
|
|
717da9d362 | ||
|
|
bd1472a844 | ||
|
|
601c1f3a27 | ||
|
|
f54054eb15 | ||
|
|
3ed22218af | ||
|
|
0195673ee5 | ||
|
|
f8ac59ea1a | ||
|
|
bd0907ec7f | ||
|
|
3a906f01c2 | ||
|
|
72e1dde777 | ||
|
|
72b5e61323 | ||
|
|
e33a4265ab | ||
|
|
ae6b98d3c2 | ||
|
|
0bb88846d2 | ||
|
|
287366fe6e | ||
|
|
e0c68825e3 | ||
|
|
417db59404 | ||
|
|
b64a41262c | ||
|
|
e33b0474e6 | ||
|
|
265b5d5b00 | ||
|
|
8a5020cd8e | ||
|
|
afc4556bdf | ||
|
|
b0cc84ba41 | ||
|
|
fa904a20f5 | ||
|
|
1ef6c10445 | ||
|
|
62fd4778c3 | ||
|
|
d838192819 | ||
|
|
fdb60a88bc | ||
|
|
9eb310808c | ||
|
|
de72e9a410 | ||
|
|
75d8cc9323 | ||
|
|
68d2abb7e4 | ||
|
|
a34e51a2f7 | ||
|
|
93746efea7 | ||
|
|
9813564213 | ||
|
|
ee74c9d920 | ||
|
|
239d78d504 | ||
|
|
22a9d8d75d | ||
|
|
6e35755f6b | ||
|
|
5db9ce7007 | ||
|
|
838e667462 | ||
|
|
939a982311 | ||
|
|
4434f05466 | ||
|
|
1d47dc1a6b | ||
|
|
7f63468f15 | ||
|
|
3c7ace8300 | ||
|
|
7a139a4085 | ||
|
|
a7ec20ad22 | ||
|
|
8ce1d0188b | ||
|
|
d2b491f07d | ||
|
|
bfaa462be5 | ||
|
|
56d35966bd | ||
|
|
3bb2750478 | ||
|
|
1d04ab971d | ||
|
|
604a72c2f0 | ||
|
|
0de7e48ccb | ||
|
|
d7059abcf2 | ||
|
|
9fe6e5c66d | ||
|
|
f3b2f702bc | ||
|
|
54553fe680 | ||
|
|
0a581841dc | ||
|
|
3eecb434ba | ||
|
|
a4cfda8175 | ||
|
|
1fb68df0ab | ||
|
|
6bfe0ced51 | ||
|
|
6562ab132f | ||
|
|
861f5e50d7 | ||
|
|
ff20f119c2 | ||
|
|
dc7f683cd6 | ||
|
|
2621c4e40e | ||
|
|
e1c4d4d004 | ||
|
|
f3ccc53586 | ||
|
|
3ac073a2c0 | ||
|
|
dd0a5e391a | ||
|
|
f7945457c0 | ||
|
|
9a767b3014 | ||
|
|
175dd8a4d1 | ||
|
|
0baeb1c230 | ||
|
|
393d617393 | ||
|
|
d287d7ff76 | ||
|
|
dbf8e31de9 | ||
|
|
b44b70d8a6 | ||
|
|
ae1c3b6d71 | ||
|
|
877699fee4 | ||
|
|
07250c6211 | ||
|
|
8e81033fa4 | ||
|
|
310595c8bb | ||
|
|
55a74129e8 | ||
|
|
91cd82c756 | ||
|
|
74c8d15a43 | ||
|
|
f3d2881c71 | ||
|
|
b20deac845 | ||
|
|
9c547513a6 | ||
|
|
81f3606a95 | ||
|
|
aa235be1bc | ||
|
|
1200fd54d0 | ||
|
|
433dd3f5a6 | ||
|
|
4a75203883 | ||
|
|
5b309cbefb | ||
|
|
94ad171196 | ||
|
|
2c8afc1881 | ||
|
|
7630a3eaa8 | ||
|
|
c924e4db88 | ||
|
|
381baaa521 | ||
|
|
a0ecbc39d8 | ||
|
|
210142915a | ||
|
|
65bf291b3c | ||
|
|
70712debf4 | ||
|
|
525027a404 | ||
|
|
db6b1cc84e | ||
|
|
a11c60838c | ||
|
|
587cfe2701 | ||
|
|
d826717793 | ||
|
|
24698a7ab9 | ||
|
|
2ba18f746a | ||
|
|
ff0905d474 | ||
|
|
b6b42c68e9 | ||
|
|
2b6b16e389 | ||
|
|
e52a9624ea | ||
|
|
7d03095c7d | ||
|
|
4773cb983b | ||
|
|
aec91a25d2 | ||
|
|
e7466e162b | ||
|
|
a077cd3ca5 | ||
|
|
6d59a9ec05 | ||
|
|
58b1f75d93 | ||
|
|
22b8e99da4 | ||
|
|
d0634e01a8 | ||
|
|
e4c4e15348 | ||
|
|
44db739e6d | ||
|
|
ae4b291c67 | ||
|
|
f567f6a9b5 | ||
|
|
2bc0d39e70 | ||
|
|
0798030e0b | ||
|
|
e5ae3f93aa | ||
|
|
1c3fed6b27 | ||
|
|
28cf763648 | ||
|
|
7be73bc005 | ||
|
|
176c7790cc | ||
|
|
a04096646f | ||
|
|
9b33c9adcf | ||
|
|
c1cf59f5a7 | ||
|
|
84db32ba42 | ||
|
|
dcfba5736c | ||
|
|
b850fa0f78 | ||
|
|
c856e7f1be | ||
|
|
47b585a596 | ||
|
|
90b54dcd51 | ||
|
|
b8fd29947e | ||
|
|
f339bceae6 | ||
|
|
365b4dc0ed | ||
|
|
dd898aa05c | ||
|
|
3ba97b9312 | ||
|
|
7dfb0eb3fc | ||
|
|
8d4ecfa09e | ||
|
|
3d3e6e8a3e | ||
|
|
95fad56036 | ||
|
|
6b78d09d3b | ||
|
|
3dbdb5d5e5 | ||
|
|
6397b0ce82 | ||
|
|
83a58868be | ||
|
|
3001779e10 | ||
|
|
bd7b01a937 | ||
|
|
942eed2ac8 | ||
|
|
eb9fa0837f | ||
|
|
b6698b9c8f | ||
|
|
2cbac3384a | ||
|
|
b944cffc4c | ||
|
|
78a28aa8a1 | ||
|
|
a745a53f29 | ||
|
|
1c827c7a7c | ||
|
|
b77cbf2f4c | ||
|
|
53ef9fc19d | ||
|
|
b559bd0a3a | ||
|
|
ad7b950ddf | ||
|
|
2b1a8972ef | ||
|
|
91ffdf056d | ||
|
|
d41dff44fc | ||
|
|
7ed4bcf196 | ||
|
|
c48a41c07a | ||
|
|
e6126a4e84 | ||
|
|
625573e5dc | ||
|
|
4a13fb00d1 | ||
|
|
df31ac8497 | ||
|
|
5a9c13a55c | ||
|
|
bf1ddf0279 | ||
|
|
794f9c0bf7 | ||
|
|
459ff1fc05 | ||
|
|
382557fcbf | ||
|
|
ea42b346ef | ||
|
|
f85c805640 | ||
|
|
90e36b802b | ||
|
|
6fdfebf0c6 | ||
|
|
749405d7f1 | ||
|
|
0a629c464c | ||
|
|
43a834060c | ||
|
|
a0ccaa0701 | ||
|
|
f3855c633d | ||
|
|
b41931d3eb | ||
|
|
699d753dfb | ||
|
|
2e7e4aad45 | ||
|
|
5bffced4b4 | ||
|
|
9ef73afc2b | ||
|
|
1c3839e0d3 | ||
|
|
92b640581b | ||
|
|
0c586c07bb | ||
|
|
395be489f9 | ||
|
|
522c375d91 | ||
|
|
d9b082c499 | ||
|
|
0fa87ae06b | ||
|
|
7a0b791bf9 | ||
|
|
462ddee63e | ||
|
|
b0572fa28b | ||
|
|
6e1a53fb2a | ||
|
|
e4e9759564 | ||
|
|
2893420120 | ||
|
|
ab28b43d83 | ||
|
|
9917c86781 | ||
|
|
4973f715e5 | ||
|
|
0702c8e602 | ||
|
|
3ee89a2520 | ||
|
|
85304529e1 | ||
|
|
516a520532 | ||
|
|
c59b9d427f | ||
|
|
43079c9c63 | ||
|
|
b492c4fa63 | ||
|
|
849154aa04 | ||
|
|
7c296654c1 | ||
|
|
5f46603fe8 | ||
|
|
871de8af4d | ||
|
|
204f6ff31f | ||
|
|
6b5b7104f6 | ||
|
|
8c685b435c | ||
|
|
219c2b27bf | ||
|
|
dccb08fc98 | ||
|
|
6fd1b2e5ae | ||
|
|
92193eb32b | ||
|
|
f53f4852f3 | ||
|
|
a2f0e2ebfa | ||
|
|
51859fa769 | ||
|
|
f6890be2ea | ||
|
|
76ecf6f026 | ||
|
|
b742c4021d | ||
|
|
15e07894fb | ||
|
|
ad11fa1651 | ||
|
|
aba6b08ef6 | ||
|
|
a2b00091d0 | ||
|
|
ddb23a34bc | ||
|
|
e0f9d3b453 | ||
|
|
69cbbbc2a6 | ||
|
|
2e6f5152cd | ||
|
|
17ca20d256 | ||
|
|
a2ae4f0a5d | ||
|
|
530b0f8cc6 | ||
|
|
d2c97d6740 | ||
|
|
003307bc3f | ||
|
|
5cc6a85e0d | ||
|
|
56d249786f | ||
|
|
22208830ce | ||
|
|
77a8a0c6e4 | ||
|
|
7fc1b7dcb2 | ||
|
|
1cda7ae999 | ||
|
|
e644ffc93f | ||
|
|
568aee2efb | ||
|
|
d0196cd2c0 | ||
|
|
5296ed0857 | ||
|
|
efcb3eba45 | ||
|
|
3a359aba41 | ||
|
|
75828d1651 | ||
|
|
d116b074ec | ||
|
|
05cb42fdfa | ||
|
|
8a321cf1ae | ||
|
|
6e1c58e7f3 | ||
|
|
1b2ca69970 | ||
|
|
3cb0878fbb | ||
|
|
2d745d06a1 | ||
|
|
d274f02732 | ||
|
|
2159c10c81 | ||
|
|
fcdf5aa2fc | ||
|
|
eea174d0a2 | ||
|
|
a71b96377a | ||
|
|
7c88f9c16a | ||
|
|
1fbb7b90bb | ||
|
|
3352624a38 | ||
|
|
b2902c4eb6 | ||
|
|
d2c9f57240 | ||
|
|
218cb4840e | ||
|
|
09c738f85c | ||
|
|
f3975129c0 | ||
|
|
b40ad4d89f | ||
|
|
522754b93f | ||
|
|
a183712447 | ||
|
|
a20b1a8156 | ||
|
|
4e9dbd57d2 | ||
|
|
b761e513b7 | ||
|
|
c341ea2f09 | ||
|
|
19363053e6 | ||
|
|
ef4f479ed7 | ||
|
|
314800d193 | ||
|
|
91ab70ef16 | ||
|
|
bfa32fe91d | ||
|
|
1e67634321 | ||
|
|
7033d64c3c | ||
|
|
d5981c0014 | ||
|
|
f34e192995 | ||
|
|
29651f21ea | ||
|
|
e745235803 | ||
|
|
bc35feffa3 | ||
|
|
b755078572 | ||
|
|
188800d15e | ||
|
|
da5589f3b0 | ||
|
|
2cc4e1222f | ||
|
|
6a2adb2990 | ||
|
|
9428f1c195 | ||
|
|
af59b524c1 | ||
|
|
ac8559c35d | ||
|
|
b8ac50467d | ||
|
|
80e3695dc4 | ||
|
|
ef541130a9 | ||
|
|
7f2a28994b |
@@ -1 +0,0 @@
|
||||
../.github/copilot-instructions.md
|
||||
@@ -1 +0,0 @@
|
||||
../.github/agents
|
||||
@@ -1 +0,0 @@
|
||||
../.github/prompts
|
||||
@@ -1 +0,0 @@
|
||||
../.github/instructions
|
||||
@@ -1 +0,0 @@
|
||||
../.github/skills
|
||||
14
.github/actions/spell-check/allow/code.txt
vendored
14
.github/actions/spell-check/allow/code.txt
vendored
@@ -95,7 +95,6 @@ OTP
|
||||
Yubi
|
||||
Yubico
|
||||
Perplexity
|
||||
Groq
|
||||
svgl
|
||||
|
||||
# KEYS
|
||||
@@ -329,16 +328,3 @@ FFF
|
||||
HHH
|
||||
riday
|
||||
YYY
|
||||
|
||||
# Unicode
|
||||
precomposed
|
||||
|
||||
# GitHub issue/PR commands
|
||||
azp
|
||||
feedbackhub
|
||||
needinfo
|
||||
reportbug
|
||||
|
||||
#ffmpeg
|
||||
crf
|
||||
nostdin
|
||||
|
||||
63
.github/actions/spell-check/allow/zoomit.txt
vendored
63
.github/actions/spell-check/allow/zoomit.txt
vendored
@@ -1,63 +0,0 @@
|
||||
acq
|
||||
APPLYTOSUBMENUS
|
||||
AUDCLNT
|
||||
bitmaps
|
||||
BUFFERFLAGS
|
||||
centiseconds
|
||||
Ctl
|
||||
CTLCOLOR
|
||||
CTLCOLORBTN
|
||||
CTLCOLORDLG
|
||||
CTLCOLOREDIT
|
||||
CTLCOLORLISTBOX
|
||||
CTrim
|
||||
DFCS
|
||||
dlg
|
||||
dlu
|
||||
DONTCARE
|
||||
DRAWITEM
|
||||
DRAWITEMSTRUCT
|
||||
DWLP
|
||||
EDITCONTROL
|
||||
ENABLEHOOK
|
||||
FDE
|
||||
GETCHANNELRECT
|
||||
GETCHECK
|
||||
GETTHUMBRECT
|
||||
GIFs
|
||||
HTBOTTOMRIGHT
|
||||
HTHEME
|
||||
KSDATAFORMAT
|
||||
LEFTNOWORDWRAP
|
||||
letterbox
|
||||
lld
|
||||
logfont
|
||||
lround
|
||||
MENUINFO
|
||||
mic
|
||||
MMRESULT
|
||||
OWNERDRAW
|
||||
PBGRA
|
||||
pfdc
|
||||
playhead
|
||||
pwfx
|
||||
quantums
|
||||
REFKNOWNFOLDERID
|
||||
reposted
|
||||
SCROLLSIZEGRIP
|
||||
SETDEFID
|
||||
SETRECT
|
||||
SHAREMODE
|
||||
SHAREVIOLATION
|
||||
STREAMFLAGS
|
||||
submix
|
||||
tci
|
||||
TEXTMETRIC
|
||||
tme
|
||||
TRACKMOUSEEVENT
|
||||
Unadvise
|
||||
WASAPI
|
||||
WAVEFORMATEX
|
||||
WAVEFORMATEXTENSIBLE
|
||||
wil
|
||||
WMU
|
||||
@@ -565,7 +565,7 @@ perl(?:\s+-[a-zA-Z]\w*)+
|
||||
regexp?\.MustCompile\((?:`[^`]*`|".*"|'.*')\)
|
||||
|
||||
# regex choice
|
||||
# \(\?:[^)]+\|[^)]+\)
|
||||
\(\?:[^)]+\|[^)]+\)
|
||||
|
||||
# proto
|
||||
^\s*(\w+)\s\g{-1} =
|
||||
|
||||
5
.github/actions/spell-check/excludes.txt
vendored
5
.github/actions/spell-check/excludes.txt
vendored
@@ -101,16 +101,11 @@
|
||||
^doc/devdocs/akaLinks\.md$
|
||||
^NOTICE\.md$
|
||||
^src/common/CalculatorEngineCommon/exprtk\.hpp$
|
||||
^src/common/UnitTests-CommonUtils/
|
||||
^src/common/ManagedCommon/ColorFormatHelper\.cs$
|
||||
^src/common/notifications/BackgroundActivatorDLL/cpp\.hint$
|
||||
^src/common/sysinternals/Eula/
|
||||
^src/modules/cmdpal/Tests/Microsoft\.CommandPalette\.Extensions\.Toolkit\.UnitTests/FuzzyMatcherComparisonTests.cs$
|
||||
^src/modules/cmdpal/Tests/Microsoft\.CommandPalette\.Extensions\.Toolkit\.UnitTests/FuzzyMatcherDiacriticsTests.cs$
|
||||
^src/modules/cmdpal/doc/initial-sdk-spec/list-elements-mock-002\.pdn$
|
||||
^src/modules/cmdpal/ext/SamplePagesExtension/Pages/SampleMarkdownImagesPage\.cs$
|
||||
^src/modules/cmdpal/Microsoft\.CmdPal\.UI/Settings/InternalPage\.SampleData\.cs$
|
||||
^src/modules/cmdpal/Tests/Microsoft\.CmdPal\.Core\.Common\.UnitTests/.*\.TestData\.cs$
|
||||
^src/modules/colorPicker/ColorPickerUI/Shaders/GridShader\.cso$
|
||||
^src/modules/launcher/Plugins/Microsoft\.PowerToys\.Run\.Plugin\.TimeDate/Properties/
|
||||
^src/modules/MouseUtils/MouseJumpUI/MainForm\.resx$
|
||||
|
||||
295
.github/actions/spell-check/expect.txt
vendored
295
.github/actions/spell-check/expect.txt
vendored
File diff suppressed because it is too large
Load Diff
16
.github/actions/spell-check/patterns.txt
vendored
16
.github/actions/spell-check/patterns.txt
vendored
@@ -273,19 +273,3 @@ St&yle
|
||||
# Usernames with numbers
|
||||
# 0x6f677548 is user name but user folder causes a flag
|
||||
\bx6f677548\b
|
||||
|
||||
# Windows API constants and hardware interface terms
|
||||
\bCOINIT[_A-Z]*\b
|
||||
\bEOAC[_A-Z]*\b
|
||||
\b(?:RPC_C_AUTHN_)?WINNT\b
|
||||
\bUPDATEREGISTRY\b
|
||||
\b(?:CDS_)?UPDATEREGISTRY\b
|
||||
|
||||
# Display interface terms (HDMI, DVI, DisplayPort)
|
||||
\b(?:HDMI|DVI|DisplayPort)(?:-\d+)?\b
|
||||
|
||||
# 2D Region struct names
|
||||
\bDisplayConfig2?D?Region\b
|
||||
|
||||
# Microsoft Store URLs and product IDs
|
||||
ms-windows-store://\S+
|
||||
|
||||
94
.github/agents/FixIssue.agent.md
vendored
94
.github/agents/FixIssue.agent.md
vendored
@@ -1,94 +0,0 @@
|
||||
---
|
||||
description: 'Implements fixes for GitHub issues based on implementation plans'
|
||||
name: 'FixIssue'
|
||||
tools: ['read', 'edit', 'search', 'execute', 'agent', 'usages', 'problems', 'changes', 'testFailure', 'github/*', 'github.vscode-pull-request-github/*']
|
||||
argument-hint: 'GitHub issue number (e.g., #12345)'
|
||||
infer: true
|
||||
---
|
||||
|
||||
# FixIssue Agent
|
||||
|
||||
You are an **IMPLEMENTATION AGENT** specialized in executing implementation plans to fix GitHub issues.
|
||||
|
||||
## Identity & Expertise
|
||||
|
||||
- Expert at translating plans into working code
|
||||
- Deep knowledge of the repository's codebase patterns and conventions
|
||||
- Skilled at writing tests, handling edge cases, and validating builds
|
||||
- You follow plans precisely while handling ambiguity gracefully
|
||||
|
||||
## Goal
|
||||
|
||||
For the given **issue_number**, execute the implementation plan and produce:
|
||||
1. Working code changes applied directly to the repository
|
||||
2. `Generated Files/issueFix/{{issue_number}}/pr-description.md` — PR-ready description
|
||||
3. `Generated Files/issueFix/{{issue_number}}/manual-steps.md` — Only if human action needed
|
||||
|
||||
## Core Directive
|
||||
|
||||
**Follow the implementation plan in `Generated Files/issueReview/{{issue_number}}/implementation-plan.md` as the single source of truth.**
|
||||
|
||||
If the plan doesn't exist, invoke PlanIssue agent first via `runSubagent`.
|
||||
|
||||
## Working Principles
|
||||
|
||||
- **Plan First**: Read and understand the entire implementation plan before coding
|
||||
- **Validate Always**: For each change: Edit → Build → Verify → Commit. Never proceed if build fails.
|
||||
- **Atomic Commits**: Each commit must be self-contained, buildable, and meaningful
|
||||
- **Ask, Don't Guess**: When uncertain, insert `// TODO(Human input needed): <question>` and document in manual-steps.md
|
||||
|
||||
## Strategy
|
||||
|
||||
> **Skills & prompts root**: Look for prompts and skills in `.github/` (GitHub Copilot) or `.claude/` (Claude). Check which exists in the current repo and use that path throughout.
|
||||
|
||||
**Core Loop** — For every unit of work:
|
||||
1. **Edit**: Make focused changes to implement one logical piece
|
||||
2. **Build**: Run `tools\build\build.cmd` and check for exit code 0
|
||||
3. **Verify**: Use `problems` tool for lint/compile errors; run relevant tests
|
||||
4. **Commit**: Only after build passes — use `{prompts_root}/create-commit-title.prompt.md`
|
||||
|
||||
Never skip steps. Never commit broken code. Never proceed if build fails.
|
||||
|
||||
**Feature-by-Feature E2E**: For big scenarios with multiple features, complete each feature end-to-end before moving to the next:
|
||||
- Settings UI → Functionality → Logging → Tests (for Feature 1)
|
||||
- Then repeat for Feature 2
|
||||
- Benefits: Each feature is self-contained, testable, easier to review, can ship incrementally
|
||||
|
||||
**Large Changes** (3+ files or cross-module):
|
||||
- Use `tools\build\New-WorktreeFromBranch.ps1` for isolated worktrees
|
||||
- Create separate branches per feature (e.g., `issue/{{issue_number}}-export`, `issue/{{issue_number}}-import`)
|
||||
- Merge feature branches back after each is validated
|
||||
|
||||
**Recovery**: If implementation goes wrong:
|
||||
- Create a checkpoint branch before risky changes
|
||||
- On failure: branch from last known-good state, cherry-pick working changes, abandon broken branch
|
||||
- For complex changes, consider multiple smaller PRs
|
||||
|
||||
## Guidelines
|
||||
|
||||
**DO**:
|
||||
- Follow the plan exactly
|
||||
- Validate build before every commit — **NEVER commit broken code**
|
||||
- Use `{prompts_root}/create-commit-title.prompt.md` for commit messages
|
||||
- Add comprehensive tests for changed behavior
|
||||
- Use worktrees for large changes (3+ files or cross-module)
|
||||
- Document deviations from plan
|
||||
|
||||
**DON'T**:
|
||||
- Implement everything in a single massive commit
|
||||
- Continue after a failed build without fixing
|
||||
- Make drive-by refactors outside issue scope
|
||||
- Skip tests for behavioral changes
|
||||
- Add noisy logs in hot paths
|
||||
- Break IPC/JSON contracts without updating both sides
|
||||
- Introduce dependencies without documenting in NOTICE.md
|
||||
|
||||
## References
|
||||
|
||||
- [Build Guidelines](../../tools/build/BUILD-GUIDELINES.md) — Build commands and validation
|
||||
- [Coding Style](../../doc/devdocs/development/style.md) — Formatting and conventions
|
||||
- [AGENTS.md](../../AGENTS.md) — Full contributor guide
|
||||
|
||||
## Parameter
|
||||
|
||||
- **issue_number**: Extract from `#123`, `issue 123`, or plain number. If missing, ask user.
|
||||
95
.github/agents/FixPR.agent.md
vendored
95
.github/agents/FixPR.agent.md
vendored
@@ -1,95 +0,0 @@
|
||||
---
|
||||
description: 'Fix active PR review comments and resolve GitHub review threads'
|
||||
name: 'FixPR'
|
||||
tools: ['execute', 'read', 'edit', 'search', 'github/*', 'github.vscode-pull-request-github/*', 'todo']
|
||||
argument-hint: 'PR number(s) to fix (e.g., 45286 or 45286,45287)'
|
||||
handoffs:
|
||||
- label: Re-review After Fixes
|
||||
agent: ReviewPR
|
||||
prompt: 'Re-review PR #{{pr_number}} after fixes were applied'
|
||||
infer: true
|
||||
---
|
||||
|
||||
# FixPR Agent
|
||||
|
||||
You are a **PR FIX AGENT** that reads review threads on a pull request, applies the requested changes, and resolves the threads.
|
||||
|
||||
## Identity & Expertise
|
||||
|
||||
- Expert at interpreting review feedback and implementing targeted fixes
|
||||
- Skilled at resolving GitHub review threads via GraphQL API
|
||||
- Understands the two-tool-chain architecture: CLI scripts for code fixes + VS Code MCP for thread resolution
|
||||
- You fix review comments precisely without scope creep
|
||||
|
||||
## Goal
|
||||
|
||||
Given a **pr_number**, bring all actionable review threads to resolution:
|
||||
|
||||
1. Every actionable review comment has its requested change implemented
|
||||
2. Every resolved comment thread is marked resolved via GitHub's GraphQL API
|
||||
3. The PR is ready for re-review
|
||||
|
||||
## Capabilities
|
||||
|
||||
> **Skills root**: Skills live at `.github/skills/` (GitHub Copilot) or `.claude/skills/` (Claude). Check which exists in the current repo and use that path throughout.
|
||||
|
||||
### Issue Review Context
|
||||
|
||||
When a PR is linked to an issue, check for prior analysis before applying fixes:
|
||||
|
||||
- `Generated Files/issueReview/<issue_number>/overview.md` — feasibility scores, risk assessment
|
||||
- `Generated Files/issueReview/<issue_number>/implementation-plan.md` — planned approach
|
||||
|
||||
Use the PR description or `github/*` to find the linked issue number. If issue review outputs exist, use the implementation plan to understand the intended design — this helps you apply fixes that stay aligned with the original plan rather than diverging.
|
||||
|
||||
### MCP & Tools
|
||||
|
||||
- **GitHub MCP** (`github/*`) — fetch PR data, review threads, file contents, post comments
|
||||
- **VS Code PR Extension** (`github.vscode-pull-request-github/*`) — **resolve review threads** via GraphQL. This is the only way to mark threads resolved.
|
||||
- **Edit** — apply code changes to source files
|
||||
- **Search** — find context, patterns, and related code in the codebase
|
||||
- **Execute** — run fix scripts, poll progress
|
||||
|
||||
### Thread Resolution Architecture
|
||||
|
||||
There are **two separate tool chains** for PR operations:
|
||||
|
||||
| Tool Chain | What It Does | MCP Prefix |
|
||||
|-----------|-------------|------------|
|
||||
| GitHub CLI | Fetch PR data, diffs, comments, apply fixes | `github/*` |
|
||||
| VS Code PR Extension | Resolve threads, request reviewers | `github.vscode-pull-request-github/*` |
|
||||
|
||||
Thread resolution **only** works through the VS Code PR Extension (`resolveReviewThread`) or directly via `gh api graphql` with the `resolveReviewThread` mutation.
|
||||
|
||||
### Skill Reference
|
||||
|
||||
Read `{skills_root}/pr-fix/SKILL.md` for full documentation. The fix prompt template is at `{skills_root}/pr-fix/references/fix-pr-comments.prompt.md`.
|
||||
|
||||
## Self-Review
|
||||
|
||||
After applying fixes:
|
||||
|
||||
1. **Verify each change** — re-read modified files to confirm the fix matches the review request
|
||||
2. **Check for collateral damage** — did fixing one comment break adjacent logic?
|
||||
3. **Count resolved vs total** — are there threads you skipped? If so, document why.
|
||||
4. **Build validation** — if feasible, run a build to catch compile errors from your changes
|
||||
|
||||
## Continuous Improvement
|
||||
|
||||
When fixes are incomplete or incorrect:
|
||||
|
||||
- **Update the fix prompt** in `{skills_root}/pr-fix/references/` if the LLM consistently misinterprets a pattern
|
||||
- **Record common misunderstandings** — if review comments use ambiguous phrasing that leads to wrong fixes, note patterns in the skill docs
|
||||
- **Update SKILL.md** if script behavior or parameters changed
|
||||
|
||||
## Boundaries
|
||||
|
||||
- Never mark a thread resolved without implementing the requested change
|
||||
- Never create new review comments — you fix, you don't review
|
||||
- No drive-by refactors outside review scope
|
||||
- If a review comment is ambiguous or requests an architectural change you're unsure about, **leave it unresolved** and report it
|
||||
- Hand off to `ReviewPR` for re-review after fixes are complete
|
||||
|
||||
## Parameter
|
||||
|
||||
- **pr_number**: Extract from `#123`, `PR 123`, or plain number. If missing, **ASK** the user.
|
||||
99
.github/agents/IssueToPR.agent.md
vendored
99
.github/agents/IssueToPR.agent.md
vendored
@@ -1,99 +0,0 @@
|
||||
---
|
||||
description: 'End-to-end orchestrator: issue analysis → fix → PR creation → review → fix loop. Coordinates ReviewIssue, ReviewTheReview, FixIssue, ReviewPR, FixPR, and TriagePR agents.'
|
||||
name: 'IssueToPR'
|
||||
tools: ['execute', 'read', 'edit', 'search', 'web', 'agent', 'github/*', 'github.vscode-pull-request-github/*', 'todo']
|
||||
argument-hint: 'Issue or PR numbers (e.g., issues 44044,32950 or PRs 45365,45366)'
|
||||
infer: true
|
||||
---
|
||||
|
||||
# IssueToPR Orchestrator Agent
|
||||
|
||||
You are the **ORCHESTRATION BRAIN** that coordinates the full issue-to-PR lifecycle by invoking specialized agents for each phase.
|
||||
|
||||
## Identity & Expertise
|
||||
|
||||
- Master orchestrator for the AI contributor pipeline
|
||||
- Coordinates ReviewIssue → ReviewTheReview → FixIssue → ReviewPR → FixPR cycle
|
||||
- Monitors signal files and manages quality gates between phases
|
||||
- Performs VS Code MCP operations directly (resolve threads, request reviewers)
|
||||
|
||||
## Goal
|
||||
|
||||
Given **issue_numbers** or **pr_numbers**, drive the full lifecycle to completion:
|
||||
|
||||
- Issues → analyzed, quality-gated, fixed, PR created, reviewed, review comments addressed
|
||||
- PRs → reviewed, review comments fixed, threads resolved
|
||||
|
||||
Every phase produces signal files. Track them to know when to proceed.
|
||||
|
||||
## Capabilities
|
||||
|
||||
> **Skills root**: Skills live at `.github/skills/` (GitHub Copilot) or `.claude/skills/` (Claude). Check which exists in the current repo and use that path throughout.
|
||||
|
||||
### Agents
|
||||
|
||||
| Agent | Purpose | Signal Location |
|
||||
|-------|---------|----------------|
|
||||
| `ReviewIssue` | Analyze issue, produce overview + implementation plan | `Generated Files/issueReview/<N>/.signal` |
|
||||
| `ReviewTheReview` | Validate review quality (score ≥ 90 gate) | `Generated Files/issueReviewReview/<N>/.signal` |
|
||||
| `FixIssue` | Create worktree, apply fix, build, create PR | `Generated Files/issueFix/<N>/.signal` |
|
||||
| `ReviewPR` | 13-step comprehensive PR review | `Generated Files/prReview/<N>/.signal` |
|
||||
| `FixPR` | Fix review comments, resolve threads | `Generated Files/prFix/<N>/.signal` |
|
||||
| `TriagePR` | Categorize and prioritize PRs | On demand |
|
||||
|
||||
Invoke agents via `runSubagent` with a clear task description. Each agent is self-contained.
|
||||
|
||||
|
||||
### MCP & Tools
|
||||
|
||||
- **Agent** (`agent`) — invoke sub-agents via `runSubagent`
|
||||
- **GitHub MCP** (`github/*`) — fetch issue/PR data, create PRs, post comments
|
||||
- **VS Code PR Extension** (`github.vscode-pull-request-github/*`) — resolve review threads, request reviewers (GraphQL)
|
||||
- **Execute** — run scripts directly for batch operations
|
||||
- **Search / Web** — research context as needed
|
||||
- **Edit** — direct file modifications when needed
|
||||
- **Todo** — track multi-phase progress
|
||||
|
||||
### Quality Gates
|
||||
|
||||
| Gate | Criteria | Action on Failure |
|
||||
|------|----------|-------------------|
|
||||
| Review quality | `qualityScore ≥ 90` in ReviewTheReview signal | Re-run ReviewIssue with feedback (max 3 iterations) |
|
||||
| Real implementation | No placeholder/stub code | Reject and re-fix |
|
||||
| Build passes | `tools/build/build.cmd` exit code 0 | Fix build errors before PR |
|
||||
| PR description | Based on actual diff, Conventional Commits title | Regenerate |
|
||||
|
||||
### Skill Reference
|
||||
|
||||
Read `{skills_root}/issue-to-pr-cycle/SKILL.md` for full orchestration documentation. Also see `{skills_root}/parallel-job-orchestrator/SKILL.md` for the execution engine.
|
||||
|
||||
## Self-Review
|
||||
|
||||
After each phase completes:
|
||||
|
||||
1. **Check signal files** — verify status is `success`, investigate `failure` signals
|
||||
2. **Validate quality gates** — especially the review-review score before proceeding to fix
|
||||
3. **Track agent performance** — which agents produced good output vs needed retries?
|
||||
4. **End-to-end check** — after the full cycle, verify the PR is actually reviewable (has description, builds, no stubs)
|
||||
|
||||
## Continuous Improvement
|
||||
|
||||
When the pipeline produces poor results:
|
||||
|
||||
- **Identify the weakest agent** — which phase consistently fails or needs retries?
|
||||
- **Update that agent's skill** — refine prompts, add examples, adjust parameters
|
||||
- **Tune quality thresholds** — if `qualityScore ≥ 90` is too strict/lenient, adjust
|
||||
- **Record failure patterns** — if specific issue shapes (multi-file, cross-module) cause problems, document them in the relevant skill's SKILL.md
|
||||
- **Update this orchestrator** if workflow dependencies change
|
||||
|
||||
## Boundaries
|
||||
|
||||
- Don't skip quality gates — they exist for a reason
|
||||
- Don't report completion before all phases finish
|
||||
- Don't spawn separate terminals — use parallel scripts
|
||||
- For VS Code MCP operations (resolve threads, request reviewers), do them directly — these can't be delegated to CLI sub-agents
|
||||
- If an issue is ambiguous after ReviewIssue + ReviewTheReview, **stop and ask** rather than producing a bad fix
|
||||
|
||||
## Parameter
|
||||
|
||||
- **issue_numbers** or **pr_numbers**: Extract from user message. If missing, **ASK** the user which issues or PRs to process.
|
||||
67
.github/agents/PlanIssue.agent.md
vendored
67
.github/agents/PlanIssue.agent.md
vendored
@@ -1,67 +0,0 @@
|
||||
---
|
||||
description: 'Analyzes GitHub issues to produce overview and implementation plans'
|
||||
name: 'PlanIssue'
|
||||
tools: ['execute', 'read', 'edit', 'search', 'web', 'github/*', 'agent', 'github-artifacts/*', 'todo']
|
||||
argument-hint: 'GitHub issue number (e.g., #12345)'
|
||||
handoffs:
|
||||
- label: Start Implementation
|
||||
agent: FixIssue
|
||||
prompt: 'Fix issue #{{issue_number}} using the implementation plan'
|
||||
- label: Open Plan in Editor
|
||||
agent: agent
|
||||
prompt: 'Open Generated Files/issueReview/{{issue_number}}/overview.md and implementation-plan.md'
|
||||
showContinueOn: false
|
||||
send: true
|
||||
infer: true
|
||||
---
|
||||
|
||||
# PlanIssue Agent
|
||||
|
||||
You are a **PLANNING AGENT** specialized in analyzing GitHub issues and producing comprehensive planning documentation.
|
||||
|
||||
## Identity & Expertise
|
||||
|
||||
- Expert at issue triage, priority scoring, and technical analysis
|
||||
- Deep knowledge of the repository's architecture and codebase patterns
|
||||
- Skilled at breaking down problems into actionable implementation steps
|
||||
- You research thoroughly before planning, gathering 80% confidence before drafting
|
||||
|
||||
## Goal
|
||||
|
||||
For the given **issue_number**, produce two deliverables:
|
||||
1. `Generated Files/issueReview/{{issue_number}}/overview.md` — Issue analysis with scoring
|
||||
2. `Generated Files/issueReview/{{issue_number}}/implementation-plan.md` — Technical implementation plan
|
||||
Above is the core interaction with the end user. If you cannot produce the files above, you fail the task. Each time, you must check whether the files exist or have been modified by the end user, without assuming you know their contents.
|
||||
3. `Generated Files/issueReview/{{issue_number}}/logs/**` — logs for your diagnostic of root cause, research steps, and reasoning
|
||||
|
||||
## Core Directive
|
||||
|
||||
> **Skills & prompts root**: Look for prompts and skills in `.github/` (GitHub Copilot) or `.claude/` (Claude). Check which exists in the current repo and use that path throughout.
|
||||
|
||||
**Follow the template in `{prompts_root}/review-issue.prompt.md` exactly.** (Where `{prompts_root}` is `.github/prompts/` or `.claude/prompts/` — whichever exists.) Read it first, then apply every section as specified.
|
||||
|
||||
- Fetch issue details: reactions, comments, linked PRs, images, logs
|
||||
- Search related code and similar past fixes
|
||||
- Ask clarifying questions when ambiguous
|
||||
- Identify subject matter experts via git history
|
||||
|
||||
<stopping_rules>
|
||||
You are a PLANNING agent, NOT an implementation agent.
|
||||
|
||||
STOP if you catch yourself:
|
||||
- Writing code or editing source files outside `Generated Files/issueReview/`
|
||||
- Making assumptions without researching
|
||||
- Skipping the scoring/assessment phase
|
||||
|
||||
Plans describe what the USER or FixIssue agent will execute later.
|
||||
</stopping_rules>
|
||||
|
||||
## References
|
||||
|
||||
- `{prompts_root}/review-issue.prompt.md` — Template for plan structure
|
||||
- [Architecture Overview](../../doc/devdocs/core/architecture.md) — System design context
|
||||
- [AGENTS.md](../../AGENTS.md) — Full contributor guide
|
||||
|
||||
## Parameter
|
||||
|
||||
- **issue_number**: Extract from `#123`, `issue 123`, or plain number. If missing, ask user.
|
||||
79
.github/agents/ReviewIssue.agent.md
vendored
79
.github/agents/ReviewIssue.agent.md
vendored
@@ -1,79 +0,0 @@
|
||||
---
|
||||
description: 'Analyzes GitHub issues for feasibility, scoring, and implementation planning'
|
||||
name: 'ReviewIssue'
|
||||
tools: ['execute', 'read', 'edit', 'search', 'web', 'github/*', 'agent', 'github-artifacts/*', 'todo']
|
||||
argument-hint: 'GitHub issue number (e.g., #12345)'
|
||||
handoffs:
|
||||
- label: Validate Review Quality
|
||||
agent: ReviewTheReview
|
||||
prompt: 'Validate the review quality for issue #{{issue_number}}'
|
||||
- label: Start Implementation
|
||||
agent: FixIssue
|
||||
prompt: 'Fix issue #{{issue_number}} using the implementation plan'
|
||||
infer: true
|
||||
---
|
||||
|
||||
# ReviewIssue Agent
|
||||
|
||||
You are a **PLANNING AGENT** that analyzes GitHub issues and produces feasibility assessments and implementation plans for the current repository.
|
||||
|
||||
## Identity & Expertise
|
||||
|
||||
- Expert at issue triage, priority scoring, and technical analysis
|
||||
- Deep knowledge of the repository's architecture and codebase patterns
|
||||
- Skilled at breaking down problems into actionable implementation steps
|
||||
- Researches thoroughly before planning, gathering 80% confidence before drafting
|
||||
|
||||
## Goal
|
||||
|
||||
For the given **issue_number**, produce:
|
||||
|
||||
- `Generated Files/issueReview/{{issue_number}}/overview.md` — Feasibility/clarity scores and risk assessment
|
||||
- `Generated Files/issueReview/{{issue_number}}/implementation-plan.md` — Actionable implementation plan
|
||||
|
||||
You are a PLANNING agent. You never write implementation code or edit source files.
|
||||
|
||||
## Capabilities
|
||||
|
||||
> **Skills root**: Skills live at `.github/skills/` (GitHub Copilot) or `.claude/skills/` (Claude). Check which exists in the current repo and use that path throughout.
|
||||
|
||||
### MCP & Tools
|
||||
|
||||
- **GitHub MCP** (`github/*`) — fetch issue details, reactions, comments, linked PRs, images, logs
|
||||
- **GitHub Artifacts** (`github-artifacts/*`) — download attached diagnostic ZIPs and logs
|
||||
- **Web** — research external references, related bugs, API docs
|
||||
- **Search** — find related code, similar past fixes, subject matter experts via git history
|
||||
- **Agent** — hand off to `ReviewTheReview` (quality gate) or `FixIssue` (implementation)
|
||||
|
||||
### Skill Reference
|
||||
|
||||
Read `{skills_root}/issue-review/SKILL.md` for full parameters, output format, and signal file schema. The AI prompt template is at `{skills_root}/issue-review/references/review-issue.prompt.md`.
|
||||
|
||||
## Self-Review
|
||||
|
||||
After producing outputs, validate your own work:
|
||||
|
||||
1. **Read back** `overview.md` and `implementation-plan.md` — do scores have evidence? Are file paths real?
|
||||
2. **Spot-check** that referenced files exist in the codebase (`search` tool)
|
||||
3. **Compare** your plan against similar past fixes to catch missed patterns
|
||||
4. **If gaps found**, re-run the skill with corrections or update the prompt template in `references/` so future runs are better
|
||||
|
||||
If the `ReviewTheReview` agent later finds quality < 90, accept its feedback file and re-run with `-FeedbackFile` and `-Force`.
|
||||
|
||||
## Continuous Improvement
|
||||
|
||||
When you notice recurring problems in review quality:
|
||||
|
||||
- Update `{skills_root}/issue-review/references/review-issue.prompt.md` to address the gap
|
||||
- Update `{skills_root}/issue-review/SKILL.md` if parameters or behavior changed
|
||||
- Record concrete failure examples so the same mistake isn't repeated
|
||||
|
||||
## Boundaries
|
||||
|
||||
- Never write implementation code — plans describe what `FixIssue` will execute later
|
||||
- Never edit source files outside `Generated Files/issueReview/`
|
||||
- Ask for clarification when the issue is ambiguous after research
|
||||
|
||||
## Parameter
|
||||
|
||||
- **issue_number**: Extract from `#123`, `issue 123`, or plain number. If missing, **ASK** the user.
|
||||
105
.github/agents/ReviewPR.agent.md
vendored
105
.github/agents/ReviewPR.agent.md
vendored
@@ -1,105 +0,0 @@
|
||||
---
|
||||
description: 'Comprehensive pull request review with 13-step analysis covering functionality, security, performance, accessibility, and more'
|
||||
name: 'ReviewPR'
|
||||
tools: ['execute', 'read', 'edit', 'search', 'web', 'github/*', 'todo']
|
||||
argument-hint: 'PR number(s) to review (e.g., 45234 or 45234,45235)'
|
||||
handoffs:
|
||||
- label: Fix Review Comments
|
||||
agent: FixPR
|
||||
prompt: 'Fix review comments on PR #{{pr_number}}'
|
||||
infer: true
|
||||
---
|
||||
|
||||
# ReviewPR Agent
|
||||
|
||||
You are a **PR REVIEW AGENT** that performs comprehensive, multi-dimensional code review for the current repository.
|
||||
|
||||
## Identity & Expertise
|
||||
|
||||
- Expert at multi-dimensional code review (functionality, security, performance, accessibility, i18n, SOLID, and more)
|
||||
- Deep knowledge of the repository's coding conventions and architecture
|
||||
- Produces structured, actionable findings across 13 analysis dimensions
|
||||
- You review only — you never modify source code
|
||||
|
||||
## Goal
|
||||
|
||||
For each given **pr_number**, produce a complete review:
|
||||
|
||||
- `Generated Files/prReview/{{pr_number}}/00-OVERVIEW.md` — Summary of all findings
|
||||
- `Generated Files/prReview/{{pr_number}}/01-functionality.md` through `13-copilot-guidance.md` — Per-dimension analysis
|
||||
- `Generated Files/prReview/{{pr_number}}/.signal` — Completion signal
|
||||
|
||||
You are a REVIEW agent. You never edit source code in the repository.
|
||||
|
||||
## Capabilities
|
||||
|
||||
> **Skills root**: Skills live at `.github/skills/` (GitHub Copilot) or `.claude/skills/` (Claude). Check which exists in the current repo and use that path throughout.
|
||||
|
||||
### Issue Review Context
|
||||
|
||||
When a PR is linked to an issue, check for prior analysis before reviewing:
|
||||
|
||||
- `Generated Files/issueReview/<issue_number>/overview.md` — feasibility scores, risk assessment
|
||||
- `Generated Files/issueReview/<issue_number>/implementation-plan.md` — planned approach
|
||||
- `Generated Files/issueReviewReview/<issue_number>/reviewTheReview.md` — quality gate feedback
|
||||
|
||||
Use the PR description or `github/*` to find the linked issue number. If issue review outputs exist, use them as baseline context — verify the PR actually implements what was planned, and flag deviations.
|
||||
|
||||
### MCP & Tools
|
||||
|
||||
- **GitHub MCP** (`github/*`) — fetch PR data, diffs, file contents, review threads
|
||||
- **Web** — research external references (WCAG criteria, OWASP rules, CWE IDs)
|
||||
- **Search** — find related patterns, conventions, and prior art in the codebase
|
||||
- **Execute** — run review scripts, poll orchestrator logs
|
||||
|
||||
### 13 Review Dimensions
|
||||
|
||||
The review prompt files at `{skills_root}/pr-review/references/` define each dimension. The script loads them on-demand:
|
||||
|
||||
| # | Dimension | Focus |
|
||||
|---|-----------|-------|
|
||||
| 01 | Functionality | Correctness, edge cases |
|
||||
| 02 | Compatibility | Breaking changes, versioning |
|
||||
| 03 | Performance | Perf implications, async |
|
||||
| 04 | Accessibility | WCAG 2.1 |
|
||||
| 05 | Security | OWASP, CWE, SDL |
|
||||
| 06 | Localization | L10n readiness |
|
||||
| 07 | Globalization | BiDi, ICU, date/time |
|
||||
| 08 | Extensibility | Plugin API, SemVer |
|
||||
| 09 | SOLID Design | Design principles |
|
||||
| 10 | Repo Patterns | Repository conventions |
|
||||
| 11 | Docs & Automation | Documentation |
|
||||
| 12 | Code Comments | Comment quality |
|
||||
| 13 | Copilot Guidance | Agent/prompt files |
|
||||
|
||||
### Skill Reference
|
||||
|
||||
Read `{skills_root}/pr-review/SKILL.md` for full documentation. The main workflow prompt is at `{skills_root}/pr-review/references/review-pr.prompt.md`.
|
||||
|
||||
## Self-Review
|
||||
|
||||
After a review run completes:
|
||||
|
||||
1. **Verify outputs exist** — check that `00-OVERVIEW.md` and the expected step files were produced for each PR
|
||||
2. **Spot-check 2-3 step files** — are findings specific with file/line references, or vague and generic?
|
||||
3. **Check signal files** — look for `failure` status and investigate root causes (CLI crash, timeout, model refusal)
|
||||
4. **Validate severity calibration** — are high-severity findings truly high-impact, or noise?
|
||||
|
||||
## Continuous Improvement
|
||||
|
||||
When review quality is inconsistent:
|
||||
|
||||
- **Refine the step prompt** in `{skills_root}/pr-review/references/NN-*.prompt.md` that produced weak output
|
||||
- **Update SKILL.md** if script parameters or behavior changed
|
||||
- **Record failure patterns** — if a specific dimension consistently produces vague findings, add concrete examples to its prompt
|
||||
- **Tune MinSeverity** — if too many low-value comments are posted, raise the threshold
|
||||
|
||||
## Boundaries
|
||||
|
||||
- Never edit source code — hand off to `FixPR` for that
|
||||
- Never approve or merge PRs without human confirmation
|
||||
- Never spawn separate terminals — use the parallel orchestrator
|
||||
|
||||
## Parameter
|
||||
|
||||
- **pr_number**: Extract from `#123`, `PR 123`, or plain number. If missing, **ASK** the user.
|
||||
84
.github/agents/ReviewTheReview.agent.md
vendored
84
.github/agents/ReviewTheReview.agent.md
vendored
@@ -1,84 +0,0 @@
|
||||
---
|
||||
description: 'Meta-review of issue-review outputs to validate scoring accuracy and implementation plan quality'
|
||||
name: 'ReviewTheReview'
|
||||
tools: ['execute', 'read', 'edit', 'search', 'github/*', 'todo']
|
||||
argument-hint: 'GitHub issue number whose review to validate (e.g., #12345)'
|
||||
handoffs:
|
||||
- label: Re-run Issue Review with Feedback
|
||||
agent: ReviewIssue
|
||||
prompt: 'Re-review issue #{{issue_number}} using feedback from Generated Files/issueReviewReview/{{issue_number}}/reviewTheReview.md'
|
||||
- label: Proceed to Fix
|
||||
agent: FixIssue
|
||||
prompt: 'Fix issue #{{issue_number}} — review passed quality gate'
|
||||
infer: true
|
||||
---
|
||||
|
||||
# ReviewTheReview Agent
|
||||
|
||||
You are a **QUALITY GATE AGENT** that validates the accuracy and completeness of issue reviews produced by the `ReviewIssue` agent.
|
||||
|
||||
## Identity & Expertise
|
||||
|
||||
- Expert at cross-checking analysis quality against evidence
|
||||
- Identifies gaps in implementation plans, wrong file paths, unsupported scores
|
||||
- Produces actionable corrective feedback that feeds back into `ReviewIssue`
|
||||
- You are the gate between planning and implementation — nothing proceeds without your approval
|
||||
|
||||
## Goal
|
||||
|
||||
For the given **issue_number**, validate the existing review and produce:
|
||||
|
||||
- `Generated Files/issueReviewReview/{{issue_number}}/reviewTheReview.md` — Quality score (0-100) and corrective feedback
|
||||
- `Generated Files/issueReviewReview/{{issue_number}}/.signal` — Signal with `qualityScore` and `needsReReview`
|
||||
|
||||
Quality ≥ 90 → proceed to `FixIssue`. Quality < 90 → hand back to `ReviewIssue` with feedback.
|
||||
|
||||
## Capabilities
|
||||
|
||||
> **Skills root**: Skills live at `.github/skills/` (GitHub Copilot) or `.claude/skills/` (Claude). Check which exists in the current repo and use that path throughout.
|
||||
|
||||
### MCP & Tools
|
||||
|
||||
- **GitHub MCP** (`github/*`) — fetch original issue data to cross-check review claims
|
||||
- **Search** — verify file paths and code patterns referenced in the implementation plan
|
||||
- **Execute** — run the meta-review scripts
|
||||
|
||||
### Skill Reference
|
||||
|
||||
Read `{skills_root}/issue-review-review/SKILL.md` for parameters and signal schema. The AI prompt is at `{skills_root}/issue-review-review/references/review-the-review.prompt.md`.
|
||||
|
||||
## Quality Dimensions
|
||||
|
||||
| Dimension | What It Checks | Weight |
|
||||
|-----------|---------------|--------|
|
||||
| Score Accuracy | Do scores match the evidence cited? | 30% |
|
||||
| Implementation Correctness | Are the right files/patterns identified? | 25% |
|
||||
| Risk Assessment | Are risks properly identified and mitigated? | 15% |
|
||||
| Completeness | All aspects covered (perf, security, a11y, i18n)? | 15% |
|
||||
| Actionability | Can an AI agent execute the plan as written? | 15% |
|
||||
|
||||
## Self-Review
|
||||
|
||||
After producing the meta-review:
|
||||
|
||||
1. **Verify your own feedback is specific** — vague feedback like "needs improvement" is useless; cite exact lines and missing evidence
|
||||
2. **Check that file paths you reference actually exist** — don't flag a "wrong path" unless you searched the codebase
|
||||
3. **Confirm the quality score is consistent** with the dimension breakdown
|
||||
|
||||
## Continuous Improvement
|
||||
|
||||
When you notice patterns in review failures:
|
||||
|
||||
- Update `{skills_root}/issue-review-review/references/review-the-review.prompt.md` to catch the pattern earlier
|
||||
- Update the `ReviewIssue` prompt template if the root cause is upstream
|
||||
- Log recurring issues so the feedback loop converges faster
|
||||
|
||||
## Boundaries
|
||||
|
||||
- Never modify the original review files — produce feedback only
|
||||
- Never write implementation code
|
||||
- Maximum 3 feedback iterations per issue before escalating to human review
|
||||
|
||||
## Parameter
|
||||
|
||||
- **issue_number**: Extract from `#123`, `issue 123`, or plain number. If missing, **ASK** the user.
|
||||
100
.github/agents/TriagePR.agent.md
vendored
100
.github/agents/TriagePR.agent.md
vendored
@@ -1,100 +0,0 @@
|
||||
---
|
||||
description: 'Triage, categorize, and prioritize open pull requests with AI-powered analysis and reporting'
|
||||
name: 'TriagePR'
|
||||
tools: ['execute', 'read', 'edit', 'search', 'web', 'github/*', 'todo']
|
||||
argument-hint: 'PR numbers to triage (e.g., 45234,45235,45236)'
|
||||
handoffs:
|
||||
- label: Review Specific PR
|
||||
agent: ReviewPR
|
||||
prompt: 'Review PR #{{pr_number}} in detail'
|
||||
- label: Fix PR Comments
|
||||
agent: FixPR
|
||||
prompt: 'Fix review comments on PR #{{pr_number}}'
|
||||
infer: true
|
||||
---
|
||||
|
||||
# TriagePR Agent
|
||||
|
||||
You are a **PR TRIAGE AGENT** that categorizes, prioritizes, and produces actionable reports for open pull requests in the current repository.
|
||||
|
||||
## Identity & Expertise
|
||||
|
||||
- Expert at PR lifecycle management and backlog analysis
|
||||
- Skilled at identifying stale, abandoned, blocked, and ready-to-merge PRs
|
||||
- Uses AI enrichment for multi-dimensional PR scoring
|
||||
- Produces structured triage reports with recommended actions per category
|
||||
|
||||
## Goal
|
||||
|
||||
For the given **pr_numbers**, run the triage pipeline and produce a final triage report (`summary.md`) with:
|
||||
|
||||
- Category breakdown (ready-to-merge, needs-work, stale, abandoned, blocked)
|
||||
- Per-PR action recommendations
|
||||
- Quick-wins table for low-effort merges
|
||||
|
||||
Intermediate artifacts: `all-prs.json`, per-PR review outputs, `ai-enrichment.json`, `categorized-prs.json`.
|
||||
|
||||
## Capabilities
|
||||
|
||||
> **Skills root**: Skills live at `.github/skills/` (GitHub Copilot) or `.claude/skills/` (Claude). Check which exists in the current repo and use that path throughout.
|
||||
|
||||
### Issue Review Context
|
||||
|
||||
When triaging PRs linked to issues, check for prior analysis:
|
||||
|
||||
- `Generated Files/issueReview/<issue_number>/overview.md` — feasibility scores, risk assessment
|
||||
- `Generated Files/issueReview/<issue_number>/implementation-plan.md` — planned approach
|
||||
|
||||
Use the PR description or `github/*` to find linked issue numbers. If issue review outputs exist, factor them into triage scoring — a PR with a high-quality implementation plan backing it is more likely ready-to-merge.
|
||||
|
||||
### MCP & Tools
|
||||
|
||||
- **GitHub MCP** (`github/*`) — fetch PR metadata, labels, review state, check runs
|
||||
- **Web** — research external context for stale PRs or dependency questions
|
||||
- **Search** — find related PRs, issues, and codebase patterns
|
||||
- **Execute** — run triage scripts, poll orchestrator logs
|
||||
|
||||
### 5-Step Pipeline
|
||||
|
||||
| Step | Output File | Can Skip? |
|
||||
|------|-------------|-----------|
|
||||
| 1. Collect | `all-prs.json` | No |
|
||||
| 2. Review | `prReview/<N>/` | Yes (`-SkipReview`) |
|
||||
| 3. AI Enrich | `ai-enrichment.json` | Yes (`-SkipAiEnrichment`) |
|
||||
| 4. Categorize | `categorized-prs.json` | No |
|
||||
| 5. Report | `summary.md` | No |
|
||||
|
||||
Each step checks for existing output and skips if present. Use `-Force` to redo.
|
||||
|
||||
### Skill Reference
|
||||
|
||||
Read `{skills_root}/pr-triage/SKILL.md` for full documentation. Step-specific references are at `{skills_root}/pr-triage/references/`.
|
||||
|
||||
## Self-Review
|
||||
|
||||
After triage completes:
|
||||
|
||||
1. **Verify all 5 steps finished** — don't report success if only steps 1-2 completed (the pipeline has 5 steps)
|
||||
2. **Spot-check AI enrichment** — open `ai-enrichment.json`, verify scores are calibrated (not all max or all zero)
|
||||
3. **Validate categorization** — do the category assignments make sense for known PRs?
|
||||
4. **Read `summary.md`** — is the report actionable with clear next-steps per PR?
|
||||
|
||||
## Continuous Improvement
|
||||
|
||||
When triage quality is inconsistent:
|
||||
|
||||
- **Tune enrichment prompts** in `{skills_root}/pr-triage/references/` if scoring dimensions produce noisy results
|
||||
- **Update categorization rules** in `Invoke-PrCategorization.ps1` if PRs are misclassified
|
||||
- **Update SKILL.md** if script parameters, steps, or outputs changed
|
||||
- **Record failure patterns** — if AI enrichment fails for specific PR shapes (huge diffs, draft PRs), add guards
|
||||
|
||||
## Boundaries
|
||||
|
||||
- Never modify source code in PRs — hand off to `ReviewPR` or `FixPR`
|
||||
- Never close or merge PRs without human confirmation
|
||||
- For large batches (20+ PRs), launch as a detached process to avoid terminal idle kill
|
||||
- Don't report completion after Step 2 — wait for all 5 steps
|
||||
|
||||
## Parameter
|
||||
|
||||
- **pr_numbers**: Extract from PR numbers in user message. If missing, **ASK** the user.
|
||||
71
.github/copilot-instructions.md
vendored
71
.github/copilot-instructions.md
vendored
@@ -1,36 +1,59 @@
|
||||
---
|
||||
description: 'PowerToys AI contributor guidance'
|
||||
description: PowerToys AI contributor guidance.
|
||||
applyTo: pullRequests
|
||||
---
|
||||
|
||||
# PowerToys – Copilot Instructions
|
||||
# PowerToys - Copilot guide (concise)
|
||||
|
||||
Concise guidance for AI contributions. For complete details, see [AGENTS.md](../AGENTS.md).
|
||||
This is the top-level guide for AI changes. Keep edits small, follow existing patterns, and cite exact paths in PRs.
|
||||
|
||||
## Key Rules
|
||||
# Repo map (1-line per area)
|
||||
- Core apps: `src/runner/**` (tray/loader), `src/settings-ui/**` (Settings app)
|
||||
- Shared libs: `src/common/**`
|
||||
- Modules: `src/modules/*` (one per utility; Command Palette in `src/modules/cmdpal/**`)
|
||||
- Build tools/docs: `tools/**`, `doc/devdocs/**`
|
||||
|
||||
- Atomic PRs: one logical change, no drive-by refactors
|
||||
- Add tests when changing behavior
|
||||
- Keep hot paths quiet (no logging in hooks/tight loops)
|
||||
# Build and test (defaults)
|
||||
- Prerequisites: Visual Studio 2022 17.4+, minimal Windows 10 1803+.
|
||||
- Build discipline:
|
||||
- One terminal per operation (build -> test). Do not switch or open new ones mid-flow.
|
||||
- After making changes, `cd` to the project folder that changed (`.csproj`/`.vcxproj`).
|
||||
- Use scripts to build, synchronously block and wait in foreground for completion: `tools/build/build.ps1|.cmd` (current folder), `build-essentials.*` (once per brand new build for missing nuget packages).
|
||||
- Treat build exit code 0 as success; any non-zero exit code is a failure. Read the errors log in the build folder (such as `build.*.*.errors.log`) and surface problems.
|
||||
- Do not start tests or launch Runner until the previous step succeeded.
|
||||
- Tests (fast and targeted):
|
||||
- Find the test project by product code prefix (for example FancyZones, AdvancedPaste). Look for a sibling folder or one to two levels up named like `<Product>*UnitTests` or `<Product>*UITests`.
|
||||
- Build the test project, wait for exit, then run only those tests via VS Test Explorer or `vstest.console.exe` with filters. Avoid `dotnet test` in this repo.
|
||||
- Add or adjust tests when changing behavior; if skipped, state why (for example comment-only or string rename).
|
||||
|
||||
## Style Enforcement
|
||||
# Pull requests (expectations)
|
||||
- Atomic: one logical change; no drive-by refactors.
|
||||
- Describe: problem, approach, risk, test evidence.
|
||||
- List: touched paths if not obvious.
|
||||
|
||||
- C#: `src/.editorconfig`, StyleCop.Analyzers
|
||||
- C++: `src/.clang-format`
|
||||
- XAML: XamlStyler
|
||||
# When to ask for clarification
|
||||
- Ambiguous spec after scanning relevant docs (see below).
|
||||
- Cross-module impact (shared enum or struct) not clear.
|
||||
- Security, elevation, or installer changes.
|
||||
|
||||
## When to Ask for Clarification
|
||||
# Logging (use existing stacks)
|
||||
- C++ logging lives in `src/common/logger/**` (`Logger::info`, `Logger::warn`, `Logger::error`, `Logger::debug`). Keep hot paths quiet (hooks, tight loops).
|
||||
- C# logging goes through `ManagedCommon.Logger` (`LogInfo`, `LogWarning`, `LogError`, `LogDebug`, `LogTrace`). Some UIs use injected `ILogger` via `LoggerInstance.Logger`.
|
||||
|
||||
- Ambiguous spec after scanning docs
|
||||
- Cross-module impact unclear
|
||||
- Security, elevation, or installer changes
|
||||
# Docs to consult
|
||||
- `tools/build/BUILD-GUIDELINES.md`
|
||||
- `doc/devdocs/core/architecture.md`
|
||||
- `doc/devdocs/core/runner.md`
|
||||
- `doc/devdocs/core/settings/readme.md`
|
||||
- `doc/devdocs/modules/readme.md`
|
||||
|
||||
## Component-Specific Instructions
|
||||
# Language style rules
|
||||
- Always enforce repo analyzers: root `.editorconfig` plus any `stylecop.json`.
|
||||
- C# code follows StyleCop.Analyzers and Microsoft.CodeAnalysis.NetAnalyzers.
|
||||
- C++ code honors `.clang-format` plus `.clang-tidy` (modernize/cppcoreguidelines/readability).
|
||||
- Markdown files wrap at 80 characters and use ATX headers with fenced code blocks that include language tags.
|
||||
- YAML files indent two spaces and add comments for complex settings while keeping keys clear.
|
||||
- PowerShell scripts use Verb-Noun names and prefer single-quoted literals while documenting parameters and satisfying PSScriptAnalyzer.
|
||||
|
||||
These are auto-applied based on file location:
|
||||
- [Runner & Settings UI](.github/instructions/runner-settings-ui.instructions.md)
|
||||
- [Common Libraries](.github/instructions/common-libraries.instructions.md)
|
||||
|
||||
## Detailed Documentation
|
||||
|
||||
- [Architecture](../doc/devdocs/core/architecture.md)
|
||||
- [Coding Style](../doc/devdocs/development/style.md)
|
||||
# Done checklist (self review before finishing)
|
||||
- Build clean? Tests updated or passed? No unintended formatting? Any new dependency? Documented skips?
|
||||
|
||||
261
.github/instructions/agent-skills.instructions.md
vendored
261
.github/instructions/agent-skills.instructions.md
vendored
@@ -1,261 +0,0 @@
|
||||
---
|
||||
description: 'Guidelines for creating high-quality Agent Skills for GitHub Copilot'
|
||||
applyTo: '**/.github/skills/**/SKILL.md, **/.claude/skills/**/SKILL.md'
|
||||
---
|
||||
|
||||
# Agent Skills File Guidelines
|
||||
|
||||
Instructions for creating effective and portable Agent Skills that enhance GitHub Copilot with specialized capabilities, workflows, and bundled resources.
|
||||
|
||||
## What Are Agent Skills?
|
||||
|
||||
Agent Skills are self-contained folders with instructions and bundled resources that teach AI agents specialized capabilities. Unlike custom instructions (which define coding standards), skills enable task-specific workflows that can include scripts, examples, templates, and reference data.
|
||||
|
||||
Key characteristics:
|
||||
- **Portable**: Works across VS Code, Copilot CLI, and Copilot coding agent
|
||||
- **Progressive loading**: Only loaded when relevant to the user's request
|
||||
- **Resource-bundled**: Can include scripts, templates, examples alongside instructions
|
||||
- **On-demand**: Activated automatically based on prompt relevance
|
||||
|
||||
## Directory Structure
|
||||
|
||||
Skills are stored in specific locations:
|
||||
|
||||
| Location | Scope | Recommendation |
|
||||
|----------|-------|----------------|
|
||||
| `.github/skills/<skill-name>/` | Project/repository | Recommended for project skills |
|
||||
| `.claude/skills/<skill-name>/` | Project/repository | Legacy, for backward compatibility |
|
||||
| `~/.github/skills/<skill-name>/` | Personal (user-wide) | Recommended for personal skills |
|
||||
| `~/.claude/skills/<skill-name>/` | Personal (user-wide) | Legacy, for backward compatibility |
|
||||
|
||||
Each skill **must** have its own subdirectory containing at minimum a `SKILL.md` file.
|
||||
|
||||
## Required SKILL.md Format
|
||||
|
||||
### Frontmatter (Required)
|
||||
|
||||
```yaml
|
||||
---
|
||||
name: webapp-testing
|
||||
description: Toolkit for testing local web applications using Playwright. Use when asked to verify frontend functionality, debug UI behavior, capture browser screenshots, check for visual regressions, or view browser console logs. Supports Chrome, Firefox, and WebKit browsers.
|
||||
license: Complete terms in LICENSE.txt
|
||||
---
|
||||
```
|
||||
|
||||
| Field | Required | Constraints |
|
||||
|-------|----------|-------------|
|
||||
| `name` | Yes | Lowercase, hyphens for spaces, max 64 characters (e.g., `webapp-testing`) |
|
||||
| `description` | Yes | Clear description of capabilities AND use cases, max 1024 characters |
|
||||
| `license` | No | Reference to LICENSE.txt (e.g., `Complete terms in LICENSE.txt`) or SPDX identifier |
|
||||
|
||||
### Description Best Practices
|
||||
|
||||
**CRITICAL**: The `description` field is the PRIMARY mechanism for automatic skill discovery. Copilot reads ONLY the `name` and `description` to decide whether to load a skill. If your description is vague, the skill will never be activated.
|
||||
|
||||
**What to include in description:**
|
||||
1. **WHAT** the skill does (capabilities)
|
||||
2. **WHEN** to use it (specific triggers, scenarios, file types, or user requests)
|
||||
3. **Keywords** that users might mention in their prompts
|
||||
|
||||
**Good description:**
|
||||
```yaml
|
||||
description: Toolkit for testing local web applications using Playwright. Use when asked to verify frontend functionality, debug UI behavior, capture browser screenshots, check for visual regressions, or view browser console logs. Supports Chrome, Firefox, and WebKit browsers.
|
||||
```
|
||||
|
||||
**Poor description:**
|
||||
```yaml
|
||||
description: Web testing helpers
|
||||
```
|
||||
|
||||
The poor description fails because:
|
||||
- No specific triggers (when should Copilot load this?)
|
||||
- No keywords (what user prompts would match?)
|
||||
- No capabilities (what can it actually do?)
|
||||
|
||||
### Body Content
|
||||
|
||||
The body contains detailed instructions that Copilot loads AFTER the skill is activated. Recommended sections:
|
||||
|
||||
| Section | Purpose |
|
||||
|---------|---------|
|
||||
| `# Title` | Brief overview of what this skill enables |
|
||||
| `## When to Use This Skill` | List of scenarios (reinforces description triggers) |
|
||||
| `## Prerequisites` | Required tools, dependencies, environment setup |
|
||||
| `## Step-by-Step Workflows` | Numbered steps for common tasks |
|
||||
| `## Troubleshooting` | Common issues and solutions table |
|
||||
| `## References` | Links to bundled docs or external resources |
|
||||
|
||||
## Bundling Resources
|
||||
|
||||
Skills can include additional files that Copilot accesses on-demand:
|
||||
|
||||
### Supported Resource Types
|
||||
|
||||
| Folder | Purpose | Loaded into Context? | Example Files |
|
||||
|--------|---------|---------------------|---------------|
|
||||
| `scripts/` | Executable automation that performs specific operations | When executed | `helper.py`, `validate.sh`, `build.ts` |
|
||||
| `references/` | Documentation the AI agent reads to inform decisions | Yes, when referenced | `api_reference.md`, `schema.md`, `workflow_guide.md` |
|
||||
| `assets/` | **Static files used AS-IS** in output (not modified by the AI agent) | No | `logo.png`, `brand-template.pptx`, `custom-font.ttf` |
|
||||
| `templates/` | **Starter code/scaffolds that the AI agent MODIFIES** and builds upon | Yes, when referenced | `viewer.html` (insert algorithm), `hello-world/` (extend) |
|
||||
|
||||
### Directory Structure Example
|
||||
|
||||
```
|
||||
.github/skills/my-skill/
|
||||
├── SKILL.md # Required: Main instructions
|
||||
├── LICENSE.txt # Recommended: License terms (Apache 2.0 typical)
|
||||
├── scripts/ # Optional: Executable automation
|
||||
│ ├── helper.py # Python script
|
||||
│ └── helper.ps1 # PowerShell script
|
||||
├── references/ # Optional: Documentation loaded into context
|
||||
│ ├── api_reference.md
|
||||
│ ├── step1-setup.md # Detailed workflow (>3 steps)
|
||||
│ └── step2-deployment.md
|
||||
├── assets/ # Optional: Static files used AS-IS in output
|
||||
│ ├── baseline.png # Reference image for comparison
|
||||
│ └── report-template.html
|
||||
└── templates/ # Optional: Starter code the AI agent modifies
|
||||
├── scaffold.py # Code scaffold the AI agent customizes
|
||||
└── config.template # Config template the AI agent fills in
|
||||
```
|
||||
|
||||
> **LICENSE.txt**: When creating a skill, download the Apache 2.0 license text from https://www.apache.org/licenses/LICENSE-2.0.txt and save as `LICENSE.txt`. Update the copyright year and owner in the appendix section.
|
||||
|
||||
### Assets vs Templates: Key Distinction
|
||||
|
||||
**Assets** are static resources **consumed unchanged** in the output:
|
||||
- A `logo.png` that gets embedded into a generated document
|
||||
- A `report-template.html` copied as output format
|
||||
- A `custom-font.ttf` applied to text rendering
|
||||
|
||||
**Templates** are starter code/scaffolds that **the AI agent actively modifies**:
|
||||
- A `scaffold.py` where the AI agent inserts logic
|
||||
- A `config.template` where the AI agent fills in values based on user requirements
|
||||
- A `hello-world/` project directory that the AI agent extends with new features
|
||||
|
||||
**Rule of thumb**: If the AI agent reads and builds upon the file content → `templates/`. If the file is used as-is in output → `assets/`.
|
||||
|
||||
### Referencing Resources in SKILL.md
|
||||
|
||||
Use relative paths to reference files within the skill directory:
|
||||
|
||||
```markdown
|
||||
## Available Scripts
|
||||
|
||||
Run the [helper script](./scripts/helper.py) to automate common tasks.
|
||||
|
||||
See [API reference](./references/api_reference.md) for detailed documentation.
|
||||
|
||||
Use the [scaffold](./templates/scaffold.py) as a starting point.
|
||||
```
|
||||
|
||||
## Progressive Loading Architecture
|
||||
|
||||
Skills use three-level loading for efficiency:
|
||||
|
||||
| Level | What Loads | When |
|
||||
|-------|------------|------|
|
||||
| 1. Discovery | `name` and `description` only | Always (lightweight metadata) |
|
||||
| 2. Instructions | Full `SKILL.md` body | When request matches description |
|
||||
| 3. Resources | Scripts, examples, docs | Only when Copilot references them |
|
||||
|
||||
This means:
|
||||
- Install many skills without consuming context
|
||||
- Only relevant content loads per task
|
||||
- Resources don't load until explicitly needed
|
||||
|
||||
## Content Guidelines
|
||||
|
||||
### Writing Style
|
||||
|
||||
- Use imperative mood: "Run", "Create", "Configure" (not "You should run")
|
||||
- Be specific and actionable
|
||||
- Include exact commands with parameters
|
||||
- Show expected outputs where helpful
|
||||
- Keep sections focused and scannable
|
||||
|
||||
### Script Requirements
|
||||
|
||||
When including scripts, prefer cross-platform languages:
|
||||
|
||||
| Language | Use Case |
|
||||
|----------|----------|
|
||||
| Python | Complex automation, data processing |
|
||||
| pwsh | PowerShell Core scripting |
|
||||
| Node.js | JavaScript-based tooling |
|
||||
| Bash/Shell | Simple automation tasks |
|
||||
|
||||
Best practices:
|
||||
- Include help/usage documentation (`--help` flag)
|
||||
- Handle errors gracefully with clear messages
|
||||
- Avoid storing credentials or secrets
|
||||
- Use relative paths where possible
|
||||
|
||||
### When to Bundle Scripts
|
||||
|
||||
Include scripts in your skill when:
|
||||
- The same code would be rewritten repeatedly by the agent
|
||||
- Deterministic reliability is critical (e.g., file manipulation, API calls)
|
||||
- Complex logic benefits from being pre-tested rather than generated each time
|
||||
- The operation has a self-contained purpose that can evolve independently
|
||||
- Testability matters — scripts can be unit tested and validated
|
||||
- Predictable behavior is preferred over dynamic generation
|
||||
|
||||
Scripts enable evolution: even simple operations benefit from being implemented as scripts when they may grow in complexity, need consistent behavior across invocations, or require future extensibility.
|
||||
|
||||
### Security Considerations
|
||||
|
||||
- Scripts rely on existing credential helpers (no credential storage)
|
||||
- Include `--force` flags only for destructive operations
|
||||
- Warn users before irreversible actions
|
||||
- Document any network operations or external calls
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Parameter Table Pattern
|
||||
|
||||
Document parameters clearly:
|
||||
|
||||
```markdown
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `--input` | Yes | - | Input file or URL to process |
|
||||
| `--action` | Yes | - | Action to perform |
|
||||
| `--verbose` | No | `false` | Enable verbose output |
|
||||
```
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
Before publishing a skill:
|
||||
|
||||
- [ ] `SKILL.md` has valid frontmatter with `name` and `description`
|
||||
- [ ] `name` is lowercase with hyphens, ≤64 characters
|
||||
- [ ] `description` clearly states **WHAT** it does, **WHEN** to use it, and relevant **KEYWORDS**
|
||||
- [ ] Body includes when to use, prerequisites, and step-by-step workflows
|
||||
- [ ] SKILL.md body kept under 500 lines (split large content into `references/` folder)
|
||||
- [ ] Large workflows (>5 steps) split into `references/` folder with clear links from SKILL.md
|
||||
- [ ] Scripts include help documentation and error handling
|
||||
- [ ] Relative paths used for all resource references
|
||||
- [ ] No hardcoded credentials or secrets
|
||||
|
||||
## Workflow Execution Pattern
|
||||
|
||||
When executing multi-step workflows, create a TODO list where each step references the relevant documentation:
|
||||
|
||||
```markdown
|
||||
## TODO
|
||||
- [ ] Step 1: Configure environment - see [workflow-setup.md](./references/workflow-setup.md#environment)
|
||||
- [ ] Step 2: Build project - see [workflow-setup.md](./references/workflow-setup.md#build)
|
||||
- [ ] Step 3: Deploy to staging - see [workflow-deployment.md](./references/workflow-deployment.md#staging)
|
||||
- [ ] Step 4: Run validation - see [workflow-deployment.md](./references/workflow-deployment.md#validation)
|
||||
- [ ] Step 5: Deploy to production - see [workflow-deployment.md](./references/workflow-deployment.md#production)
|
||||
```
|
||||
|
||||
This ensures traceability and allows resuming workflows if interrupted.
|
||||
|
||||
## Related Resources
|
||||
|
||||
- [Agent Skills Specification](https://agentskills.io/)
|
||||
- [VS Code Agent Skills Documentation](https://code.visualstudio.com/docs/copilot/customization/agent-skills)
|
||||
- [Reference Skills Repository](https://github.com/anthropics/skills)
|
||||
- [Awesome Copilot Skills](https://github.com/github/awesome-copilot/blob/main/docs/README.skills.md)
|
||||
791
.github/instructions/agents.instructions.md
vendored
791
.github/instructions/agents.instructions.md
vendored
@@ -1,791 +0,0 @@
|
||||
---
|
||||
description: 'Guidelines for creating custom agent files for GitHub Copilot'
|
||||
applyTo: '**/*.agent.md'
|
||||
---
|
||||
|
||||
# Custom Agent File Guidelines
|
||||
|
||||
Instructions for creating effective and maintainable custom agent files that provide specialized expertise for specific development tasks in GitHub Copilot.
|
||||
|
||||
## Project Context
|
||||
|
||||
- Target audience: Developers creating custom agents for GitHub Copilot
|
||||
- File format: Markdown with YAML frontmatter
|
||||
- File naming convention: lowercase with hyphens (e.g., `test-specialist.agent.md`)
|
||||
- Location: `.github/agents/` directory (repository-level) or `agents/` directory (organization/enterprise-level)
|
||||
- Purpose: Define specialized agents with tailored expertise, tools, and instructions for specific tasks
|
||||
- Official documentation: https://docs.github.com/en/copilot/how-tos/use-copilot-agents/coding-agent/create-custom-agents
|
||||
|
||||
## Required Frontmatter
|
||||
|
||||
Every agent file must include YAML frontmatter with the following fields:
|
||||
|
||||
```yaml
|
||||
---
|
||||
description: 'Brief description of the agent purpose and capabilities'
|
||||
name: 'Agent Display Name'
|
||||
tools: ['read', 'edit', 'search']
|
||||
model: 'Claude Sonnet 4.5'
|
||||
target: 'vscode'
|
||||
infer: true
|
||||
---
|
||||
```
|
||||
|
||||
### Core Frontmatter Properties
|
||||
|
||||
#### **description** (REQUIRED)
|
||||
- Single-quoted string, clearly stating the agent's purpose and domain expertise
|
||||
- Should be concise (50-150 characters) and actionable
|
||||
- Example: `'Focuses on test coverage, quality, and testing best practices'`
|
||||
|
||||
#### **name** (OPTIONAL)
|
||||
- Display name for the agent in the UI
|
||||
- If omitted, defaults to filename (without `.md` or `.agent.md`)
|
||||
- Use title case and be descriptive
|
||||
- Example: `'Testing Specialist'`
|
||||
|
||||
#### **tools** (OPTIONAL)
|
||||
- List of tool names or aliases the agent can use
|
||||
- Supports comma-separated string or YAML array format
|
||||
- If omitted, agent has access to all available tools
|
||||
- See "Tool Configuration" section below for details
|
||||
|
||||
#### **model** (STRONGLY RECOMMENDED)
|
||||
- Specifies which AI model the agent should use
|
||||
- Supported in VS Code, JetBrains IDEs, Eclipse, and Xcode
|
||||
- Example: `'Claude Sonnet 4.5'`, `'gpt-4'`, `'gpt-4o'`
|
||||
- Choose based on agent complexity and required capabilities
|
||||
|
||||
#### **target** (OPTIONAL)
|
||||
- Specifies target environment: `'vscode'` or `'github-copilot'`
|
||||
- If omitted, agent is available in both environments
|
||||
- Use when agent has environment-specific features
|
||||
|
||||
#### **infer** (OPTIONAL)
|
||||
- Boolean controlling whether Copilot can automatically use this agent based on context
|
||||
- Default: `true` if omitted
|
||||
- Set to `false` to require manual agent selection
|
||||
|
||||
#### **metadata** (OPTIONAL, GitHub.com only)
|
||||
- Object with name-value pairs for agent annotation
|
||||
- Example: `metadata: { category: 'testing', version: '1.0' }`
|
||||
- Not supported in VS Code
|
||||
|
||||
#### **mcp-servers** (OPTIONAL, Organization/Enterprise only)
|
||||
- Configure MCP servers available only to this agent
|
||||
- Only supported for organization/enterprise level agents
|
||||
- See "MCP Server Configuration" section below
|
||||
|
||||
## Tool Configuration
|
||||
|
||||
### Tool Specification Strategies
|
||||
|
||||
**Enable all tools** (default):
|
||||
```yaml
|
||||
# Omit tools property entirely, or use:
|
||||
tools: ['*']
|
||||
```
|
||||
|
||||
**Enable specific tools**:
|
||||
```yaml
|
||||
tools: ['read', 'edit', 'search', 'execute']
|
||||
```
|
||||
|
||||
**Enable MCP server tools**:
|
||||
```yaml
|
||||
tools: ['read', 'edit', 'github/*', 'playwright/navigate']
|
||||
```
|
||||
|
||||
**Disable all tools**:
|
||||
```yaml
|
||||
tools: []
|
||||
```
|
||||
|
||||
### Standard Tool Aliases
|
||||
|
||||
All aliases are case-insensitive:
|
||||
|
||||
| Alias | Alternative Names | Category | Description |
|
||||
|-------|------------------|----------|-------------|
|
||||
| `execute` | shell, Bash, powershell | Shell execution | Execute commands in appropriate shell |
|
||||
| `read` | Read, NotebookRead, view | File reading | Read file contents |
|
||||
| `edit` | Edit, MultiEdit, Write, NotebookEdit | File editing | Edit and modify files |
|
||||
| `search` | Grep, Glob, search | Code search | Search for files or text in files |
|
||||
| `agent` | custom-agent, Task | Agent invocation | Invoke other custom agents |
|
||||
| `web` | WebSearch, WebFetch | Web access | Fetch web content and search |
|
||||
| `todo` | TodoWrite | Task management | Create and manage task lists (VS Code only) |
|
||||
|
||||
### Built-in MCP Server Tools
|
||||
|
||||
**GitHub MCP Server**:
|
||||
```yaml
|
||||
tools: ['github/*'] # All GitHub tools
|
||||
tools: ['github/get_file_contents', 'github/search_repositories'] # Specific tools
|
||||
```
|
||||
- All read-only tools available by default
|
||||
- Token scoped to source repository
|
||||
|
||||
**Playwright MCP Server**:
|
||||
```yaml
|
||||
tools: ['playwright/*'] # All Playwright tools
|
||||
tools: ['playwright/navigate', 'playwright/screenshot'] # Specific tools
|
||||
```
|
||||
- Configured to access localhost only
|
||||
- Useful for browser automation and testing
|
||||
|
||||
### Tool Selection Best Practices
|
||||
|
||||
- **Principle of Least Privilege**: Only enable tools necessary for the agent's purpose
|
||||
- **Security**: Limit `execute` access unless explicitly required
|
||||
- **Focus**: Fewer tools = clearer agent purpose and better performance
|
||||
- **Documentation**: Comment why specific tools are required for complex configurations
|
||||
|
||||
## Sub-Agent Invocation (Agent Orchestration)
|
||||
|
||||
Agents can invoke other agents using `runSubagent` to orchestrate multi-step workflows.
|
||||
|
||||
### How It Works
|
||||
|
||||
Include `agent` in tools list to enable sub-agent invocation:
|
||||
|
||||
```yaml
|
||||
tools: ['read', 'edit', 'search', 'agent']
|
||||
```
|
||||
|
||||
Then invoke other agents with `runSubagent`:
|
||||
|
||||
```javascript
|
||||
const result = await runSubagent({
|
||||
description: 'What this step does',
|
||||
prompt: `You are the [Specialist] specialist.
|
||||
|
||||
Context:
|
||||
- Parameter: ${parameterValue}
|
||||
- Input: ${inputPath}
|
||||
- Output: ${outputPath}
|
||||
|
||||
Task:
|
||||
1. Do the specific work
|
||||
2. Write results to output location
|
||||
3. Return summary of completion`
|
||||
});
|
||||
```
|
||||
|
||||
### Basic Pattern
|
||||
|
||||
Structure each sub-agent call with:
|
||||
|
||||
1. **description**: Clear one-line purpose of the sub-agent invocation
|
||||
2. **prompt**: Detailed instructions with substituted variables
|
||||
|
||||
The prompt should include:
|
||||
- Who the sub-agent is (specialist role)
|
||||
- What context it needs (parameters, paths)
|
||||
- What to do (concrete tasks)
|
||||
- Where to write output
|
||||
- What to return (summary)
|
||||
|
||||
### Example: Multi-Step Processing
|
||||
|
||||
```javascript
|
||||
// Step 1: Process data
|
||||
const processing = await runSubagent({
|
||||
description: 'Transform raw input data',
|
||||
prompt: `You are the Data Processor specialist.
|
||||
|
||||
Project: ${projectName}
|
||||
Input: ${basePath}/raw/
|
||||
Output: ${basePath}/processed/
|
||||
|
||||
Task:
|
||||
1. Read all files from input directory
|
||||
2. Apply transformations
|
||||
3. Write processed files to output
|
||||
4. Create summary: ${basePath}/processed/summary.md
|
||||
|
||||
Return: Number of files processed and any issues found`
|
||||
});
|
||||
|
||||
// Step 2: Analyze (depends on Step 1)
|
||||
const analysis = await runSubagent({
|
||||
description: 'Analyze processed data',
|
||||
prompt: `You are the Data Analyst specialist.
|
||||
|
||||
Project: ${projectName}
|
||||
Input: ${basePath}/processed/
|
||||
Output: ${basePath}/analysis/
|
||||
|
||||
Task:
|
||||
1. Read processed files from input
|
||||
2. Generate analysis report
|
||||
3. Write to: ${basePath}/analysis/report.md
|
||||
|
||||
Return: Key findings and identified patterns`
|
||||
});
|
||||
```
|
||||
|
||||
### Key Points
|
||||
|
||||
- **Pass variables in prompts**: Use `${variableName}` for all dynamic values
|
||||
- **Keep prompts focused**: Clear, specific tasks for each sub-agent
|
||||
- **Return summaries**: Each sub-agent should report what it accomplished
|
||||
- **Sequential execution**: Use `await` to maintain order when steps depend on each other
|
||||
- **Error handling**: Check results before proceeding to dependent steps
|
||||
|
||||
### ⚠️ Tool Availability Requirement
|
||||
|
||||
**Critical**: If a sub-agent requires specific tools (e.g., `edit`, `execute`, `search`), the orchestrator must include those tools in its own `tools` list. Sub-agents cannot access tools that aren't available to their parent orchestrator.
|
||||
|
||||
**Example**:
|
||||
```yaml
|
||||
# If your sub-agents need to edit files, execute commands, or search code
|
||||
tools: ['read', 'edit', 'search', 'execute', 'agent']
|
||||
```
|
||||
|
||||
The orchestrator's tool permissions act as a ceiling for all invoked sub-agents. Plan your tool list carefully to ensure all sub-agents have the tools they need.
|
||||
|
||||
### ⚠️ Important Limitation
|
||||
|
||||
**Sub-agent orchestration is NOT suitable for large-scale data processing.** Avoid using `runSubagent` when:
|
||||
- Processing hundreds or thousands of files
|
||||
- Handling large datasets
|
||||
- Performing bulk transformations on big codebases
|
||||
- Orchestrating more than 5-10 sequential steps
|
||||
|
||||
Each sub-agent call adds latency and context overhead. For high-volume processing, implement logic directly in a single agent instead. Use orchestration only for coordinating specialized tasks on focused, manageable datasets.
|
||||
|
||||
## Agent Prompt Structure
|
||||
|
||||
The markdown content below the frontmatter defines the agent's behavior, expertise, and instructions. Well-structured prompts typically include:
|
||||
|
||||
1. **Agent Identity and Role**: Who the agent is and its primary role
|
||||
2. **Core Responsibilities**: What specific tasks the agent performs
|
||||
3. **Approach and Methodology**: How the agent works to accomplish tasks
|
||||
4. **Guidelines and Constraints**: What to do/avoid and quality standards
|
||||
5. **Output Expectations**: Expected output format and quality
|
||||
|
||||
### Prompt Writing Best Practices
|
||||
|
||||
- **Be Specific and Direct**: Use imperative mood ("Analyze", "Generate"); avoid vague terms
|
||||
- **Define Boundaries**: Clearly state scope limits and constraints
|
||||
- **Include Context**: Explain domain expertise and reference relevant frameworks
|
||||
- **Focus on Behavior**: Describe how the agent should think and work
|
||||
- **Use Structured Format**: Headers, bullets, and lists make prompts scannable
|
||||
|
||||
## Variable Definition and Extraction
|
||||
|
||||
Agents can define dynamic parameters to extract values from user input and use them throughout the agent's behavior and sub-agent communications. This enables flexible, context-aware agents that adapt to user-provided data.
|
||||
|
||||
### When to Use Variables
|
||||
|
||||
**Use variables when**:
|
||||
- Agent behavior depends on user input
|
||||
- Need to pass dynamic values to sub-agents
|
||||
- Want to make agents reusable across different contexts
|
||||
- Require parameterized workflows
|
||||
- Need to track or reference user-provided context
|
||||
|
||||
**Examples**:
|
||||
- Extract project name from user prompt
|
||||
- Capture certification name for pipeline processing
|
||||
- Identify file paths or directories
|
||||
- Extract configuration options
|
||||
- Parse feature names or module identifiers
|
||||
|
||||
### Variable Declaration Pattern
|
||||
|
||||
Define variables section early in the agent prompt to document expected parameters:
|
||||
|
||||
```markdown
|
||||
# Agent Name
|
||||
|
||||
## Dynamic Parameters
|
||||
|
||||
- **Parameter Name**: Description and usage
|
||||
- **Another Parameter**: How it's extracted and used
|
||||
|
||||
## Your Mission
|
||||
|
||||
Process [PARAMETER_NAME] to accomplish [task].
|
||||
```
|
||||
|
||||
### Variable Extraction Methods
|
||||
|
||||
#### 1. **Explicit User Input**
|
||||
Ask the user to provide the variable if not detected in the prompt:
|
||||
|
||||
```markdown
|
||||
## Your Mission
|
||||
|
||||
Process the project by analyzing your codebase.
|
||||
|
||||
### Step 1: Identify Project
|
||||
If no project name is provided, **ASK THE USER** for:
|
||||
- Project name or identifier
|
||||
- Base path or directory location
|
||||
- Configuration type (if applicable)
|
||||
|
||||
Use this information to contextualize all subsequent tasks.
|
||||
```
|
||||
|
||||
#### 2. **Implicit Extraction from Prompt**
|
||||
Automatically extract variables from the user's natural language input:
|
||||
|
||||
```javascript
|
||||
// Example: Extract certification name from user input
|
||||
const userInput = "Process My Certification";
|
||||
|
||||
// Extract key information
|
||||
const certificationName = extractCertificationName(userInput);
|
||||
// Result: "My Certification"
|
||||
|
||||
const basePath = `certifications/${certificationName}`;
|
||||
// Result: "certifications/My Certification"
|
||||
```
|
||||
|
||||
#### 3. **Contextual Variable Resolution**
|
||||
Use file context or workspace information to derive variables:
|
||||
|
||||
```markdown
|
||||
## Variable Resolution Strategy
|
||||
|
||||
1. **From User Prompt**: First, look for explicit mentions in user input
|
||||
2. **From File Context**: Check current file name or path
|
||||
3. **From Workspace**: Use workspace folder or active project
|
||||
4. **From Settings**: Reference configuration files
|
||||
5. **Ask User**: If all else fails, request missing information
|
||||
```
|
||||
|
||||
### Using Variables in Agent Prompts
|
||||
|
||||
#### Variable Substitution in Instructions
|
||||
|
||||
Use template variables in agent prompts to make them dynamic:
|
||||
|
||||
```markdown
|
||||
# Agent Name
|
||||
|
||||
## Dynamic Parameters
|
||||
- **Project Name**: ${projectName}
|
||||
- **Base Path**: ${basePath}
|
||||
- **Output Directory**: ${outputDir}
|
||||
|
||||
## Your Mission
|
||||
|
||||
Process the **${projectName}** project located at `${basePath}`.
|
||||
|
||||
## Process Steps
|
||||
|
||||
1. Read input from: `${basePath}/input/`
|
||||
2. Process files according to project configuration
|
||||
3. Write results to: `${outputDir}/`
|
||||
4. Generate summary report
|
||||
|
||||
## Quality Standards
|
||||
|
||||
- Maintain project-specific coding standards for **${projectName}**
|
||||
- Follow directory structure: `${basePath}/[structure]`
|
||||
```
|
||||
|
||||
#### Passing Variables to Sub-Agents
|
||||
|
||||
When invoking a sub-agent, pass all context through template variables in the prompt:
|
||||
|
||||
```javascript
|
||||
// Extract and prepare variables
|
||||
const basePath = `projects/${projectName}`;
|
||||
const inputPath = `${basePath}/src/`;
|
||||
const outputPath = `${basePath}/docs/`;
|
||||
|
||||
// Pass to sub-agent with all variables substituted
|
||||
const result = await runSubagent({
|
||||
description: 'Generate project documentation',
|
||||
prompt: `You are the Documentation specialist.
|
||||
|
||||
Project: ${projectName}
|
||||
Input: ${inputPath}
|
||||
Output: ${outputPath}
|
||||
|
||||
Task:
|
||||
1. Read source files from ${inputPath}
|
||||
2. Generate comprehensive documentation
|
||||
3. Write to ${outputPath}/index.md
|
||||
4. Include code examples and usage guides
|
||||
|
||||
Return: Summary of documentation generated (file count, word count)`
|
||||
});
|
||||
```
|
||||
|
||||
The sub-agent receives all necessary context embedded in the prompt. Variables are resolved before sending the prompt, so the sub-agent works with concrete paths and values, not variable placeholders.
|
||||
|
||||
### Real-World Example: Code Review Orchestrator
|
||||
|
||||
Example of a simple orchestrator that validates code through multiple specialized agents:
|
||||
|
||||
```javascript
|
||||
async function reviewCodePipeline(repositoryName, prNumber) {
|
||||
const basePath = `projects/${repositoryName}/pr-${prNumber}`;
|
||||
|
||||
// Step 1: Security Review
|
||||
const security = await runSubagent({
|
||||
description: 'Scan for security vulnerabilities',
|
||||
prompt: `You are the Security Reviewer specialist.
|
||||
|
||||
Repository: ${repositoryName}
|
||||
PR: ${prNumber}
|
||||
Code: ${basePath}/changes/
|
||||
|
||||
Task:
|
||||
1. Scan code for OWASP Top 10 vulnerabilities
|
||||
2. Check for injection attacks, auth flaws
|
||||
3. Write findings to ${basePath}/security-review.md
|
||||
|
||||
Return: List of critical, high, and medium issues found`
|
||||
});
|
||||
|
||||
// Step 2: Test Coverage Check
|
||||
const coverage = await runSubagent({
|
||||
description: 'Verify test coverage for changes',
|
||||
prompt: `You are the Test Coverage specialist.
|
||||
|
||||
Repository: ${repositoryName}
|
||||
PR: ${prNumber}
|
||||
Changes: ${basePath}/changes/
|
||||
|
||||
Task:
|
||||
1. Analyze code coverage for modified files
|
||||
2. Identify untested critical paths
|
||||
3. Write report to ${basePath}/coverage-report.md
|
||||
|
||||
Return: Current coverage percentage and gaps`
|
||||
});
|
||||
|
||||
// Step 3: Aggregate Results
|
||||
const finalReport = await runSubagent({
|
||||
description: 'Compile all review findings',
|
||||
prompt: `You are the Review Aggregator specialist.
|
||||
|
||||
Repository: ${repositoryName}
|
||||
Reports: ${basePath}/*.md
|
||||
|
||||
Task:
|
||||
1. Read all review reports from ${basePath}/
|
||||
2. Synthesize findings into single report
|
||||
3. Determine overall verdict (APPROVE/NEEDS_FIXES/BLOCK)
|
||||
4. Write to ${basePath}/final-review.md
|
||||
|
||||
Return: Final verdict and executive summary`
|
||||
});
|
||||
|
||||
return finalReport;
|
||||
}
|
||||
```
|
||||
|
||||
This pattern applies to any orchestration scenario: extract variables, call sub-agents with clear context, await results.
|
||||
|
||||
|
||||
### Variable Best Practices
|
||||
|
||||
#### 1. **Clear Documentation**
|
||||
Always document what variables are expected:
|
||||
|
||||
```markdown
|
||||
## Required Variables
|
||||
- **projectName**: The name of the project (string, required)
|
||||
- **basePath**: Root directory for project files (path, required)
|
||||
|
||||
## Optional Variables
|
||||
- **mode**: Processing mode - quick/standard/detailed (enum, default: standard)
|
||||
- **outputFormat**: Output format - markdown/json/html (enum, default: markdown)
|
||||
|
||||
## Derived Variables
|
||||
- **outputDir**: Automatically set to ${basePath}/output
|
||||
- **logFile**: Automatically set to ${basePath}/.log.md
|
||||
```
|
||||
|
||||
#### 2. **Consistent Naming**
|
||||
Use consistent variable naming conventions:
|
||||
|
||||
```javascript
|
||||
// Good: Clear, descriptive naming
|
||||
const variables = {
|
||||
projectName, // What project to work on
|
||||
basePath, // Where project files are located
|
||||
outputDirectory, // Where to save results
|
||||
processingMode, // How to process (detail level)
|
||||
configurationPath // Where config files are
|
||||
};
|
||||
|
||||
// Avoid: Ambiguous or inconsistent
|
||||
const bad_variables = {
|
||||
name, // Too generic
|
||||
path, // Unclear which path
|
||||
mode, // Too short
|
||||
config // Too vague
|
||||
};
|
||||
```
|
||||
|
||||
#### 3. **Validation and Constraints**
|
||||
Document valid values and constraints:
|
||||
|
||||
```markdown
|
||||
## Variable Constraints
|
||||
|
||||
**projectName**:
|
||||
- Type: string (alphanumeric, hyphens, underscores allowed)
|
||||
- Length: 1-100 characters
|
||||
- Required: yes
|
||||
- Pattern: `/^[a-zA-Z0-9_-]+$/`
|
||||
|
||||
**processingMode**:
|
||||
- Type: enum
|
||||
- Valid values: "quick" (< 5min), "standard" (5-15min), "detailed" (15+ min)
|
||||
- Default: "standard"
|
||||
- Required: no
|
||||
```
|
||||
|
||||
## MCP Server Configuration (Organization/Enterprise Only)
|
||||
|
||||
MCP servers extend agent capabilities with additional tools. Only supported for organization and enterprise-level agents.
|
||||
|
||||
### Configuration Format
|
||||
|
||||
```yaml
|
||||
---
|
||||
name: my-custom-agent
|
||||
description: 'Agent with MCP integration'
|
||||
tools: ['read', 'edit', 'custom-mcp/tool-1']
|
||||
mcp-servers:
|
||||
custom-mcp:
|
||||
type: 'local'
|
||||
command: 'some-command'
|
||||
args: ['--arg1', '--arg2']
|
||||
tools: ["*"]
|
||||
env:
|
||||
ENV_VAR_NAME: ${{ secrets.API_KEY }}
|
||||
---
|
||||
```
|
||||
|
||||
### MCP Server Properties
|
||||
|
||||
- **type**: Server type (`'local'` or `'stdio'`)
|
||||
- **command**: Command to start the MCP server
|
||||
- **args**: Array of command arguments
|
||||
- **tools**: Tools to enable from this server (`["*"]` for all)
|
||||
- **env**: Environment variables (supports secrets)
|
||||
|
||||
### Environment Variables and Secrets
|
||||
|
||||
Secrets must be configured in repository settings under "copilot" environment.
|
||||
|
||||
**Supported syntax**:
|
||||
```yaml
|
||||
env:
|
||||
# Environment variable only
|
||||
VAR_NAME: COPILOT_MCP_ENV_VAR_VALUE
|
||||
|
||||
# Variable with header
|
||||
VAR_NAME: $COPILOT_MCP_ENV_VAR_VALUE
|
||||
VAR_NAME: ${COPILOT_MCP_ENV_VAR_VALUE}
|
||||
|
||||
# GitHub Actions-style (YAML only)
|
||||
VAR_NAME: ${{ secrets.COPILOT_MCP_ENV_VAR_VALUE }}
|
||||
VAR_NAME: ${{ var.COPILOT_MCP_ENV_VAR_VALUE }}
|
||||
```
|
||||
|
||||
## File Organization and Naming
|
||||
|
||||
### Repository-Level Agents
|
||||
- Location: `.github/agents/`
|
||||
- Scope: Available only in the specific repository
|
||||
- Access: Uses repository-configured MCP servers
|
||||
|
||||
### Organization/Enterprise-Level Agents
|
||||
- Location: `.github-private/agents/` (then move to `agents/` root)
|
||||
- Scope: Available across all repositories in org/enterprise
|
||||
- Access: Can configure dedicated MCP servers
|
||||
|
||||
### Naming Conventions
|
||||
- Use lowercase with hyphens: `test-specialist.agent.md`
|
||||
- Name should reflect agent purpose
|
||||
- Filename becomes default agent name (if `name` not specified)
|
||||
- Allowed characters: `.`, `-`, `_`, `a-z`, `A-Z`, `0-9`
|
||||
|
||||
## Agent Processing and Behavior
|
||||
|
||||
### Versioning
|
||||
- Based on Git commit SHAs for the agent file
|
||||
- Create branches/tags for different agent versions
|
||||
- Instantiated using latest version for repository/branch
|
||||
- PR interactions use same agent version for consistency
|
||||
|
||||
### Name Conflicts
|
||||
Priority (highest to lowest):
|
||||
1. Repository-level agent
|
||||
2. Organization-level agent
|
||||
3. Enterprise-level agent
|
||||
|
||||
Lower-level configurations override higher-level ones with the same name.
|
||||
|
||||
### Tool Processing
|
||||
- `tools` list filters available tools (built-in and MCP)
|
||||
- No tools specified = all tools enabled
|
||||
- Empty list (`[]`) = all tools disabled
|
||||
- Specific list = only those tools enabled
|
||||
- Unrecognized tool names are ignored (allows environment-specific tools)
|
||||
|
||||
### MCP Server Processing Order
|
||||
1. Out-of-the-box MCP servers (e.g., GitHub MCP)
|
||||
2. Custom agent MCP configuration (org/enterprise only)
|
||||
3. Repository-level MCP configurations
|
||||
|
||||
Each level can override settings from previous levels.
|
||||
|
||||
## Agent Creation Checklist
|
||||
|
||||
### Frontmatter
|
||||
- [ ] `description` field present and descriptive (50-150 chars)
|
||||
- [ ] `description` wrapped in single quotes
|
||||
- [ ] `name` specified (optional but recommended)
|
||||
- [ ] `tools` configured appropriately (or intentionally omitted)
|
||||
- [ ] `model` specified for optimal performance
|
||||
- [ ] `target` set if environment-specific
|
||||
- [ ] `infer` set to `false` if manual selection required
|
||||
|
||||
### Prompt Content
|
||||
- [ ] Clear agent identity and role defined
|
||||
- [ ] Core responsibilities listed explicitly
|
||||
- [ ] Approach and methodology explained
|
||||
- [ ] Guidelines and constraints specified
|
||||
- [ ] Output expectations documented
|
||||
- [ ] Examples provided where helpful
|
||||
- [ ] Instructions are specific and actionable
|
||||
- [ ] Scope and boundaries clearly defined
|
||||
- [ ] Total content under 30,000 characters
|
||||
|
||||
### File Structure
|
||||
- [ ] Filename follows lowercase-with-hyphens convention
|
||||
- [ ] File placed in correct directory (`.github/agents/` or `agents/`)
|
||||
- [ ] Filename uses only allowed characters
|
||||
- [ ] File extension is `.agent.md`
|
||||
|
||||
### Quality Assurance
|
||||
- [ ] Agent purpose is unique and not duplicative
|
||||
- [ ] Tools are minimal and necessary
|
||||
- [ ] Instructions are clear and unambiguous
|
||||
- [ ] Agent has been tested with representative tasks
|
||||
- [ ] Documentation references are current
|
||||
- [ ] Security considerations addressed (if applicable)
|
||||
|
||||
## Common Agent Patterns
|
||||
|
||||
### Testing Specialist
|
||||
**Purpose**: Focus on test coverage and quality
|
||||
**Tools**: All tools (for comprehensive test creation)
|
||||
**Approach**: Analyze, identify gaps, write tests, avoid production code changes
|
||||
|
||||
### Implementation Planner
|
||||
**Purpose**: Create detailed technical plans and specifications
|
||||
**Tools**: Limited to `['read', 'search', 'edit']`
|
||||
**Approach**: Analyze requirements, create documentation, avoid implementation
|
||||
|
||||
### Code Reviewer
|
||||
**Purpose**: Review code quality and provide feedback
|
||||
**Tools**: `['read', 'search']` only
|
||||
**Approach**: Analyze, suggest improvements, no direct modifications
|
||||
|
||||
### Refactoring Specialist
|
||||
**Purpose**: Improve code structure and maintainability
|
||||
**Tools**: `['read', 'search', 'edit']`
|
||||
**Approach**: Analyze patterns, propose refactorings, implement safely
|
||||
|
||||
### Security Auditor
|
||||
**Purpose**: Identify security issues and vulnerabilities
|
||||
**Tools**: `['read', 'search', 'web']`
|
||||
**Approach**: Scan code, check against OWASP, report findings
|
||||
|
||||
## Common Mistakes to Avoid
|
||||
|
||||
### Frontmatter Errors
|
||||
- ❌ Missing `description` field
|
||||
- ❌ Description not wrapped in quotes
|
||||
- ❌ Invalid tool names without checking documentation
|
||||
- ❌ Incorrect YAML syntax (indentation, quotes)
|
||||
|
||||
### Tool Configuration Issues
|
||||
- ❌ Granting excessive tool access unnecessarily
|
||||
- ❌ Missing required tools for agent's purpose
|
||||
- ❌ Not using tool aliases consistently
|
||||
- ❌ Forgetting MCP server namespace (`server-name/tool`)
|
||||
|
||||
### Prompt Content Problems
|
||||
- ❌ Vague, ambiguous instructions
|
||||
- ❌ Conflicting or contradictory guidelines
|
||||
- ❌ Lack of clear scope definition
|
||||
- ❌ Missing output expectations
|
||||
- ❌ Overly verbose instructions (exceeding character limits)
|
||||
- ❌ No examples or context for complex tasks
|
||||
|
||||
### Organizational Issues
|
||||
- ❌ Filename doesn't reflect agent purpose
|
||||
- ❌ Wrong directory (confusing repo vs org level)
|
||||
- ❌ Using spaces or special characters in filename
|
||||
- ❌ Duplicate agent names causing conflicts
|
||||
|
||||
## Testing and Validation
|
||||
|
||||
### Manual Testing
|
||||
1. Create the agent file with proper frontmatter
|
||||
2. Reload VS Code or refresh GitHub.com
|
||||
3. Select the agent from the dropdown in Copilot Chat
|
||||
4. Test with representative user queries
|
||||
5. Verify tool access works as expected
|
||||
6. Confirm output meets expectations
|
||||
|
||||
### Integration Testing
|
||||
- Test agent with different file types in scope
|
||||
- Verify MCP server connectivity (if configured)
|
||||
- Check agent behavior with missing context
|
||||
- Test error handling and edge cases
|
||||
- Validate agent switching and handoffs
|
||||
|
||||
### Quality Checks
|
||||
- Run through agent creation checklist
|
||||
- Review against common mistakes list
|
||||
- Compare with example agents in repository
|
||||
- Get peer review for complex agents
|
||||
- Document any special configuration needs
|
||||
|
||||
## Additional Resources
|
||||
|
||||
### Official Documentation
|
||||
- [Creating Custom Agents](https://docs.github.com/en/copilot/how-tos/use-copilot-agents/coding-agent/create-custom-agents)
|
||||
- [Custom Agents Configuration](https://docs.github.com/en/copilot/reference/custom-agents-configuration)
|
||||
- [Custom Agents in VS Code](https://code.visualstudio.com/docs/copilot/customization/custom-agents)
|
||||
- [MCP Integration](https://docs.github.com/en/copilot/how-tos/use-copilot-agents/coding-agent/extend-coding-agent-with-mcp)
|
||||
|
||||
### Community Resources
|
||||
- [Awesome Copilot Agents Collection](https://github.com/github/awesome-copilot/tree/main/agents)
|
||||
- [Customization Library Examples](https://docs.github.com/en/copilot/tutorials/customization-library/custom-agents)
|
||||
- [Your First Custom Agent Tutorial](https://docs.github.com/en/copilot/tutorials/customization-library/custom-agents/your-first-custom-agent)
|
||||
|
||||
### Related Files
|
||||
- [Prompt Files Guidelines](./prompt.instructions.md) - For creating prompt files
|
||||
- [Instructions Guidelines](./instructions.instructions.md) - For creating instruction files
|
||||
|
||||
## Version Compatibility Notes
|
||||
|
||||
### GitHub.com (Coding Agent)
|
||||
- ✅ Fully supports all standard frontmatter properties
|
||||
- ✅ Repository and org/enterprise level agents
|
||||
- ✅ MCP server configuration (org/enterprise)
|
||||
- ❌ Does not support `model`, `argument-hint`, `handoffs` properties
|
||||
|
||||
### VS Code / JetBrains / Eclipse / Xcode
|
||||
- ✅ Supports `model` property for AI model selection
|
||||
- ✅ Supports `argument-hint` and `handoffs` properties
|
||||
- ✅ User profile and workspace-level agents
|
||||
- ❌ Cannot configure MCP servers at repository level
|
||||
- ⚠️ Some properties may behave differently
|
||||
|
||||
When creating agents for multiple environments, focus on common properties and test in all target environments. Use `target` property to create environment-specific agents when necessary.
|
||||
@@ -1,187 +0,0 @@
|
||||
---
|
||||
description: 'Best practices for Azure DevOps Pipeline YAML files'
|
||||
applyTo: '**/azure-pipelines.yml, **/azure-pipelines*.yml, **/*.pipeline.yml'
|
||||
---
|
||||
|
||||
# Azure DevOps Pipeline YAML Best Practices
|
||||
|
||||
Guidelines for creating maintainable, secure, and efficient Azure DevOps pipelines in PowerToys.
|
||||
|
||||
## General Guidelines
|
||||
|
||||
- Use YAML syntax consistently with proper indentation (2 spaces)
|
||||
- Always include meaningful names and display names for pipelines, stages, jobs, and steps
|
||||
- Implement proper error handling and conditional execution
|
||||
- Use variables and parameters to make pipelines reusable and maintainable
|
||||
- Follow the principle of least privilege for service connections and permissions
|
||||
- Include comprehensive logging and diagnostics for troubleshooting
|
||||
|
||||
## Pipeline Structure
|
||||
|
||||
- Organize complex pipelines using stages for better visualization and control
|
||||
- Use jobs to group related steps and enable parallel execution when possible
|
||||
- Implement proper dependencies between stages and jobs
|
||||
- Use templates for reusable pipeline components
|
||||
- Keep pipeline files focused and modular - split large pipelines into multiple files
|
||||
|
||||
## Build Best Practices
|
||||
|
||||
- Use specific agent pool versions and VM images for consistency
|
||||
- Cache dependencies (npm, NuGet, Maven, etc.) to improve build performance
|
||||
- Implement proper artifact management with meaningful names and retention policies
|
||||
- Use build variables for version numbers and build metadata
|
||||
- Include code quality gates (lint checks, testing, security scans)
|
||||
- Ensure builds are reproducible and environment-independent
|
||||
|
||||
## Testing Integration
|
||||
|
||||
- Run unit tests as part of the build process
|
||||
- Publish test results in standard formats (JUnit, VSTest, etc.)
|
||||
- Include code coverage reporting and quality gates
|
||||
- Implement integration and end-to-end tests in appropriate stages
|
||||
- Use test impact analysis when available to optimize test execution
|
||||
- Fail fast on test failures to provide quick feedback
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- Use Azure Key Vault for sensitive configuration and secrets
|
||||
- Implement proper secret management with variable groups
|
||||
- Use service connections with minimal required permissions
|
||||
- Enable security scans (dependency vulnerabilities, static analysis)
|
||||
- Implement approval gates for production deployments
|
||||
- Use managed identities when possible instead of service principals
|
||||
|
||||
## Deployment Strategies
|
||||
|
||||
- Implement proper environment promotion (dev → staging → production)
|
||||
- Use deployment jobs with proper environment targeting
|
||||
- Implement blue-green or canary deployment strategies when appropriate
|
||||
- Include rollback mechanisms and health checks
|
||||
- Use infrastructure as code (ARM, Bicep, Terraform) for consistent deployments
|
||||
- Implement proper configuration management per environment
|
||||
|
||||
## Variable and Parameter Management
|
||||
|
||||
- Use variable groups for shared configuration across pipelines
|
||||
- Implement runtime parameters for flexible pipeline execution
|
||||
- Use conditional variables based on branches or environments
|
||||
- Secure sensitive variables and mark them as secrets
|
||||
- Document variable purposes and expected values
|
||||
- Use variable templates for complex variable logic
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
- Use parallel jobs and matrix strategies when appropriate
|
||||
- Implement proper caching strategies for dependencies and build outputs
|
||||
- Use shallow clone for Git operations when full history isn't needed
|
||||
- Optimize Docker image builds with multi-stage builds and layer caching
|
||||
- Monitor pipeline performance and optimize bottlenecks
|
||||
- Use pipeline resource triggers efficiently
|
||||
|
||||
## Monitoring and Observability
|
||||
|
||||
- Include comprehensive logging throughout the pipeline
|
||||
- Use Azure Monitor and Application Insights for deployment tracking
|
||||
- Implement proper notification strategies for failures and successes
|
||||
- Include deployment health checks and automated rollback triggers
|
||||
- Use pipeline analytics to identify improvement opportunities
|
||||
- Document pipeline behavior and troubleshooting steps
|
||||
|
||||
## Template and Reusability
|
||||
|
||||
- Create pipeline templates for common patterns
|
||||
- Use extends templates for complete pipeline inheritance
|
||||
- Implement step templates for reusable task sequences
|
||||
- Use variable templates for complex variable logic
|
||||
- Version templates appropriately for stability
|
||||
- Document template parameters and usage examples
|
||||
|
||||
## Branch and Trigger Strategy
|
||||
|
||||
- Implement appropriate triggers for different branch types
|
||||
- Use path filters to trigger builds only when relevant files change
|
||||
- Configure proper CI/CD triggers for main/master branches
|
||||
- Use pull request triggers for code validation
|
||||
- Implement scheduled triggers for maintenance tasks
|
||||
- Consider resource triggers for multi-repository scenarios
|
||||
|
||||
## Example Structure
|
||||
|
||||
```yaml
|
||||
# azure-pipelines.yml
|
||||
trigger:
|
||||
branches:
|
||||
include:
|
||||
- main
|
||||
- develop
|
||||
paths:
|
||||
exclude:
|
||||
- docs/*
|
||||
- README.md
|
||||
|
||||
variables:
|
||||
- group: shared-variables
|
||||
- name: buildConfiguration
|
||||
value: 'Release'
|
||||
|
||||
stages:
|
||||
- stage: Build
|
||||
displayName: 'Build and Test'
|
||||
jobs:
|
||||
- job: Build
|
||||
displayName: 'Build Application'
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
steps:
|
||||
- task: UseDotNet@2
|
||||
displayName: 'Use .NET SDK'
|
||||
inputs:
|
||||
version: '8.x'
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: 'Restore dependencies'
|
||||
inputs:
|
||||
command: 'restore'
|
||||
projects: '**/*.csproj'
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: 'Build application'
|
||||
inputs:
|
||||
command: 'build'
|
||||
projects: '**/*.csproj'
|
||||
arguments: '--configuration $(buildConfiguration) --no-restore'
|
||||
|
||||
- stage: Deploy
|
||||
displayName: 'Deploy to Staging'
|
||||
dependsOn: Build
|
||||
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
|
||||
jobs:
|
||||
- deployment: DeployToStaging
|
||||
displayName: 'Deploy to Staging Environment'
|
||||
environment: 'staging'
|
||||
strategy:
|
||||
runOnce:
|
||||
deploy:
|
||||
steps:
|
||||
- download: current
|
||||
displayName: 'Download drop artifact'
|
||||
artifact: drop
|
||||
- task: AzureWebApp@1
|
||||
displayName: 'Deploy to Azure Web App'
|
||||
inputs:
|
||||
azureSubscription: 'staging-service-connection'
|
||||
appType: 'webApp'
|
||||
appName: 'myapp-staging'
|
||||
package: '$(Pipeline.Workspace)/drop/**/*.zip'
|
||||
```
|
||||
|
||||
## Common Anti-Patterns to Avoid
|
||||
|
||||
- Hardcoding sensitive values directly in YAML files
|
||||
- Using overly broad triggers that cause unnecessary builds
|
||||
- Mixing build and deployment logic in a single stage
|
||||
- Not implementing proper error handling and cleanup
|
||||
- Using deprecated task versions without upgrade plans
|
||||
- Creating monolithic pipelines that are difficult to maintain
|
||||
- Not using proper naming conventions for clarity
|
||||
- Ignoring pipeline security best practices
|
||||
@@ -1,61 +0,0 @@
|
||||
---
|
||||
description: 'Guidelines for shared libraries including logging, IPC, settings, DPI, telemetry, and utilities consumed by multiple modules'
|
||||
applyTo: 'src/common/**'
|
||||
---
|
||||
|
||||
# Common Libraries – Shared Code Guidance
|
||||
|
||||
Guidelines for modifying shared code in `src/common/`. Changes here can have wide-reaching impact across the entire PowerToys codebase.
|
||||
|
||||
## Scope
|
||||
|
||||
- Logging infrastructure (`src/common/logger/`)
|
||||
- IPC primitives and named pipe utilities
|
||||
- Settings serialization and management
|
||||
- DPI awareness and scaling utilities
|
||||
- Telemetry helpers
|
||||
- General utilities (JSON parsing, string helpers, etc.)
|
||||
|
||||
## Guidelines
|
||||
|
||||
### API Stability
|
||||
|
||||
- Avoid breaking public headers/APIs; if changed, search & update all callers
|
||||
- Coordinate ABI-impacting struct/class layout changes; keep binary compatibility
|
||||
- When modifying public interfaces, grep the entire codebase for usages
|
||||
|
||||
### Performance
|
||||
|
||||
- Watch perf in hot paths (hooks, timers, serialization)
|
||||
- Avoid avoidable allocations in frequently called code
|
||||
- Profile changes that touch performance-sensitive areas
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Ask before adding third-party deps or changing serialization formats
|
||||
- New dependencies must be MIT-licensed or approved by PM team
|
||||
- Add any new external packages to `NOTICE.md`
|
||||
|
||||
### Logging
|
||||
|
||||
- C++ logging uses spdlog (`Logger::info`, `Logger::warn`, `Logger::error`, `Logger::debug`)
|
||||
- Initialize with `init_logger()` early in startup
|
||||
- Keep hot paths quiet – no logging in tight loops or hooks
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- No unintended ABI breaks
|
||||
- No noisy logs in hot paths
|
||||
- New non-obvious symbols briefly commented
|
||||
- All callers updated when interfaces change
|
||||
|
||||
## Code Style
|
||||
|
||||
- **C++**: Follow `.clang-format` in `src/`; use Modern C++ patterns per C++ Core Guidelines
|
||||
- **C#**: Follow `src/.editorconfig`; enforce StyleCop.Analyzers
|
||||
|
||||
## Validation
|
||||
|
||||
- Build: `tools\build\build.cmd` from `src/common/` folder
|
||||
- Verify no ABI breaks: grep for changed function/struct names across codebase
|
||||
- Check logs: ensure no new logging in performance-critical paths
|
||||
256
.github/instructions/instructions.instructions.md
vendored
256
.github/instructions/instructions.instructions.md
vendored
@@ -1,256 +0,0 @@
|
||||
---
|
||||
description: 'Guidelines for creating high-quality custom instruction files for GitHub Copilot'
|
||||
applyTo: '**/*.instructions.md'
|
||||
---
|
||||
|
||||
# Custom Instructions File Guidelines
|
||||
|
||||
Instructions for creating effective and maintainable custom instruction files that guide GitHub Copilot in generating domain-specific code and following project conventions.
|
||||
|
||||
## Project Context
|
||||
|
||||
- Target audience: Developers and GitHub Copilot working with domain-specific code
|
||||
- File format: Markdown with YAML frontmatter
|
||||
- File naming convention: lowercase with hyphens (e.g., `react-best-practices.instructions.md`)
|
||||
- Location: `.github/instructions/` directory
|
||||
- Purpose: Provide context-aware guidance for code generation, review, and documentation
|
||||
|
||||
## Required Frontmatter
|
||||
|
||||
Every instruction file must include YAML frontmatter with the following fields:
|
||||
|
||||
```yaml
|
||||
---
|
||||
description: 'Brief description of the instruction purpose and scope'
|
||||
applyTo: 'glob pattern for target files (e.g., **/*.ts, **/*.py)'
|
||||
---
|
||||
```
|
||||
|
||||
### Frontmatter Guidelines
|
||||
|
||||
- **description**: Single-quoted string, 1-500 characters, clearly stating the purpose
|
||||
- **applyTo**: Glob pattern(s) specifying which files these instructions apply to
|
||||
- Single pattern: `'**/*.ts'`
|
||||
- Multiple patterns: `'**/*.ts, **/*.tsx, **/*.js'`
|
||||
- Specific files: `'src/**/*.py'`
|
||||
- All files: `'**'`
|
||||
|
||||
## File Structure
|
||||
|
||||
A well-structured instruction file should include the following sections:
|
||||
|
||||
### 1. Title and Overview
|
||||
|
||||
- Clear, descriptive title using `#` heading
|
||||
- Brief introduction explaining the purpose and scope
|
||||
- Optional: Project context section with key technologies and versions
|
||||
|
||||
### 2. Core Sections
|
||||
|
||||
Organize content into logical sections based on the domain:
|
||||
|
||||
- **General Instructions**: High-level guidelines and principles
|
||||
- **Best Practices**: Recommended patterns and approaches
|
||||
- **Code Standards**: Naming conventions, formatting, style rules
|
||||
- **Architecture/Structure**: Project organization and design patterns
|
||||
- **Common Patterns**: Frequently used implementations
|
||||
- **Security**: Security considerations (if applicable)
|
||||
- **Performance**: Optimization guidelines (if applicable)
|
||||
- **Testing**: Testing standards and approaches (if applicable)
|
||||
|
||||
### 3. Examples and Code Snippets
|
||||
|
||||
Provide concrete examples with clear labels:
|
||||
|
||||
```markdown
|
||||
### Good Example
|
||||
\`\`\`language
|
||||
// Recommended approach
|
||||
code example here
|
||||
\`\`\`
|
||||
|
||||
### Bad Example
|
||||
\`\`\`language
|
||||
// Avoid this pattern
|
||||
code example here
|
||||
\`\`\`
|
||||
```
|
||||
|
||||
### 4. Validation and Verification (Optional but Recommended)
|
||||
|
||||
- Build commands to verify code
|
||||
- Lint checks and formatting tools
|
||||
- Testing requirements
|
||||
- Verification steps
|
||||
|
||||
## Content Guidelines
|
||||
|
||||
### Writing Style
|
||||
|
||||
- Use clear, concise language
|
||||
- Write in imperative mood ("Use", "Implement", "Avoid")
|
||||
- Be specific and actionable
|
||||
- Avoid ambiguous terms like "should", "might", "possibly"
|
||||
- Use bullet points and lists for readability
|
||||
- Keep sections focused and scannable
|
||||
|
||||
### Best Practices
|
||||
|
||||
- **Be Specific**: Provide concrete examples rather than abstract concepts
|
||||
- **Show Why**: Explain the reasoning behind recommendations when it adds value
|
||||
- **Use Tables**: For comparing options, listing rules, or showing patterns
|
||||
- **Include Examples**: Real code snippets are more effective than descriptions
|
||||
- **Stay Current**: Reference current versions and best practices
|
||||
- **Link Resources**: Include official documentation and authoritative sources
|
||||
|
||||
### Common Patterns to Include
|
||||
|
||||
1. **Naming Conventions**: How to name variables, functions, classes, files
|
||||
2. **Code Organization**: File structure, module organization, import order
|
||||
3. **Error Handling**: Preferred error handling patterns
|
||||
4. **Dependencies**: How to manage and document dependencies
|
||||
5. **Comments and Documentation**: When and how to document code
|
||||
6. **Version Information**: Target language/framework versions
|
||||
|
||||
## Patterns to Follow
|
||||
|
||||
### Bullet Points and Lists
|
||||
|
||||
```markdown
|
||||
## Security Best Practices
|
||||
|
||||
- Always validate user input before processing
|
||||
- Use parameterized queries to prevent SQL injection
|
||||
- Store secrets in environment variables, never in code
|
||||
- Implement proper authentication and authorization
|
||||
- Enable HTTPS for all production endpoints
|
||||
```
|
||||
|
||||
### Tables for Structured Information
|
||||
|
||||
```markdown
|
||||
## Common Issues
|
||||
|
||||
| Issue | Solution | Example |
|
||||
| ---------------- | ------------------- | ----------------------------- |
|
||||
| Magic numbers | Use named constants | `const MAX_RETRIES = 3` |
|
||||
| Deep nesting | Extract functions | Refactor nested if statements |
|
||||
| Hardcoded values | Use configuration | Store API URLs in config |
|
||||
```
|
||||
|
||||
### Code Comparison
|
||||
|
||||
```markdown
|
||||
### Good Example - Using TypeScript interfaces
|
||||
\`\`\`typescript
|
||||
interface User {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
function getUser(id: string): User {
|
||||
// Implementation
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
### Bad Example - Using any type
|
||||
\`\`\`typescript
|
||||
function getUser(id: any): any {
|
||||
// Loses type safety
|
||||
}
|
||||
\`\`\`
|
||||
```
|
||||
|
||||
### Conditional Guidance
|
||||
|
||||
```markdown
|
||||
## Framework Selection
|
||||
|
||||
- **For small projects**: Use Minimal API approach
|
||||
- **For large projects**: Use controller-based architecture with clear separation
|
||||
- **For microservices**: Consider domain-driven design patterns
|
||||
```
|
||||
|
||||
## Patterns to Avoid
|
||||
|
||||
- **Overly verbose explanations**: Keep it concise and scannable
|
||||
- **Outdated information**: Always reference current versions and practices
|
||||
- **Ambiguous guidelines**: Be specific about what to do or avoid
|
||||
- **Missing examples**: Abstract rules without concrete code examples
|
||||
- **Contradictory advice**: Ensure consistency throughout the file
|
||||
- **Copy-paste from documentation**: Add value by distilling and providing context
|
||||
|
||||
## Testing Your Instructions
|
||||
|
||||
Before finalizing instruction files:
|
||||
|
||||
1. **Test with Copilot**: Try the instructions with actual prompts in VS Code
|
||||
2. **Verify Examples**: Ensure code examples are correct and run without errors
|
||||
3. **Check Glob Patterns**: Confirm `applyTo` patterns match intended files
|
||||
|
||||
## Example Structure
|
||||
|
||||
Here's a minimal example structure for a new instruction file:
|
||||
|
||||
```markdown
|
||||
---
|
||||
description: 'Brief description of purpose'
|
||||
applyTo: '**/*.ext'
|
||||
---
|
||||
|
||||
# Technology Name Development
|
||||
|
||||
Brief introduction and context.
|
||||
|
||||
## General Instructions
|
||||
|
||||
- High-level guideline 1
|
||||
- High-level guideline 2
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Specific practice 1
|
||||
- Specific practice 2
|
||||
|
||||
## Code Standards
|
||||
|
||||
### Naming Conventions
|
||||
- Rule 1
|
||||
- Rule 2
|
||||
|
||||
### File Organization
|
||||
- Structure 1
|
||||
- Structure 2
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Pattern 1
|
||||
Description and example
|
||||
|
||||
\`\`\`language
|
||||
code example
|
||||
\`\`\`
|
||||
|
||||
### Pattern 2
|
||||
Description and example
|
||||
|
||||
## Validation
|
||||
|
||||
- Build command: `command to verify`
|
||||
- Lint checks: `command to lint`
|
||||
- Testing: `command to test`
|
||||
```
|
||||
|
||||
## Maintenance
|
||||
|
||||
- Review instructions when dependencies or frameworks are updated
|
||||
- Update examples to reflect current best practices
|
||||
- Remove outdated patterns or deprecated features
|
||||
- Add new patterns as they emerge in the community
|
||||
- Keep glob patterns accurate as project structure evolves
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [Custom Instructions Documentation](https://code.visualstudio.com/docs/copilot/customization/custom-instructions)
|
||||
- [Awesome Copilot Instructions](https://github.com/github/awesome-copilot/tree/main/instructions)
|
||||
88
.github/instructions/prompt.instructions.md
vendored
88
.github/instructions/prompt.instructions.md
vendored
@@ -1,88 +0,0 @@
|
||||
---
|
||||
description: 'Guidelines for creating high-quality prompt files for GitHub Copilot'
|
||||
applyTo: '**/*.prompt.md'
|
||||
---
|
||||
|
||||
# Copilot Prompt Files Guidelines
|
||||
|
||||
Instructions for creating effective and maintainable prompt files that guide GitHub Copilot in delivering consistent, high-quality outcomes across any repository.
|
||||
|
||||
## Scope and Principles
|
||||
- Target audience: maintainers and contributors authoring reusable prompts for Copilot Chat.
|
||||
- Goals: predictable behaviour, clear expectations, minimal permissions, and portability across repositories.
|
||||
- Primary references: VS Code documentation on prompt files and organization-specific conventions.
|
||||
|
||||
## Frontmatter Requirements
|
||||
|
||||
Every prompt file should include YAML frontmatter with the following fields:
|
||||
|
||||
### Required/Recommended Fields
|
||||
|
||||
| Field | Required | Description |
|
||||
|-------|----------|-------------|
|
||||
| `description` | Recommended | A short description of the prompt (single sentence, actionable outcome) |
|
||||
| `name` | Optional | The name shown after typing `/` in chat. Defaults to filename if not specified |
|
||||
| `agent` | Recommended | The agent to use: `ask`, `edit`, `agent`, or a custom agent name. Defaults to current agent |
|
||||
| `model` | Optional | The language model to use. Defaults to currently selected model |
|
||||
| `tools` | Optional | List of tool/tool set names available for this prompt |
|
||||
| `argument-hint` | Optional | Hint text shown in chat input to guide user interaction |
|
||||
|
||||
### Guidelines
|
||||
|
||||
- Use consistent quoting (single quotes recommended) and keep one field per line for readability and version control clarity
|
||||
- If `tools` are specified and current agent is `ask` or `edit`, the default agent becomes `agent`
|
||||
- Preserve any additional metadata (`language`, `tags`, `visibility`, etc.) required by your organization
|
||||
|
||||
## File Naming and Placement
|
||||
- Use kebab-case filenames ending with `.prompt.md` and store them under `.github/prompts/` unless your workspace standard specifies another directory.
|
||||
- Provide a short filename that communicates the action (for example, `generate-readme.prompt.md` rather than `prompt1.prompt.md`).
|
||||
|
||||
## Body Structure
|
||||
- Start with an `#` level heading that matches the prompt intent so it surfaces well in Quick Pick search.
|
||||
- Organize content with predictable sections. Recommended baseline: `Mission` or `Primary Directive`, `Scope & Preconditions`, `Inputs`, `Workflow` (step-by-step), `Output Expectations`, and `Quality Assurance`.
|
||||
- Adjust section names to fit the domain, but retain the logical flow: why → context → inputs → actions → outputs → validation.
|
||||
- Reference related prompts or instruction files using relative links to aid discoverability.
|
||||
|
||||
## Input and Context Handling
|
||||
- Use `${input:variableName[:placeholder]}` for required values and explain when the user must supply them. Provide defaults or alternatives where possible.
|
||||
- Call out contextual variables such as `${selection}`, `${file}`, `${workspaceFolder}` only when they are essential, and describe how Copilot should interpret them.
|
||||
- Document how to proceed when mandatory context is missing (for example, “Request the file path and stop if it remains undefined”).
|
||||
|
||||
## Tool and Permission Guidance
|
||||
- Limit `tools` to the smallest set that enables the task. List them in the preferred execution order when the sequence matters.
|
||||
- If the prompt inherits tools from a chat mode, mention that relationship and state any critical tool behaviours or side effects.
|
||||
- Warn about destructive operations (file creation, edits, terminal commands) and include guard rails or confirmation steps in the workflow.
|
||||
|
||||
## Instruction Tone and Style
|
||||
- Write in direct, imperative sentences targeted at Copilot (for example, “Analyze”, “Generate”, “Summarize”).
|
||||
- Keep sentences short and unambiguous, following Google Developer Documentation translation best practices to support localization.
|
||||
- Avoid idioms, humor, or culturally specific references; favor neutral, inclusive language.
|
||||
|
||||
## Output Definition
|
||||
- Specify the format, structure, and location of expected results (for example, “Create an architecture decision record file using the template below, such as `docs/architecture-decisions/record-XXXX.md`).
|
||||
- Include success criteria and failure triggers so Copilot knows when to halt or retry.
|
||||
- Provide validation steps—manual checks, automated commands, or acceptance criteria lists—that reviewers can execute after running the prompt.
|
||||
|
||||
## Examples and Reusable Assets
|
||||
- Embed Good/Bad examples or scaffolds (Markdown templates, JSON stubs) that the prompt should produce or follow.
|
||||
- Maintain reference tables (capabilities, status codes, role descriptions) inline to keep the prompt self-contained. Update these tables when upstream resources change.
|
||||
- Link to authoritative documentation instead of duplicating lengthy guidance.
|
||||
|
||||
## Quality Assurance Checklist
|
||||
- [ ] Frontmatter fields are complete, accurate, and least-privilege.
|
||||
- [ ] Inputs include placeholders, default behaviours, and fallbacks.
|
||||
- [ ] Workflow covers preparation, execution, and post-processing without gaps.
|
||||
- [ ] Output expectations include formatting and storage details.
|
||||
- [ ] Validation steps are actionable (commands, diff checks, review prompts).
|
||||
- [ ] Security, compliance, and privacy policies referenced by the prompt are current.
|
||||
- [ ] Prompt executes successfully in VS Code (`Chat: Run Prompt`) using representative scenarios.
|
||||
|
||||
## Maintenance Guidance
|
||||
- Version-control prompts alongside the code they affect; update them when dependencies, tooling, or review processes change.
|
||||
- Review prompts periodically to ensure tool lists, model requirements, and linked documents remain valid.
|
||||
- Coordinate with other repositories: when a prompt proves broadly useful, extract common guidance into instruction files or shared prompt packs.
|
||||
|
||||
## Additional Resources
|
||||
- [Prompt Files Documentation](https://code.visualstudio.com/docs/copilot/customization/prompt-files#_prompt-file-format)
|
||||
- [Awesome Copilot Prompt Files](https://github.com/github/awesome-copilot/tree/main/prompts)
|
||||
- [Tool Configuration](https://code.visualstudio.com/docs/copilot/chat/chat-agent-mode#_agent-mode-tools)
|
||||
@@ -1,68 +0,0 @@
|
||||
---
|
||||
description: 'Guidelines for Runner and Settings UI components that communicate via named pipes and manage module lifecycle'
|
||||
applyTo: 'src/runner/**,src/settings-ui/**'
|
||||
---
|
||||
|
||||
# Runner & Settings UI – Core Components Guidance
|
||||
|
||||
Guidelines for modifying the Runner (tray/module loader) and Settings UI (configuration app). These components communicate via Windows Named Pipes using JSON messages.
|
||||
|
||||
## Runner (`src/runner/`)
|
||||
|
||||
### Scope
|
||||
|
||||
- Module bootstrap, hotkey management, settings bridge, update/elevation handling
|
||||
|
||||
### Guidelines
|
||||
|
||||
- If IPC/JSON contracts change, mirror updates in `src/settings-ui/**`
|
||||
- Keep module discovery in `src/runner/main.cpp` in sync when adding/removing modules
|
||||
- Keep startup lean: avoid blocking/network calls in early init path
|
||||
- Preserve GPO & elevation behaviors; confirm no regression in policy handling
|
||||
- Ask before modifying update workflow or elevation logic
|
||||
|
||||
### Acceptance Criteria
|
||||
|
||||
- Stable startup, consistent contracts, no unnecessary logging noise
|
||||
|
||||
## Settings UI (`src/settings-ui/`)
|
||||
|
||||
### Scope
|
||||
|
||||
- WinUI/WPF UI, communicates with Runner over named pipes; manages persisted settings schema
|
||||
|
||||
### Guidelines
|
||||
|
||||
- Don't break settings schema silently; add migration when shape changes
|
||||
- If IPC/JSON contracts change, align with `src/runner/**` implementation
|
||||
- Keep UI responsive: marshal to UI thread for UI-bound operations
|
||||
- Reuse existing styles/resources; avoid duplicate theme keys
|
||||
- Add/adjust migration or serialization tests when changing persisted settings
|
||||
|
||||
### Acceptance Criteria
|
||||
|
||||
- Schema integrity preserved, responsive UI, consistent contracts, no style duplication
|
||||
|
||||
## Shared Concerns
|
||||
|
||||
### IPC Contract Changes
|
||||
|
||||
When modifying the JSON message format between Runner and Settings UI:
|
||||
|
||||
1. Update both `src/runner/` and `src/settings-ui/` in the same PR
|
||||
2. Preserve backward compatibility where possible
|
||||
3. Add migration logic for settings schema changes
|
||||
4. Test both directions of communication
|
||||
|
||||
### Code Style
|
||||
|
||||
- **C++ (Runner)**: Follow `.clang-format` in `src/`
|
||||
- **C# (Settings UI)**: Follow `src/.editorconfig`, use StyleCop.Analyzers
|
||||
- **XAML**: Use XamlStyler or run `.\.pipelines\applyXamlStyling.ps1 -Main`
|
||||
|
||||
## Validation
|
||||
|
||||
- Build Runner: `tools\build\build.cmd` from `src/runner/`
|
||||
- Build Settings UI: `tools\build\build.cmd` from `src/settings-ui/`
|
||||
- Test IPC: Launch both Runner and Settings UI, verify communication works
|
||||
- Schema changes: Run serialization tests if settings shape changed
|
||||
@@ -1,228 +0,0 @@
|
||||
---
|
||||
description: 'Instructions for building Model Context Protocol (MCP) servers using the TypeScript SDK'
|
||||
applyTo: '**/*.ts, **/*.js, **/package.json'
|
||||
---
|
||||
|
||||
# TypeScript MCP Server Development
|
||||
|
||||
## Instructions
|
||||
|
||||
- Use the **@modelcontextprotocol/sdk** npm package: `npm install @modelcontextprotocol/sdk`
|
||||
- Import from specific paths: `@modelcontextprotocol/sdk/server/mcp.js`, `@modelcontextprotocol/sdk/server/stdio.js`, etc.
|
||||
- Use `McpServer` class for high-level server implementation with automatic protocol handling
|
||||
- Use `Server` class for low-level control with manual request handlers
|
||||
- Use **zod** for input/output schema validation: `npm install zod@3`
|
||||
- Always provide `title` field for tools, resources, and prompts for better UI display
|
||||
- Use `registerTool()`, `registerResource()`, and `registerPrompt()` methods (recommended over older APIs)
|
||||
- Define schemas using zod: `{ inputSchema: { param: z.string() }, outputSchema: { result: z.string() } }`
|
||||
- Return both `content` (for display) and `structuredContent` (for structured data) from tools
|
||||
- For HTTP servers, use `StreamableHTTPServerTransport` with Express or similar frameworks
|
||||
- For local integrations, use `StdioServerTransport` for stdio-based communication
|
||||
- Create new transport instances per request to prevent request ID collisions (stateless mode)
|
||||
- Use session management with `sessionIdGenerator` for stateful servers
|
||||
- Enable DNS rebinding protection for local servers: `enableDnsRebindingProtection: true`
|
||||
- Configure CORS headers and expose `Mcp-Session-Id` for browser-based clients
|
||||
- Use `ResourceTemplate` for dynamic resources with URI parameters: `new ResourceTemplate('resource://{param}', { list: undefined })`
|
||||
- Support completions for better UX using `completable()` wrapper from `@modelcontextprotocol/sdk/server/completable.js`
|
||||
- Implement sampling with `server.server.createMessage()` to request LLM completions from clients
|
||||
- Use `server.server.elicitInput()` to request additional user input during tool execution
|
||||
- Enable notification debouncing for bulk updates: `debouncedNotificationMethods: ['notifications/tools/list_changed']`
|
||||
- Dynamic updates: call `.enable()`, `.disable()`, `.update()`, or `.remove()` on registered items to emit `listChanged` notifications
|
||||
- Use `getDisplayName()` from `@modelcontextprotocol/sdk/shared/metadataUtils.js` for UI display names
|
||||
- Test servers with MCP Inspector: `npx @modelcontextprotocol/inspector`
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Keep tool implementations focused on single responsibilities
|
||||
- Provide clear, descriptive titles and descriptions for LLM understanding
|
||||
- Use proper TypeScript types for all parameters and return values
|
||||
- Implement comprehensive error handling with try-catch blocks
|
||||
- Return `isError: true` in tool results for error conditions
|
||||
- Use async/await for all asynchronous operations
|
||||
- Close database connections and clean up resources properly
|
||||
- Validate input parameters before processing
|
||||
- Use structured logging for debugging without polluting stdout/stderr
|
||||
- Consider security implications when exposing file system or network access
|
||||
- Implement proper resource cleanup on transport close events
|
||||
- Use environment variables for configuration (ports, API keys, etc.)
|
||||
- Document tool capabilities and limitations clearly
|
||||
- Test with multiple clients to ensure compatibility
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Basic Server Setup (HTTP)
|
||||
```typescript
|
||||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
||||
import express from 'express';
|
||||
|
||||
const server = new McpServer({
|
||||
name: 'my-server',
|
||||
version: '1.0.0'
|
||||
});
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
app.post('/mcp', async (req, res) => {
|
||||
const transport = new StreamableHTTPServerTransport({
|
||||
sessionIdGenerator: undefined,
|
||||
enableJsonResponse: true
|
||||
});
|
||||
|
||||
res.on('close', () => transport.close());
|
||||
|
||||
await server.connect(transport);
|
||||
await transport.handleRequest(req, res, req.body);
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
```
|
||||
|
||||
### Basic Server Setup (stdio)
|
||||
```typescript
|
||||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
||||
|
||||
const server = new McpServer({
|
||||
name: 'my-server',
|
||||
version: '1.0.0'
|
||||
});
|
||||
|
||||
// ... register tools, resources, prompts ...
|
||||
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
```
|
||||
|
||||
### Simple Tool
|
||||
```typescript
|
||||
import { z } from 'zod';
|
||||
|
||||
server.registerTool(
|
||||
'calculate',
|
||||
{
|
||||
title: 'Calculator',
|
||||
description: 'Perform basic calculations',
|
||||
inputSchema: { a: z.number(), b: z.number(), op: z.enum(['+', '-', '*', '/']) },
|
||||
outputSchema: { result: z.number() }
|
||||
},
|
||||
async ({ a, b, op }) => {
|
||||
const result = op === '+' ? a + b : op === '-' ? a - b :
|
||||
op === '*' ? a * b : a / b;
|
||||
const output = { result };
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify(output) }],
|
||||
structuredContent: output
|
||||
};
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
### Dynamic Resource
|
||||
```typescript
|
||||
import { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
|
||||
server.registerResource(
|
||||
'user',
|
||||
new ResourceTemplate('users://{userId}', { list: undefined }),
|
||||
{
|
||||
title: 'User Profile',
|
||||
description: 'Fetch user profile data'
|
||||
},
|
||||
async (uri, { userId }) => ({
|
||||
contents: [{
|
||||
uri: uri.href,
|
||||
text: `User ${userId} data here`
|
||||
}]
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
### Tool with Sampling
|
||||
```typescript
|
||||
server.registerTool(
|
||||
'summarize',
|
||||
{
|
||||
title: 'Text Summarizer',
|
||||
description: 'Summarize text using LLM',
|
||||
inputSchema: { text: z.string() },
|
||||
outputSchema: { summary: z.string() }
|
||||
},
|
||||
async ({ text }) => {
|
||||
const response = await server.server.createMessage({
|
||||
messages: [{
|
||||
role: 'user',
|
||||
content: { type: 'text', text: `Summarize: ${text}` }
|
||||
}],
|
||||
maxTokens: 500
|
||||
});
|
||||
|
||||
const summary = response.content.type === 'text' ?
|
||||
response.content.text : 'Unable to summarize';
|
||||
const output = { summary };
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify(output) }],
|
||||
structuredContent: output
|
||||
};
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
### Prompt with Completion
|
||||
```typescript
|
||||
import { completable } from '@modelcontextprotocol/sdk/server/completable.js';
|
||||
|
||||
server.registerPrompt(
|
||||
'review',
|
||||
{
|
||||
title: 'Code Review',
|
||||
description: 'Review code with specific focus',
|
||||
argsSchema: {
|
||||
language: completable(z.string(), value =>
|
||||
['typescript', 'python', 'javascript', 'java']
|
||||
.filter(l => l.startsWith(value))
|
||||
),
|
||||
code: z.string()
|
||||
}
|
||||
},
|
||||
({ language, code }) => ({
|
||||
messages: [{
|
||||
role: 'user',
|
||||
content: {
|
||||
type: 'text',
|
||||
text: `Review this ${language} code:\n\n${code}`
|
||||
}
|
||||
}]
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
```typescript
|
||||
server.registerTool(
|
||||
'risky-operation',
|
||||
{
|
||||
title: 'Risky Operation',
|
||||
description: 'An operation that might fail',
|
||||
inputSchema: { input: z.string() },
|
||||
outputSchema: { result: z.string() }
|
||||
},
|
||||
async ({ input }) => {
|
||||
try {
|
||||
const result = await performRiskyOperation(input);
|
||||
const output = { result };
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify(output) }],
|
||||
structuredContent: output
|
||||
};
|
||||
} catch (err: unknown) {
|
||||
const error = err as Error;
|
||||
return {
|
||||
content: [{ type: 'text', text: `Error: ${error.message}` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
```
|
||||
57
.github/prompts/create-commit-title.prompt.md
vendored
57
.github/prompts/create-commit-title.prompt.md
vendored
@@ -1,49 +1,16 @@
|
||||
---
|
||||
agent: 'agent'
|
||||
description: 'Generate an 80-character git commit title for the local diff'
|
||||
mode: 'agent'
|
||||
model: GPT-5-Codex (Preview)
|
||||
description: 'Generate an 80-character git commit title for the local diff.'
|
||||
---
|
||||
|
||||
# Generate Commit Title
|
||||
**Goal:** Provide a ready-to-paste git commit title (<= 80 characters) that captures the most important local changes since `HEAD`.
|
||||
|
||||
## Purpose
|
||||
Provide a single-line, ready-to-paste git commit title (<= 80 characters) that reflects the most important local changes since `HEAD`.
|
||||
|
||||
## Input to collect
|
||||
- Run exactly one command to view the local diff:
|
||||
```@terminal
|
||||
git diff HEAD
|
||||
```
|
||||
|
||||
## How to decide the title
|
||||
1. From the diff, find the dominant area (e.g., `src/modules/*`, `doc/devdocs/**`) and the change type (bug fix, docs update, config tweak).
|
||||
2. Draft an imperative, plain-ASCII title that:
|
||||
- Mentions the primary component when obvious (e.g., `FancyZones:` or `Docs:`)
|
||||
- Stays within 80 characters and has no trailing punctuation
|
||||
|
||||
## Final output
|
||||
- Reply with only the commit title on a single line—no extra text.
|
||||
|
||||
## PR title convention (when asked)
|
||||
Use Conventional Commits style:
|
||||
|
||||
`<type>(<scope>): <summary>`
|
||||
|
||||
**Allowed types**
|
||||
- feat, fix, docs, refactor, perf, test, build, ci, chore
|
||||
|
||||
**Scope rules**
|
||||
- Use a short, PowerToys-focused scope (one word preferred). Common scopes:
|
||||
- Core: `runner`, `settings-ui`, `common`, `docs`, `build`, `ci`, `installer`, `gpo`, `dsc`
|
||||
- Modules: `fancyzones`, `powerrename`, `awake`, `colorpicker`, `imageresizer`, `keyboardmanager`, `mouseutils`, `peek`, `hosts`, `file-locksmith`, `screen-ruler`, `text-extractor`, `cropandlock`, `paste`, `powerlauncher`
|
||||
- If unclear, pick the closest module or subsystem; omit only if unavoidable
|
||||
|
||||
**Summary rules**
|
||||
- Imperative, present tense (“add”, “update”, “remove”, “fix”)
|
||||
- Keep it <= 72 characters when possible; be specific, avoid “misc changes”
|
||||
|
||||
**Examples**
|
||||
- `feat(fancyzones): add canvas template duplication`
|
||||
- `fix(mouseutils): guard crosshair toggle when dpi info missing`
|
||||
- `docs(runner): document tray icon states`
|
||||
- `build(installer): align wix v5 suffix flag`
|
||||
- `ci(ci): cache pipeline artifacts for x64`
|
||||
**Workflow:**
|
||||
1. Run a single command to view the local diff since the last commit:
|
||||
```@terminal
|
||||
git diff HEAD
|
||||
```
|
||||
2. From that diff, identify the dominant area (reference key paths like `src/modules/*`, `doc/devdocs/**`, etc.), the type of change (bug fix, docs update, config tweak), and any notable impact.
|
||||
3. Draft a concise, imperative commit title summarizing the dominant change. Keep it plain ASCII, <= 80 characters, and avoid trailing punctuation. Mention the primary component when obvious (for example `FancyZones:` or `Docs:`).
|
||||
4. Respond with only the final commit title on a single line so it can be pasted directly into `git commit`.
|
||||
|
||||
8
.github/prompts/create-pr-summary.prompt.md
vendored
8
.github/prompts/create-pr-summary.prompt.md
vendored
@@ -1,10 +1,9 @@
|
||||
---
|
||||
agent: 'agent'
|
||||
description: 'Generate a PowerToys-ready pull request description from the local diff'
|
||||
mode: 'agent'
|
||||
model: GPT-5-Codex (Preview)
|
||||
description: 'Generate a PowerToys-ready pull request description from the local diff.'
|
||||
---
|
||||
|
||||
# Generate PR Summary
|
||||
|
||||
**Goal:** Produce a ready-to-paste PR title and description that follows PowerToys conventions by comparing the current branch against a user-selected target branch.
|
||||
|
||||
**Repo guardrails:**
|
||||
@@ -21,4 +20,3 @@ description: 'Generate a PowerToys-ready pull request description from the local
|
||||
5. Confirm validation: list tests executed with results or state why tests were skipped in line with repo guidance.
|
||||
6. Load `.github/pull_request_template.md`, mirror its section order, and populate it with the gathered facts. Include only relevant checklist entries, marking them `[x]/[ ]` and noting any intentional omissions as "N/A".
|
||||
7. Present the filled template inside a fenced ```markdown code block with no extra commentary so it is ready to paste into a PR, clearly flagging any placeholders that still need user input.
|
||||
8. Prepend the PR title above the filled template, applying the Conventional Commit type/scope rules from `.github/prompts/create-commit-title.prompt.md`; pick the dominant component from the diff and keep the title concise and imperative.
|
||||
|
||||
72
.github/prompts/fix-issue.prompt.md
vendored
72
.github/prompts/fix-issue.prompt.md
vendored
@@ -1,72 +0,0 @@
|
||||
---
|
||||
agent: 'agent'
|
||||
description: 'Execute the fix for a GitHub issue using the previously generated implementation plan'
|
||||
---
|
||||
|
||||
# Fix GitHub Issue
|
||||
|
||||
## Dependencies
|
||||
Source review prompt (for generating the implementation plan if missing):
|
||||
- .github/prompts/review-issue.prompt.md
|
||||
|
||||
Required plan file (single source of truth):
|
||||
- Generated Files/issueReview/{{issue_number}}/implementation-plan.md
|
||||
|
||||
## Dependency Handling
|
||||
1) If `implementation-plan.md` exists → proceed.
|
||||
2) If missing → run the review prompt:
|
||||
- Invoke: `.github/prompts/review-issue.prompt.md`
|
||||
- Pass: `issue_number={{issue_number}}`
|
||||
- Then re-check for `implementation-plan.md`.
|
||||
3) If still missing → stop and generate:
|
||||
- `Generated Files/issueFix/{{issue_number}}/manual-steps.md` containing:
|
||||
“implementation-plan.md not found; please run .github/prompts/review-issue.prompt.md for #{{issue_number}}.”
|
||||
|
||||
# GOAL
|
||||
For **#{{issue_number}}**:
|
||||
- Use implementation-plan.md as the single authority.
|
||||
- Apply code and test changes directly in the repository.
|
||||
- Produce a PR-ready description.
|
||||
|
||||
# OUTPUT FILES
|
||||
1) Generated Files/issueFix/{{issue_number}}/pr-description.md
|
||||
2) Generated Files/issueFix/{{issue_number}}/manual-steps.md # only if human interaction or external setup is required
|
||||
|
||||
# EXECUTION RULES
|
||||
1) Read implementation-plan.md and execute:
|
||||
- Layers & Files → edit/create as listed
|
||||
- Pattern Choices → follow repository conventions
|
||||
- Fundamentals (perf, security, compatibility, accessibility)
|
||||
- Logging & Exceptions
|
||||
- Telemetry (only if explicitly included in the plan)
|
||||
- Risks & Mitigations
|
||||
- Tests to Add
|
||||
2) Locate affected files via `rg` or `git grep`.
|
||||
3) Add/update tests to enforce the fixed behavior.
|
||||
4) If any ambiguity exists, add:
|
||||
// TODO(Human input needed): <clarification needed>
|
||||
5) Verify locally: build & tests run successfully.
|
||||
|
||||
# pr-description.md should include:
|
||||
- Title: `Fix: <short summary> (#{{issue_number}})`
|
||||
- What changed and why the fix works
|
||||
- Files or modules touched
|
||||
- Risks & mitigations (implemented)
|
||||
- Tests added/updated and how to run them
|
||||
- Telemetry behavior (if applicable)
|
||||
- Validation / reproduction steps
|
||||
- `Closes #{{issue_number}}`
|
||||
|
||||
# manual-steps.md (only if needed)
|
||||
- List required human actions: secrets, config, approvals, missing info, or code comments requiring human decisions.
|
||||
|
||||
# IMPORTANT
|
||||
- Apply code and tests directly; do not produce patch files.
|
||||
- Follow implementation-plan.md as the source of truth.
|
||||
- Insert comments for human review where a decision or input is required.
|
||||
- Use repository conventions and deterministic, minimal changes.
|
||||
|
||||
# FINALIZE
|
||||
- Write pr-description.md
|
||||
- Write manual-steps.md only if needed
|
||||
- Print concise success message or note items requiring human interaction
|
||||
70
.github/prompts/fix-pr-active-comments.prompt.md
vendored
70
.github/prompts/fix-pr-active-comments.prompt.md
vendored
@@ -1,70 +0,0 @@
|
||||
---
|
||||
description: 'Fix active pull request comments with scoped changes'
|
||||
name: 'fix-pr-active-comments'
|
||||
agent: 'agent'
|
||||
argument-hint: 'PR number or active PR URL'
|
||||
---
|
||||
|
||||
# Fix Active PR Comments
|
||||
|
||||
## Mission
|
||||
Resolve active pull request comments by applying only simple fixes. For complex refactors, write a plan instead of changing code.
|
||||
|
||||
## Scope & Preconditions
|
||||
- You must have an active pull request context or a provided PR number.
|
||||
- Only implement simple changes. Do not implement large refactors.
|
||||
- If required context is missing, request it and stop.
|
||||
|
||||
## Inputs
|
||||
- Required: ${input:pr_number:PR number or URL}
|
||||
- Optional: ${input:comment_scope:files or areas to focus on}
|
||||
- Optional: ${input:fixing_guidelines:additional fixing guidelines from the user}
|
||||
|
||||
## Workflow
|
||||
1. Locate all active (unresolved) PR review comments for the given PR.
|
||||
2. For each comment, classify the change scope:
|
||||
- Simple change: limited edits, localized fix, low risk, no broad redesign.
|
||||
- Large refactor: multi-file redesign, architecture change, or risky behavior change.
|
||||
3. For each large refactor request:
|
||||
- Do not modify code.
|
||||
- Write a planning document to Generated Files/prReview/${input:pr_number}/fixPlan/.
|
||||
4. For each simple change request:
|
||||
- Implement the fix with minimal edits.
|
||||
- Run quick checks if needed.
|
||||
- Commit and push the change.
|
||||
5. For comments that seem invalid, unclear, or not applicable (even if simple):
|
||||
- Do not change code.
|
||||
- Add the item to a summary table in Generated Files/prReview/${input:pr_number}/fixPlan/overview.md.
|
||||
- Consult back to the end user in a friendly, polite tone.
|
||||
6. Respond to each comment that you fixed:
|
||||
- Reply in the active conversation.
|
||||
- Use a polite or friendly tone.
|
||||
- Keep the response under 200 words.
|
||||
- Resolve the comment after replying.
|
||||
|
||||
## Output Expectations
|
||||
- Simple fixes: code changes committed and pushed.
|
||||
- Large refactors: a plan file saved to Generated Files/prReview/${input:pr_number}/fixPlan/.
|
||||
- Invalid or unclear comments: captured in Generated Files/prReview/${input:pr_number}/fixPlan/overview.md.
|
||||
- Each fixed comment has a reply under 200 words and is resolved.
|
||||
|
||||
## Plan File Template
|
||||
Use this template for each large refactor item:
|
||||
|
||||
# Fix Plan: <short title>
|
||||
|
||||
## Context
|
||||
- Comment link:
|
||||
- Impacted areas:
|
||||
|
||||
## Overview Table Template
|
||||
Use this table in Generated Files/prReview/${input:pr_number}/fixPlan/overview.md:
|
||||
|
||||
| Comment link | Summary | Reason not applied | Suggested follow-up |
|
||||
| --- | --- | --- | --- |
|
||||
| | | | |
|
||||
|
||||
## Quality Assurance
|
||||
- Verify plan file path exists.
|
||||
- Ensure no code changes were made for large refactor items.
|
||||
- Confirm replies are under 200 words and comments are resolved.
|
||||
16
.github/prompts/fix-spelling.prompt.md
vendored
16
.github/prompts/fix-spelling.prompt.md
vendored
@@ -1,16 +1,15 @@
|
||||
---
|
||||
agent: 'agent'
|
||||
description: 'Resolve Code scanning / check-spelling comments on the active PR'
|
||||
mode: 'agent'
|
||||
model: GPT-5-Codex (Preview)
|
||||
description: 'Resolve Code scanning / check-spelling comments on the active PR.'
|
||||
---
|
||||
|
||||
# Fix Spelling Comments
|
||||
|
||||
**Goal:** Clear every outstanding GitHub pull request comment created by the `Code scanning / check-spelling` workflow by explicitly allowing intentional terms.
|
||||
|
||||
**Guardrails:**
|
||||
- Update only discussion threads authored by `github-actions` or `github-actions[bot]` that mention `Code scanning results / check-spelling`.
|
||||
- Prefer improving the wording in the originally flagged file when it clarifies intent without changing meaning; if the wording is already clear/standard for the context, handle it via `.github/actions/spell-check/expect.txt` and reuse existing entries.
|
||||
- Limit edits to the flagged text and `.github/actions/spell-check/expect.txt`; leave all other files and topics untouched.
|
||||
- Resolve findings solely by editing `.github/actions/spell-check/expect.txt`; reuse existing entries.
|
||||
- Leave all other files and topics untouched.
|
||||
|
||||
**Prerequisites:**
|
||||
- Install GitHub CLI if it is not present: `winget install GitHub.cli`.
|
||||
@@ -19,6 +18,5 @@ description: 'Resolve Code scanning / check-spelling comments on the active PR'
|
||||
**Workflow:**
|
||||
1. Determine the active pull request with a single `gh pr view --json number` call (default to the current branch).
|
||||
2. Fetch all PR discussion data once via `gh pr view --json comments,reviews` and filter to check-spelling comments authored by `github-actions` or `github-actions[bot]` that are not minimized; when several remain, process only the most recent comment body.
|
||||
3. For each flagged token, first consider tightening or rephrasing the original text to avoid the false positive while keeping the meaning intact; if the existing wording is already normal and professional for the context, proceed to allowlisting instead of changing it.
|
||||
4. When allowlisting, review `.github/actions/spell-check/expect.txt` for an equivalent term (for example an existing lowercase variant); when found, reuse that normalized term rather than adding a new entry, even if the flagged token differs only by casing. Only add a new entry after confirming no equivalent already exists.
|
||||
5. Add any remaining missing token to `.github/actions/spell-check/expect.txt`, keeping surrounding formatting intact.
|
||||
3. For each flagged token, review `.github/actions/spell-check/expect.txt` for an equivalent term (for example an existing lowercase variant); when found, reuse that normalized term rather than adding a new entry, even if the flagged token differs only by casing. Only add a new entry after confirming no equivalent already exists.
|
||||
4. Add any remaining missing token to `.github/actions/spell-check/expect.txt`, keeping surrounding formatting intact.
|
||||
165
.github/prompts/review-issue.prompt.md
vendored
165
.github/prompts/review-issue.prompt.md
vendored
@@ -1,165 +0,0 @@
|
||||
---
|
||||
agent: 'agent'
|
||||
description: 'Review a GitHub issue, score it (0-100), and generate an implementation plan'
|
||||
---
|
||||
|
||||
# Review GitHub Issue
|
||||
|
||||
## Goal
|
||||
For **#{{issue_number}}** produce:
|
||||
1) `Generated Files/issueReview/{{issue_number}}/overview.md`
|
||||
2) `Generated Files/issueReview/{{issue_number}}/implementation-plan.md`
|
||||
|
||||
## Inputs
|
||||
Figure out required inputs {{issue_number}} from the invocation context; if anything is missing, ask for the value or note it as a gap.
|
||||
|
||||
# CONTEXT (brief)
|
||||
Ground evidence using `gh issue view {{issue_number}} --json number,title,body,author,createdAt,updatedAt,state,labels,milestone,reactions,comments,linkedPullRequests`, download images via MCP `github_issue_images` to better understand the issue context. Finally, use MCP `github_issue_attachments` to download logs with parameter `extractFolder` as `Generated Files/issueReview/{{issue_number}}/logs`, and analyze the downloaded logs if available to identify relevant issues. Locate the source code in the current workspace (use `rg`/`git grep` as needed). Link related issues and PRs.
|
||||
|
||||
## When to call MCP tools
|
||||
If the following MCP "github-artifacts" tools are available in the environment, use them:
|
||||
- `github_issue_images`: use when the issue/PR likely contains screenshots or other visual evidence (UI bugs, glitches, design problems).
|
||||
- `github_issue_attachments`: use when the issue/PR mentions attached ZIPs (PowerToysReport_*.zip, logs.zip, debug.zip) or asks to analyze logs/diagnostics. Always provide `extractFolder` as `Generated Files/issueReview/{{issue_number}}/logs`
|
||||
|
||||
If these tools are not available (not listed by the runtime), start the MCP server "github-artifacts" first.
|
||||
|
||||
# OVERVIEW.MD
|
||||
## Summary
|
||||
Issue, state, milestone, labels. **Signals**: 👍/❤️/👎, comment count, last activity, linked PRs.
|
||||
|
||||
## At-a-Glance Score Table
|
||||
Present all ratings in a compact table for quick scanning:
|
||||
|
||||
| Dimension | Score | Assessment | Key Drivers |
|
||||
|-----------|-------|------------|-------------|
|
||||
| **A) Business Importance** | X/100 | Low/Medium/High | Top 2 factors with scores |
|
||||
| **B) Community Excitement** | X/100 | Low/Medium/High | Top 2 factors with scores |
|
||||
| **C) Technical Feasibility** | X/100 | Low/Medium/High | Top 2 factors with scores |
|
||||
| **D) Requirement Clarity** | X/100 | Low/Medium/High | Top 2 factors with scores |
|
||||
| **Overall Priority** | X/100 | Low/Medium/High/Critical | Average or weighted summary |
|
||||
| **Effort Estimate** | X days (T-shirt) | XS/S/M/L/XL/XXL/Epic | Type: bug/feature/chore |
|
||||
| **Similar Issues Found** | X open, Y closed | — | Quick reference to related work |
|
||||
| **Potential Assignees** | @username, @username | — | Top contributors to module |
|
||||
|
||||
**Assessment bands**: 0-25 Low, 26-50 Medium, 51-75 High, 76-100 Critical
|
||||
|
||||
## Ratings (0–100) — add evidence & short rationale
|
||||
### A) Business Importance
|
||||
- Labels (priority/security/regression): **≤35**
|
||||
- Milestone/roadmap: **≤25**
|
||||
- Customer/contract impact: **≤20**
|
||||
- Unblocks/platform leverage: **≤20**
|
||||
### B) Community Excitement
|
||||
- 👍+❤️ normalized: **≤45**
|
||||
- Comment volume & unique participants: **≤25**
|
||||
- Recent activity (≤30d): **≤15**
|
||||
- Duplicates/related issues: **≤15**
|
||||
### C) Technical Feasibility
|
||||
- Contained surface/clear seams: **≤30**
|
||||
- Existing patterns/utilities: **≤25**
|
||||
- Risk (perf/sec/compat) manageable: **≤25**
|
||||
- Testability & CI support: **≤20**
|
||||
### D) Requirement Clarity
|
||||
- Behavior/repro/constraints: **≤60**
|
||||
- Non-functionals (perf/sec/i18n/a11y): **≤25**
|
||||
- Decision owners/acceptance signals: **≤15**
|
||||
|
||||
## Effort
|
||||
Days + **T-shirt** (XS 0.5–1d, S 1–2, M 2–4, L 4–7, XL 7–14, XXL 14–30, Epic >30).
|
||||
Type/level: bug/feature/chore/docs/refactor/test-only; severity/value tier.
|
||||
|
||||
## Suggested Actions
|
||||
Provide actionable recommendations for issue triage and assignment:
|
||||
|
||||
### A) Requirement Clarification (if Clarity score <50)
|
||||
**When Requirement Clarity (Dimension D) is Medium or Low:**
|
||||
- Identify specific gaps in issue description: missing repro steps, unclear expected behavior, undefined acceptance criteria, missing non-functional requirements
|
||||
- Draft 3-5 clarifying questions to post as issue comment
|
||||
- Suggest additional information needed: screenshots, logs, environment details, OS version, PowerToys version, error messages
|
||||
- If behavior is ambiguous, propose 2-3 interpretation scenarios and ask reporter to confirm
|
||||
- Example questions:
|
||||
- "Can you provide exact steps to reproduce this issue?"
|
||||
- "What is the expected behavior vs. what you're actually seeing?"
|
||||
- "Does this happen on Windows 10, 11, or both?"
|
||||
- "Can you attach a screenshot or screen recording?"
|
||||
|
||||
### B) Correct Label Suggestions
|
||||
- Analyze issue type, module, and severity to suggest missing or incorrect labels
|
||||
- Recommend labels from: `Issue-Bug`, `Issue-Feature`, `Issue-Docs`, `Issue-Task`, `Priority-High`, `Priority-Medium`, `Priority-Low`, `Needs-Triage`, `Needs-Author-Feedback`, `Product-<ModuleName>`, etc.
|
||||
- If Requirement Clarity is low (<50), add `Needs-Author-Feedback` label
|
||||
- If current labels are incorrect or incomplete, provide specific label changes with rationale
|
||||
|
||||
### C) Find Similar Issues & Past Fixes
|
||||
- Search for similar issues using `gh issue list --search "keywords" --state all --json number,title,state,closedAt`
|
||||
- Identify patterns: duplicate issues, related bugs, or similar feature requests
|
||||
- For closed issues, find linked PRs that fixed them: check `linkedPullRequests` in issue data
|
||||
- Provide 3-5 examples of similar issues with format: `#<number> - <title> (closed by PR #<pr>)` or `(still open)`
|
||||
|
||||
### D) Identify Subject Matter Experts
|
||||
- Use git blame/log to find who fixed similar issues in the past
|
||||
- Search for PR authors who touched relevant files: `git log --all --format='%aN' -- <file_paths> | sort | uniq -c | sort -rn | head -5`
|
||||
- Check issue/PR history for frequent contributors to the affected module
|
||||
- Suggest 2-3 potential assignees with context: `@<username> - <reason>` (e.g., "fixed similar rendering bug in #12345", "maintains FancyZones module")
|
||||
|
||||
### E) Semantic Search for Related Work
|
||||
- Use semantic_search tool to find similar issues, code patterns, or past discussions
|
||||
- Search queries should include: issue keywords, module names, error messages, feature descriptions
|
||||
- Cross-reference semantic results with GitHub issue search for comprehensive coverage
|
||||
|
||||
**Output format for Suggested Actions section in overview.md:**
|
||||
```markdown
|
||||
## Suggested Actions
|
||||
|
||||
### Clarifying Questions (if Clarity <50)
|
||||
Post these questions as issue comment to gather missing information:
|
||||
1. <question>
|
||||
2. <question>
|
||||
3. <question>
|
||||
|
||||
**Recommended label**: `Needs-Author-Feedback`
|
||||
|
||||
### Label Recommendations
|
||||
- Add: `<label>` - <reason>
|
||||
- Remove: `<label>` - <reason>
|
||||
- Current labels are appropriate ✓
|
||||
|
||||
### Similar Issues Found
|
||||
1. #<number> - <title> (<state>, closed by PR #<pr> on <date>)
|
||||
2. #<number> - <title> (<state>)
|
||||
...
|
||||
|
||||
### Potential Assignees
|
||||
- @<username> - <reason>
|
||||
- @<username> - <reason>
|
||||
|
||||
### Related Code/Discussions
|
||||
- <semantic search findings>
|
||||
```
|
||||
|
||||
# IMPLEMENTATION-PLAN.MD
|
||||
1) **Problem Framing** — restate problem; current vs expected; scope boundaries.
|
||||
2) **Layers & Files** — layers (UI/domain/data/infra/build). For each, list **files/dirs to modify** and **new files** (exact paths + why). Prefer repo patterns; cite examples/PRs.
|
||||
3) **Pattern Choices** — reuse existing; if new, justify trade-offs & transition.
|
||||
4) **Fundamentals** (brief plan or N/A + reason):
|
||||
- Performance (hot paths, allocs, caching/streaming)
|
||||
- Security (validation, authN/Z, secrets, SSRF/XSS/CSRF)
|
||||
- G11N/L10N (resources, number/date, pluralization)
|
||||
- Compatibility (public APIs, formats, OS/runtime/toolchain)
|
||||
- Extensibility (DI seams, options/flags, plugin points)
|
||||
- Accessibility (roles, labels, focus, keyboard, contrast)
|
||||
- SOLID & repo conventions (naming, folders, dependency direction)
|
||||
5) **Logging & Exception Handling**
|
||||
- Where to log; levels; structured fields; correlation/traces.
|
||||
- What to catch vs rethrow; retries/backoff; user-visible errors.
|
||||
- **Privacy**: never log secrets/PII; redaction policy.
|
||||
6) **Telemetry (optional — business metrics only)**
|
||||
- Events/metrics (name, when, props); success signal; privacy/sampling; dashboards/alerts.
|
||||
7) **Risks & Mitigations** — flags/canary/shadow-write/config guards.
|
||||
8) **Task Breakdown (agent-ready)** — table (leave a blank line before the header so Markdown renders correctly):
|
||||
|
||||
| Task | Intent | Files/Areas | Steps | Tests (brief) | Owner (Agent/Human) | Human interaction needed? (why) |
|
||||
|---|---|---|---|---|---|---|
|
||||
|
||||
9) **Tests to Add (only)**
|
||||
- **Unit**: targets, cases (success/edge/error), mocks/fixtures, path, notes.
|
||||
- **UI** (if applicable): flows, locator strategy, env/data/flags, path, flake mitigation.
|
||||
198
.github/prompts/review-pr.prompt.md
vendored
198
.github/prompts/review-pr.prompt.md
vendored
@@ -1,198 +0,0 @@
|
||||
---
|
||||
agent: 'agent'
|
||||
description: 'Perform a comprehensive PR review with per-step Markdown and machine-readable outputs'
|
||||
---
|
||||
|
||||
# Review Pull Request
|
||||
|
||||
**Goal**: Given `{{pr_number}}`, run a *one-topic-per-step* review. Write files to `Generated Files/prReview/{{pr_number}}/` (replace `{{pr_number}}` with the integer). Emit machine‑readable blocks for a GitHub MCP to post review comments.
|
||||
|
||||
## PR selection
|
||||
Resolve the target PR using these fallbacks in order:
|
||||
1. Parse the invocation text for an explicit identifier (first integer following patterns such as a leading hash and digits or the text `PR:` followed by digits).
|
||||
2. If no PR is found yet, locate the newest `Generated Files/prReview/_batch/batch-overview-*.md` file (highest timestamp in filename, fallback newest mtime) and take the first entry in its `## PRs` list whose review folder is missing `00-OVERVIEW.md` or contains `__error.flag`.
|
||||
3. If the batch file has no pending PRs, query assignments with `gh pr list --assignee @me --state open --json number,updatedAt --limit 20` and pick the most recently updated PR that does not already have a completed review folder.
|
||||
4. If still unknown, run `gh pr view --json number` in the current branch and use that result when it is unambiguous.
|
||||
5. If every step above fails, prompt the user for a PR number before proceeding.
|
||||
|
||||
## Fetch PR data with `gh`
|
||||
- `gh pr view {{pr_number}} --json number,baseRefName,headRefName,baseRefOid,headRefOid,changedFiles,files`
|
||||
- `gh api repos/:owner/:repo/pulls/{{pr_number}}/files?per_page=250` # patches for line mapping
|
||||
|
||||
### Incremental review workflow
|
||||
1. **Check for existing review**: Read `Generated Files/prReview/{{pr_number}}/00-OVERVIEW.md`
|
||||
2. **Extract state**: Parse `Last reviewed SHA:` from review metadata section
|
||||
3. **Detect changes**: Run `Get-PrIncrementalChanges.ps1 -PullRequestNumber {{pr_number}} -LastReviewedCommitSha {{sha}}`
|
||||
4. **Analyze result**:
|
||||
- `NeedFullReview: true` → Review all files in the PR
|
||||
- `NeedFullReview: false` and `IsIncremental: true` → Review only files in `ChangedFiles` array
|
||||
- `ChangedFiles` is empty → No changes, skip review (update iteration history with "No changes since last review")
|
||||
5. **Apply smart filtering**: Use the file patterns in smart step filtering table to skip irrelevant steps
|
||||
6. **Update metadata**: After completing review, save current `headRefOid` as `Last reviewed SHA:` in `00-OVERVIEW.md`
|
||||
|
||||
### Reusable PowerShell scripts
|
||||
Scripts live in `.github/review-tools/` to avoid repeated manual approvals during PR reviews:
|
||||
|
||||
| Script | Usage |
|
||||
| --- | --- |
|
||||
| `.github/review-tools/Get-GitHubRawFile.ps1` | Download a repository file at a given ref, optionally with line numbers. |
|
||||
| `.github/review-tools/Get-GitHubPrFilePatch.ps1` | Fetch the unified diff for a specific file within a pull request via `gh api`. |
|
||||
| `.github/review-tools/Get-PrIncrementalChanges.ps1` | Compare last reviewed SHA with current PR head to identify incremental changes. Returns JSON with changed files, new commits, and whether full review is needed. |
|
||||
| `.github/review-tools/Test-IncrementalReview.ps1` | Test helper to preview incremental review detection for a PR. Use before running full review to see what changed. |
|
||||
|
||||
Always prefer these scripts (or new ones added under `.github/review-tools/`) over raw `gh api` or similar shell commands so the review flow does not trigger interactive approval prompts.
|
||||
|
||||
## Output files
|
||||
Folder: `Generated Files/prReview/{{pr_number}}/`
|
||||
Files: `00-OVERVIEW.md`, `01-functionality.md`, `02-compatibility.md`, `03-performance.md`, `04-accessibility.md`, `05-security.md`, `06-localization.md`, `07-globalization.md`, `08-extensibility.md`, `09-solid-design.md`, `10-repo-patterns.md`, `11-docs-automation.md`, `12-code-comments.md`, `13-copilot-guidance.md` *(only if guidance md exists).*
|
||||
- **Write-after-step rule:** Immediately after completing each TODO step, persist that step's markdown file before proceeding to the next. Generate `00-OVERVIEW.md` only after every step file has been refreshed for the current run.
|
||||
|
||||
## Iteration management
|
||||
- Determine the current review iteration by reading `00-OVERVIEW.md` (look for `Review iteration:`). If missing, assume iteration `1`.
|
||||
- Extract the last reviewed SHA from `00-OVERVIEW.md` (look for `Last reviewed SHA:` in the review metadata section). If missing, this is iteration 1.
|
||||
- **Incremental review detection**:
|
||||
1. Call `.github/review-tools/Get-PrIncrementalChanges.ps1 -PullRequestNumber {{pr_number}} -LastReviewedCommitSha {{last_sha}}` to get delta analysis.
|
||||
2. Parse the JSON result to determine if incremental review is possible (`IsIncremental: true`, `NeedFullReview: false`).
|
||||
3. If force-push detected or first review, proceed with full review of all changed files.
|
||||
4. If incremental, review only the files listed in `ChangedFiles` array and apply smart step filtering (see below).
|
||||
- Increment the iteration for each review run and propagate the new value to all step files and the overview.
|
||||
- Preserve prior iteration notes by keeping/expanding an `## Iteration history` section in each markdown file, appending the newest summary under `### Iteration <N>`.
|
||||
- Summaries should capture key deltas since the previous iteration so reruns can pick up context quickly.
|
||||
- **After review completion**, update `Last reviewed SHA:` in `00-OVERVIEW.md` with the current `headRefOid` and update the timestamp.
|
||||
|
||||
### Smart step filtering (incremental reviews only)
|
||||
When performing incremental review, skip steps that are irrelevant based on changed file types:
|
||||
|
||||
| File pattern | Required steps | Skippable steps |
|
||||
| --- | --- | --- |
|
||||
| `**/*.cs`, `**/*.cpp`, `**/*.h` | Functionality, Compatibility, Performance, Security, SOLID, Repo patterns, Code comments | (depends on files) |
|
||||
| `**/*.resx`, `**/Resources/*.xaml` | Localization, Globalization | Most others |
|
||||
| `**/*.md` (docs) | Docs & automation | Most others (unless copilot guidance) |
|
||||
| `**/*copilot*.md`, `.github/prompts/*.md` | Copilot guidance, Docs & automation | Most others |
|
||||
| `**/*.csproj`, `**/*.vcxproj`, `**/packages.config` | Compatibility, Security, Repo patterns | Localization, Globalization, Accessibility |
|
||||
| `**/UI/**`, `**/*View.xaml` | Accessibility, Localization | Performance (unless perf-sensitive controls) |
|
||||
|
||||
**Default**: If uncertain or files span multiple categories, run all applicable steps. When in doubt, be conservative and review more rather than less.
|
||||
|
||||
## TODO steps (one concern each)
|
||||
1) Functionality
|
||||
2) Compatibility
|
||||
3) Performance
|
||||
4) Accessibility
|
||||
5) Security
|
||||
6) Localization
|
||||
7) Globalization
|
||||
8) Extensibility
|
||||
9) SOLID principles
|
||||
10) Repo patterns
|
||||
11) Docs & automation coverage for the changes
|
||||
12) Code comments
|
||||
13) Copilot guidance (conditional): if changed folders contain `*copilot*.md` or `.github/prompts/*.md`, review diffs **against** that guidance and write `13-copilot-guidance.md` (omit if none).
|
||||
|
||||
## Per-step file template (use verbatim)
|
||||
```md
|
||||
# <STEP TITLE>
|
||||
**PR:** (populate with PR identifier) — Base:<baseRefName> Head:<headRefName>
|
||||
**Review iteration:** ITERATION
|
||||
|
||||
## Iteration history
|
||||
- Maintain subsections titled `### Iteration N` in reverse chronological order (append the latest at the top) with 2–4 bullet highlights.
|
||||
|
||||
### Iteration ITERATION
|
||||
- <Latest key point 1>
|
||||
- <Latest key point 2>
|
||||
|
||||
## Checks executed
|
||||
- List the concrete checks for *this step only* (5–10 bullets).
|
||||
|
||||
## Findings
|
||||
(If none, write **None**. Defaults have one or more blocks:)
|
||||
|
||||
```mcp-review-comment
|
||||
{"file":"relative/path.ext","start_line":123,"end_line":125,"severity":"high|medium|low|info","tags":["<step-slug>","pr-tag-here"],"related_files":["optional/other/file1"],"body":"Problem → Why it matters → Concrete fix. If spans multiple files, name them here."}
|
||||
```
|
||||
Use the second tag to encode the PR number.
|
||||
|
||||
```
|
||||
## Overview file (`00-OVERVIEW.md`) template
|
||||
```md
|
||||
# PR Review Overview — (populate with PR identifier)
|
||||
**Review iteration:** ITERATION
|
||||
**Changed files:** <n> | **High severity issues:** <count>
|
||||
|
||||
## Review metadata
|
||||
**Last reviewed SHA:** <headRefOid from gh pr view>
|
||||
**Last review timestamp:** <ISO8601 timestamp>
|
||||
**Review mode:** <Full|Incremental (N files changed since iteration X)>
|
||||
**Base ref:** <baseRefName>
|
||||
**Head ref:** <headRefName>
|
||||
|
||||
## Step results
|
||||
Write lines like: `01 Functionality — <OK|Issues|Skipped> (see 01-functionality.md)` … through step 13.
|
||||
Mark steps as "Skipped" when using incremental review smart filtering.
|
||||
|
||||
## Iteration history
|
||||
- Maintain subsections titled `### Iteration N` mirroring the per-step convention with concise deltas and cross-links to the relevant step files.
|
||||
- For incremental reviews, list the specific files that changed and which commits were added.
|
||||
```
|
||||
|
||||
## Line numbers & multi‑file issues
|
||||
- Map head‑side lines from `patch` hunks (`@@ -a,b +c,d @@` → new lines `+c..+c+d-1`).
|
||||
- For cross‑file issues: set the primary `"file"`, list others in `"related_files"`, and name them in `"body"`.
|
||||
|
||||
## Posting (for MCP)
|
||||
- Parse all ```mcp-review-comment``` blocks across step files and post as PR review comments.
|
||||
- If posting isn’t available, still write all files.
|
||||
|
||||
## Constraint
|
||||
Read/analyze only; don't modify code. Keep comments small, specific, and fix‑oriented.
|
||||
|
||||
**Testing**: Use `.github/review-tools/Test-IncrementalReview.ps1 -PullRequestNumber 42374` to preview incremental detection before running full review.
|
||||
|
||||
## Scratch cache for large PRs
|
||||
|
||||
Create a local scratch workspace to progressively summarize diffs and reload state across runs.
|
||||
|
||||
### Paths
|
||||
- Root: `Generated Files/prReview/{{pr_number}}/__tmp/`
|
||||
- Files:
|
||||
- `index.jsonl` — append-only JSON Lines index of artifacts.
|
||||
- `todo-queue.json` — pending items (files/chunks/steps).
|
||||
- `rollup-<step>-v<N>.md` — iterative per-step aggregates.
|
||||
- `file-<hash>.txt` — optional saved chunk text (when needed).
|
||||
|
||||
### JSON schema (per line in `index.jsonl`)
|
||||
```json
|
||||
{"type":"chunk|summary|issue|crosslink",
|
||||
"path":"relative/file.ext","chunk_id":"f-12","step":"functionality|compatibility|...",
|
||||
"base_sha":"...", "head_sha":"...", "range":[start,end], "version":1,
|
||||
"notes":"short text or key:value map", "created_utc":"ISO8601"}
|
||||
```
|
||||
|
||||
### Phases (stateful; resume-safe)
|
||||
0. **Discover** PR + SHAs: `gh pr view <PR> --json baseRefName,headRefName,baseRefOid,headRefOid,files`.
|
||||
1. **Chunk** each changed file (head): split into ~300–600 LOC or ~4k chars; stable `chunk_id` = hash(path+start).
|
||||
- Save `chunk` records. Optionally write `file-<hash>.txt` for expensive chunks.
|
||||
2. **Summarize** per chunk: intent, APIs, risks per TODO step; emit `summary` records (≤600 tokens each).
|
||||
3. **Issues**: convert findings to machine-readable blocks and emit `issue` records (later rendered to step MD).
|
||||
4. **Rollups**: build/update `rollup-<step>-v<N>.md` from `summary`+`issue`. Keep prior versions.
|
||||
5. **Finalize**: write per-step files + `00-OVERVIEW.md` from rollups. Post comments via MCP if available.
|
||||
|
||||
### Re-use & token limits
|
||||
- Always **reload** `index.jsonl` first; skip chunks with same `head_sha` and `range`.
|
||||
- **Incremental review optimization**: When `Get-PrIncrementalChanges.ps1` returns a subset of changed files, load only chunks from those files. Reuse existing chunks/summaries for unchanged files.
|
||||
- Prefer re-summarizing only changed chunks; merge chunk summaries → file summaries → step rollups.
|
||||
- When context is tight, load only the minimal chunk text (or its saved `file-<hash>.txt`) needed for a comment.
|
||||
|
||||
### Original vs diff
|
||||
- Fetch base content when needed: prefer `git show <baseRefName>:<path>`; fallback `gh api repos/:owner/:repo/contents/<path>?ref=<base_sha>` (base64).
|
||||
- Use patch hunks from `gh api .../pulls/<PR>/files` to compute **head** line numbers.
|
||||
|
||||
### Queue-driven loop
|
||||
- Seed `todo-queue.json` with all changed files.
|
||||
- Process: chunk → summarize → detect issues → roll up.
|
||||
- Append to `index.jsonl` after each step; never rewrite previous lines (append-only).
|
||||
|
||||
### Hygiene
|
||||
- `__tmp/` is implementation detail; do not include in final artifacts.
|
||||
- It is safe to delete to force a clean pass; the next run rebuilds it.
|
||||
79
.github/review-tools/Get-GitHubPrFilePatch.ps1
vendored
79
.github/review-tools/Get-GitHubPrFilePatch.ps1
vendored
@@ -1,79 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Retrieves the unified diff patch for a specific file in a GitHub pull request.
|
||||
|
||||
.DESCRIPTION
|
||||
This script fetches the patch content (unified diff format) for a specified file
|
||||
within a pull request. It uses the GitHub CLI (gh) to query the GitHub API and
|
||||
retrieve file change information.
|
||||
|
||||
.PARAMETER PullRequestNumber
|
||||
The pull request number to query.
|
||||
|
||||
.PARAMETER FilePath
|
||||
The relative path to the file in the repository (e.g., "src/modules/main.cpp").
|
||||
|
||||
.PARAMETER RepositoryOwner
|
||||
The GitHub repository owner. Defaults to "microsoft".
|
||||
|
||||
.PARAMETER RepositoryName
|
||||
The GitHub repository name. Defaults to "PowerToys".
|
||||
|
||||
.EXAMPLE
|
||||
.\Get-GitHubPrFilePatch.ps1 -PullRequestNumber 42374 -FilePath "src/modules/cmdpal/main.cpp"
|
||||
Retrieves the patch for main.cpp in PR #42374.
|
||||
|
||||
.EXAMPLE
|
||||
.\Get-GitHubPrFilePatch.ps1 -PullRequestNumber 42374 -FilePath "README.md" -RepositoryOwner "myorg" -RepositoryName "myrepo"
|
||||
Retrieves the patch from a different repository.
|
||||
|
||||
.NOTES
|
||||
Requires GitHub CLI (gh) to be installed and authenticated.
|
||||
Run 'gh auth login' if not already authenticated.
|
||||
|
||||
.LINK
|
||||
https://cli.github.com/
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true, HelpMessage = "Pull request number")]
|
||||
[int]$PullRequestNumber,
|
||||
|
||||
[Parameter(Mandatory = $true, HelpMessage = "Relative path to the file in the repository")]
|
||||
[string]$FilePath,
|
||||
|
||||
[Parameter(Mandatory = $false, HelpMessage = "Repository owner")]
|
||||
[string]$RepositoryOwner = "microsoft",
|
||||
|
||||
[Parameter(Mandatory = $false, HelpMessage = "Repository name")]
|
||||
[string]$RepositoryName = "PowerToys"
|
||||
)
|
||||
|
||||
# Construct GitHub API path for pull request files
|
||||
$apiPath = "repos/$RepositoryOwner/$RepositoryName/pulls/$PullRequestNumber/files?per_page=250"
|
||||
|
||||
# Query GitHub API to get all files in the pull request
|
||||
try {
|
||||
$pullRequestFiles = gh api $apiPath | ConvertFrom-Json
|
||||
} catch {
|
||||
Write-Error "Failed to query GitHub API for PR #$PullRequestNumber. Ensure gh CLI is authenticated. Details: $_"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Find the matching file in the pull request
|
||||
$matchedFile = $pullRequestFiles | Where-Object { $_.filename -eq $FilePath }
|
||||
|
||||
if (-not $matchedFile) {
|
||||
Write-Error "File '$FilePath' not found in PR #$PullRequestNumber."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Check if patch content exists
|
||||
if (-not $matchedFile.patch) {
|
||||
Write-Warning "File '$FilePath' has no patch content (possibly binary or too large)."
|
||||
return
|
||||
}
|
||||
|
||||
# Output the patch content
|
||||
$matchedFile.patch
|
||||
91
.github/review-tools/Get-GitHubRawFile.ps1
vendored
91
.github/review-tools/Get-GitHubRawFile.ps1
vendored
@@ -1,91 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Downloads and displays the content of a file from a GitHub repository at a specific git reference.
|
||||
|
||||
.DESCRIPTION
|
||||
This script fetches the raw content of a file from a GitHub repository using GitHub's raw content API.
|
||||
It can optionally display line numbers and supports any valid git reference (branch, tag, or commit SHA).
|
||||
|
||||
.PARAMETER FilePath
|
||||
The relative path to the file in the repository (e.g., "src/modules/main.cpp").
|
||||
|
||||
.PARAMETER GitReference
|
||||
The git reference (branch name, tag, or commit SHA) to fetch the file from. Defaults to "main".
|
||||
|
||||
.PARAMETER RepositoryOwner
|
||||
The GitHub repository owner. Defaults to "microsoft".
|
||||
|
||||
.PARAMETER RepositoryName
|
||||
The GitHub repository name. Defaults to "PowerToys".
|
||||
|
||||
.PARAMETER ShowLineNumbers
|
||||
When specified, displays line numbers before each line of content.
|
||||
|
||||
.PARAMETER StartLineNumber
|
||||
The starting line number to use when ShowLineNumbers is enabled. Defaults to 1.
|
||||
|
||||
.EXAMPLE
|
||||
.\Get-GitHubRawFile.ps1 -FilePath "README.md" -GitReference "main"
|
||||
Downloads and displays the README.md file from the main branch.
|
||||
|
||||
.EXAMPLE
|
||||
.\Get-GitHubRawFile.ps1 -FilePath "src/runner/main.cpp" -GitReference "dev/feature-branch" -ShowLineNumbers
|
||||
Downloads main.cpp from a feature branch and displays it with line numbers.
|
||||
|
||||
.EXAMPLE
|
||||
.\Get-GitHubRawFile.ps1 -FilePath "LICENSE" -GitReference "abc123def" -ShowLineNumbers -StartLineNumber 10
|
||||
Downloads the LICENSE file from a specific commit and displays it with line numbers starting at 10.
|
||||
|
||||
.NOTES
|
||||
Requires internet connectivity to access GitHub's raw content API.
|
||||
Does not require GitHub CLI authentication for public repositories.
|
||||
|
||||
.LINK
|
||||
https://docs.github.com/en/rest/repos/contents
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true, HelpMessage = "Relative path to the file in the repository")]
|
||||
[string]$FilePath,
|
||||
|
||||
[Parameter(Mandatory = $false, HelpMessage = "Git reference (branch, tag, or commit SHA)")]
|
||||
[string]$GitReference = "main",
|
||||
|
||||
[Parameter(Mandatory = $false, HelpMessage = "Repository owner")]
|
||||
[string]$RepositoryOwner = "microsoft",
|
||||
|
||||
[Parameter(Mandatory = $false, HelpMessage = "Repository name")]
|
||||
[string]$RepositoryName = "PowerToys",
|
||||
|
||||
[Parameter(Mandatory = $false, HelpMessage = "Display line numbers before each line")]
|
||||
[switch]$ShowLineNumbers,
|
||||
|
||||
[Parameter(Mandatory = $false, HelpMessage = "Starting line number for display")]
|
||||
[int]$StartLineNumber = 1
|
||||
)
|
||||
|
||||
# Construct the raw content URL
|
||||
$rawContentUrl = "https://raw.githubusercontent.com/$RepositoryOwner/$RepositoryName/$GitReference/$FilePath"
|
||||
|
||||
# Fetch the file content from GitHub
|
||||
try {
|
||||
$response = Invoke-WebRequest -UseBasicParsing -Uri $rawContentUrl
|
||||
} catch {
|
||||
Write-Error "Failed to fetch file from $rawContentUrl. Details: $_"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Split content into individual lines
|
||||
$contentLines = $response.Content -split "`n"
|
||||
|
||||
# Display the content with or without line numbers
|
||||
if ($ShowLineNumbers) {
|
||||
$currentLineNumber = $StartLineNumber
|
||||
foreach ($line in $contentLines) {
|
||||
Write-Output ("{0:d4}: {1}" -f $currentLineNumber, $line)
|
||||
$currentLineNumber++
|
||||
}
|
||||
} else {
|
||||
$contentLines | ForEach-Object { Write-Output $_ }
|
||||
}
|
||||
173
.github/review-tools/Get-PrIncrementalChanges.ps1
vendored
173
.github/review-tools/Get-PrIncrementalChanges.ps1
vendored
@@ -1,173 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Detects changes between the last reviewed commit and current head of a pull request.
|
||||
|
||||
.DESCRIPTION
|
||||
This script compares a previously reviewed commit SHA with the current head of a pull request
|
||||
to determine what has changed. It helps enable incremental reviews by identifying new commits
|
||||
and modified files since the last review iteration.
|
||||
|
||||
The script handles several scenarios:
|
||||
- First review (no previous SHA provided)
|
||||
- No changes (current SHA matches last reviewed SHA)
|
||||
- Force-push detected (last reviewed SHA no longer in history)
|
||||
- Incremental changes (new commits added since last review)
|
||||
|
||||
.PARAMETER PullRequestNumber
|
||||
The pull request number to analyze.
|
||||
|
||||
.PARAMETER LastReviewedCommitSha
|
||||
The commit SHA that was last reviewed. If omitted, this is treated as a first review.
|
||||
|
||||
.PARAMETER RepositoryOwner
|
||||
The GitHub repository owner. Defaults to "microsoft".
|
||||
|
||||
.PARAMETER RepositoryName
|
||||
The GitHub repository name. Defaults to "PowerToys".
|
||||
|
||||
.OUTPUTS
|
||||
JSON object containing:
|
||||
- PullRequestNumber: The PR number being analyzed
|
||||
- CurrentHeadSha: The current head commit SHA
|
||||
- LastReviewedSha: The last reviewed commit SHA (if provided)
|
||||
- BaseRefName: Base branch name
|
||||
- HeadRefName: Head branch name
|
||||
- IsIncremental: Boolean indicating if incremental review is possible
|
||||
- NeedFullReview: Boolean indicating if a full review is required
|
||||
- ChangedFiles: Array of files that changed (filename, status, additions, deletions)
|
||||
- NewCommits: Array of commits added since last review (sha, message, author, date)
|
||||
- Summary: Human-readable description of changes
|
||||
|
||||
.EXAMPLE
|
||||
.\Get-PrIncrementalChanges.ps1 -PullRequestNumber 42374
|
||||
Analyzes PR #42374 with no previous review (first review scenario).
|
||||
|
||||
.EXAMPLE
|
||||
.\Get-PrIncrementalChanges.ps1 -PullRequestNumber 42374 -LastReviewedCommitSha "abc123def456"
|
||||
Compares current PR state against the last reviewed commit to identify incremental changes.
|
||||
|
||||
.EXAMPLE
|
||||
$changes = .\Get-PrIncrementalChanges.ps1 -PullRequestNumber 42374 -LastReviewedCommitSha "abc123" | ConvertFrom-Json
|
||||
if ($changes.IsIncremental) { Write-Host "Can perform incremental review" }
|
||||
Captures the output as a PowerShell object for further processing.
|
||||
|
||||
.NOTES
|
||||
Requires GitHub CLI (gh) to be installed and authenticated.
|
||||
Run 'gh auth login' if not already authenticated.
|
||||
|
||||
.LINK
|
||||
https://cli.github.com/
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true, HelpMessage = "Pull request number")]
|
||||
[int]$PullRequestNumber,
|
||||
|
||||
[Parameter(Mandatory = $false, HelpMessage = "Commit SHA that was last reviewed")]
|
||||
[string]$LastReviewedCommitSha,
|
||||
|
||||
[Parameter(Mandatory = $false, HelpMessage = "Repository owner")]
|
||||
[string]$RepositoryOwner = "microsoft",
|
||||
|
||||
[Parameter(Mandatory = $false, HelpMessage = "Repository name")]
|
||||
[string]$RepositoryName = "PowerToys"
|
||||
)
|
||||
|
||||
# Fetch current pull request state from GitHub
|
||||
try {
|
||||
$pullRequestData = gh pr view $PullRequestNumber --json headRefOid,headRefName,baseRefName,baseRefOid | ConvertFrom-Json
|
||||
} catch {
|
||||
Write-Error "Failed to fetch PR #$PullRequestNumber details. Details: $_"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$currentHeadSha = $pullRequestData.headRefOid
|
||||
$baseRefName = $pullRequestData.baseRefName
|
||||
$headRefName = $pullRequestData.headRefName
|
||||
|
||||
# Initialize result object
|
||||
$analysisResult = @{
|
||||
PullRequestNumber = $PullRequestNumber
|
||||
CurrentHeadSha = $currentHeadSha
|
||||
BaseRefName = $baseRefName
|
||||
HeadRefName = $headRefName
|
||||
LastReviewedSha = $LastReviewedCommitSha
|
||||
IsIncremental = $false
|
||||
NeedFullReview = $true
|
||||
ChangedFiles = @()
|
||||
NewCommits = @()
|
||||
Summary = ""
|
||||
}
|
||||
|
||||
# Scenario 1: First review (no previous SHA provided)
|
||||
if ([string]::IsNullOrWhiteSpace($LastReviewedCommitSha)) {
|
||||
$analysisResult.Summary = "Initial review - no previous iteration found"
|
||||
$analysisResult.NeedFullReview = $true
|
||||
return $analysisResult | ConvertTo-Json -Depth 10
|
||||
}
|
||||
|
||||
# Scenario 2: No changes since last review
|
||||
if ($currentHeadSha -eq $LastReviewedCommitSha) {
|
||||
$analysisResult.Summary = "No changes since last review (SHA: $currentHeadSha)"
|
||||
$analysisResult.NeedFullReview = $false
|
||||
$analysisResult.IsIncremental = $true
|
||||
return $analysisResult | ConvertTo-Json -Depth 10
|
||||
}
|
||||
|
||||
# Scenario 3: Check for force-push (last reviewed SHA no longer exists in history)
|
||||
try {
|
||||
$null = gh api "repos/$RepositoryOwner/$RepositoryName/commits/$LastReviewedCommitSha" 2>&1
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
# SHA not found - likely force-push or branch rewrite
|
||||
$analysisResult.Summary = "Force-push detected - last reviewed SHA $LastReviewedCommitSha no longer exists. Full review required."
|
||||
$analysisResult.NeedFullReview = $true
|
||||
return $analysisResult | ConvertTo-Json -Depth 10
|
||||
}
|
||||
} catch {
|
||||
$analysisResult.Summary = "Cannot verify last reviewed SHA $LastReviewedCommitSha - assuming force-push. Full review required."
|
||||
$analysisResult.NeedFullReview = $true
|
||||
return $analysisResult | ConvertTo-Json -Depth 10
|
||||
}
|
||||
|
||||
# Scenario 4: Get incremental changes between last reviewed SHA and current head
|
||||
try {
|
||||
$compareApiPath = "repos/$RepositoryOwner/$RepositoryName/compare/$LastReviewedCommitSha...$currentHeadSha"
|
||||
$comparisonData = gh api $compareApiPath | ConvertFrom-Json
|
||||
|
||||
# Extract new commits information
|
||||
$analysisResult.NewCommits = $comparisonData.commits | ForEach-Object {
|
||||
@{
|
||||
Sha = $_.sha.Substring(0, 7)
|
||||
Message = $_.commit.message.Split("`n")[0] # First line only
|
||||
Author = $_.commit.author.name
|
||||
Date = $_.commit.author.date
|
||||
}
|
||||
}
|
||||
|
||||
# Extract changed files information
|
||||
$analysisResult.ChangedFiles = $comparisonData.files | ForEach-Object {
|
||||
@{
|
||||
Filename = $_.filename
|
||||
Status = $_.status # added, modified, removed, renamed
|
||||
Additions = $_.additions
|
||||
Deletions = $_.deletions
|
||||
Changes = $_.changes
|
||||
}
|
||||
}
|
||||
|
||||
$fileCount = $analysisResult.ChangedFiles.Count
|
||||
$commitCount = $analysisResult.NewCommits.Count
|
||||
|
||||
$analysisResult.IsIncremental = $true
|
||||
$analysisResult.NeedFullReview = $false
|
||||
$analysisResult.Summary = "Incremental review: $commitCount new commit(s), $fileCount file(s) changed since SHA $($LastReviewedCommitSha.Substring(0, 7))"
|
||||
|
||||
} catch {
|
||||
Write-Error "Failed to compare commits. Details: $_"
|
||||
$analysisResult.Summary = "Error comparing commits - defaulting to full review"
|
||||
$analysisResult.NeedFullReview = $true
|
||||
}
|
||||
|
||||
# Return the analysis result as JSON
|
||||
return $analysisResult | ConvertTo-Json -Depth 10
|
||||
156
.github/review-tools/Start-PrReviewBatch.ps1
vendored
156
.github/review-tools/Start-PrReviewBatch.ps1
vendored
@@ -1,156 +0,0 @@
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string] $CategorizedPrsPath,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string] $ReviewRoot,
|
||||
|
||||
[int] $MaxConcurrent = 6,
|
||||
[int] $IdleMinutes = 5,
|
||||
[int] $MaxRetries = 2,
|
||||
[int] $PollSeconds = 20
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Get-ReviewedPrNumbers {
|
||||
param([string] $Root)
|
||||
|
||||
@(Get-ChildItem $Root -Directory -ErrorAction SilentlyContinue |
|
||||
Where-Object { Test-Path (Join-Path $_.FullName "00-OVERVIEW.md") } |
|
||||
ForEach-Object { [int]$_.Name })
|
||||
}
|
||||
|
||||
function Get-LatestWriteTime {
|
||||
param([string] $Folder)
|
||||
|
||||
if (-not (Test-Path $Folder)) {
|
||||
return $null
|
||||
}
|
||||
|
||||
$files = Get-ChildItem $Folder -File -ErrorAction SilentlyContinue
|
||||
if (-not $files) {
|
||||
return $null
|
||||
}
|
||||
|
||||
($files | Sort-Object LastWriteTime -Descending | Select-Object -First 1).LastWriteTime
|
||||
}
|
||||
|
||||
function Start-PrReviewJob {
|
||||
param(
|
||||
[int] $PrNumber,
|
||||
[string] $WorkingDir
|
||||
)
|
||||
|
||||
Start-Job -ScriptBlock {
|
||||
param($wd, $n)
|
||||
Set-Location $wd
|
||||
& copilot -p "Review PR #$n using the review-pr.prompt.md workflow. Write all output files to 'Generated Files/prReview/$n/'" --yolo -s 2>&1
|
||||
} -ArgumentList $WorkingDir, $PrNumber
|
||||
}
|
||||
|
||||
if (-not (Test-Path $CategorizedPrsPath)) {
|
||||
throw "Categorized PRs file not found: $CategorizedPrsPath"
|
||||
}
|
||||
|
||||
if (-not (Test-Path $ReviewRoot)) {
|
||||
New-Item -Path $ReviewRoot -ItemType Directory -Force | Out-Null
|
||||
}
|
||||
|
||||
$data = Get-Content $CategorizedPrsPath -Raw | ConvertFrom-Json
|
||||
$allPrs = @($data.Prs | ForEach-Object { [int]$_.Number })
|
||||
$workingDir = (Get-Location).Path
|
||||
|
||||
$running = @{}
|
||||
$retries = @{}
|
||||
$failed = New-Object System.Collections.Generic.HashSet[int]
|
||||
|
||||
Write-Host "Starting review batch: $($allPrs.Count) PRs" -ForegroundColor Cyan
|
||||
|
||||
while ($true) {
|
||||
$reviewed = Get-ReviewedPrNumbers -Root $ReviewRoot
|
||||
$remaining = @($allPrs | Where-Object { $_ -notin $reviewed -and -not $failed.Contains($_) })
|
||||
|
||||
if ($remaining.Count -eq 0 -and $running.Count -eq 0) {
|
||||
Write-Host "ALL DONE!" -ForegroundColor Green
|
||||
break
|
||||
}
|
||||
|
||||
foreach ($entry in @($running.GetEnumerator())) {
|
||||
$pr = $entry.Key
|
||||
$job = $entry.Value
|
||||
$folder = Join-Path $ReviewRoot $pr
|
||||
$latestWrite = Get-LatestWriteTime -Folder $folder
|
||||
$idleFor = if ($latestWrite) { (New-TimeSpan -Start $latestWrite -End (Get-Date)).TotalMinutes } else { $null }
|
||||
|
||||
$isDone = $job.State -in @("Completed", "Failed", "Stopped")
|
||||
$hasOverview = Test-Path (Join-Path $folder "00-OVERVIEW.md")
|
||||
$isIdleTooLong = $idleFor -ne $null -and $idleFor -ge $IdleMinutes
|
||||
|
||||
if ($isDone -and -not $hasOverview) {
|
||||
$retries[$pr] = ($retries[$pr] + 1)
|
||||
if ($retries[$pr] -le $MaxRetries) {
|
||||
Write-Host "PR #$pr finished without overview. Retrying ($($retries[$pr])/$MaxRetries)..." -ForegroundColor Yellow
|
||||
Remove-Job $job -Force -ErrorAction SilentlyContinue
|
||||
$running.Remove($pr)
|
||||
} else {
|
||||
Write-Host "PR #$pr failed after $MaxRetries retries." -ForegroundColor Red
|
||||
$null = $failed.Add($pr)
|
||||
New-Item -Path (Join-Path $folder "__error.flag") -ItemType File -Force | Out-Null
|
||||
Remove-Job $job -Force -ErrorAction SilentlyContinue
|
||||
$running.Remove($pr)
|
||||
}
|
||||
} elseif (-not $hasOverview -and $isIdleTooLong) {
|
||||
$retries[$pr] = ($retries[$pr] + 1)
|
||||
if ($retries[$pr] -le $MaxRetries) {
|
||||
Write-Host "PR #$pr idle for $([int]$idleFor)m. Restarting ($($retries[$pr])/$MaxRetries)..." -ForegroundColor Yellow
|
||||
Stop-Job $job -ErrorAction SilentlyContinue
|
||||
Remove-Job $job -Force -ErrorAction SilentlyContinue
|
||||
$running.Remove($pr)
|
||||
} else {
|
||||
Write-Host "PR #$pr idle repeatedly; giving up after $MaxRetries retries." -ForegroundColor Red
|
||||
$null = $failed.Add($pr)
|
||||
New-Item -Path (Join-Path $folder "__error.flag") -ItemType File -Force | Out-Null
|
||||
Stop-Job $job -ErrorAction SilentlyContinue
|
||||
Remove-Job $job -Force -ErrorAction SilentlyContinue
|
||||
$running.Remove($pr)
|
||||
}
|
||||
} elseif ($isDone -and $hasOverview) {
|
||||
Remove-Job $job -Force -ErrorAction SilentlyContinue
|
||||
$running.Remove($pr)
|
||||
}
|
||||
}
|
||||
|
||||
$reviewed = Get-ReviewedPrNumbers -Root $ReviewRoot
|
||||
$remaining = @($allPrs | Where-Object { $_ -notin $reviewed -and -not $failed.Contains($_) })
|
||||
|
||||
while ($running.Count -lt $MaxConcurrent -and $remaining.Count -gt 0) {
|
||||
$next = $remaining | Select-Object -First 1
|
||||
$remaining = $remaining | Select-Object -Skip 1
|
||||
|
||||
if (-not $retries.ContainsKey($next)) {
|
||||
$retries[$next] = 0
|
||||
}
|
||||
|
||||
if ($retries[$next] -gt $MaxRetries) {
|
||||
continue
|
||||
}
|
||||
|
||||
$job = Start-PrReviewJob -PrNumber $next -WorkingDir $workingDir
|
||||
$running[$next] = $job
|
||||
Write-Host "Started PR #$next (running: $($running.Count))" -ForegroundColor Cyan
|
||||
}
|
||||
|
||||
$reviewedCount = $reviewed.Count
|
||||
$pendingCount = $remaining.Count
|
||||
Write-Host "Progress: $reviewedCount/$($allPrs.Count) complete | Running: $($running.Count) | Pending: $pendingCount | Failed: $($failed.Count)" -ForegroundColor Gray
|
||||
|
||||
if ($remaining.Count -eq 0 -and $running.Count -eq 0) {
|
||||
if ($failed.Count -gt 0) {
|
||||
Write-Host "Completed with failures: $($failed.Count)." -ForegroundColor Yellow
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
Start-Sleep -Seconds $PollSeconds
|
||||
}
|
||||
170
.github/review-tools/Test-IncrementalReview.ps1
vendored
170
.github/review-tools/Test-IncrementalReview.ps1
vendored
@@ -1,170 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Tests and previews incremental review detection for a pull request.
|
||||
|
||||
.DESCRIPTION
|
||||
This helper script validates the incremental review detection logic by analyzing an existing
|
||||
PR review folder. It reads the last reviewed SHA from the overview file, compares it with
|
||||
the current PR state, and displays detailed information about what has changed.
|
||||
|
||||
This is useful for:
|
||||
- Testing the incremental review system before running a full review
|
||||
- Understanding what changed since the last review iteration
|
||||
- Verifying that review metadata was properly recorded
|
||||
|
||||
.PARAMETER PullRequestNumber
|
||||
The pull request number to test incremental review detection for.
|
||||
|
||||
.PARAMETER RepositoryOwner
|
||||
The GitHub repository owner. Defaults to "microsoft".
|
||||
|
||||
.PARAMETER RepositoryName
|
||||
The GitHub repository name. Defaults to "PowerToys".
|
||||
|
||||
.OUTPUTS
|
||||
Colored console output displaying:
|
||||
- Current and last reviewed commit SHAs
|
||||
- Whether incremental review is possible
|
||||
- List of new commits since last review
|
||||
- List of changed files with status indicators
|
||||
- Recommended review strategy
|
||||
|
||||
.EXAMPLE
|
||||
.\Test-IncrementalReview.ps1 -PullRequestNumber 42374
|
||||
Tests incremental review detection for PR #42374.
|
||||
|
||||
.EXAMPLE
|
||||
.\Test-IncrementalReview.ps1 -PullRequestNumber 42374 -RepositoryOwner "myorg" -RepositoryName "myrepo"
|
||||
Tests incremental review for a PR in a different repository.
|
||||
|
||||
.NOTES
|
||||
Requires GitHub CLI (gh) to be installed and authenticated.
|
||||
Run 'gh auth login' if not already authenticated.
|
||||
|
||||
Prerequisites:
|
||||
- PR review folder must exist at "Generated Files\prReview\{PRNumber}"
|
||||
- 00-OVERVIEW.md must exist in the review folder
|
||||
- For incremental detection, overview must contain "Last reviewed SHA" metadata
|
||||
|
||||
.LINK
|
||||
https://cli.github.com/
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true, HelpMessage = "Pull request number to test")]
|
||||
[int]$PullRequestNumber,
|
||||
|
||||
[Parameter(Mandatory = $false, HelpMessage = "Repository owner")]
|
||||
[string]$RepositoryOwner = "microsoft",
|
||||
|
||||
[Parameter(Mandatory = $false, HelpMessage = "Repository name")]
|
||||
[string]$RepositoryName = "PowerToys"
|
||||
)
|
||||
|
||||
# Resolve paths to review folder and overview file
|
||||
$repositoryRoot = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
||||
$reviewFolderPath = Join-Path $repositoryRoot "Generated Files\prReview\$PullRequestNumber"
|
||||
$overviewFilePath = Join-Path $reviewFolderPath "00-OVERVIEW.md"
|
||||
|
||||
Write-Host "=== Testing Incremental Review for PR #$PullRequestNumber ===" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# Check if review folder exists
|
||||
if (-not (Test-Path $reviewFolderPath)) {
|
||||
Write-Host "❌ Review folder not found: $reviewFolderPath" -ForegroundColor Red
|
||||
Write-Host "This appears to be a new review (iteration 1)" -ForegroundColor Yellow
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Check if overview file exists
|
||||
if (-not (Test-Path $overviewFilePath)) {
|
||||
Write-Host "❌ Overview file not found: $overviewFilePath" -ForegroundColor Red
|
||||
Write-Host "This appears to be an incomplete review" -ForegroundColor Yellow
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Read overview file and extract last reviewed SHA
|
||||
Write-Host "📄 Reading overview file..." -ForegroundColor Green
|
||||
$overviewFileContent = Get-Content $overviewFilePath -Raw
|
||||
|
||||
if ($overviewFileContent -match '\*\*Last reviewed SHA:\*\*\s+(\w+)') {
|
||||
$lastReviewedSha = $Matches[1]
|
||||
Write-Host "✅ Found last reviewed SHA: $lastReviewedSha" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "⚠️ No 'Last reviewed SHA' found in overview - this may be an old format" -ForegroundColor Yellow
|
||||
Write-Host "Proceeding without incremental detection (full review will be needed)" -ForegroundColor Yellow
|
||||
exit 0
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "🔍 Running incremental change detection..." -ForegroundColor Cyan
|
||||
|
||||
# Call the incremental changes detection script
|
||||
$incrementalChangesScriptPath = Join-Path $PSScriptRoot "Get-PrIncrementalChanges.ps1"
|
||||
if (-not (Test-Path $incrementalChangesScriptPath)) {
|
||||
Write-Host "❌ Script not found: $incrementalChangesScriptPath" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
try {
|
||||
$analysisResult = & $incrementalChangesScriptPath `
|
||||
-PullRequestNumber $PullRequestNumber `
|
||||
-LastReviewedCommitSha $lastReviewedSha `
|
||||
-RepositoryOwner $RepositoryOwner `
|
||||
-RepositoryName $RepositoryName | ConvertFrom-Json
|
||||
|
||||
# Display analysis results
|
||||
Write-Host ""
|
||||
Write-Host "=== Incremental Review Analysis ===" -ForegroundColor Cyan
|
||||
Write-Host "Current HEAD SHA: $($analysisResult.CurrentHeadSha)" -ForegroundColor White
|
||||
Write-Host "Last reviewed SHA: $($analysisResult.LastReviewedSha)" -ForegroundColor White
|
||||
Write-Host "Base branch: $($analysisResult.BaseRefName)" -ForegroundColor White
|
||||
Write-Host "Head branch: $($analysisResult.HeadRefName)" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host "Is incremental? $($analysisResult.IsIncremental)" -ForegroundColor $(if ($analysisResult.IsIncremental) { "Green" } else { "Yellow" })
|
||||
Write-Host "Need full review? $($analysisResult.NeedFullReview)" -ForegroundColor $(if ($analysisResult.NeedFullReview) { "Yellow" } else { "Green" })
|
||||
Write-Host ""
|
||||
Write-Host "Summary: $($analysisResult.Summary)" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# Display new commits if any
|
||||
if ($analysisResult.NewCommits -and $analysisResult.NewCommits.Count -gt 0) {
|
||||
Write-Host "📝 New commits ($($analysisResult.NewCommits.Count)):" -ForegroundColor Green
|
||||
foreach ($commit in $analysisResult.NewCommits) {
|
||||
Write-Host " - $($commit.Sha): $($commit.Message)" -ForegroundColor Gray
|
||||
}
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
# Display changed files if any
|
||||
if ($analysisResult.ChangedFiles -and $analysisResult.ChangedFiles.Count -gt 0) {
|
||||
Write-Host "📁 Changed files ($($analysisResult.ChangedFiles.Count)):" -ForegroundColor Green
|
||||
foreach ($file in $analysisResult.ChangedFiles) {
|
||||
$statusDisplayColor = switch ($file.Status) {
|
||||
"added" { "Green" }
|
||||
"removed" { "Red" }
|
||||
"modified" { "Yellow" }
|
||||
"renamed" { "Cyan" }
|
||||
default { "White" }
|
||||
}
|
||||
Write-Host " - [$($file.Status)] $($file.Filename) (+$($file.Additions)/-$($file.Deletions))" -ForegroundColor $statusDisplayColor
|
||||
}
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
# Suggest review strategy based on analysis
|
||||
Write-Host "=== Recommended Review Strategy ===" -ForegroundColor Cyan
|
||||
if ($analysisResult.NeedFullReview) {
|
||||
Write-Host "🔄 Full review recommended" -ForegroundColor Yellow
|
||||
} elseif ($analysisResult.IsIncremental -and ($analysisResult.ChangedFiles.Count -eq 0)) {
|
||||
Write-Host "✅ No changes detected - no review needed" -ForegroundColor Green
|
||||
} elseif ($analysisResult.IsIncremental) {
|
||||
Write-Host "⚡ Incremental review possible - review only changed files" -ForegroundColor Green
|
||||
Write-Host "💡 Consider applying smart step filtering based on file types" -ForegroundColor Cyan
|
||||
}
|
||||
|
||||
} catch {
|
||||
Write-Host "❌ Error running incremental change detection: $_" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
313
.github/review-tools/review-tools.instructions.md
vendored
313
.github/review-tools/review-tools.instructions.md
vendored
@@ -1,313 +0,0 @@
|
||||
---
|
||||
description: PowerShell scripts for efficient PR reviews in PowerToys repository
|
||||
applyTo: '**'
|
||||
---
|
||||
|
||||
# PR Review Tools - Reference Guide
|
||||
|
||||
PowerShell scripts to support efficient and incremental pull request reviews in the PowerToys repository.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Prerequisites
|
||||
- PowerShell 7+ (or Windows PowerShell 5.1+)
|
||||
- GitHub CLI (`gh`) installed and authenticated (`gh auth login`)
|
||||
- Access to the PowerToys repository
|
||||
|
||||
### Testing Your Setup
|
||||
|
||||
Run the full test suite (recommended):
|
||||
```powershell
|
||||
cd "d:\PowerToys-00c1\.github\review-tools"
|
||||
.\Run-ReviewToolsTests.ps1
|
||||
```
|
||||
|
||||
Expected: 9-10 tests passing
|
||||
|
||||
### Individual Script Tests
|
||||
|
||||
**Test incremental change detection:**
|
||||
```powershell
|
||||
.\Get-PrIncrementalChanges.ps1 -PullRequestNumber 42374
|
||||
```
|
||||
Expected: JSON output showing review analysis
|
||||
|
||||
**Preview incremental review:**
|
||||
```powershell
|
||||
.\Test-IncrementalReview.ps1 -PullRequestNumber 42374
|
||||
```
|
||||
Expected: Analysis showing current vs last reviewed SHA
|
||||
|
||||
**Fetch file content:**
|
||||
```powershell
|
||||
.\Get-GitHubRawFile.ps1 -FilePath "README.md" -GitReference "main"
|
||||
```
|
||||
Expected: README content displayed
|
||||
|
||||
**Get PR file patch:**
|
||||
```powershell
|
||||
.\Get-GitHubPrFilePatch.ps1 -PullRequestNumber 42374 -FilePath ".github/actions/spell-check/expect.txt"
|
||||
```
|
||||
Expected: Unified diff output
|
||||
|
||||
## Available Scripts
|
||||
|
||||
### Get-GitHubRawFile.ps1
|
||||
|
||||
Downloads and displays file content from a GitHub repository at a specific git reference.
|
||||
|
||||
**Purpose:** Retrieve baseline file content for comparison during PR reviews.
|
||||
|
||||
**Parameters:**
|
||||
- `FilePath` (required): Relative path to file in repository
|
||||
- `GitReference` (optional): Git ref (branch, tag, SHA). Default: "main"
|
||||
- `RepositoryOwner` (optional): Repository owner. Default: "microsoft"
|
||||
- `RepositoryName` (optional): Repository name. Default: "PowerToys"
|
||||
- `ShowLineNumbers` (switch): Prefix each line with line number
|
||||
- `StartLineNumber` (optional): Starting line number when using `-ShowLineNumbers`. Default: 1
|
||||
|
||||
**Usage:**
|
||||
```powershell
|
||||
.\Get-GitHubRawFile.ps1 -FilePath "src/runner/main.cpp" -GitReference "main" -ShowLineNumbers
|
||||
```
|
||||
|
||||
### Get-GitHubPrFilePatch.ps1
|
||||
|
||||
Fetches the unified diff (patch) for a specific file in a pull request.
|
||||
|
||||
**Purpose:** Get the exact changes made to a file in a PR for detailed review.
|
||||
|
||||
**Parameters:**
|
||||
- `PullRequestNumber` (required): Pull request number
|
||||
- `FilePath` (required): Relative path to file in the PR
|
||||
- `RepositoryOwner` (optional): Repository owner. Default: "microsoft"
|
||||
- `RepositoryName` (optional): Repository name. Default: "PowerToys"
|
||||
|
||||
**Usage:**
|
||||
```powershell
|
||||
.\Get-GitHubPrFilePatch.ps1 -PullRequestNumber 42374 -FilePath "src/modules/cmdpal/main.cpp"
|
||||
```
|
||||
|
||||
**Output:** Unified diff showing changes made to the file.
|
||||
|
||||
### Get-PrIncrementalChanges.ps1
|
||||
|
||||
Compares the last reviewed commit with the current PR head to identify incremental changes.
|
||||
|
||||
**Purpose:** Enable efficient incremental reviews by detecting what changed since the last review iteration.
|
||||
|
||||
**Parameters:**
|
||||
- `PullRequestNumber` (required): Pull request number
|
||||
- `LastReviewedCommitSha` (optional): SHA of the commit that was last reviewed. If omitted, assumes first review.
|
||||
- `RepositoryOwner` (optional): Repository owner. Default: "microsoft"
|
||||
- `RepositoryName` (optional): Repository name. Default: "PowerToys"
|
||||
|
||||
**Usage:**
|
||||
```powershell
|
||||
.\Get-PrIncrementalChanges.ps1 -PullRequestNumber 42374 -LastReviewedCommitSha "abc123def456"
|
||||
```
|
||||
|
||||
**Output:** JSON object with detailed change analysis:
|
||||
```json
|
||||
{
|
||||
"PullRequestNumber": 42374,
|
||||
"CurrentHeadSha": "xyz789abc123",
|
||||
"LastReviewedSha": "abc123def456",
|
||||
"IsIncremental": true,
|
||||
"NeedFullReview": false,
|
||||
"ChangedFiles": [
|
||||
{
|
||||
"Filename": "src/modules/cmdpal/main.cpp",
|
||||
"Status": "modified",
|
||||
"Additions": 15,
|
||||
"Deletions": 8,
|
||||
"Changes": 23
|
||||
}
|
||||
],
|
||||
"NewCommits": [
|
||||
{
|
||||
"Sha": "def456",
|
||||
"Message": "Fix memory leak",
|
||||
"Author": "John Doe",
|
||||
"Date": "2025-11-07T10:30:00Z"
|
||||
}
|
||||
],
|
||||
"Summary": "Incremental review: 1 new commit(s), 1 file(s) changed since SHA abc123d"
|
||||
}
|
||||
```
|
||||
|
||||
**Scenarios Handled:**
|
||||
- **No LastReviewedCommitSha**: Returns `NeedFullReview: true` (first review)
|
||||
- **SHA matches current HEAD**: Returns empty `ChangedFiles` (no changes)
|
||||
- **Force-push detected**: Returns `NeedFullReview: true` (SHA not in history)
|
||||
- **Incremental changes**: Returns list of changed files and new commits
|
||||
|
||||
### Test-IncrementalReview.ps1
|
||||
|
||||
Helper script to test and preview incremental review detection before running the full review.
|
||||
|
||||
**Purpose:** Validate incremental review functionality and preview what changed.
|
||||
|
||||
**Parameters:**
|
||||
- `PullRequestNumber` (required): Pull request number
|
||||
- `RepositoryOwner` (optional): Repository owner. Default: "microsoft"
|
||||
- `RepositoryName` (optional): Repository name. Default: "PowerToys"
|
||||
|
||||
**Usage:**
|
||||
```powershell
|
||||
.\Test-IncrementalReview.ps1 -PullRequestNumber 42374
|
||||
```
|
||||
|
||||
**Output:** Colored console output showing:
|
||||
- Current and last reviewed SHAs
|
||||
- Whether incremental review is possible
|
||||
- List of new commits and changed files
|
||||
- Recommended review strategy
|
||||
|
||||
## Workflow Integration
|
||||
|
||||
These scripts integrate with the PR review prompt (`.github/prompts/review-pr.prompt.md`).
|
||||
|
||||
### Typical Review Flow
|
||||
|
||||
1. **Initial Review (Iteration 1)**
|
||||
- Review prompt processes the PR
|
||||
- Creates `Generated Files/prReview/{PR}/00-OVERVIEW.md`
|
||||
- Includes review metadata section with current HEAD SHA
|
||||
|
||||
2. **Subsequent Reviews (Iteration 2+)**
|
||||
- Review prompt reads `00-OVERVIEW.md` to get last reviewed SHA
|
||||
- Calls `Get-PrIncrementalChanges.ps1` to detect what changed
|
||||
- If incremental:
|
||||
- Reviews only changed files
|
||||
- Skips irrelevant review steps (e.g., skip Localization if no `.resx` files changed)
|
||||
- Uses `Get-GitHubPrFilePatch.ps1` to get patches for changed files
|
||||
- Updates `00-OVERVIEW.md` with new SHA and iteration number
|
||||
|
||||
### Manual Testing Workflow
|
||||
|
||||
Preview changes before review:
|
||||
```powershell
|
||||
# Check what changed in PR #42374 since last review
|
||||
.\Test-IncrementalReview.ps1 -PullRequestNumber 42374
|
||||
|
||||
# Get incremental changes programmatically
|
||||
$changes = .\Get-PrIncrementalChanges.ps1 -PullRequestNumber 42374 -LastReviewedCommitSha "abc123" | ConvertFrom-Json
|
||||
|
||||
if (-not $changes.NeedFullReview) {
|
||||
Write-Host "Only need to review $($changes.ChangedFiles.Count) files"
|
||||
|
||||
# Review each changed file
|
||||
foreach ($file in $changes.ChangedFiles) {
|
||||
Write-Host "Reviewing $($file.Filename)..."
|
||||
.\Get-GitHubPrFilePatch.ps1 -PullRequestNumber 42374 -FilePath $file.Filename
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling and Troubleshooting
|
||||
|
||||
### Common Requirements
|
||||
|
||||
All scripts:
|
||||
- Exit with code 1 on error
|
||||
- Write detailed error messages to stderr
|
||||
- Require `gh` CLI to be installed and authenticated
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Error: "gh not found"**
|
||||
- **Solution**: Install GitHub CLI from https://cli.github.com/ and run `gh auth login`
|
||||
|
||||
**Error: "Failed to query GitHub API"**
|
||||
- **Solution**: Verify `gh` authentication with `gh auth status`
|
||||
- **Solution**: Check PR number exists and you have repository access
|
||||
|
||||
**Error: "PR not found"**
|
||||
- **Solution**: Verify the PR number is correct and still exists
|
||||
- **Solution**: Ensure repository owner and name are correct
|
||||
|
||||
**Error: "SHA not found" or "Force-push detected"**
|
||||
- **Explanation**: Last reviewed SHA no longer exists in branch history (force-push occurred)
|
||||
- **Solution**: A full review is required; incremental review not possible
|
||||
|
||||
**Tests show "FAIL" but functionality works**
|
||||
- **Explanation**: Some tests may show exit code failures even when logic is correct
|
||||
- **Solution**: Check test output message - if it says "Correctly detected", functionality is working
|
||||
|
||||
**Error: "Could not find insertion point"**
|
||||
- **Explanation**: Overview file doesn't have expected "**Changed files:**" line
|
||||
- **Solution**: Verify overview file format is correct or regenerate it
|
||||
|
||||
### Verification Checklist
|
||||
|
||||
After setup, verify:
|
||||
- [ ] `Run-ReviewToolsTests.ps1` shows 9+ tests passing
|
||||
- [ ] `Get-PrIncrementalChanges.ps1` returns valid JSON
|
||||
- [ ] `Test-IncrementalReview.ps1` analyzes a PR without errors
|
||||
- [ ] `Get-GitHubRawFile.ps1` downloads files correctly
|
||||
- [ ] `Get-GitHubPrFilePatch.ps1` retrieves patches correctly
|
||||
|
||||
## Best Practices
|
||||
|
||||
### For Review Authors
|
||||
|
||||
1. **Test before full review**: Use `Test-IncrementalReview.ps1` to preview changes
|
||||
2. **Check for force-push**: Review the analysis output - force-pushes require full reviews
|
||||
3. **Smart step filtering**: Skip review steps for file types that didn't change
|
||||
|
||||
### For Script Users
|
||||
|
||||
1. **Use absolute paths**: When specifying folders, use absolute paths to avoid ambiguity
|
||||
2. **Check exit codes**: Scripts exit with code 1 on error - check `$LASTEXITCODE` in automation
|
||||
3. **Parse JSON output**: Use `ConvertFrom-Json` to work with structured output from `Get-PrIncrementalChanges.ps1`
|
||||
4. **Handle empty results**: Check `ChangedFiles.Count` before iterating
|
||||
|
||||
### Performance Tips
|
||||
|
||||
1. **Batch operations**: When reviewing multiple PRs, collect all PR numbers and process in batch
|
||||
2. **Cache raw files**: Download baseline files once and reuse for multiple comparisons
|
||||
3. **Filter early**: Use incremental detection to skip unnecessary file reviews
|
||||
4. **Parallel processing**: Consider processing independent PRs in parallel
|
||||
|
||||
## Integration with AI Review Systems
|
||||
|
||||
These tools are designed to work with AI-powered review systems:
|
||||
|
||||
1. **Copilot Instructions**: This file serves as reference documentation for GitHub Copilot
|
||||
2. **Structured Output**: JSON output from scripts is easily parsed by AI systems
|
||||
3. **Incremental Intelligence**: AI can focus on changed files for more efficient reviews
|
||||
4. **Metadata Tracking**: Review iterations are tracked for context-aware suggestions
|
||||
|
||||
### Example AI Integration
|
||||
|
||||
```powershell
|
||||
# Get incremental changes
|
||||
$analysis = .\Get-PrIncrementalChanges.ps1 -PullRequestNumber $PR | ConvertFrom-Json
|
||||
|
||||
# Feed to AI review system
|
||||
$reviewPrompt = @"
|
||||
Review the following changed files in PR #$PR:
|
||||
$($analysis.ChangedFiles | ForEach-Object { "- $($_.Filename) ($($_.Status))" } | Out-String)
|
||||
|
||||
Focus on incremental changes only. Previous review was at SHA $($analysis.LastReviewedSha).
|
||||
"@
|
||||
|
||||
# Execute AI review with context
|
||||
Invoke-AIReview -Prompt $reviewPrompt -Files $analysis.ChangedFiles
|
||||
```
|
||||
|
||||
## Support and Further Information
|
||||
|
||||
For detailed script documentation, use PowerShell's help system:
|
||||
```powershell
|
||||
Get-Help .\Get-PrIncrementalChanges.ps1 -Full
|
||||
Get-Help .\Test-IncrementalReview.ps1 -Detailed
|
||||
```
|
||||
|
||||
Related documentation:
|
||||
- `.github/prompts/review-pr.prompt.md` - Complete review workflow guide
|
||||
- `doc/devdocs/` - PowerToys development documentation
|
||||
- GitHub CLI documentation: https://cli.github.com/manual/
|
||||
|
||||
For issues or questions, refer to the PowerToys contribution guidelines.
|
||||
201
.github/skills/continuous-issue-triage/LICENSE.txt
vendored
201
.github/skills/continuous-issue-triage/LICENSE.txt
vendored
@@ -1,201 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to the Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2026 Microsoft Corporation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
346
.github/skills/continuous-issue-triage/SKILL.md
vendored
346
.github/skills/continuous-issue-triage/SKILL.md
vendored
@@ -1,346 +0,0 @@
|
||||
---
|
||||
name: continuous-issue-triage
|
||||
description: Automated issue triage assistant for periodic (daily/weekly) issue queue management. Use when asked to triage issues, review issue backlog, find trending issues, identify stale issues needing response, categorize unlabeled issues, find issues ready for fix, draft reply messages, check for issues needing clarification, find closeable issues after PR merge, or run periodic issue health checks. Supports both open and closed issues with activity tracking between runs.
|
||||
license: Complete terms in LICENSE.txt
|
||||
---
|
||||
|
||||
# Continuous Issue Triage Skill
|
||||
|
||||
Automated periodic triage of GitHub issues to keep the issue queue healthy. Designed to run daily, twice-weekly, or weekly, tracking activity between runs and categorizing issues by actionable priority.
|
||||
|
||||
## Output Directory
|
||||
|
||||
All artifacts are placed under `Generated Files/triage-issues/` at the repository root (gitignored).
|
||||
|
||||
```
|
||||
Generated Files/triage-issues/
|
||||
├── triage-state.json # Persistent state between runs
|
||||
├── current-run/
|
||||
│ ├── summary.md # Executive summary for this run
|
||||
│ ├── trending.md # Trending issues report
|
||||
│ ├── needs-label.md # Issues missing area labels
|
||||
│ ├── ready-for-fix.md # Issues confident for fix
|
||||
│ ├── needs-info.md # Issues needing author feedback
|
||||
│ ├── needs-clarification.md # Clarification requests (not bugs)
|
||||
│ ├── closeable.md # Issues ready to close
|
||||
│ └── draft-replies/ # Pre-drafted reply messages
|
||||
│ └── issue-XXXXX.md
|
||||
├── history/
|
||||
│ └── YYYY-MM-DD/ # Historical run archives
|
||||
└── issue-cache/ # Cached issue reviews (reuse review-issue)
|
||||
└── XXXXX/
|
||||
├── overview.md
|
||||
└── implementation-plan.md
|
||||
```
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
- Run periodic triage (daily, twice-weekly, weekly)
|
||||
- Find trending issues with high activity
|
||||
- Identify unlabeled issues needing categorization
|
||||
- Find issues ready for implementation
|
||||
- Draft replies for issues needing clarification
|
||||
- Identify closeable issues after PR merge/release
|
||||
- Track follow-up actions between triage sessions
|
||||
- Review closed issues with new comments
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- GitHub CLI (`gh`) installed and authenticated
|
||||
- MCP Server: github-mcp-server (optional, for images/attachments)
|
||||
- Access to `.github/prompts/review-issue.prompt.md` for deep analysis
|
||||
|
||||
## Workflow Overview
|
||||
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ 1. Load Previous State │
|
||||
│ (triage-state.json) │
|
||||
└─────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────┐
|
||||
│ 2. Collect Active Issues │
|
||||
│ - Recently updated open │
|
||||
│ - Closed with new comments │
|
||||
│ - Previously flagged │
|
||||
└─────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────┐
|
||||
│ 3. Categorize Issues │
|
||||
│ (Apply category rules) │
|
||||
└─────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────┐
|
||||
│ 4. Deep Analysis (selective) │
|
||||
│ (Use review-issue prompt) │
|
||||
└─────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────┐
|
||||
│ 5. Generate Reports & Drafts │
|
||||
└─────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────┐
|
||||
│ 6. Save State for Next Run │
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Issue Categories
|
||||
|
||||
Issues are categorized into actionable buckets with prioritization scores:
|
||||
|
||||
| Category | Emoji | Criteria | Human Action |
|
||||
|----------|-------|----------|--------------|
|
||||
| **Trending** | 🔥 | 5+ new comments since last run | Review conversation, respond |
|
||||
| **Needs-Label** | 🏷️ | Missing `Product-*` or `Area-*` label | Apply suggested label |
|
||||
| **Ready-for-Fix** | ✅ | High clarity, feasible, validated | Assign or implement |
|
||||
| **Needs-Info** | ❓ | Missing repro, impact, or expected result | Post drafted questions |
|
||||
| **Needs-Clarification** | 💬 | Question/discussion, not a bug | Post explanation reply |
|
||||
| **Closeable** | ✔️ | Fixed by PR, released, or resolved | Close with message |
|
||||
| **Stale-Waiting** | ⏳ | Waiting on author >14 days | Ping or close |
|
||||
| **Duplicate-Candidate** | 🔁 | Similar to existing issue | Link and close |
|
||||
|
||||
## Detailed Workflow Docs
|
||||
|
||||
Read steps progressively—only load what you need:
|
||||
|
||||
- [Step 1: State Management](./references/step1-state-management.md)
|
||||
- [Step 2: Issue Collection](./references/step2-collection.md)
|
||||
- [Step 3: Categorization Rules](./references/step3-categorization.md)
|
||||
- [Step 4: Deep Analysis](./references/step4-deep-analysis.md)
|
||||
- [Step 5: Report Generation](./references/step5-reports.md)
|
||||
- [Step 6: Reply Templates](./references/step6-reply-templates.md)
|
||||
|
||||
## Available Scripts
|
||||
|
||||
| Script | Purpose |
|
||||
|--------|---------|
|
||||
| [run-triage.ps1](./scripts/run-triage.ps1) | **Main orchestrator** - runs full triage with parallel Copilot CLI |
|
||||
| [collect-active-issues.ps1](./scripts/collect-active-issues.ps1) | Fetch issues updated since last run (standalone) |
|
||||
| [categorize-issues.ps1](./scripts/categorize-issues.ps1) | Apply categorization rules (standalone) |
|
||||
| [generate-summary.ps1](./scripts/generate-summary.ps1) | Create executive summary (standalone) |
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. **First Run**: Creates initial state, analyzes recent activity
|
||||
2. **Subsequent Runs**: Compares against previous state, highlights changes (delta)
|
||||
|
||||
### Running the Triage
|
||||
|
||||
**PowerShell 7 Required** - Uses parallel processing for efficiency.
|
||||
|
||||
```powershell
|
||||
# Basic run (weekly, 5 parallel, 5min timeout, 3 retries)
|
||||
.\.github\skills\continuous-issue-triage\scripts\run-triage.ps1
|
||||
|
||||
# Daily run with more parallelism
|
||||
.\.github\skills\continuous-issue-triage\scripts\run-triage.ps1 -RunType daily -MaxParallel 10
|
||||
|
||||
# With specific model
|
||||
.\.github\skills\continuous-issue-triage\scripts\run-triage.ps1 -Model "claude-sonnet-4"
|
||||
|
||||
# Force re-analyze all (ignore cache)
|
||||
.\.github\skills\continuous-issue-triage\scripts\run-triage.ps1 -Force
|
||||
|
||||
# With MCP config
|
||||
.\.github\skills\continuous-issue-triage\scripts\run-triage.ps1 -McpConfig ".\.github\mcp.json"
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
| Parameter | Default | Description |
|
||||
|-----------|---------|-------------|
|
||||
| `-RunType` | weekly | daily, twice-weekly, weekly |
|
||||
| `-MaxParallel` | 5 | Concurrent Copilot CLI invocations |
|
||||
| `-TimeoutMinutes` | 5 | Timeout per issue analysis |
|
||||
| `-MaxRetries` | 3 | Retries on timeout/failure |
|
||||
| `-Model` | (default) | Copilot model to use |
|
||||
| `-McpConfig` | (none) | Path to MCP config file |
|
||||
| `-LookbackDays` | 7 | Days to look back on first run |
|
||||
| `-Force` | false | Re-analyze all, ignore cache |
|
||||
|
||||
### Example Invocation (via Copilot Chat)
|
||||
|
||||
```
|
||||
"Run issue triage" or "Triage issues for this week"
|
||||
```
|
||||
|
||||
The skill will:
|
||||
1. Check for existing `triage-state.json`
|
||||
2. Collect issues updated since last run (or last 7 days for first run)
|
||||
3. **Run parallel Copilot CLI analysis** with timeout/retry handling
|
||||
4. Categorize and prioritize (using cached results where valid)
|
||||
5. Generate actionable reports with draft replies
|
||||
6. Save state for next run (delta tracking)
|
||||
|
||||
## Parallel Execution Model
|
||||
|
||||
The skill uses PowerShell 7's `ForEach-Object -Parallel` to analyze issues concurrently:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ run-triage.ps1 │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Issue #123 ──┐ │
|
||||
│ Issue #124 ──┼── ForEach-Object -Parallel ─┬── Result #123 │
|
||||
│ Issue #125 ──┤ (ThrottleLimit: 5) ├── Result #124 │
|
||||
│ Issue #126 ──┤ ├── Result #125 │
|
||||
│ Issue #127 ──┘ └── Result #126 │
|
||||
│ ... │
|
||||
│ Each issue: │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ copilot -p "Analyze #N..." --yolo │ │
|
||||
│ │ ├── Timeout: 5 minutes │ │
|
||||
│ │ ├── Retry: up to 3 times │ │
|
||||
│ │ └── Output: JSON analysis result │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Timeout & Retry Handling
|
||||
|
||||
- Each Copilot CLI invocation has a **5 minute timeout** (configurable)
|
||||
- On timeout: job is killed, waits 10 seconds, retries
|
||||
- **3 retries maximum** before marking as failed
|
||||
- Failed analyses are logged and reported separately
|
||||
|
||||
## Delta Tracking
|
||||
|
||||
The skill tracks state between runs to report **what changed**:
|
||||
|
||||
```json
|
||||
{
|
||||
"lastRun": "2026-02-05T10:30:00Z",
|
||||
"issueSnapshots": {
|
||||
"12345": {
|
||||
"lastSeenAt": "2026-02-05T...",
|
||||
"category": "trending",
|
||||
"priorityScore": 82
|
||||
}
|
||||
},
|
||||
"analysisResults": {
|
||||
"12345": {
|
||||
"success": true,
|
||||
"analyzedAt": "2026-02-05T...",
|
||||
"data": { ... }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Delta Report Shows**:
|
||||
- Issues with **new activity** since last run
|
||||
- **Newly analyzed** vs **cached** results
|
||||
- Category **changes** (e.g., was needs-info, now ready-for-fix)
|
||||
- **Analysis failures** that need retry
|
||||
|
||||
## Output Format
|
||||
|
||||
### Executive Summary (`summary.md`)
|
||||
|
||||
```markdown
|
||||
# Issue Triage Summary - 2026-02-05
|
||||
|
||||
**Run Type**: Weekly | **Issues Analyzed**: 47 | **Since**: 2026-01-29
|
||||
|
||||
## Action Required by Category
|
||||
|
||||
| Category | Count | Top Priority |
|
||||
|----------|-------|--------------|
|
||||
| 🔥 Trending | 3 | #12345 (12 new comments) |
|
||||
| 🏷️ Needs-Label | 5 | #12346 (suggest: FancyZones) |
|
||||
| ✅ Ready-for-Fix | 2 | #12347 (score: 85/100) |
|
||||
| ❓ Needs-Info | 8 | #12348 (missing repro) |
|
||||
| 💬 Needs-Clarification | 4 | #12349 (question about feature) |
|
||||
| ✔️ Closeable | 6 | #12350 (fixed in v0.99) |
|
||||
|
||||
## Quick Actions
|
||||
|
||||
- [ ] Review #12345 - trending with negative sentiment
|
||||
- [ ] Label #12346 as Product-FancyZones
|
||||
- [ ] Assign #12347 to @contributor
|
||||
- [ ] Post clarification on #12348 (draft ready)
|
||||
- [ ] Close #12350 with release note link
|
||||
```
|
||||
|
||||
## State Schema
|
||||
|
||||
See [State Management](./references/step1-state-management.md) for full schema.
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "1.0",
|
||||
"lastRun": "2026-02-05T10:30:00Z",
|
||||
"lastRunType": "weekly",
|
||||
"issueSnapshots": {
|
||||
"12345": {
|
||||
"number": 12345,
|
||||
"title": "FancyZones: Window snapping issue",
|
||||
"state": "open",
|
||||
"lastSeenAt": "2026-02-05T...",
|
||||
"category": "trending",
|
||||
"priorityScore": 82
|
||||
}
|
||||
},
|
||||
"analysisResults": {
|
||||
"12345": {
|
||||
"success": true,
|
||||
"analyzedAt": "2026-02-05T10:30:00Z",
|
||||
"data": {
|
||||
"issueNumber": 12345,
|
||||
"category": "trending",
|
||||
"categoryReason": "8 new comments, heated discussion",
|
||||
"priorityScore": 82,
|
||||
"suggestedAction": "Review conversation urgently",
|
||||
"draftReply": "...",
|
||||
"clarityScore": 75,
|
||||
"feasibilityScore": 80
|
||||
}
|
||||
}
|
||||
},
|
||||
"statistics": {
|
||||
"totalRunCount": 12,
|
||||
"issuesAnalyzed": 234
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Cache Invalidation Rules
|
||||
|
||||
Analysis results are **cached** and reused when:
|
||||
- Issue has **no new activity** since last analysis
|
||||
- Analysis is **less than 7 days old**
|
||||
- `-Force` flag is **not** specified
|
||||
|
||||
Re-analysis triggers:
|
||||
- New comments on the issue
|
||||
- Issue state changed
|
||||
- Cache older than 7 days
|
||||
- Explicit `-Force` flag
|
||||
|
||||
## Integration with review-issue Prompt
|
||||
|
||||
For issues in **Ready-for-Fix** or complex **Needs-Info** categories, this skill automatically invokes the [review-issue prompt](../../prompts/review-issue.prompt.md) to generate:
|
||||
- Detailed `overview.md` with scoring
|
||||
- `implementation-plan.md` for ready issues
|
||||
|
||||
Results are cached in `issue-cache/XXXXX/` and reused across runs.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Issue | Solution |
|
||||
|-------|----------|
|
||||
| No `triage-state.json` | First run—will create initial state |
|
||||
| PowerShell version error | Requires PowerShell 7+ for `-Parallel` |
|
||||
| Copilot CLI not found | Install: `gh extension install github/gh-copilot` |
|
||||
| Too many timeouts | Increase `-TimeoutMinutes` or reduce `-MaxParallel` |
|
||||
| High failure rate | Check `issue-cache/*/error.log` for details |
|
||||
| Stale cache | Use `-Force` to re-analyze all issues |
|
||||
| gh rate limit | Wait or reduce `-MaxParallel` |
|
||||
| Empty analysis results | Check Copilot CLI auth: `gh auth status` |
|
||||
|
||||
## Conventions
|
||||
|
||||
- **Preserve history**: Archive each run to `history/YYYY-MM-DD/`
|
||||
- **Draft replies**: Always human-review before posting
|
||||
- **Label suggestions**: Confidence threshold 70% for auto-suggest
|
||||
- **Closed issues**: Track for 30 days after close for late comments
|
||||
@@ -1,223 +0,0 @@
|
||||
# Step 1: State Management
|
||||
|
||||
The triage skill maintains persistent state between runs to track issue activity and pending actions.
|
||||
|
||||
## State File Location
|
||||
|
||||
```
|
||||
Generated Files/triage-issues/triage-state.json
|
||||
```
|
||||
|
||||
## Initial State Creation
|
||||
|
||||
On first run (no existing state file), create initial state:
|
||||
|
||||
```powershell
|
||||
# Check if state exists
|
||||
$statePath = "Generated Files/triage-issues/triage-state.json"
|
||||
if (-not (Test-Path $statePath)) {
|
||||
# First run - create initial state
|
||||
$initialState = @{
|
||||
version = "1.0"
|
||||
lastRun = $null
|
||||
lastRunType = $null
|
||||
issueSnapshots = @{}
|
||||
pendingFollowUps = @()
|
||||
closedWithActivity = @()
|
||||
configuration = @{
|
||||
trendingThreshold = 5
|
||||
staleWaitingDays = 14
|
||||
closedTrackingDays = 30
|
||||
labelConfidenceThreshold = 70
|
||||
}
|
||||
}
|
||||
New-Item -ItemType Directory -Force -Path (Split-Path $statePath)
|
||||
$initialState | ConvertTo-Json -Depth 10 | Set-Content $statePath
|
||||
}
|
||||
```
|
||||
|
||||
## Full State Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "1.0",
|
||||
"lastRun": "2026-02-05T10:30:00Z",
|
||||
"lastRunType": "weekly",
|
||||
"issueSnapshots": {
|
||||
"12345": {
|
||||
"number": 12345,
|
||||
"title": "FancyZones: Window snapping not working",
|
||||
"state": "open",
|
||||
"labels": ["Product-FancyZones", "Issue-Bug"],
|
||||
"commentCount": 15,
|
||||
"lastCommentAt": "2026-02-04T15:30:00Z",
|
||||
"lastCommentAuthor": "user123",
|
||||
"reactions": {
|
||||
"thumbsUp": 10,
|
||||
"thumbsDown": 0,
|
||||
"heart": 2
|
||||
},
|
||||
"category": "trending",
|
||||
"categoryReason": "12 new comments since last run",
|
||||
"priorityScore": 75,
|
||||
"pendingAction": "review",
|
||||
"actionTaken": false,
|
||||
"actionTakenAt": null,
|
||||
"draftReplyPath": null,
|
||||
"linkedPRs": [],
|
||||
"firstSeenAt": "2026-01-15T...",
|
||||
"lastAnalyzedAt": "2026-02-01T..."
|
||||
}
|
||||
},
|
||||
"pendingFollowUps": [
|
||||
{
|
||||
"issueNumber": 12346,
|
||||
"action": "post-clarification",
|
||||
"scheduledFor": "2026-02-07T...",
|
||||
"draftPath": "draft-replies/issue-12346.md",
|
||||
"status": "pending"
|
||||
}
|
||||
],
|
||||
"closedWithActivity": [
|
||||
{
|
||||
"issueNumber": 12350,
|
||||
"closedAt": "2026-01-20T...",
|
||||
"lastCheckedAt": "2026-02-05T...",
|
||||
"newCommentsSinceClosed": 2,
|
||||
"needsReview": true
|
||||
}
|
||||
],
|
||||
"configuration": {
|
||||
"trendingThreshold": 5,
|
||||
"staleWaitingDays": 14,
|
||||
"closedTrackingDays": 30,
|
||||
"labelConfidenceThreshold": 70
|
||||
},
|
||||
"statistics": {
|
||||
"totalRunCount": 12,
|
||||
"issuesTriaged": 234,
|
||||
"repliesPosted": 45,
|
||||
"issuesClosed": 89
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Loading State
|
||||
|
||||
```powershell
|
||||
function Load-TriageState {
|
||||
param([string]$StatePath = "Generated Files/triage-issues/triage-state.json")
|
||||
|
||||
if (Test-Path $StatePath) {
|
||||
$state = Get-Content $StatePath | ConvertFrom-Json -AsHashtable
|
||||
Write-Host "Loaded state from $($state.lastRun)"
|
||||
return $state
|
||||
}
|
||||
|
||||
Write-Host "No previous state found - initializing fresh run"
|
||||
return $null
|
||||
}
|
||||
```
|
||||
|
||||
## Saving State
|
||||
|
||||
After each run, update and save the state:
|
||||
|
||||
```powershell
|
||||
function Save-TriageState {
|
||||
param(
|
||||
[hashtable]$State,
|
||||
[string]$StatePath = "Generated Files/triage-issues/triage-state.json",
|
||||
[switch]$Archive
|
||||
)
|
||||
|
||||
$State.lastRun = (Get-Date).ToUniversalTime().ToString("o")
|
||||
|
||||
# Archive previous run if requested
|
||||
if ($Archive -and (Test-Path $StatePath)) {
|
||||
$archiveDate = (Get-Date).ToString("yyyy-MM-dd")
|
||||
$archivePath = "Generated Files/triage-issues/history/$archiveDate"
|
||||
New-Item -ItemType Directory -Force -Path $archivePath
|
||||
Copy-Item $StatePath "$archivePath/triage-state.json"
|
||||
|
||||
# Also archive current-run folder
|
||||
if (Test-Path "Generated Files/triage-issues/current-run") {
|
||||
Copy-Item -Recurse "Generated Files/triage-issues/current-run" $archivePath
|
||||
}
|
||||
}
|
||||
|
||||
$State | ConvertTo-Json -Depth 10 | Set-Content $StatePath
|
||||
Write-Host "State saved at $($State.lastRun)"
|
||||
}
|
||||
```
|
||||
|
||||
## State Transitions
|
||||
|
||||
### Issue Snapshot Lifecycle
|
||||
|
||||
```
|
||||
NEW ISSUE DETECTED
|
||||
↓
|
||||
┌──────────────────┐
|
||||
│ issueSnapshots │ ← Add with initial data
|
||||
│ category: null │
|
||||
└──────────────────┘
|
||||
↓
|
||||
CATEGORIZATION PASS
|
||||
↓
|
||||
┌──────────────────┐
|
||||
│ category: set │ ← trending/needs-label/etc.
|
||||
│ priorityScore │
|
||||
│ pendingAction │
|
||||
└──────────────────┘
|
||||
↓
|
||||
HUMAN TAKES ACTION (external)
|
||||
↓
|
||||
┌──────────────────┐
|
||||
│ actionTaken: true│ ← Mark as handled
|
||||
│ actionTakenAt │
|
||||
└──────────────────┘
|
||||
↓
|
||||
NEXT RUN: RE-EVALUATE
|
||||
↓
|
||||
┌──────────────────┐
|
||||
│ category: update │ ← May change category
|
||||
│ reset action? │ if new activity
|
||||
└──────────────────┘
|
||||
```
|
||||
|
||||
### Detecting Changes Between Runs
|
||||
|
||||
```powershell
|
||||
function Get-IssueChanges {
|
||||
param(
|
||||
[hashtable]$PreviousSnapshot,
|
||||
[hashtable]$CurrentData
|
||||
)
|
||||
|
||||
$changes = @{
|
||||
newComments = $CurrentData.commentCount - $PreviousSnapshot.commentCount
|
||||
stateChanged = $CurrentData.state -ne $PreviousSnapshot.state
|
||||
labelsChanged = (Compare-Object $PreviousSnapshot.labels $CurrentData.labels).Count -gt 0
|
||||
reactionsChanged = $CurrentData.reactions.thumbsUp -ne $PreviousSnapshot.reactions.thumbsUp
|
||||
}
|
||||
|
||||
return $changes
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration Options
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---------|---------|-------------|
|
||||
| `trendingThreshold` | 5 | Minimum new comments to flag as trending |
|
||||
| `staleWaitingDays` | 14 | Days waiting on author before stale |
|
||||
| `closedTrackingDays` | 30 | Days to monitor closed issues for new comments |
|
||||
| `labelConfidenceThreshold` | 70 | Minimum confidence % for label suggestions |
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always archive before overwriting**: Preserve history for audit trail
|
||||
2. **Atomic updates**: Update state only after successful run completion
|
||||
3. **Graceful degradation**: If state is corrupted, allow fresh start
|
||||
4. **Version field**: Enables future schema migrations
|
||||
@@ -1,225 +0,0 @@
|
||||
# Step 2: Issue Collection
|
||||
|
||||
Collect issues that need triage attention based on activity since last run.
|
||||
|
||||
## Collection Strategy
|
||||
|
||||
### Issue Sources
|
||||
|
||||
1. **Recently Updated Open Issues**: Any open issue with activity since last run
|
||||
2. **Closed Issues with New Comments**: People may ask questions on closed issues
|
||||
3. **Previously Flagged Issues**: Issues with pending actions from last run
|
||||
4. **New Issues**: Issues created since last run
|
||||
|
||||
## GitHub CLI Commands
|
||||
|
||||
### Collect Recently Updated Open Issues
|
||||
|
||||
```powershell
|
||||
# Get open issues updated since last run
|
||||
$since = "2026-01-29T00:00:00Z" # From triage-state.json.lastRun
|
||||
|
||||
gh issue list `
|
||||
--state open `
|
||||
--json number,title,body,author,createdAt,updatedAt,state,labels,milestone,reactions,comments `
|
||||
--limit 500 `
|
||||
| ConvertFrom-Json `
|
||||
| Where-Object { [datetime]$_.updatedAt -gt [datetime]$since }
|
||||
```
|
||||
|
||||
### Collect Closed Issues with Recent Activity
|
||||
|
||||
```powershell
|
||||
# Closed issues that might have new comments
|
||||
$trackingDays = 30
|
||||
|
||||
gh issue list `
|
||||
--state closed `
|
||||
--json number,title,updatedAt,closedAt,comments `
|
||||
--limit 200 `
|
||||
| ConvertFrom-Json `
|
||||
| Where-Object {
|
||||
$closedDate = [datetime]$_.closedAt
|
||||
$updatedDate = [datetime]$_.updatedAt
|
||||
$cutoff = (Get-Date).AddDays(-$trackingDays)
|
||||
|
||||
# Closed within tracking window AND updated after closed
|
||||
($closedDate -gt $cutoff) -and ($updatedDate -gt $closedDate)
|
||||
}
|
||||
```
|
||||
|
||||
### Full Issue Details
|
||||
|
||||
For each issue needing analysis, fetch complete data:
|
||||
|
||||
```powershell
|
||||
function Get-IssueDetails {
|
||||
param([int]$IssueNumber)
|
||||
|
||||
$issue = gh issue view $IssueNumber `
|
||||
--json number,title,body,author,createdAt,updatedAt,state,labels,milestone,reactions,comments,linkedPullRequests `
|
||||
| ConvertFrom-Json
|
||||
|
||||
return @{
|
||||
number = $issue.number
|
||||
title = $issue.title
|
||||
body = $issue.body
|
||||
author = $issue.author.login
|
||||
state = $issue.state
|
||||
createdAt = $issue.createdAt
|
||||
updatedAt = $issue.updatedAt
|
||||
labels = $issue.labels | ForEach-Object { $_.name }
|
||||
milestone = $issue.milestone.title
|
||||
reactions = @{
|
||||
thumbsUp = ($issue.reactions | Where-Object { $_.content -eq "THUMBS_UP" }).Count
|
||||
thumbsDown = ($issue.reactions | Where-Object { $_.content -eq "THUMBS_DOWN" }).Count
|
||||
heart = ($issue.reactions | Where-Object { $_.content -eq "HEART" }).Count
|
||||
}
|
||||
commentCount = $issue.comments.Count
|
||||
comments = $issue.comments | ForEach-Object {
|
||||
@{
|
||||
author = $_.author.login
|
||||
createdAt = $_.createdAt
|
||||
body = $_.body
|
||||
}
|
||||
}
|
||||
linkedPRs = $issue.linkedPullRequests | ForEach-Object {
|
||||
@{
|
||||
number = $_.number
|
||||
title = $_.title
|
||||
state = $_.state
|
||||
mergedAt = $_.mergedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Filtering Logic
|
||||
|
||||
### First Run (No Previous State)
|
||||
|
||||
```powershell
|
||||
# Collect issues from last 7 days
|
||||
$lookbackDays = 7
|
||||
$since = (Get-Date).AddDays(-$lookbackDays).ToUniversalTime().ToString("o")
|
||||
|
||||
$openIssues = gh issue list --state open --json number,updatedAt --limit 500 `
|
||||
| ConvertFrom-Json `
|
||||
| Where-Object { [datetime]$_.updatedAt -gt [datetime]$since }
|
||||
|
||||
Write-Host "First run: Found $($openIssues.Count) issues from last $lookbackDays days"
|
||||
```
|
||||
|
||||
### Subsequent Runs
|
||||
|
||||
```powershell
|
||||
function Get-IssuesToTriage {
|
||||
param(
|
||||
[hashtable]$State,
|
||||
[string]$RunType = "weekly" # daily, twice-weekly, weekly
|
||||
)
|
||||
|
||||
$since = [datetime]$State.lastRun
|
||||
$issues = @()
|
||||
|
||||
# 1. Open issues updated since last run
|
||||
$openUpdated = gh issue list --state open --json number,updatedAt --limit 500 `
|
||||
| ConvertFrom-Json `
|
||||
| Where-Object { [datetime]$_.updatedAt -gt $since }
|
||||
$issues += $openUpdated
|
||||
|
||||
# 2. Closed issues we're tracking
|
||||
foreach ($tracked in $State.closedWithActivity) {
|
||||
$issueData = gh issue view $tracked.issueNumber --json updatedAt,comments | ConvertFrom-Json
|
||||
if ([datetime]$issueData.updatedAt -gt [datetime]$tracked.lastCheckedAt) {
|
||||
$issues += @{ number = $tracked.issueNumber; source = "closed-tracking" }
|
||||
}
|
||||
}
|
||||
|
||||
# 3. Issues with pending actions (re-check status)
|
||||
foreach ($pending in $State.pendingFollowUps) {
|
||||
if ($pending.status -eq "pending") {
|
||||
$issues += @{ number = $pending.issueNumber; source = "pending-action" }
|
||||
}
|
||||
}
|
||||
|
||||
# 4. Issues previously categorized but action not taken
|
||||
foreach ($snapshot in $State.issueSnapshots.Values) {
|
||||
if ($snapshot.pendingAction -and -not $snapshot.actionTaken) {
|
||||
if ($issues.number -notcontains $snapshot.number) {
|
||||
$issues += @{ number = $snapshot.number; source = "unhandled" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $issues | Sort-Object -Property number -Unique
|
||||
}
|
||||
```
|
||||
|
||||
## Comment Analysis
|
||||
|
||||
For trending detection, analyze comment activity:
|
||||
|
||||
```powershell
|
||||
function Get-CommentDelta {
|
||||
param(
|
||||
[int]$IssueNumber,
|
||||
[hashtable]$PreviousSnapshot
|
||||
)
|
||||
|
||||
$current = gh issue view $IssueNumber --json comments | ConvertFrom-Json
|
||||
|
||||
$previousCount = if ($PreviousSnapshot) { $PreviousSnapshot.commentCount } else { 0 }
|
||||
$previousLastComment = if ($PreviousSnapshot) { $PreviousSnapshot.lastCommentAt } else { $null }
|
||||
|
||||
$newComments = $current.comments | Where-Object {
|
||||
-not $previousLastComment -or [datetime]$_.createdAt -gt [datetime]$previousLastComment
|
||||
}
|
||||
|
||||
return @{
|
||||
totalComments = $current.comments.Count
|
||||
newCommentCount = $newComments.Count
|
||||
newComments = $newComments | ForEach-Object {
|
||||
@{
|
||||
author = $_.author.login
|
||||
createdAt = $_.createdAt
|
||||
bodyPreview = $_.body.Substring(0, [Math]::Min(200, $_.body.Length))
|
||||
}
|
||||
}
|
||||
lastCommentAt = ($current.comments | Sort-Object createdAt -Descending | Select-Object -First 1).createdAt
|
||||
lastCommentAuthor = ($current.comments | Sort-Object createdAt -Descending | Select-Object -First 1).author.login
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Output Format
|
||||
|
||||
Save collected issues to working file:
|
||||
|
||||
```powershell
|
||||
$collectedIssues | ConvertTo-Json -Depth 10 | Set-Content "Generated Files/triage-issues/current-run/collected-issues.json"
|
||||
```
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
GitHub API has rate limits. For large backlogs:
|
||||
|
||||
```powershell
|
||||
# Check rate limit
|
||||
gh api rate_limit --jq '.resources.core'
|
||||
|
||||
# Batch requests with delay if needed
|
||||
$batchSize = 50
|
||||
$delaySeconds = 2
|
||||
|
||||
for ($i = 0; $i -lt $issues.Count; $i += $batchSize) {
|
||||
$batch = $issues[$i..([Math]::Min($i + $batchSize - 1, $issues.Count - 1))]
|
||||
# Process batch...
|
||||
Start-Sleep -Seconds $delaySeconds
|
||||
}
|
||||
```
|
||||
|
||||
## Next Step
|
||||
|
||||
After collection, proceed to [Step 3: Categorization](./step3-categorization.md).
|
||||
@@ -1,432 +0,0 @@
|
||||
# Step 3: Categorization Rules
|
||||
|
||||
Apply categorization rules to assign each issue to an actionable bucket.
|
||||
|
||||
## Category Definitions
|
||||
|
||||
| Category | ID | Priority | Criteria |
|
||||
|----------|-----|----------|----------|
|
||||
| 🔥 **Trending** | `trending` | 1 | 5+ new comments since last run |
|
||||
| 🏷️ **Needs-Label** | `needs-label` | 2 | Missing `Product-*` or `Area-*` label |
|
||||
| ✅ **Ready-for-Fix** | `ready-for-fix` | 3 | High clarity (≥70), feasible (≥60), validated |
|
||||
| ❓ **Needs-Info** | `needs-info` | 4 | Missing repro, impact, or expected result |
|
||||
| 💬 **Needs-Clarification** | `needs-clarification` | 5 | Question/discussion, not actionable bug |
|
||||
| ✔️ **Closeable** | `closeable` | 6 | Fixed by merged PR, or released, or resolved |
|
||||
| ⏳ **Stale-Waiting** | `stale-waiting` | 7 | Waiting on author >14 days after ask |
|
||||
| 🔁 **Duplicate-Candidate** | `duplicate-candidate` | 8 | Likely duplicate of existing issue |
|
||||
|
||||
## Categorization Algorithm
|
||||
|
||||
```
|
||||
FOR EACH issue in collected_issues:
|
||||
|
||||
# Priority order - first match wins
|
||||
|
||||
1. CHECK TRENDING
|
||||
IF new_comments >= 5:
|
||||
category = "trending"
|
||||
CONTINUE
|
||||
|
||||
2. CHECK CLOSEABLE
|
||||
IF has_merged_PR AND PR_in_released_version:
|
||||
category = "closeable"
|
||||
reason = "Fixed in PR #X, released in vY.Z"
|
||||
CONTINUE
|
||||
IF state == "open" AND all_linked_PRs_merged:
|
||||
category = "closeable"
|
||||
reason = "All linked PRs merged"
|
||||
CONTINUE
|
||||
|
||||
3. CHECK NEEDS-LABEL
|
||||
IF missing_product_or_area_label:
|
||||
category = "needs-label"
|
||||
suggested_label = analyze_content()
|
||||
CONTINUE
|
||||
|
||||
4. CHECK STALE-WAITING
|
||||
IF has_label("Needs-Author-Feedback"):
|
||||
IF days_since_last_author_response > 14:
|
||||
category = "stale-waiting"
|
||||
CONTINUE
|
||||
|
||||
5. CHECK NEEDS-CLARIFICATION (question, not bug)
|
||||
IF is_question_not_bug():
|
||||
category = "needs-clarification"
|
||||
draft_reply = generate_explanation()
|
||||
CONTINUE
|
||||
|
||||
6. CHECK NEEDS-INFO
|
||||
IF missing_repro_steps OR missing_expected_result OR missing_version:
|
||||
category = "needs-info"
|
||||
missing_items = identify_gaps()
|
||||
draft_questions = generate_questions()
|
||||
CONTINUE
|
||||
|
||||
7. CHECK READY-FOR-FIX
|
||||
IF clarity_score >= 70 AND feasibility_score >= 60:
|
||||
category = "ready-for-fix"
|
||||
CONTINUE
|
||||
|
||||
8. CHECK DUPLICATE
|
||||
IF similar_issues_found AND confidence > 80:
|
||||
category = "duplicate-candidate"
|
||||
duplicate_of = [similar_issue_numbers]
|
||||
CONTINUE
|
||||
|
||||
9. DEFAULT
|
||||
category = "review-needed"
|
||||
# Needs human judgment
|
||||
```
|
||||
|
||||
## Category Rule Details
|
||||
|
||||
### 🔥 Trending Detection
|
||||
|
||||
```powershell
|
||||
function Test-Trending {
|
||||
param(
|
||||
[hashtable]$Issue,
|
||||
[hashtable]$PreviousSnapshot,
|
||||
[int]$Threshold = 5
|
||||
)
|
||||
|
||||
$previousCount = if ($PreviousSnapshot) { $PreviousSnapshot.commentCount } else { 0 }
|
||||
$newComments = $Issue.commentCount - $previousCount
|
||||
|
||||
if ($newComments -ge $Threshold) {
|
||||
return @{
|
||||
isTrending = $true
|
||||
newCommentCount = $newComments
|
||||
reason = "$newComments new comments since last triage"
|
||||
sentiment = Get-CommentSentiment $Issue.comments # Optional
|
||||
}
|
||||
}
|
||||
|
||||
return @{ isTrending = $false }
|
||||
}
|
||||
```
|
||||
|
||||
### 🏷️ Label Analysis
|
||||
|
||||
```powershell
|
||||
function Test-NeedsLabel {
|
||||
param([hashtable]$Issue)
|
||||
|
||||
$productLabels = $Issue.labels | Where-Object { $_ -like "Product-*" }
|
||||
$areaLabels = $Issue.labels | Where-Object { $_ -like "Area-*" }
|
||||
|
||||
if ($productLabels.Count -eq 0 -and $areaLabels.Count -eq 0) {
|
||||
# Analyze content to suggest label
|
||||
$suggestion = Get-LabelSuggestion $Issue
|
||||
|
||||
return @{
|
||||
needsLabel = $true
|
||||
missingType = "product-or-area"
|
||||
suggestedLabels = $suggestion.labels
|
||||
confidence = $suggestion.confidence
|
||||
reason = $suggestion.reason
|
||||
}
|
||||
}
|
||||
|
||||
return @{ needsLabel = $false }
|
||||
}
|
||||
|
||||
function Get-LabelSuggestion {
|
||||
param([hashtable]$Issue)
|
||||
|
||||
# Keyword mapping to products
|
||||
$productKeywords = @{
|
||||
"Product-FancyZones" = @("fancy zones", "fancyzones", "zone", "snap", "layout", "window arrangement")
|
||||
"Product-PowerToys Run" = @("run", "launcher", "alt+space", "search", "plugin")
|
||||
"Product-Color Picker" = @("color picker", "colorpicker", "eyedropper", "hex", "rgb")
|
||||
"Product-Keyboard Manager" = @("keyboard", "remap", "shortcut", "key")
|
||||
"Product-Mouse Utils" = @("mouse", "crosshairs", "find my mouse", "highlighter", "pointer")
|
||||
"Product-File Explorer" = @("file explorer", "preview", "thumbnail", "markdown preview", "svg")
|
||||
"Product-Image Resizer" = @("image resizer", "resize", "bulk resize")
|
||||
"Product-PowerRename" = @("rename", "power rename", "bulk rename", "regex rename")
|
||||
"Product-Awake" = @("awake", "keep awake", "prevent sleep", "caffeinate")
|
||||
"Product-Shortcut Guide" = @("shortcut guide", "win key", "keyboard shortcuts")
|
||||
"Product-Text Extractor" = @("text extractor", "ocr", "screen text", "copy text from screen")
|
||||
"Product-Hosts File Editor" = @("hosts", "hosts file", "dns")
|
||||
"Product-Peek" = @("peek", "quick preview", "spacebar preview")
|
||||
"Product-Crop And Lock" = @("crop", "crop and lock", "window crop")
|
||||
"Product-Paste As Plain Text" = @("paste", "plain text", "paste as")
|
||||
"Product-Registry Preview" = @("registry", "reg file", "registry preview")
|
||||
"Product-Environment Variables" = @("environment", "env", "variables", "path")
|
||||
"Product-Command Not Found" = @("command not found", "winget suggest")
|
||||
"Product-New+" = @("new+", "new plus", "file template")
|
||||
"Product-Advanced Paste" = @("advanced paste", "ai paste", "clipboard")
|
||||
"Product-Workspaces" = @("workspaces", "workspace", "project launcher")
|
||||
"Product-Cmd Palette" = @("command palette", "cmd palette", "palette")
|
||||
"Product-ZoomIt" = @("zoomit", "zoom it", "screen zoom", "magnifier")
|
||||
}
|
||||
|
||||
$titleLower = $Issue.title.ToLower()
|
||||
$bodyLower = if ($Issue.body) { $Issue.body.ToLower() } else { "" }
|
||||
$combined = "$titleLower $bodyLower"
|
||||
|
||||
$matches = @()
|
||||
foreach ($product in $productKeywords.Keys) {
|
||||
$keywords = $productKeywords[$product]
|
||||
$matchCount = ($keywords | Where-Object { $combined -match $_ }).Count
|
||||
if ($matchCount -gt 0) {
|
||||
$matches += @{
|
||||
label = $product
|
||||
matchCount = $matchCount
|
||||
confidence = [Math]::Min(100, $matchCount * 25 + 25)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$best = $matches | Sort-Object confidence -Descending | Select-Object -First 1
|
||||
|
||||
if ($best -and $best.confidence -ge 50) {
|
||||
return @{
|
||||
labels = @($best.label)
|
||||
confidence = $best.confidence
|
||||
reason = "Matched $($best.matchCount) keywords for $($best.label)"
|
||||
}
|
||||
}
|
||||
|
||||
return @{
|
||||
labels = @()
|
||||
confidence = 0
|
||||
reason = "No confident label match - needs human review"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ✅ Ready-for-Fix Detection
|
||||
|
||||
Leverage the `review-issue` prompt scores:
|
||||
|
||||
```powershell
|
||||
function Test-ReadyForFix {
|
||||
param(
|
||||
[hashtable]$Issue,
|
||||
[string]$CachePath = "Generated Files/triage-issues/issue-cache"
|
||||
)
|
||||
|
||||
$overviewPath = "$CachePath/$($Issue.number)/overview.md"
|
||||
|
||||
if (-not (Test-Path $overviewPath)) {
|
||||
# Need to run deep analysis first
|
||||
return @{ needsAnalysis = $true }
|
||||
}
|
||||
|
||||
# Parse scores from cached overview
|
||||
$overview = Get-Content $overviewPath -Raw
|
||||
$clarityScore = [regex]::Match($overview, 'Requirement Clarity.*?(\d+)/100').Groups[1].Value
|
||||
$feasibilityScore = [regex]::Match($overview, 'Technical Feasibility.*?(\d+)/100').Groups[1].Value
|
||||
|
||||
if ([int]$clarityScore -ge 70 -and [int]$feasibilityScore -ge 60) {
|
||||
return @{
|
||||
readyForFix = $true
|
||||
clarityScore = [int]$clarityScore
|
||||
feasibilityScore = [int]$feasibilityScore
|
||||
reason = "High clarity ($clarityScore) and feasible ($feasibilityScore)"
|
||||
}
|
||||
}
|
||||
|
||||
return @{ readyForFix = $false }
|
||||
}
|
||||
```
|
||||
|
||||
### ❓ Needs-Info Detection
|
||||
|
||||
```powershell
|
||||
function Test-NeedsInfo {
|
||||
param([hashtable]$Issue)
|
||||
|
||||
$missingItems = @()
|
||||
$body = $Issue.body
|
||||
|
||||
# Check for repro steps
|
||||
if ($body -notmatch '(?i)(steps to reproduce|repro|how to reproduce|reproduction)') {
|
||||
$missingItems += "reproduction steps"
|
||||
}
|
||||
|
||||
# Check for expected result
|
||||
if ($body -notmatch '(?i)(expected|should|supposed to)') {
|
||||
$missingItems += "expected behavior"
|
||||
}
|
||||
|
||||
# Check for version
|
||||
if ($body -notmatch '(?i)(version|v\d+\.\d+|\d+\.\d+\.\d+)') {
|
||||
$missingItems += "PowerToys version"
|
||||
}
|
||||
|
||||
# Check for OS version
|
||||
if ($body -notmatch '(?i)(windows 1[01]|win1[01]|22h2|23h2|24h2|build \d+)') {
|
||||
$missingItems += "Windows version"
|
||||
}
|
||||
|
||||
# Check for actual result (for bugs)
|
||||
if ($Issue.labels -contains "Issue-Bug") {
|
||||
if ($body -notmatch '(?i)(actual|instead|but|however|currently)') {
|
||||
$missingItems += "actual behavior/result"
|
||||
}
|
||||
}
|
||||
|
||||
if ($missingItems.Count -gt 0) {
|
||||
return @{
|
||||
needsInfo = $true
|
||||
missingItems = $missingItems
|
||||
reason = "Missing: " + ($missingItems -join ", ")
|
||||
}
|
||||
}
|
||||
|
||||
return @{ needsInfo = $false }
|
||||
}
|
||||
```
|
||||
|
||||
### 💬 Needs-Clarification (Not a Bug)
|
||||
|
||||
```powershell
|
||||
function Test-NeedsClarification {
|
||||
param([hashtable]$Issue)
|
||||
|
||||
$questionPatterns = @(
|
||||
'(?i)^(how (do|can|to)|why (does|is|doesn''t)|is (it|there|this) (possible|a way))',
|
||||
'(?i)\?$', # Ends with question mark
|
||||
'(?i)(wondering|curious|question|asking)',
|
||||
'(?i)(is this (intended|by design|expected))',
|
||||
'(?i)(can (someone|you) (explain|help))'
|
||||
)
|
||||
|
||||
$titleAndBody = $Issue.title + " " + $Issue.body
|
||||
|
||||
$isQuestion = $false
|
||||
foreach ($pattern in $questionPatterns) {
|
||||
if ($titleAndBody -match $pattern) {
|
||||
$isQuestion = $true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
# Also check if explicitly marked as question
|
||||
if ($Issue.labels -contains "Issue-Question" -or $Issue.labels -contains "Type-Question") {
|
||||
$isQuestion = $true
|
||||
}
|
||||
|
||||
if ($isQuestion -and ($Issue.labels -notcontains "Issue-Bug")) {
|
||||
return @{
|
||||
needsClarification = $true
|
||||
type = "question"
|
||||
reason = "Appears to be a question/inquiry rather than bug report"
|
||||
}
|
||||
}
|
||||
|
||||
return @{ needsClarification = $false }
|
||||
}
|
||||
```
|
||||
|
||||
### ✔️ Closeable Detection
|
||||
|
||||
```powershell
|
||||
function Test-Closeable {
|
||||
param([hashtable]$Issue)
|
||||
|
||||
$closeReasons = @()
|
||||
|
||||
# Check for merged linked PRs
|
||||
$mergedPRs = $Issue.linkedPRs | Where-Object { $_.state -eq "MERGED" }
|
||||
if ($mergedPRs.Count -gt 0) {
|
||||
$closeReasons += @{
|
||||
type = "fixed-by-pr"
|
||||
prNumbers = $mergedPRs.number
|
||||
reason = "Fixed by PR(s): #" + ($mergedPRs.number -join ", #")
|
||||
}
|
||||
}
|
||||
|
||||
# Check comments for "fixed in" or "released in"
|
||||
$recentComments = $Issue.comments | Sort-Object createdAt -Descending | Select-Object -First 5
|
||||
foreach ($comment in $recentComments) {
|
||||
if ($comment.body -match '(?i)(fixed in|released in|available in|shipped in) v?(\d+\.\d+)') {
|
||||
$version = $Matches[2]
|
||||
$closeReasons += @{
|
||||
type = "released"
|
||||
version = $version
|
||||
reason = "Released in v$version"
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
# Check if marked as duplicate
|
||||
if ($Issue.labels -contains "Resolution-Duplicate") {
|
||||
$closeReasons += @{
|
||||
type = "duplicate"
|
||||
reason = "Marked as duplicate"
|
||||
}
|
||||
}
|
||||
|
||||
# Check if marked as won't fix
|
||||
if ($Issue.labels -contains "Resolution-Won't Fix" -or $Issue.labels -contains "Resolution-By-Design") {
|
||||
$closeReasons += @{
|
||||
type = "wont-fix"
|
||||
reason = "Marked as won't fix / by design"
|
||||
}
|
||||
}
|
||||
|
||||
if ($closeReasons.Count -gt 0) {
|
||||
return @{
|
||||
closeable = $true
|
||||
reasons = $closeReasons
|
||||
}
|
||||
}
|
||||
|
||||
return @{ closeable = $false }
|
||||
}
|
||||
```
|
||||
|
||||
## Priority Scoring
|
||||
|
||||
Combine signals for overall priority within category:
|
||||
|
||||
```powershell
|
||||
function Get-PriorityScore {
|
||||
param([hashtable]$Issue)
|
||||
|
||||
$score = 50 # Base score
|
||||
|
||||
# Reaction boost
|
||||
$thumbsUp = $Issue.reactions.thumbsUp
|
||||
$score += [Math]::Min(20, $thumbsUp * 2)
|
||||
|
||||
# Comment engagement
|
||||
$score += [Math]::Min(15, $Issue.commentCount)
|
||||
|
||||
# Recency boost (updated recently)
|
||||
$daysSinceUpdate = ((Get-Date) - [datetime]$Issue.updatedAt).Days
|
||||
if ($daysSinceUpdate -le 7) { $score += 10 }
|
||||
elseif ($daysSinceUpdate -le 30) { $score += 5 }
|
||||
|
||||
# Label boosts
|
||||
if ($Issue.labels -contains "Priority-High") { $score += 15 }
|
||||
if ($Issue.labels -match "Regression") { $score += 20 }
|
||||
if ($Issue.labels -match "Security") { $score += 25 }
|
||||
|
||||
return [Math]::Min(100, $score)
|
||||
}
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
Save categorization results:
|
||||
|
||||
```json
|
||||
{
|
||||
"12345": {
|
||||
"category": "trending",
|
||||
"categoryReason": "8 new comments since last run",
|
||||
"priorityScore": 82,
|
||||
"additionalFlags": ["negative-sentiment"],
|
||||
"suggestedAction": "Review urgent - heated discussion"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Next Step
|
||||
|
||||
Proceed to [Step 4: Deep Analysis](./step4-deep-analysis.md) for complex issues.
|
||||
@@ -1,274 +0,0 @@
|
||||
# Step 4: Deep Analysis
|
||||
|
||||
For issues requiring detailed analysis, leverage the `review-issue` prompt to generate comprehensive reviews.
|
||||
|
||||
## When to Run Deep Analysis
|
||||
|
||||
| Category | Deep Analysis? | Reason |
|
||||
|----------|---------------|--------|
|
||||
| Trending | Optional | If conversation is contentious |
|
||||
| Needs-Label | No | Label detection is keyword-based |
|
||||
| Ready-for-Fix | Yes (cached) | Need scores for validation |
|
||||
| Needs-Info | Optional | To identify specific gaps |
|
||||
| Needs-Clarification | No | Simple question detection |
|
||||
| Closeable | No | Mechanical check |
|
||||
| Stale-Waiting | No | Time-based |
|
||||
| Duplicate-Candidate | Optional | Similar issue search |
|
||||
|
||||
## Integration with review-issue Prompt
|
||||
|
||||
The `review-issue` prompt generates two artifacts:
|
||||
- `overview.md` - Scoring, signals, suggested actions
|
||||
- `implementation-plan.md` - Technical breakdown
|
||||
|
||||
### Invoking the Prompt
|
||||
|
||||
```markdown
|
||||
# Within the agent's execution, reference the prompt:
|
||||
|
||||
For issue #{{issue_number}}, I need detailed analysis.
|
||||
|
||||
Use the review-issue prompt at `.github/prompts/review-issue.prompt.md` to generate:
|
||||
1. `Generated Files/triage-issues/issue-cache/{{issue_number}}/overview.md`
|
||||
2. `Generated Files/triage-issues/issue-cache/{{issue_number}}/implementation-plan.md`
|
||||
```
|
||||
|
||||
### Caching Strategy
|
||||
|
||||
```
|
||||
Generated Files/triage-issues/issue-cache/
|
||||
├── 12345/
|
||||
│ ├── overview.md
|
||||
│ ├── implementation-plan.md
|
||||
│ └── metadata.json
|
||||
└── 12346/
|
||||
└── ...
|
||||
```
|
||||
|
||||
**metadata.json**:
|
||||
```json
|
||||
{
|
||||
"issueNumber": 12345,
|
||||
"analyzedAt": "2026-02-05T10:30:00Z",
|
||||
"issueUpdatedAt": "2026-02-04T15:30:00Z",
|
||||
"commentCountAtAnalysis": 15,
|
||||
"isStale": false
|
||||
}
|
||||
```
|
||||
|
||||
### Cache Invalidation
|
||||
|
||||
Re-run analysis if:
|
||||
1. Issue has new comments since last analysis
|
||||
2. Issue state changed (open ↔ closed)
|
||||
3. Labels changed significantly
|
||||
4. More than 7 days since last analysis
|
||||
5. User explicitly requests refresh
|
||||
|
||||
```powershell
|
||||
function Test-CacheValid {
|
||||
param(
|
||||
[int]$IssueNumber,
|
||||
[hashtable]$CurrentIssueData
|
||||
)
|
||||
|
||||
$cachePath = "Generated Files/triage-issues/issue-cache/$IssueNumber"
|
||||
$metadataPath = "$cachePath/metadata.json"
|
||||
|
||||
if (-not (Test-Path $metadataPath)) {
|
||||
return @{ valid = $false; reason = "No cached analysis" }
|
||||
}
|
||||
|
||||
$metadata = Get-Content $metadataPath | ConvertFrom-Json
|
||||
|
||||
# Check freshness
|
||||
$daysSinceAnalysis = ((Get-Date) - [datetime]$metadata.analyzedAt).Days
|
||||
if ($daysSinceAnalysis -gt 7) {
|
||||
return @{ valid = $false; reason = "Cache older than 7 days" }
|
||||
}
|
||||
|
||||
# Check for new comments
|
||||
if ($CurrentIssueData.commentCount -gt $metadata.commentCountAtAnalysis) {
|
||||
return @{ valid = $false; reason = "New comments added" }
|
||||
}
|
||||
|
||||
# Check for state change
|
||||
if ($CurrentIssueData.updatedAt -gt $metadata.issueUpdatedAt) {
|
||||
return @{ valid = $false; reason = "Issue updated since analysis" }
|
||||
}
|
||||
|
||||
return @{ valid = $true }
|
||||
}
|
||||
```
|
||||
|
||||
## Selective Analysis
|
||||
|
||||
Don't analyze every issue - be selective:
|
||||
|
||||
### Batch 1: High-Priority Analysis
|
||||
|
||||
Analyze first:
|
||||
- Trending issues with negative sentiment
|
||||
- Potential ready-for-fix candidates (unclear if ready)
|
||||
- Issues with high reaction counts (>10 👍)
|
||||
|
||||
### Batch 2: Moderate Priority
|
||||
|
||||
Analyze if time permits:
|
||||
- Needs-Info issues (to draft better questions)
|
||||
- Complex duplicate candidates
|
||||
|
||||
### Batch 3: Skip Analysis
|
||||
|
||||
Don't analyze:
|
||||
- Clear closeable issues
|
||||
- Stale-waiting issues
|
||||
- Already-analyzed recent issues
|
||||
|
||||
## Extracting Scores from Analysis
|
||||
|
||||
After running `review-issue`, parse the `overview.md`:
|
||||
|
||||
```powershell
|
||||
function Get-AnalysisScores {
|
||||
param([string]$OverviewPath)
|
||||
|
||||
$content = Get-Content $OverviewPath -Raw
|
||||
|
||||
# Extract from the At-a-Glance Score Table
|
||||
$scores = @{}
|
||||
|
||||
# Business Importance
|
||||
if ($content -match '\*\*A\) Business Importance\*\*.*?(\d+)/100') {
|
||||
$scores.businessImportance = [int]$Matches[1]
|
||||
}
|
||||
|
||||
# Community Excitement
|
||||
if ($content -match '\*\*B\) Community Excitement\*\*.*?(\d+)/100') {
|
||||
$scores.communityExcitement = [int]$Matches[1]
|
||||
}
|
||||
|
||||
# Technical Feasibility
|
||||
if ($content -match '\*\*C\) Technical Feasibility\*\*.*?(\d+)/100') {
|
||||
$scores.technicalFeasibility = [int]$Matches[1]
|
||||
}
|
||||
|
||||
# Requirement Clarity
|
||||
if ($content -match '\*\*D\) Requirement Clarity\*\*.*?(\d+)/100') {
|
||||
$scores.requirementClarity = [int]$Matches[1]
|
||||
}
|
||||
|
||||
# Overall Priority
|
||||
if ($content -match '\*\*Overall Priority\*\*.*?(\d+)/100') {
|
||||
$scores.overallPriority = [int]$Matches[1]
|
||||
}
|
||||
|
||||
# Effort Estimate
|
||||
if ($content -match '\*\*Effort Estimate\*\*.*?(\d+) days.*?(XS|S|M|L|XL|XXL|Epic)') {
|
||||
$scores.effortDays = [int]$Matches[1]
|
||||
$scores.effortTShirt = $Matches[2]
|
||||
}
|
||||
|
||||
return $scores
|
||||
}
|
||||
```
|
||||
|
||||
## Similar Issue Search
|
||||
|
||||
For duplicate detection, search existing issues:
|
||||
|
||||
```powershell
|
||||
function Find-SimilarIssues {
|
||||
param([hashtable]$Issue)
|
||||
|
||||
# Extract key terms from title
|
||||
$searchTerms = $Issue.title -split '\s+' | Where-Object { $_.Length -gt 3 }
|
||||
$searchQuery = ($searchTerms | Select-Object -First 5) -join ' '
|
||||
|
||||
# Search both open and closed
|
||||
$similar = gh issue list `
|
||||
--search "$searchQuery" `
|
||||
--state all `
|
||||
--json number,title,state,closedAt,labels `
|
||||
--limit 10 `
|
||||
| ConvertFrom-Json `
|
||||
| Where-Object { $_.number -ne $Issue.number }
|
||||
|
||||
# Score similarity
|
||||
$results = $similar | ForEach-Object {
|
||||
$similarity = Get-TitleSimilarity $Issue.title $_.title
|
||||
@{
|
||||
number = $_.number
|
||||
title = $_.title
|
||||
state = $_.state
|
||||
closedAt = $_.closedAt
|
||||
similarityScore = $similarity
|
||||
}
|
||||
} | Where-Object { $_.similarityScore -gt 50 } | Sort-Object similarityScore -Descending
|
||||
|
||||
return $results
|
||||
}
|
||||
|
||||
function Get-TitleSimilarity {
|
||||
param(
|
||||
[string]$Title1,
|
||||
[string]$Title2
|
||||
)
|
||||
|
||||
$words1 = $Title1.ToLower() -split '\W+' | Where-Object { $_.Length -gt 2 }
|
||||
$words2 = $Title2.ToLower() -split '\W+' | Where-Object { $_.Length -gt 2 }
|
||||
|
||||
$common = ($words1 | Where-Object { $words2 -contains $_ }).Count
|
||||
$total = [Math]::Max($words1.Count, $words2.Count)
|
||||
|
||||
if ($total -eq 0) { return 0 }
|
||||
|
||||
return [int](($common / $total) * 100)
|
||||
}
|
||||
```
|
||||
|
||||
## MCP Tools for Rich Context
|
||||
|
||||
When available, use MCP tools for additional context:
|
||||
|
||||
### Images (UI issues)
|
||||
|
||||
```markdown
|
||||
If the issue mentions screenshots or UI problems, use MCP:
|
||||
|
||||
github_issue_images(owner: "microsoft", repo: "PowerToys", issueNumber: 12345)
|
||||
```
|
||||
|
||||
### Attachments (Logs)
|
||||
|
||||
```markdown
|
||||
If the issue mentions logs or diagnostic reports:
|
||||
|
||||
github_issue_attachments(
|
||||
owner: "microsoft",
|
||||
repo: "PowerToys",
|
||||
issueNumber: 12345,
|
||||
extractFolder: "Generated Files/triage-issues/issue-cache/12345/logs"
|
||||
)
|
||||
```
|
||||
|
||||
## Analysis Output
|
||||
|
||||
Save analysis metadata for state tracking:
|
||||
|
||||
```powershell
|
||||
$metadata = @{
|
||||
issueNumber = $Issue.number
|
||||
analyzedAt = (Get-Date).ToUniversalTime().ToString("o")
|
||||
issueUpdatedAt = $Issue.updatedAt
|
||||
commentCountAtAnalysis = $Issue.commentCount
|
||||
scores = $extractedScores
|
||||
suggestedCategory = $determinedCategory
|
||||
}
|
||||
|
||||
$metadata | ConvertTo-Json | Set-Content "$cachePath/metadata.json"
|
||||
```
|
||||
|
||||
## Next Step
|
||||
|
||||
Proceed to [Step 5: Report Generation](./step5-reports.md).
|
||||
@@ -1,316 +0,0 @@
|
||||
# Step 5: Report Generation
|
||||
|
||||
Generate actionable reports for each category and an executive summary.
|
||||
|
||||
## Report Structure
|
||||
|
||||
```
|
||||
Generated Files/triage-issues/current-run/
|
||||
├── summary.md # Executive summary (start here)
|
||||
├── trending.md # 🔥 Trending issues
|
||||
├── needs-label.md # 🏷️ Issues missing labels
|
||||
├── ready-for-fix.md # ✅ Ready for implementation
|
||||
├── needs-info.md # ❓ Needs author feedback
|
||||
├── needs-clarification.md # 💬 Questions/discussions
|
||||
├── closeable.md # ✔️ Can be closed
|
||||
├── stale-waiting.md # ⏳ Waiting on author
|
||||
├── duplicate-candidate.md # 🔁 Potential duplicates
|
||||
└── draft-replies/ # Pre-drafted messages
|
||||
├── issue-12345.md
|
||||
└── issue-12346.md
|
||||
```
|
||||
|
||||
## Executive Summary Template
|
||||
|
||||
**File**: `summary.md`
|
||||
|
||||
```markdown
|
||||
# Issue Triage Summary - {{DATE}}
|
||||
|
||||
**Run Type**: {{RUN_TYPE}} | **Issues Analyzed**: {{TOTAL_COUNT}} | **Since**: {{LAST_RUN_DATE}}
|
||||
|
||||
## 📊 Quick Stats
|
||||
|
||||
| Metric | Value | Change |
|
||||
|--------|-------|--------|
|
||||
| Total issues scanned | {{TOTAL}} | {{DELTA}} |
|
||||
| New issues since last run | {{NEW_COUNT}} | — |
|
||||
| Issues with new activity | {{ACTIVE_COUNT}} | — |
|
||||
| Closed issues with comments | {{CLOSED_ACTIVE}} | — |
|
||||
|
||||
## ⚡ Action Required by Category
|
||||
|
||||
| Category | Count | Top Priority | Draft Ready? |
|
||||
|----------|-------|--------------|--------------|
|
||||
| 🔥 Trending | {{COUNT}} | [#{{NUM}}]({{LINK}}) ({{COMMENTS}} new comments) | — |
|
||||
| 🏷️ Needs-Label | {{COUNT}} | [#{{NUM}}]({{LINK}}) (suggest: {{LABEL}}) | — |
|
||||
| ✅ Ready-for-Fix | {{COUNT}} | [#{{NUM}}]({{LINK}}) (score: {{SCORE}}/100) | — |
|
||||
| ❓ Needs-Info | {{COUNT}} | [#{{NUM}}]({{LINK}}) (missing: {{ITEMS}}) | ✅ |
|
||||
| 💬 Needs-Clarification | {{COUNT}} | [#{{NUM}}]({{LINK}}) | ✅ |
|
||||
| ✔️ Closeable | {{COUNT}} | [#{{NUM}}]({{LINK}}) ({{REASON}}) | ✅ |
|
||||
| ⏳ Stale-Waiting | {{COUNT}} | [#{{NUM}}]({{LINK}}) ({{DAYS}} days) | ✅ |
|
||||
| 🔁 Duplicate-Candidate | {{COUNT}} | [#{{NUM}}]({{LINK}}) → #{{DUP_OF}} | — |
|
||||
|
||||
## 🎯 Top 5 Priority Actions
|
||||
|
||||
1. **[Urgent]** Review [#{{NUM}}]({{LINK}}) - {{REASON}}
|
||||
2. **[High]** Post clarification on [#{{NUM}}]({{LINK}}) - draft ready
|
||||
3. **[High]** Assign [#{{NUM}}]({{LINK}}) - ready for implementation
|
||||
4. **[Medium]** Label [#{{NUM}}]({{LINK}}) as {{LABEL}}
|
||||
5. **[Low]** Close [#{{NUM}}]({{LINK}}) - fixed in v{{VERSION}}
|
||||
|
||||
## 📁 Detailed Reports
|
||||
|
||||
- [Trending Issues](./trending.md)
|
||||
- [Needs Label](./needs-label.md)
|
||||
- [Ready for Fix](./ready-for-fix.md)
|
||||
- [Needs Information](./needs-info.md)
|
||||
- [Needs Clarification](./needs-clarification.md)
|
||||
- [Closeable](./closeable.md)
|
||||
- [Stale Waiting](./stale-waiting.md)
|
||||
- [Duplicate Candidates](./duplicate-candidate.md)
|
||||
|
||||
## 📝 Draft Replies Ready
|
||||
|
||||
{{COUNT}} draft replies prepared in `draft-replies/`:
|
||||
{{DRAFT_LIST}}
|
||||
|
||||
## ⏭️ Follow-ups from Last Run
|
||||
|
||||
| Issue | Previous Action | Status |
|
||||
|-------|-----------------|--------|
|
||||
| [#{{NUM}}]({{LINK}}) | Posted clarification | ✅ Resolved |
|
||||
| [#{{NUM}}]({{LINK}}) | Requested info | ⏳ No response |
|
||||
| [#{{NUM}}]({{LINK}}) | Assigned to @{{USER}} | 🔄 In progress |
|
||||
|
||||
---
|
||||
*Generated by continuous-issue-triage skill | Next suggested run: {{NEXT_RUN}}*
|
||||
```
|
||||
|
||||
## Category Report Templates
|
||||
|
||||
### Trending Report (`trending.md`)
|
||||
|
||||
```markdown
|
||||
# 🔥 Trending Issues
|
||||
|
||||
Issues with significant activity since last triage ({{THRESHOLD}}+ new comments).
|
||||
|
||||
| # | Issue | New Comments | Total | Sentiment | Last Activity |
|
||||
|---|-------|--------------|-------|-----------|---------------|
|
||||
| 1 | [#{{NUM}}]({{LINK}}) {{TITLE}} | +{{NEW}} | {{TOTAL}} | {{SENTIMENT}} | {{TIME_AGO}} |
|
||||
|
||||
---
|
||||
|
||||
## #{{ISSUE_NUM}}: {{TITLE}}
|
||||
|
||||
**Activity**: +{{NEW}} comments ({{TOTAL}} total) | **Sentiment**: {{SENTIMENT}}
|
||||
|
||||
### Recent Discussion Summary
|
||||
|
||||
{{SUMMARY_OF_RECENT_COMMENTS}}
|
||||
|
||||
### Key Participants
|
||||
|
||||
- @{{USER1}} ({{COMMENT_COUNT}} comments) - {{STANCE}}
|
||||
- @{{USER2}} ({{COMMENT_COUNT}} comments) - {{STANCE}}
|
||||
|
||||
### Recommended Action
|
||||
|
||||
{{RECOMMENDATION}}
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
### Needs-Label Report (`needs-label.md`)
|
||||
|
||||
```markdown
|
||||
# 🏷️ Issues Missing Area/Product Labels
|
||||
|
||||
These issues need categorization for proper routing.
|
||||
|
||||
| # | Issue | Suggested Label | Confidence | Reason |
|
||||
|---|-------|-----------------|------------|--------|
|
||||
| 1 | [#{{NUM}}]({{LINK}}) {{TITLE}} | `{{LABEL}}` | {{CONF}}% | {{REASON}} |
|
||||
|
||||
---
|
||||
|
||||
## Quick Apply Commands
|
||||
|
||||
```bash
|
||||
# Apply suggested labels (review first!)
|
||||
gh issue edit {{NUM}} --add-label "{{LABEL}}"
|
||||
gh issue edit {{NUM}} --add-label "{{LABEL}}"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Detailed Analysis
|
||||
|
||||
### #{{ISSUE_NUM}}: {{TITLE}}
|
||||
|
||||
**Suggested**: `{{LABEL}}` ({{CONFIDENCE}}% confidence)
|
||||
|
||||
**Why**: {{DETAILED_REASON}}
|
||||
|
||||
**Alternative labels to consider**: {{ALTERNATIVES}}
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
### Ready-for-Fix Report (`ready-for-fix.md`)
|
||||
|
||||
```markdown
|
||||
# ✅ Issues Ready for Implementation
|
||||
|
||||
High-clarity issues that are technically feasible.
|
||||
|
||||
| # | Issue | Clarity | Feasibility | Effort | Potential Assignee |
|
||||
|---|-------|---------|-------------|--------|-------------------|
|
||||
| 1 | [#{{NUM}}]({{LINK}}) {{TITLE}} | {{CLARITY}}/100 | {{FEASIBILITY}}/100 | {{EFFORT}} | @{{USER}} |
|
||||
|
||||
---
|
||||
|
||||
## #{{ISSUE_NUM}}: {{TITLE}}
|
||||
|
||||
**Scores**: Clarity {{CLARITY}}/100 | Feasibility {{FEASIBILITY}}/100 | Priority {{PRIORITY}}/100
|
||||
|
||||
**Effort**: {{DAYS}} days ({{TSHIRT}})
|
||||
|
||||
**Product Area**: {{LABELS}}
|
||||
|
||||
### Problem Summary
|
||||
|
||||
{{BRIEF_PROBLEM}}
|
||||
|
||||
### Implementation Hints
|
||||
|
||||
{{FROM_IMPLEMENTATION_PLAN}}
|
||||
|
||||
### Suggested Assignees
|
||||
|
||||
- @{{USER1}} - {{REASON}}
|
||||
- @{{USER2}} - {{REASON}}
|
||||
|
||||
**Full Analysis**: [issue-cache/{{NUM}}/overview.md](../issue-cache/{{NUM}}/overview.md)
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
### Needs-Info Report (`needs-info.md`)
|
||||
|
||||
```markdown
|
||||
# ❓ Issues Needing More Information
|
||||
|
||||
These issues lack details needed for investigation or planning.
|
||||
|
||||
| # | Issue | Missing | Days Open | Draft Ready |
|
||||
|---|-------|---------|-----------|-------------|
|
||||
| 1 | [#{{NUM}}]({{LINK}}) {{TITLE}} | {{MISSING}} | {{DAYS}} | [View](./draft-replies/issue-{{NUM}}.md) |
|
||||
|
||||
---
|
||||
|
||||
## #{{ISSUE_NUM}}: {{TITLE}}
|
||||
|
||||
**Missing Information**:
|
||||
- [ ] {{ITEM_1}}
|
||||
- [ ] {{ITEM_2}}
|
||||
- [ ] {{ITEM_3}}
|
||||
|
||||
**Draft Reply**: [draft-replies/issue-{{NUM}}.md](./draft-replies/issue-{{NUM}}.md)
|
||||
|
||||
### Quick Post
|
||||
|
||||
```bash
|
||||
gh issue comment {{NUM}} --body-file "Generated Files/triage-issues/current-run/draft-replies/issue-{{NUM}}.md"
|
||||
gh issue edit {{NUM}} --add-label "Needs-Author-Feedback"
|
||||
```
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
### Closeable Report (`closeable.md`)
|
||||
|
||||
```markdown
|
||||
# ✔️ Issues Ready to Close
|
||||
|
||||
These issues can be closed with appropriate messaging.
|
||||
|
||||
| # | Issue | Close Reason | PR/Version | Draft Ready |
|
||||
|---|-------|--------------|------------|-------------|
|
||||
| 1 | [#{{NUM}}]({{LINK}}) {{TITLE}} | {{REASON}} | {{REFERENCE}} | [View](./draft-replies/issue-{{NUM}}.md) |
|
||||
|
||||
---
|
||||
|
||||
## Batch Close Commands
|
||||
|
||||
```bash
|
||||
# Review drafts first, then close with message
|
||||
|
||||
# Fixed by PR
|
||||
gh issue close {{NUM}} --comment "Fixed in #{{PR_NUM}}. This fix is available in v{{VERSION}}. Thank you for reporting!"
|
||||
|
||||
# Duplicate
|
||||
gh issue close {{NUM}} --comment "Closing as duplicate of #{{DUP_NUM}}. Please follow that issue for updates."
|
||||
|
||||
# By design / Won't fix
|
||||
gh issue close {{NUM}} --comment "After review, this is working as designed. {{EXPLANATION}}"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## #{{ISSUE_NUM}}: {{TITLE}}
|
||||
|
||||
**Reason**: {{DETAILED_REASON}}
|
||||
|
||||
**Draft Close Message**: [draft-replies/issue-{{NUM}}.md](./draft-replies/issue-{{NUM}}.md)
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
## Generation Script
|
||||
|
||||
```powershell
|
||||
function New-TriageReports {
|
||||
param(
|
||||
[hashtable]$CategorizedIssues,
|
||||
[hashtable]$State,
|
||||
[string]$OutputPath = "Generated Files/triage-issues/current-run"
|
||||
)
|
||||
|
||||
# Ensure directory exists
|
||||
New-Item -ItemType Directory -Force -Path $OutputPath
|
||||
New-Item -ItemType Directory -Force -Path "$OutputPath/draft-replies"
|
||||
|
||||
# Group by category
|
||||
$byCategory = $CategorizedIssues.Values | Group-Object -Property category
|
||||
|
||||
# Generate category reports
|
||||
foreach ($group in $byCategory) {
|
||||
$categoryName = $group.Name
|
||||
$issues = $group.Group | Sort-Object priorityScore -Descending
|
||||
|
||||
$reportPath = "$OutputPath/$categoryName.md"
|
||||
$reportContent = New-CategoryReport -Category $categoryName -Issues $issues
|
||||
$reportContent | Set-Content $reportPath
|
||||
}
|
||||
|
||||
# Generate summary
|
||||
$summaryContent = New-ExecutiveSummary -Categories $byCategory -State $State
|
||||
$summaryContent | Set-Content "$OutputPath/summary.md"
|
||||
|
||||
Write-Host "Reports generated at $OutputPath"
|
||||
}
|
||||
```
|
||||
|
||||
## Report Conventions
|
||||
|
||||
1. **Always link to GitHub**: Use `[#NUM](https://github.com/microsoft/PowerToys/issues/NUM)`
|
||||
2. **Include quick commands**: Provide `gh` CLI commands for easy action
|
||||
3. **Sort by priority**: Highest priority issues first within each category
|
||||
4. **Cross-reference drafts**: Link to draft replies when available
|
||||
5. **Show deltas**: Compare to previous run where applicable
|
||||
|
||||
## Next Step
|
||||
|
||||
Proceed to [Step 6: Reply Templates](./step6-reply-templates.md) for draft message generation.
|
||||
@@ -1,340 +0,0 @@
|
||||
# Step 6: Reply Templates
|
||||
|
||||
Generate draft replies for issues requiring human response.
|
||||
|
||||
## Draft Reply Location
|
||||
|
||||
```
|
||||
Generated Files/triage-issues/current-run/draft-replies/
|
||||
├── issue-12345.md # Needs-info draft
|
||||
├── issue-12346.md # Clarification draft
|
||||
├── issue-12347.md # Close message draft
|
||||
└── ...
|
||||
```
|
||||
|
||||
## Reply Categories
|
||||
|
||||
| Category | Reply Type | Tone | Key Elements |
|
||||
|----------|------------|------|--------------|
|
||||
| Needs-Info | Question list | Friendly, helpful | Specific questions, context why needed |
|
||||
| Needs-Clarification | Explanation | Educational, patient | Answer the question, link to docs |
|
||||
| Closeable (fixed) | Thank you + reference | Grateful | PR link, version, appreciation |
|
||||
| Closeable (duplicate) | Redirect | Brief, helpful | Link to original, explain |
|
||||
| Closeable (by-design) | Explanation | Respectful | Rationale, alternatives |
|
||||
| Stale-Waiting | Gentle ping | Patient | Reminder, offer to close |
|
||||
|
||||
## Template: Needs-Info Reply
|
||||
|
||||
**File**: `issue-XXXXX.md`
|
||||
|
||||
```markdown
|
||||
Hi @{{AUTHOR}},
|
||||
|
||||
Thank you for reporting this issue! To help us investigate further, could you please provide the following information?
|
||||
|
||||
{{#IF_MISSING_REPRO}}
|
||||
**Reproduction Steps**
|
||||
- What exact steps lead to this issue?
|
||||
- Can you provide a minimal, consistent way to reproduce it?
|
||||
{{/IF_MISSING_REPRO}}
|
||||
|
||||
{{#IF_MISSING_VERSION}}
|
||||
**Environment Details**
|
||||
- PowerToys version (Settings > General > Version):
|
||||
- Windows version (winver):
|
||||
- Did this work in a previous version? If so, which one?
|
||||
{{/IF_MISSING_VERSION}}
|
||||
|
||||
{{#IF_MISSING_EXPECTED}}
|
||||
**Expected vs Actual Behavior**
|
||||
- What did you expect to happen?
|
||||
- What actually happened instead?
|
||||
{{/IF_MISSING_EXPECTED}}
|
||||
|
||||
{{#IF_MISSING_SCREENSHOTS}}
|
||||
**Visual Evidence** (if applicable)
|
||||
- Could you attach a screenshot or screen recording showing the issue?
|
||||
{{/IF_MISSING_SCREENSHOTS}}
|
||||
|
||||
{{#IF_MISSING_LOGS}}
|
||||
**Diagnostic Logs**
|
||||
- Please run PowerToys and reproduce the issue
|
||||
- Generate a bug report: Settings > General > "Generate Bug Report"
|
||||
- Attach the resulting ZIP file
|
||||
{{/IF_MISSING_LOGS}}
|
||||
|
||||
This information will help us reproduce and fix the issue faster. Thanks!
|
||||
```
|
||||
|
||||
## Template: Needs-Clarification Reply
|
||||
|
||||
**File**: `issue-XXXXX.md`
|
||||
|
||||
```markdown
|
||||
Hi @{{AUTHOR}},
|
||||
|
||||
Thanks for reaching out! Let me help clarify this:
|
||||
|
||||
{{EXPLANATION}}
|
||||
|
||||
{{#IF_BY_DESIGN}}
|
||||
This behavior is actually by design. Here's the reasoning:
|
||||
- {{REASON_1}}
|
||||
- {{REASON_2}}
|
||||
{{/IF_BY_DESIGN}}
|
||||
|
||||
{{#IF_HOW_TO}}
|
||||
Here's how you can achieve what you're looking for:
|
||||
1. {{STEP_1}}
|
||||
2. {{STEP_2}}
|
||||
3. {{STEP_3}}
|
||||
{{/IF_HOW_TO}}
|
||||
|
||||
{{#IF_DOCS_LINK}}
|
||||
You can find more information in our documentation:
|
||||
- [{{DOC_TITLE}}]({{DOC_LINK}})
|
||||
{{/IF_DOCS_LINK}}
|
||||
|
||||
{{#IF_RELATED_ISSUE}}
|
||||
There's also an existing discussion about this in #{{RELATED_NUM}} that might be helpful.
|
||||
{{/IF_RELATED_ISSUE}}
|
||||
|
||||
{{#IF_FEATURE_REQUEST}}
|
||||
If you'd like to request this as a new feature, I'd suggest:
|
||||
1. Search existing issues to see if it's already requested
|
||||
2. If not, open a new feature request issue with your use case
|
||||
|
||||
We track feature popularity through 👍 reactions, so feel free to upvote any existing requests that match your needs!
|
||||
{{/IF_FEATURE_REQUEST}}
|
||||
|
||||
Let me know if you have any other questions!
|
||||
```
|
||||
|
||||
## Template: Close (Fixed by PR)
|
||||
|
||||
**File**: `issue-XXXXX.md`
|
||||
|
||||
```markdown
|
||||
Hi @{{AUTHOR}},
|
||||
|
||||
Great news! This issue has been addressed in PR #{{PR_NUM}}.
|
||||
|
||||
{{#IF_RELEASED}}
|
||||
✅ **The fix is now available in PowerToys v{{VERSION}}**
|
||||
|
||||
You can update to the latest version through:
|
||||
- Microsoft Store (automatic updates)
|
||||
- GitHub Releases: https://github.com/microsoft/PowerToys/releases/tag/v{{VERSION}}
|
||||
- WinGet: `winget upgrade Microsoft.PowerToys`
|
||||
{{/IF_RELEASED}}
|
||||
|
||||
{{#IF_NOT_RELEASED}}
|
||||
The fix has been merged and will be included in the next release (v{{NEXT_VERSION}}).
|
||||
|
||||
You can track the release progress in our [milestones](https://github.com/microsoft/PowerToys/milestones).
|
||||
{{/IF_NOT_RELEASED}}
|
||||
|
||||
Thank you for reporting this issue and helping improve PowerToys! 🙏
|
||||
|
||||
Closing this issue as resolved. If you encounter any further problems, please don't hesitate to open a new issue.
|
||||
```
|
||||
|
||||
## Template: Close (Duplicate)
|
||||
|
||||
**File**: `issue-XXXXX.md`
|
||||
|
||||
```markdown
|
||||
Hi @{{AUTHOR}},
|
||||
|
||||
Thanks for reporting this! It looks like this issue is a duplicate of #{{ORIGINAL_NUM}}.
|
||||
|
||||
To avoid splitting the discussion, I'm closing this in favor of the original issue. Please:
|
||||
- 👍 React to #{{ORIGINAL_NUM}} to show your interest
|
||||
- Add any additional context or reproduction details as a comment there
|
||||
- Subscribe to #{{ORIGINAL_NUM}} for updates
|
||||
|
||||
{{#IF_DIFFERENT_CONTEXT}}
|
||||
I noticed your report includes some additional context that might be helpful. I'll add a comment to #{{ORIGINAL_NUM}} referencing this issue.
|
||||
{{/IF_DIFFERENT_CONTEXT}}
|
||||
|
||||
Thank you for understanding!
|
||||
```
|
||||
|
||||
## Template: Close (By Design / Won't Fix)
|
||||
|
||||
**File**: `issue-XXXXX.md`
|
||||
|
||||
```markdown
|
||||
Hi @{{AUTHOR}},
|
||||
|
||||
Thank you for taking the time to report this and share your feedback.
|
||||
|
||||
After reviewing this issue, we've determined that this behavior is **{{RESOLUTION_TYPE}}**.
|
||||
|
||||
{{#IF_BY_DESIGN}}
|
||||
### Why This Is By Design
|
||||
|
||||
{{RATIONALE}}
|
||||
|
||||
This design choice was made because:
|
||||
- {{REASON_1}}
|
||||
- {{REASON_2}}
|
||||
{{/IF_BY_DESIGN}}
|
||||
|
||||
{{#IF_WONT_FIX}}
|
||||
### Why We're Not Addressing This
|
||||
|
||||
{{RATIONALE}}
|
||||
|
||||
We've decided not to implement this change because:
|
||||
- {{REASON_1}}
|
||||
- {{REASON_2}}
|
||||
{{/IF_WONT_FIX}}
|
||||
|
||||
{{#IF_WORKAROUND}}
|
||||
### Workaround
|
||||
|
||||
In the meantime, you might try:
|
||||
{{WORKAROUND}}
|
||||
{{/IF_WORKAROUND}}
|
||||
|
||||
{{#IF_ALTERNATIVE}}
|
||||
### Alternative Approaches
|
||||
|
||||
You might consider:
|
||||
- {{ALTERNATIVE_1}}
|
||||
- {{ALTERNATIVE_2}}
|
||||
{{/IF_ALTERNATIVE}}
|
||||
|
||||
We appreciate your understanding. If you have additional context that might change our assessment, please let us know!
|
||||
```
|
||||
|
||||
## Template: Stale-Waiting Ping
|
||||
|
||||
**File**: `issue-XXXXX.md`
|
||||
|
||||
```markdown
|
||||
Hi @{{AUTHOR}},
|
||||
|
||||
We haven't heard back from you in a while. Are you still experiencing this issue?
|
||||
|
||||
{{#IF_WAITING_FOR_INFO}}
|
||||
We're still waiting for the additional information requested above to help investigate this issue.
|
||||
{{/IF_WAITING_FOR_INFO}}
|
||||
|
||||
{{#IF_WAITING_FOR_CONFIRMATION}}
|
||||
Could you confirm if the suggested solution worked for you?
|
||||
{{/IF_WAITING_FOR_CONFIRMATION}}
|
||||
|
||||
If we don't hear back within the next {{DAYS}} days, we'll close this issue to keep our backlog manageable. You're always welcome to reopen it or create a new issue if the problem persists.
|
||||
|
||||
Thanks for your understanding! 🙏
|
||||
```
|
||||
|
||||
## Template: Closed Issue with New Comment
|
||||
|
||||
**File**: `issue-XXXXX.md`
|
||||
|
||||
```markdown
|
||||
Hi @{{COMMENTER}},
|
||||
|
||||
Thanks for your comment! This issue was closed {{TIME_AGO}} because {{CLOSE_REASON}}.
|
||||
|
||||
{{#IF_SAME_ISSUE}}
|
||||
If you're experiencing the same issue and it's not resolved, please open a new issue with:
|
||||
- Your PowerToys version
|
||||
- Steps to reproduce
|
||||
- Any error messages or screenshots
|
||||
|
||||
This helps us track and prioritize effectively.
|
||||
{{/IF_SAME_ISSUE}}
|
||||
|
||||
{{#IF_QUESTION}}
|
||||
Regarding your question:
|
||||
{{ANSWER}}
|
||||
{{/IF_QUESTION}}
|
||||
|
||||
{{#IF_DIFFERENT_ISSUE}}
|
||||
It sounds like you might be experiencing a different issue. Please open a new issue with details about your specific problem so we can help you better.
|
||||
{{/IF_DIFFERENT_ISSUE}}
|
||||
```
|
||||
|
||||
## Draft Generation Logic
|
||||
|
||||
```powershell
|
||||
function New-DraftReply {
|
||||
param(
|
||||
[hashtable]$Issue,
|
||||
[string]$Category,
|
||||
[hashtable]$AnalysisData
|
||||
)
|
||||
|
||||
$draftPath = "Generated Files/triage-issues/current-run/draft-replies/issue-$($Issue.number).md"
|
||||
|
||||
switch ($Category) {
|
||||
"needs-info" {
|
||||
$content = New-NeedsInfoDraft -Issue $Issue -Missing $AnalysisData.missingItems
|
||||
}
|
||||
"needs-clarification" {
|
||||
$content = New-ClarificationDraft -Issue $Issue -QuestionType $AnalysisData.questionType
|
||||
}
|
||||
"closeable" {
|
||||
$content = New-CloseDraft -Issue $Issue -CloseReason $AnalysisData.closeReason
|
||||
}
|
||||
"stale-waiting" {
|
||||
$content = New-StalePingDraft -Issue $Issue -DaysWaiting $AnalysisData.daysWaiting
|
||||
}
|
||||
default {
|
||||
return $null # No draft needed
|
||||
}
|
||||
}
|
||||
|
||||
# Add metadata header
|
||||
$header = @"
|
||||
---
|
||||
issue: $($Issue.number)
|
||||
title: $($Issue.title)
|
||||
category: $Category
|
||||
generated: $(Get-Date -Format "o")
|
||||
status: draft
|
||||
---
|
||||
|
||||
"@
|
||||
|
||||
($header + $content) | Set-Content $draftPath
|
||||
return $draftPath
|
||||
}
|
||||
```
|
||||
|
||||
## Draft Review Checklist
|
||||
|
||||
Before posting any draft:
|
||||
|
||||
- [ ] Read the full issue context
|
||||
- [ ] Check for recent comments not in analysis
|
||||
- [ ] Personalize if needed (remove boilerplate feel)
|
||||
- [ ] Verify links work
|
||||
- [ ] Ensure tone is appropriate
|
||||
- [ ] Remove any placeholder text (`{{...}}`)
|
||||
|
||||
## Posting Drafts
|
||||
|
||||
```bash
|
||||
# Post a single draft
|
||||
gh issue comment 12345 --body-file "Generated Files/triage-issues/current-run/draft-replies/issue-12345.md"
|
||||
|
||||
# Add label if needed
|
||||
gh issue edit 12345 --add-label "Needs-Author-Feedback"
|
||||
|
||||
# Close with message
|
||||
gh issue close 12345 --comment "$(cat draft-replies/issue-12345.md)"
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Never auto-post**: Always human review before posting
|
||||
2. **Be empathetic**: Remember there's a person on the other side
|
||||
3. **Be specific**: Generic responses feel dismissive
|
||||
4. **Provide value**: Every reply should move the issue forward
|
||||
5. **Link resources**: Documentation, related issues, PRs
|
||||
6. **Thank contributors**: Acknowledge their time and effort
|
||||
@@ -1,217 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Runs Copilot CLI analysis on issues using review-issue prompt.
|
||||
|
||||
.DESCRIPTION
|
||||
Kicks off GitHub Copilot CLI to analyze each issue using
|
||||
the review-issue.prompt.md file. Processes sequentially with timeout handling.
|
||||
|
||||
.PARAMETER IssueNumbers
|
||||
Array of issue numbers to analyze. If not provided, collects from recent activity.
|
||||
|
||||
.PARAMETER TimeoutMinutes
|
||||
Timeout for each Copilot analysis. Default: 8
|
||||
|
||||
.PARAMETER MaxRetryCount
|
||||
Maximum retries on timeout/failure. Default: 3
|
||||
|
||||
.PARAMETER Model
|
||||
Copilot model to use (optional).
|
||||
|
||||
.EXAMPLE
|
||||
.\analyze-issues-parallel.ps1 -IssueNumbers @(45201, 45107, 45321)
|
||||
|
||||
.EXAMPLE
|
||||
.\analyze-issues-parallel.ps1 -TimeoutMinutes 10 -MaxRetries 2
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter()]
|
||||
[int[]]$IssueNumbers,
|
||||
|
||||
[Parameter()]
|
||||
[int]$TimeoutMinutes = 8,
|
||||
|
||||
[Parameter()]
|
||||
[int]$MaxRetryCount = 3,
|
||||
|
||||
[Parameter()]
|
||||
[string]$Model,
|
||||
|
||||
[Parameter()]
|
||||
[int]$LookbackDays = 14,
|
||||
|
||||
[Parameter()]
|
||||
[int]$MaxIssues = 15
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$repoRoot = (git rev-parse --show-toplevel 2>$null); if (-not $repoRoot) { $repoRoot = (Get-Location).Path }; $repoRoot = (Resolve-Path $repoRoot).Path
|
||||
|
||||
# Resolve config directory name (.github or .claude) from script location
|
||||
$_cfgDir = if ($PSScriptRoot -match '[\\/](\.github|\.claude)[\\/]') { $Matches[1] } else { '.github' }
|
||||
$triageRoot = Join-Path $repoRoot "Generated Files\triage-issues"
|
||||
$issueCachePath = Join-Path $triageRoot "issue-cache"
|
||||
$promptPath = Join-Path $repoRoot "$_cfgDir\prompts\review-issue.prompt.md"
|
||||
|
||||
# Ensure directories exist
|
||||
if (-not (Test-Path $issueCachePath)) {
|
||||
New-Item -ItemType Directory -Path $issueCachePath -Force | Out-Null
|
||||
}
|
||||
|
||||
Write-Host "═══════════════════════════════════════════════════════════════" -ForegroundColor Cyan
|
||||
Write-Host " Issue Analysis with Copilot CLI" -ForegroundColor Cyan
|
||||
Write-Host " Using: review-issue.prompt.md" -ForegroundColor Cyan
|
||||
Write-Host "═══════════════════════════════════════════════════════════════" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# If no issues provided, collect from recent activity
|
||||
if (-not $IssueNumbers -or $IssueNumbers.Count -eq 0) {
|
||||
Write-Host "Collecting issues from last $LookbackDays days..." -ForegroundColor Yellow
|
||||
|
||||
$issues = gh issue list --state open --json number,title,comments,updatedAt --limit 200 | ConvertFrom-Json
|
||||
$recent = $issues | Where-Object { [datetime]$_.updatedAt -gt (Get-Date).AddDays(-$LookbackDays) }
|
||||
|
||||
# Prioritize: trending first, then by recency
|
||||
$prioritized = $recent | Sort-Object { -$_.comments.Count }, { [datetime]$_.updatedAt } -Descending
|
||||
$IssueNumbers = ($prioritized | Select-Object -First $MaxIssues).number
|
||||
|
||||
Write-Host " Found $($recent.Count) recent issues, selected top $($IssueNumbers.Count) for analysis" -ForegroundColor Green
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Issues to analyze: $($IssueNumbers -join ', ')" -ForegroundColor Cyan
|
||||
Write-Host "Timeout: ${TimeoutMinutes}m | Retries: $MaxRetryCount" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
|
||||
# Results tracking
|
||||
$results = @{}
|
||||
$startTime = Get-Date
|
||||
$totalIssues = $IssueNumbers.Count
|
||||
$current = 0
|
||||
|
||||
foreach ($issueNum in $IssueNumbers) {
|
||||
$current++
|
||||
$issueDir = Join-Path $issueCachePath $issueNum
|
||||
if (-not (Test-Path $issueDir)) {
|
||||
New-Item -ItemType Directory -Path $issueDir -Force | Out-Null
|
||||
}
|
||||
|
||||
$logFile = Join-Path $issueDir "analysis.log"
|
||||
$errorFile = Join-Path $issueDir "error.log"
|
||||
$statusFile = Join-Path $issueDir "status.json"
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "[$current/$totalIssues] #$issueNum - Beginning analysis..." -ForegroundColor Yellow
|
||||
|
||||
$success = $false
|
||||
$lastError = $null
|
||||
$retryCount = 0
|
||||
|
||||
for ($retry = 0; $retry -lt $MaxRetryCount -and -not $success; $retry++) {
|
||||
$retryCount = $retry + 1
|
||||
|
||||
if ($retry -gt 0) {
|
||||
Write-Host " [RETRY] Attempt $retryCount/$MaxRetryCount (waiting 10s)..." -ForegroundColor Yellow
|
||||
Start-Sleep -Seconds 10
|
||||
}
|
||||
|
||||
try {
|
||||
# Build the prompt - use the review-issue prompt directly
|
||||
$prompt = @"
|
||||
Analyze GitHub issue #$issueNum using the methodology from $_cfgDir/prompts/review-issue.prompt.md
|
||||
|
||||
First, fetch the issue data:
|
||||
gh issue view $issueNum --json number,title,body,author,createdAt,updatedAt,state,labels,milestone,reactions,comments,linkedPullRequests
|
||||
|
||||
Then produce a concise JSON summary with this structure (output ONLY the JSON):
|
||||
{
|
||||
"issueNumber": $issueNum,
|
||||
"title": "issue title",
|
||||
"category": "trending|needs-label|ready-for-fix|needs-info|needs-clarification|closeable|stale-waiting|duplicate-candidate|review-needed",
|
||||
"categoryReason": "brief explanation",
|
||||
"priorityScore": 0-100,
|
||||
"clarityScore": 0-100,
|
||||
"feasibilityScore": 0-100,
|
||||
"suggestedAction": "what human should do",
|
||||
"suggestedLabels": ["label1", "label2"],
|
||||
"missingInfo": ["item1", "item2"],
|
||||
"draftReply": "if needs-info or needs-clarification, draft the reply"
|
||||
}
|
||||
"@
|
||||
|
||||
# Build Copilot CLI arguments
|
||||
$copilotArgs = @('-p', $prompt, '--yolo', '--agent', 'ReviewIssue')
|
||||
if ($Model) {
|
||||
$copilotArgs += @('--model', $Model)
|
||||
}
|
||||
|
||||
Write-Host " Running copilot CLI..." -ForegroundColor Gray
|
||||
|
||||
# Run copilot directly (not in job)
|
||||
$output = & copilot @copilotArgs 2>&1
|
||||
$outputStr = $output | Out-String
|
||||
|
||||
# Save the output
|
||||
$outputStr | Out-File -FilePath $logFile -Force
|
||||
|
||||
# Check for valid output
|
||||
if ($outputStr.Length -gt 200) {
|
||||
$success = $true
|
||||
Write-Host " [SUCCESS] Analysis complete ($($outputStr.Length) chars)" -ForegroundColor Green
|
||||
}
|
||||
else {
|
||||
$lastError = "Output too short ($($outputStr.Length) chars)"
|
||||
Write-Host " [WARN] $lastError" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$lastError = $_.Exception.Message
|
||||
Write-Host " [ERROR] $lastError" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
|
||||
# Save status
|
||||
$status = @{
|
||||
issueNumber = $issueNum
|
||||
success = $success
|
||||
attempts = $retryCount
|
||||
lastError = $lastError
|
||||
analyzedAt = (Get-Date).ToUniversalTime().ToString("o")
|
||||
}
|
||||
$status | ConvertTo-Json | Out-File -FilePath $statusFile -Force
|
||||
$results[$issueNum] = $status
|
||||
|
||||
if (-not $success) {
|
||||
$lastError | Out-File -FilePath $errorFile -Force
|
||||
Write-Host " [FAILED] All $MaxRetryCount attempts failed: $lastError" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
|
||||
$elapsed = (Get-Date) - $startTime
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "═══════════════════════════════════════════════════════════════" -ForegroundColor Cyan
|
||||
Write-Host " Analysis Complete" -ForegroundColor Cyan
|
||||
Write-Host "═══════════════════════════════════════════════════════════════" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "Duration: $([math]::Round($elapsed.TotalMinutes, 1)) minutes" -ForegroundColor Gray
|
||||
Write-Host "Total issues: $($IssueNumbers.Count)" -ForegroundColor Gray
|
||||
|
||||
$successCount = ($results.Values | Where-Object { $_.success }).Count
|
||||
$failCount = ($results.Values | Where-Object { -not $_.success }).Count
|
||||
|
||||
Write-Host "Successful: $successCount" -ForegroundColor Green
|
||||
Write-Host "Failed: $failCount" -ForegroundColor $(if ($failCount -gt 0) { 'Red' } else { 'Gray' })
|
||||
|
||||
if ($failCount -gt 0) {
|
||||
Write-Host ""
|
||||
Write-Host "Failed issues:" -ForegroundColor Red
|
||||
$results.Values | Where-Object { -not $_.success } | ForEach-Object {
|
||||
Write-Host " #$($_.issueNumber): $($_.lastError)" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Results saved to: $issueCachePath" -ForegroundColor Cyan
|
||||
@@ -1,376 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Categorizes collected issues into actionable buckets.
|
||||
|
||||
.DESCRIPTION
|
||||
Applies categorization rules to issues collected by collect-active-issues.ps1.
|
||||
Outputs categorized results with priority scores and suggested actions.
|
||||
|
||||
.PARAMETER InputPath
|
||||
Path to collected issues JSON. Default: Generated Files/triage-issues/current-run/collected-issues.json
|
||||
|
||||
.PARAMETER StatePath
|
||||
Path to triage state JSON. Default: Generated Files/triage-issues/triage-state.json
|
||||
|
||||
.PARAMETER OutputPath
|
||||
Path to save categorized results. Default: Generated Files/triage-issues/current-run/categorized-issues.json
|
||||
|
||||
.PARAMETER TrendingThreshold
|
||||
Minimum new comments to flag as trending. Default: 5
|
||||
|
||||
.EXAMPLE
|
||||
.\categorize-issues.ps1
|
||||
|
||||
.EXAMPLE
|
||||
.\categorize-issues.ps1 -TrendingThreshold 10
|
||||
#>
|
||||
|
||||
param(
|
||||
[Parameter()]
|
||||
[string]$InputPath = "Generated Files/triage-issues/current-run/collected-issues.json",
|
||||
|
||||
[Parameter()]
|
||||
[string]$StatePath = "Generated Files/triage-issues/triage-state.json",
|
||||
|
||||
[Parameter()]
|
||||
[string]$OutputPath = "Generated Files/triage-issues/current-run/categorized-issues.json",
|
||||
|
||||
[Parameter()]
|
||||
[int]$TrendingThreshold = 5
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# Product keyword mapping
|
||||
$ProductKeywords = @{
|
||||
"Product-FancyZones" = @("fancy zones", "fancyzones", "zone", "snap", "layout", "window arrangement", "virtual desktop")
|
||||
"Product-PowerToys Run" = @("run", "launcher", "alt+space", "alt space", "search", "plugin", "powertoys run")
|
||||
"Product-Color Picker" = @("color picker", "colorpicker", "eyedropper", "hex", "rgb", "color code")
|
||||
"Product-Keyboard Manager" = @("keyboard", "remap", "shortcut", "key mapping", "keyboard manager")
|
||||
"Product-Mouse Utils" = @("mouse", "crosshairs", "find my mouse", "highlighter", "pointer", "mouse without borders")
|
||||
"Product-File Explorer" = @("file explorer", "preview", "thumbnail", "markdown preview", "svg preview", "preview pane")
|
||||
"Product-Image Resizer" = @("image resizer", "resize image", "bulk resize", "resize pictures")
|
||||
"Product-PowerRename" = @("rename", "power rename", "powerrename", "bulk rename", "regex rename")
|
||||
"Product-Awake" = @("awake", "keep awake", "prevent sleep", "caffeinate", "stay awake")
|
||||
"Product-Shortcut Guide" = @("shortcut guide", "win key", "windows key guide")
|
||||
"Product-Text Extractor" = @("text extractor", "ocr", "screen text", "copy text from screen")
|
||||
"Product-Hosts File Editor" = @("hosts", "hosts file", "dns mapping")
|
||||
"Product-Peek" = @("peek", "quick preview", "spacebar preview", "file peek")
|
||||
"Product-Crop And Lock" = @("crop", "crop and lock", "window crop", "cropped window")
|
||||
"Product-Paste As Plain Text" = @("paste", "plain text", "paste as plain")
|
||||
"Product-Registry Preview" = @("registry", "reg file", "registry preview")
|
||||
"Product-Environment Variables" = @("environment", "env variable", "path variable", "system variable")
|
||||
"Product-Command Not Found" = @("command not found", "winget suggest", "command suggestion")
|
||||
"Product-New+" = @("new\+", "newplus", "file template", "new file")
|
||||
"Product-Advanced Paste" = @("advanced paste", "ai paste", "clipboard ai", "smart paste")
|
||||
"Product-Workspaces" = @("workspaces", "workspace launcher", "project layout")
|
||||
"Product-Cmd Palette" = @("command palette", "cmd palette", "quick command")
|
||||
"Product-ZoomIt" = @("zoomit", "zoom it", "screen zoom", "presentation zoom")
|
||||
}
|
||||
|
||||
# Load collected issues
|
||||
if (-not (Test-Path $InputPath)) {
|
||||
Write-Error "Input file not found: $InputPath. Run collect-active-issues.ps1 first."
|
||||
exit 1
|
||||
}
|
||||
|
||||
$collected = Get-Content $InputPath | ConvertFrom-Json
|
||||
|
||||
# Load previous state
|
||||
$previousState = $null
|
||||
if (Test-Path $StatePath) {
|
||||
$previousState = Get-Content $StatePath | ConvertFrom-Json
|
||||
}
|
||||
|
||||
function Get-IssueDetails {
|
||||
param([int]$IssueNumber)
|
||||
|
||||
$json = gh issue view $IssueNumber `
|
||||
--json number,title,body,author,createdAt,updatedAt,state,labels,milestone,reactions,comments,linkedPullRequests 2>$null
|
||||
|
||||
if (-not $json) { return $null }
|
||||
|
||||
$issue = $json | ConvertFrom-Json
|
||||
|
||||
return @{
|
||||
number = $issue.number
|
||||
title = $issue.title
|
||||
body = $issue.body
|
||||
author = $issue.author.login
|
||||
state = $issue.state
|
||||
createdAt = $issue.createdAt
|
||||
updatedAt = $issue.updatedAt
|
||||
labels = @($issue.labels | ForEach-Object { $_.name })
|
||||
milestone = $issue.milestone.title
|
||||
reactions = @{
|
||||
thumbsUp = ($issue.reactions | Where-Object { $_.content -eq "THUMBS_UP" }).Count
|
||||
thumbsDown = ($issue.reactions | Where-Object { $_.content -eq "THUMBS_DOWN" }).Count
|
||||
heart = ($issue.reactions | Where-Object { $_.content -eq "HEART" }).Count
|
||||
}
|
||||
commentCount = $issue.comments.Count
|
||||
comments = @($issue.comments | ForEach-Object {
|
||||
@{
|
||||
author = $_.author.login
|
||||
createdAt = $_.createdAt
|
||||
body = $_.body
|
||||
}
|
||||
})
|
||||
linkedPRs = @($issue.linkedPullRequests | ForEach-Object {
|
||||
@{
|
||||
number = $_.number
|
||||
state = $_.state
|
||||
mergedAt = $_.mergedAt
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function Get-LabelSuggestion {
|
||||
param([hashtable]$Issue)
|
||||
|
||||
$titleLower = $Issue.title.ToLower()
|
||||
$bodyLower = if ($Issue.body) { $Issue.body.ToLower() } else { "" }
|
||||
$combined = "$titleLower $bodyLower"
|
||||
|
||||
$matches = @()
|
||||
foreach ($product in $ProductKeywords.Keys) {
|
||||
$keywords = $ProductKeywords[$product]
|
||||
$matchCount = ($keywords | Where-Object { $combined -match $_ }).Count
|
||||
if ($matchCount -gt 0) {
|
||||
$matches += @{
|
||||
label = $product
|
||||
matchCount = $matchCount
|
||||
confidence = [Math]::Min(100, $matchCount * 25 + 25)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$best = $matches | Sort-Object confidence -Descending | Select-Object -First 1
|
||||
|
||||
if ($best -and $best.confidence -ge 50) {
|
||||
return @{
|
||||
labels = @($best.label)
|
||||
confidence = $best.confidence
|
||||
reason = "Matched $($best.matchCount) keywords"
|
||||
}
|
||||
}
|
||||
|
||||
return @{ labels = @(); confidence = 0; reason = "No confident match" }
|
||||
}
|
||||
|
||||
function Get-PriorityScore {
|
||||
param([hashtable]$Issue)
|
||||
|
||||
$score = 50
|
||||
|
||||
# Reactions
|
||||
$score += [Math]::Min(20, $Issue.reactions.thumbsUp * 2)
|
||||
|
||||
# Comments
|
||||
$score += [Math]::Min(15, $Issue.commentCount)
|
||||
|
||||
# Recency
|
||||
$daysSinceUpdate = ((Get-Date) - [datetime]$Issue.updatedAt).Days
|
||||
if ($daysSinceUpdate -le 7) { $score += 10 }
|
||||
elseif ($daysSinceUpdate -le 30) { $score += 5 }
|
||||
|
||||
# Labels
|
||||
if ($Issue.labels -contains "Priority-High") { $score += 15 }
|
||||
if ($Issue.labels -match "Regression") { $score += 20 }
|
||||
if ($Issue.labels -match "Security") { $score += 25 }
|
||||
|
||||
return [Math]::Min(100, $score)
|
||||
}
|
||||
|
||||
# Process each issue
|
||||
$categorized = @{}
|
||||
$issueCount = $collected.issues.Count
|
||||
$current = 0
|
||||
|
||||
Write-Host "Categorizing $issueCount issues..."
|
||||
Write-Host ""
|
||||
|
||||
foreach ($collectedIssue in $collected.issues) {
|
||||
$current++
|
||||
$issueNum = $collectedIssue.number
|
||||
|
||||
Write-Host "[$current/$issueCount] Processing #$issueNum..."
|
||||
|
||||
# Get full issue details
|
||||
$issue = Get-IssueDetails -IssueNumber $issueNum
|
||||
if (-not $issue) {
|
||||
Write-Host " Warning: Could not fetch issue #$issueNum"
|
||||
continue
|
||||
}
|
||||
|
||||
# Get previous snapshot
|
||||
$previousSnapshot = $null
|
||||
if ($previousState -and $previousState.issueSnapshots.$issueNum) {
|
||||
$previousSnapshot = $previousState.issueSnapshots.$issueNum
|
||||
}
|
||||
|
||||
# Calculate new comments
|
||||
$previousCommentCount = if ($previousSnapshot) { $previousSnapshot.commentCount } else { 0 }
|
||||
$newComments = $issue.commentCount - $previousCommentCount
|
||||
|
||||
# Categorize (priority order - first match wins)
|
||||
$category = $null
|
||||
$categoryReason = $null
|
||||
$suggestedAction = $null
|
||||
$additionalData = @{}
|
||||
|
||||
# 1. Trending
|
||||
if ($newComments -ge $TrendingThreshold) {
|
||||
$category = "trending"
|
||||
$categoryReason = "$newComments new comments since last run"
|
||||
$suggestedAction = "Review conversation urgently"
|
||||
}
|
||||
|
||||
# 2. Closeable (check for merged PRs)
|
||||
if (-not $category) {
|
||||
$mergedPRs = $issue.linkedPRs | Where-Object { $_.state -eq "MERGED" }
|
||||
if ($mergedPRs.Count -gt 0 -and $issue.state -eq "OPEN") {
|
||||
$category = "closeable"
|
||||
$categoryReason = "Has merged PR(s): #" + ($mergedPRs.number -join ", #")
|
||||
$suggestedAction = "Close with thank you message"
|
||||
$additionalData.mergedPRs = $mergedPRs.number
|
||||
}
|
||||
}
|
||||
|
||||
# 3. Needs-Label
|
||||
if (-not $category) {
|
||||
$productLabels = $issue.labels | Where-Object { $_ -like "Product-*" }
|
||||
$areaLabels = $issue.labels | Where-Object { $_ -like "Area-*" }
|
||||
|
||||
if ($productLabels.Count -eq 0 -and $areaLabels.Count -eq 0) {
|
||||
$suggestion = Get-LabelSuggestion -Issue $issue
|
||||
$category = "needs-label"
|
||||
$categoryReason = "Missing Product/Area label"
|
||||
$suggestedAction = "Apply label: $($suggestion.labels -join ', ')"
|
||||
$additionalData.suggestedLabels = $suggestion.labels
|
||||
$additionalData.labelConfidence = $suggestion.confidence
|
||||
}
|
||||
}
|
||||
|
||||
# 4. Stale-Waiting
|
||||
if (-not $category) {
|
||||
if ($issue.labels -contains "Needs-Author-Feedback") {
|
||||
$lastAuthorComment = $issue.comments |
|
||||
Where-Object { $_.author -eq $issue.author } |
|
||||
Sort-Object createdAt -Descending |
|
||||
Select-Object -First 1
|
||||
|
||||
if ($lastAuthorComment) {
|
||||
$daysSince = ((Get-Date) - [datetime]$lastAuthorComment.createdAt).Days
|
||||
if ($daysSince -gt 14) {
|
||||
$category = "stale-waiting"
|
||||
$categoryReason = "Waiting on author for $daysSince days"
|
||||
$suggestedAction = "Ping or close"
|
||||
$additionalData.daysWaiting = $daysSince
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# 5. Needs-Clarification (question, not bug)
|
||||
if (-not $category) {
|
||||
$isQuestion = $false
|
||||
$titleAndBody = "$($issue.title) $($issue.body)"
|
||||
|
||||
if ($titleAndBody -match '\?$' -or
|
||||
$titleAndBody -match '(?i)(how (do|can|to)|why (does|is)|is (it|there) possible)' -or
|
||||
$issue.labels -contains "Issue-Question") {
|
||||
$isQuestion = $true
|
||||
}
|
||||
|
||||
if ($isQuestion -and ($issue.labels -notcontains "Issue-Bug")) {
|
||||
$category = "needs-clarification"
|
||||
$categoryReason = "Appears to be a question/inquiry"
|
||||
$suggestedAction = "Draft explanation reply"
|
||||
}
|
||||
}
|
||||
|
||||
# 6. Needs-Info
|
||||
if (-not $category) {
|
||||
$missingItems = @()
|
||||
$body = $issue.body
|
||||
|
||||
if ($body -and $body.Length -gt 0) {
|
||||
if ($body -notmatch '(?i)(steps to reproduce|repro|how to reproduce)') {
|
||||
$missingItems += "repro steps"
|
||||
}
|
||||
if ($body -notmatch '(?i)(expected|should|supposed to)') {
|
||||
$missingItems += "expected behavior"
|
||||
}
|
||||
if ($body -notmatch '(?i)(version|v\d+\.\d+)') {
|
||||
$missingItems += "PowerToys version"
|
||||
}
|
||||
} else {
|
||||
$missingItems += "description"
|
||||
}
|
||||
|
||||
if ($missingItems.Count -gt 0) {
|
||||
$category = "needs-info"
|
||||
$categoryReason = "Missing: " + ($missingItems -join ", ")
|
||||
$suggestedAction = "Post clarifying questions"
|
||||
$additionalData.missingItems = $missingItems
|
||||
}
|
||||
}
|
||||
|
||||
# 7. Default: review-needed
|
||||
if (-not $category) {
|
||||
$category = "review-needed"
|
||||
$categoryReason = "Needs human review for categorization"
|
||||
$suggestedAction = "Manual triage"
|
||||
}
|
||||
|
||||
# Calculate priority score
|
||||
$priorityScore = Get-PriorityScore -Issue $issue
|
||||
|
||||
# Store result
|
||||
$categorized[$issueNum] = @{
|
||||
number = $issue.number
|
||||
title = $issue.title
|
||||
state = $issue.state
|
||||
labels = $issue.labels
|
||||
category = $category
|
||||
categoryReason = $categoryReason
|
||||
priorityScore = $priorityScore
|
||||
suggestedAction = $suggestedAction
|
||||
newComments = $newComments
|
||||
totalComments = $issue.commentCount
|
||||
reactions = $issue.reactions
|
||||
updatedAt = $issue.updatedAt
|
||||
additionalData = $additionalData
|
||||
}
|
||||
|
||||
Write-Host " -> $category (priority: $priorityScore)"
|
||||
}
|
||||
|
||||
# Group by category for summary
|
||||
$byCategory = $categorized.Values | Group-Object category
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "=== Categorization Summary ==="
|
||||
foreach ($group in $byCategory | Sort-Object Count -Descending) {
|
||||
Write-Host " $($group.Name): $($group.Count) issues"
|
||||
}
|
||||
|
||||
# Save results
|
||||
$output = @{
|
||||
categorizedAt = (Get-Date).ToUniversalTime().ToString("o")
|
||||
totalCategorized = $categorized.Count
|
||||
byCategory = @{}
|
||||
issues = $categorized
|
||||
}
|
||||
|
||||
foreach ($group in $byCategory) {
|
||||
$output.byCategory[$group.Name] = @{
|
||||
count = $group.Count
|
||||
topIssues = @($group.Group | Sort-Object priorityScore -Descending | Select-Object -First 3 | ForEach-Object { $_.number })
|
||||
}
|
||||
}
|
||||
|
||||
$output | ConvertTo-Json -Depth 10 | Set-Content $OutputPath
|
||||
Write-Host ""
|
||||
Write-Host "Results saved to: $OutputPath"
|
||||
@@ -1,188 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Collects GitHub issues with activity since the last triage run.
|
||||
|
||||
.DESCRIPTION
|
||||
Fetches open issues updated since the last run, closed issues with new comments,
|
||||
and issues with pending follow-up actions.
|
||||
|
||||
.PARAMETER Since
|
||||
ISO 8601 datetime string. Collect issues updated after this time.
|
||||
If not specified, reads from triage-state.json.
|
||||
|
||||
.PARAMETER LookbackDays
|
||||
For first run (no state), how many days to look back. Default: 7.
|
||||
|
||||
.PARAMETER OutputPath
|
||||
Path to save collected issues JSON. Default: Generated Files/triage-issues/current-run/collected-issues.json
|
||||
|
||||
.PARAMETER Limit
|
||||
Maximum issues to collect per query. Default: 500.
|
||||
|
||||
.EXAMPLE
|
||||
.\collect-active-issues.ps1
|
||||
|
||||
.EXAMPLE
|
||||
.\collect-active-issues.ps1 -Since "2026-01-29T00:00:00Z" -Limit 100
|
||||
#>
|
||||
|
||||
param(
|
||||
[Parameter()]
|
||||
[string]$Since,
|
||||
|
||||
[Parameter()]
|
||||
[int]$LookbackDays = 7,
|
||||
|
||||
[Parameter()]
|
||||
[string]$OutputPath = "Generated Files/triage-issues/current-run/collected-issues.json",
|
||||
|
||||
[Parameter()]
|
||||
[int]$Limit = 500
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# Determine the "since" timestamp
|
||||
if (-not $Since) {
|
||||
$statePath = "Generated Files/triage-issues/triage-state.json"
|
||||
if (Test-Path $statePath) {
|
||||
$state = Get-Content $statePath | ConvertFrom-Json
|
||||
if ($state.lastRun) {
|
||||
$Since = $state.lastRun
|
||||
Write-Host "Using last run timestamp: $Since"
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $Since) {
|
||||
$Since = (Get-Date).AddDays(-$LookbackDays).ToUniversalTime().ToString("o")
|
||||
Write-Host "First run - looking back $LookbackDays days to: $Since"
|
||||
}
|
||||
}
|
||||
|
||||
$sinceDate = [datetime]$Since
|
||||
|
||||
# Ensure output directory exists
|
||||
$outputDir = Split-Path $OutputPath -Parent
|
||||
if (-not (Test-Path $outputDir)) {
|
||||
New-Item -ItemType Directory -Force -Path $outputDir | Out-Null
|
||||
}
|
||||
|
||||
$collectedIssues = @()
|
||||
|
||||
# 1. Collect open issues updated since last run
|
||||
Write-Host "Fetching open issues updated since $Since..."
|
||||
$openIssues = gh issue list `
|
||||
--state open `
|
||||
--json number,title,updatedAt `
|
||||
--limit $Limit 2>$null | ConvertFrom-Json
|
||||
|
||||
$filteredOpen = $openIssues | Where-Object {
|
||||
[datetime]$_.updatedAt -gt $sinceDate
|
||||
}
|
||||
Write-Host " Found $($filteredOpen.Count) open issues with recent activity"
|
||||
|
||||
foreach ($issue in $filteredOpen) {
|
||||
$collectedIssues += @{
|
||||
number = $issue.number
|
||||
title = $issue.title
|
||||
source = "open-updated"
|
||||
updatedAt = $issue.updatedAt
|
||||
}
|
||||
}
|
||||
|
||||
# 2. Collect closed issues with recent activity (within tracking window)
|
||||
Write-Host "Fetching closed issues with recent comments..."
|
||||
$trackingDays = 30
|
||||
$trackingCutoff = (Get-Date).AddDays(-$trackingDays)
|
||||
|
||||
$closedIssues = gh issue list `
|
||||
--state closed `
|
||||
--json number,title,updatedAt,closedAt `
|
||||
--limit 200 2>$null | ConvertFrom-Json
|
||||
|
||||
$activeClosedIssues = $closedIssues | Where-Object {
|
||||
$closedAt = [datetime]$_.closedAt
|
||||
$updatedAt = [datetime]$_.updatedAt
|
||||
# Closed within tracking window AND updated after being closed
|
||||
($closedAt -gt $trackingCutoff) -and ($updatedAt -gt $closedAt)
|
||||
}
|
||||
Write-Host " Found $($activeClosedIssues.Count) closed issues with post-close activity"
|
||||
|
||||
foreach ($issue in $activeClosedIssues) {
|
||||
$collectedIssues += @{
|
||||
number = $issue.number
|
||||
title = $issue.title
|
||||
source = "closed-with-activity"
|
||||
updatedAt = $issue.updatedAt
|
||||
closedAt = $issue.closedAt
|
||||
}
|
||||
}
|
||||
|
||||
# 3. Check pending follow-ups from state
|
||||
if (Test-Path $statePath) {
|
||||
$state = Get-Content $statePath | ConvertFrom-Json
|
||||
|
||||
if ($state.pendingFollowUps) {
|
||||
Write-Host "Checking $($state.pendingFollowUps.Count) pending follow-ups..."
|
||||
foreach ($pending in $state.pendingFollowUps) {
|
||||
if ($pending.status -eq "pending") {
|
||||
if ($collectedIssues.number -notcontains $pending.issueNumber) {
|
||||
$collectedIssues += @{
|
||||
number = $pending.issueNumber
|
||||
source = "pending-followup"
|
||||
action = $pending.action
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Check unhandled issues from previous run
|
||||
if ($state.issueSnapshots) {
|
||||
$unhandled = $state.issueSnapshots.PSObject.Properties | Where-Object {
|
||||
$snapshot = $_.Value
|
||||
$snapshot.pendingAction -and -not $snapshot.actionTaken
|
||||
}
|
||||
|
||||
if ($unhandled) {
|
||||
Write-Host "Found $($unhandled.Count) unhandled issues from previous run"
|
||||
foreach ($prop in $unhandled) {
|
||||
$snapshot = $prop.Value
|
||||
if ($collectedIssues.number -notcontains $snapshot.number) {
|
||||
$collectedIssues += @{
|
||||
number = $snapshot.number
|
||||
title = $snapshot.title
|
||||
source = "unhandled-previous"
|
||||
previousCategory = $snapshot.category
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Deduplicate by issue number
|
||||
$uniqueIssues = $collectedIssues | Group-Object number | ForEach-Object {
|
||||
$_.Group | Select-Object -First 1
|
||||
}
|
||||
|
||||
# Summary
|
||||
Write-Host ""
|
||||
Write-Host "=== Collection Summary ==="
|
||||
Write-Host "Total unique issues: $($uniqueIssues.Count)"
|
||||
Write-Host " - Open with activity: $(($uniqueIssues | Where-Object { $_.source -eq 'open-updated' }).Count)"
|
||||
Write-Host " - Closed with activity: $(($uniqueIssues | Where-Object { $_.source -eq 'closed-with-activity' }).Count)"
|
||||
Write-Host " - Pending follow-ups: $(($uniqueIssues | Where-Object { $_.source -eq 'pending-followup' }).Count)"
|
||||
Write-Host " - Unhandled previous: $(($uniqueIssues | Where-Object { $_.source -eq 'unhandled-previous' }).Count)"
|
||||
|
||||
# Save results
|
||||
$output = @{
|
||||
collectedAt = (Get-Date).ToUniversalTime().ToString("o")
|
||||
since = $Since
|
||||
totalCount = $uniqueIssues.Count
|
||||
issues = $uniqueIssues
|
||||
}
|
||||
|
||||
$output | ConvertTo-Json -Depth 10 | Set-Content $OutputPath
|
||||
Write-Host ""
|
||||
Write-Host "Results saved to: $OutputPath"
|
||||
@@ -1,210 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Generates executive summary and category reports from categorized issues.
|
||||
|
||||
.DESCRIPTION
|
||||
Creates markdown reports for each category and an executive summary
|
||||
for the current triage run.
|
||||
|
||||
.PARAMETER InputPath
|
||||
Path to categorized issues JSON. Default: Generated Files/triage-issues/current-run/categorized-issues.json
|
||||
|
||||
.PARAMETER OutputPath
|
||||
Directory for generated reports. Default: Generated Files/triage-issues/current-run
|
||||
|
||||
.PARAMETER RepoUrl
|
||||
GitHub repository URL for issue links. Default: https://github.com/microsoft/PowerToys/issues
|
||||
|
||||
.EXAMPLE
|
||||
.\generate-summary.ps1
|
||||
#>
|
||||
|
||||
param(
|
||||
[Parameter()]
|
||||
[string]$InputPath = "Generated Files/triage-issues/current-run/categorized-issues.json",
|
||||
|
||||
[Parameter()]
|
||||
[string]$OutputPath = "Generated Files/triage-issues/current-run",
|
||||
|
||||
[Parameter()]
|
||||
[string]$RepoUrl = "https://github.com/microsoft/PowerToys/issues"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# Category display info
|
||||
$CategoryInfo = @{
|
||||
"trending" = @{ emoji = "🔥"; name = "Trending"; priority = 1 }
|
||||
"needs-label" = @{ emoji = "🏷️"; name = "Needs-Label"; priority = 2 }
|
||||
"ready-for-fix" = @{ emoji = "✅"; name = "Ready-for-Fix"; priority = 3 }
|
||||
"needs-info" = @{ emoji = "❓"; name = "Needs-Info"; priority = 4 }
|
||||
"needs-clarification" = @{ emoji = "💬"; name = "Needs-Clarification"; priority = 5 }
|
||||
"closeable" = @{ emoji = "✔️"; name = "Closeable"; priority = 6 }
|
||||
"stale-waiting" = @{ emoji = "⏳"; name = "Stale-Waiting"; priority = 7 }
|
||||
"duplicate-candidate" = @{ emoji = "🔁"; name = "Duplicate-Candidate"; priority = 8 }
|
||||
"review-needed" = @{ emoji = "👀"; name = "Review-Needed"; priority = 9 }
|
||||
}
|
||||
|
||||
# Load categorized issues
|
||||
if (-not (Test-Path $InputPath)) {
|
||||
Write-Error "Input file not found: $InputPath. Run categorize-issues.ps1 first."
|
||||
exit 1
|
||||
}
|
||||
|
||||
$data = Get-Content $InputPath | ConvertFrom-Json -AsHashtable
|
||||
|
||||
# Ensure output directories
|
||||
New-Item -ItemType Directory -Force -Path $OutputPath | Out-Null
|
||||
New-Item -ItemType Directory -Force -Path "$OutputPath/draft-replies" | Out-Null
|
||||
|
||||
# Group issues by category
|
||||
$byCategory = @{}
|
||||
foreach ($issueNum in $data.issues.Keys) {
|
||||
$issue = $data.issues[$issueNum]
|
||||
$cat = $issue.category
|
||||
if (-not $byCategory[$cat]) {
|
||||
$byCategory[$cat] = @()
|
||||
}
|
||||
$byCategory[$cat] += $issue
|
||||
}
|
||||
|
||||
# Sort each category by priority
|
||||
foreach ($cat in $byCategory.Keys) {
|
||||
$byCategory[$cat] = $byCategory[$cat] | Sort-Object priorityScore -Descending
|
||||
}
|
||||
|
||||
# Generate Executive Summary
|
||||
$summaryLines = @()
|
||||
$summaryLines += "# Issue Triage Summary - $(Get-Date -Format 'yyyy-MM-dd')"
|
||||
$summaryLines += ""
|
||||
$summaryLines += "**Run Time**: $(Get-Date -Format 'HH:mm UTC') | **Issues Analyzed**: $($data.totalCategorized)"
|
||||
$summaryLines += ""
|
||||
$summaryLines += "## ⚡ Action Required by Category"
|
||||
$summaryLines += ""
|
||||
$summaryLines += "| Category | Count | Top Priority | Suggested Action |"
|
||||
$summaryLines += "|----------|-------|--------------|------------------|"
|
||||
|
||||
foreach ($catId in $CategoryInfo.Keys | Sort-Object { $CategoryInfo[$_].priority }) {
|
||||
$info = $CategoryInfo[$catId]
|
||||
$issues = $byCategory[$catId]
|
||||
|
||||
if ($issues -and $issues.Count -gt 0) {
|
||||
$top = $issues[0]
|
||||
$topLink = "[#$($top.number)]($RepoUrl/$($top.number))"
|
||||
$topInfo = $top.categoryReason
|
||||
if ($topInfo.Length -gt 40) { $topInfo = $topInfo.Substring(0, 37) + "..." }
|
||||
|
||||
$summaryLines += "| $($info.emoji) $($info.name) | $($issues.Count) | $topLink | $topInfo |"
|
||||
}
|
||||
}
|
||||
|
||||
$summaryLines += ""
|
||||
$summaryLines += "## 🎯 Top 10 Priority Actions"
|
||||
$summaryLines += ""
|
||||
|
||||
# Get top 10 across all categories
|
||||
$allIssues = @()
|
||||
foreach ($cat in $byCategory.Keys) {
|
||||
$allIssues += $byCategory[$cat]
|
||||
}
|
||||
$topIssues = $allIssues | Sort-Object priorityScore -Descending | Select-Object -First 10
|
||||
|
||||
$priority = 1
|
||||
foreach ($issue in $topIssues) {
|
||||
$info = $CategoryInfo[$issue.category]
|
||||
$urgency = if ($issue.priorityScore -ge 80) { "**[Urgent]**" }
|
||||
elseif ($issue.priorityScore -ge 60) { "**[High]**" }
|
||||
elseif ($issue.priorityScore -ge 40) { "[Medium]" }
|
||||
else { "[Low]" }
|
||||
|
||||
$summaryLines += "$priority. $urgency $($info.emoji) [#$($issue.number)]($RepoUrl/$($issue.number)) - $($issue.categoryReason)"
|
||||
$priority++
|
||||
}
|
||||
|
||||
$summaryLines += ""
|
||||
$summaryLines += "## 📁 Detailed Reports"
|
||||
$summaryLines += ""
|
||||
|
||||
foreach ($catId in $CategoryInfo.Keys | Sort-Object { $CategoryInfo[$_].priority }) {
|
||||
$info = $CategoryInfo[$catId]
|
||||
$issues = $byCategory[$catId]
|
||||
|
||||
if ($issues -and $issues.Count -gt 0) {
|
||||
$summaryLines += "- [$($info.emoji) $($info.name)](./$catId.md) ($($issues.Count) issues)"
|
||||
}
|
||||
}
|
||||
|
||||
$summaryLines += ""
|
||||
$summaryLines += "---"
|
||||
$summaryLines += "*Generated by continuous-issue-triage skill*"
|
||||
|
||||
$summaryLines -join "`n" | Set-Content "$OutputPath/summary.md"
|
||||
Write-Host "Generated: summary.md"
|
||||
|
||||
# Generate individual category reports
|
||||
foreach ($catId in $byCategory.Keys) {
|
||||
$info = $CategoryInfo[$catId]
|
||||
$issues = $byCategory[$catId]
|
||||
|
||||
if (-not $issues -or $issues.Count -eq 0) { continue }
|
||||
|
||||
$reportLines = @()
|
||||
$reportLines += "# $($info.emoji) $($info.name) Issues"
|
||||
$reportLines += ""
|
||||
$reportLines += "**Total**: $($issues.Count) issues"
|
||||
$reportLines += ""
|
||||
$reportLines += "## Overview"
|
||||
$reportLines += ""
|
||||
$reportLines += "| # | Issue | Priority | Reason | Labels |"
|
||||
$reportLines += "|---|-------|----------|--------|--------|"
|
||||
|
||||
foreach ($issue in $issues) {
|
||||
$labelStr = ($issue.labels | Select-Object -First 3) -join ", "
|
||||
if ($issue.labels.Count -gt 3) { $labelStr += "..." }
|
||||
$reason = $issue.categoryReason
|
||||
if ($reason.Length -gt 50) { $reason = $reason.Substring(0, 47) + "..." }
|
||||
|
||||
$reportLines += "| [#$($issue.number)]($RepoUrl/$($issue.number)) | $($issue.title.Substring(0, [Math]::Min(50, $issue.title.Length))) | $($issue.priorityScore)/100 | $reason | $labelStr |"
|
||||
}
|
||||
|
||||
$reportLines += ""
|
||||
$reportLines += "## Detailed Breakdown"
|
||||
$reportLines += ""
|
||||
|
||||
foreach ($issue in $issues) {
|
||||
$reportLines += "### [#$($issue.number)]($RepoUrl/$($issue.number)): $($issue.title)"
|
||||
$reportLines += ""
|
||||
$reportLines += "- **Priority Score**: $($issue.priorityScore)/100"
|
||||
$reportLines += "- **Category Reason**: $($issue.categoryReason)"
|
||||
$reportLines += "- **Suggested Action**: $($issue.suggestedAction)"
|
||||
$reportLines += "- **Reactions**: 👍 $($issue.reactions.thumbsUp) | ❤️ $($issue.reactions.heart)"
|
||||
$reportLines += "- **Comments**: $($issue.totalComments) total ($($issue.newComments) new)"
|
||||
$reportLines += "- **Labels**: $($issue.labels -join ', ')"
|
||||
|
||||
if ($issue.additionalData) {
|
||||
if ($issue.additionalData.suggestedLabels) {
|
||||
$reportLines += "- **Suggested Labels**: $($issue.additionalData.suggestedLabels -join ', ') (confidence: $($issue.additionalData.labelConfidence)%)"
|
||||
}
|
||||
if ($issue.additionalData.missingItems) {
|
||||
$reportLines += "- **Missing Info**: $($issue.additionalData.missingItems -join ', ')"
|
||||
}
|
||||
if ($issue.additionalData.mergedPRs) {
|
||||
$reportLines += "- **Merged PRs**: #$($issue.additionalData.mergedPRs -join ', #')"
|
||||
}
|
||||
if ($issue.additionalData.daysWaiting) {
|
||||
$reportLines += "- **Days Waiting**: $($issue.additionalData.daysWaiting)"
|
||||
}
|
||||
}
|
||||
|
||||
$reportLines += ""
|
||||
$reportLines += "---"
|
||||
$reportLines += ""
|
||||
}
|
||||
|
||||
$reportLines -join "`n" | Set-Content "$OutputPath/$catId.md"
|
||||
Write-Host "Generated: $catId.md"
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "All reports generated in: $OutputPath"
|
||||
Write-Host "Start with: summary.md"
|
||||
@@ -1,692 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Runs continuous issue triage using GitHub Copilot CLI with parallel processing.
|
||||
|
||||
.DESCRIPTION
|
||||
Orchestrates the full triage workflow:
|
||||
1. Collects active issues
|
||||
2. Analyzes issues in parallel using Copilot CLI
|
||||
3. Categorizes results
|
||||
4. Generates reports
|
||||
5. Updates state for delta tracking
|
||||
|
||||
.PARAMETER RunType
|
||||
Type of triage run: daily, twice-weekly, weekly. Default: weekly
|
||||
|
||||
.PARAMETER MaxParallel
|
||||
Maximum parallel Copilot CLI invocations. Default: 5
|
||||
|
||||
.PARAMETER TimeoutMinutes
|
||||
Timeout for each Copilot analysis. Default: 5
|
||||
|
||||
.PARAMETER MaxRetries
|
||||
Maximum retries on timeout. Default: 3
|
||||
|
||||
.PARAMETER Model
|
||||
Copilot model to use (optional).
|
||||
|
||||
.PARAMETER McpConfig
|
||||
Path to MCP config file (optional).
|
||||
|
||||
.PARAMETER LookbackDays
|
||||
For first run, days to look back. Default: 7
|
||||
|
||||
.PARAMETER Force
|
||||
Force re-analysis of all issues, ignoring cache.
|
||||
|
||||
.EXAMPLE
|
||||
.\run-triage.ps1
|
||||
|
||||
.EXAMPLE
|
||||
.\run-triage.ps1 -RunType daily -MaxParallel 10 -Model "claude-sonnet-4"
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter()]
|
||||
[ValidateSet("daily", "twice-weekly", "weekly")]
|
||||
[string]$RunType = "weekly",
|
||||
|
||||
[Parameter()]
|
||||
[int]$MaxParallel = 5,
|
||||
|
||||
[Parameter()]
|
||||
[int]$TimeoutMinutes = 5,
|
||||
|
||||
[Parameter()]
|
||||
[int]$MaxRetries = 3,
|
||||
|
||||
[Parameter()]
|
||||
[string]$Model,
|
||||
|
||||
[Parameter()]
|
||||
[string]$McpConfig,
|
||||
|
||||
[Parameter()]
|
||||
[int]$LookbackDays = 7,
|
||||
|
||||
[Parameter()]
|
||||
[switch]$Force
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$repoRoot = git rev-parse --show-toplevel 2>$null
|
||||
|
||||
# Resolve config directory name (.github or .claude) from script location
|
||||
$_cfgDir = if ($PSScriptRoot -match '[\\/](\.github|\.claude)[\\/]') { $Matches[1] } else { '.github' }
|
||||
if (-not $repoRoot) {
|
||||
$repoRoot = (Get-Location).Path
|
||||
}
|
||||
|
||||
# Paths
|
||||
$triageRoot = Join-Path $repoRoot "Generated Files/triage-issues"
|
||||
$currentRunPath = Join-Path $triageRoot "current-run"
|
||||
$statePath = Join-Path $triageRoot "triage-state.json"
|
||||
$issueCachePath = Join-Path $triageRoot "issue-cache"
|
||||
$historyPath = Join-Path $triageRoot "history"
|
||||
|
||||
# Ensure directories exist
|
||||
@($triageRoot, $currentRunPath, $issueCachePath, $historyPath) | ForEach-Object {
|
||||
if (-not (Test-Path $_)) {
|
||||
New-Item -ItemType Directory -Path $_ -Force | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "═══════════════════════════════════════════════════════════════" -ForegroundColor Cyan
|
||||
Write-Host " PowerToys Issue Triage - $RunType run" -ForegroundColor Cyan
|
||||
Write-Host " Started: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" -ForegroundColor Cyan
|
||||
Write-Host "═══════════════════════════════════════════════════════════════" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
#region State Management
|
||||
Write-Host "[1/6] Loading previous state..." -ForegroundColor Yellow
|
||||
|
||||
$state = $null
|
||||
if (Test-Path $statePath) {
|
||||
$state = Get-Content $statePath -Raw | ConvertFrom-Json -AsHashtable
|
||||
Write-Host " ✓ Loaded state from: $($state.lastRun)" -ForegroundColor Green
|
||||
Write-Host " Previous run type: $($state.lastRunType)" -ForegroundColor Gray
|
||||
Write-Host " Known issues: $($state.issueSnapshots.Count)" -ForegroundColor Gray
|
||||
} else {
|
||||
Write-Host " First run - initializing fresh state" -ForegroundColor Yellow
|
||||
$state = @{
|
||||
version = "1.0"
|
||||
lastRun = $null
|
||||
lastRunType = $null
|
||||
issueSnapshots = @{}
|
||||
pendingFollowUps = @()
|
||||
closedWithActivity = @()
|
||||
analysisResults = @{}
|
||||
statistics = @{
|
||||
totalRunCount = 0
|
||||
issuesAnalyzed = 0
|
||||
repliesPosted = 0
|
||||
issuesClosed = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Issue Collection
|
||||
Write-Host ""
|
||||
Write-Host "[2/6] Collecting active issues..." -ForegroundColor Yellow
|
||||
|
||||
$since = if ($state.lastRun) { $state.lastRun } else { (Get-Date).AddDays(-$LookbackDays).ToUniversalTime().ToString("o") }
|
||||
Write-Host " Looking for issues updated since: $since" -ForegroundColor Gray
|
||||
|
||||
# Collect open issues with recent activity
|
||||
$openIssuesJson = gh issue list --state open --json number,title,updatedAt,labels --limit 500 2>$null
|
||||
$openIssues = $openIssuesJson | ConvertFrom-Json | Where-Object {
|
||||
[datetime]$_.updatedAt -gt [datetime]$since
|
||||
}
|
||||
|
||||
# Collect closed issues with post-close activity (within 30 days)
|
||||
$closedIssuesJson = gh issue list --state closed --json number,title,updatedAt,closedAt --limit 200 2>$null
|
||||
$closedIssues = $closedIssuesJson | ConvertFrom-Json | Where-Object {
|
||||
$closedAt = [datetime]$_.closedAt
|
||||
$updatedAt = [datetime]$_.updatedAt
|
||||
$cutoff = (Get-Date).AddDays(-30)
|
||||
($closedAt -gt $cutoff) -and ($updatedAt -gt $closedAt)
|
||||
}
|
||||
|
||||
# Combine and dedupe
|
||||
$allIssues = @()
|
||||
$allIssues += $openIssues | ForEach-Object { @{ number = $_.number; title = $_.title; state = "open"; updatedAt = $_.updatedAt } }
|
||||
$allIssues += $closedIssues | ForEach-Object { @{ number = $_.number; title = $_.title; state = "closed"; updatedAt = $_.updatedAt } }
|
||||
|
||||
# Add pending follow-ups from previous run
|
||||
if ($state.pendingFollowUps) {
|
||||
foreach ($pending in $state.pendingFollowUps) {
|
||||
if ($pending.status -eq "pending" -and ($allIssues.number -notcontains $pending.issueNumber)) {
|
||||
$allIssues += @{ number = $pending.issueNumber; title = "pending-followup"; state = "unknown" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$uniqueIssues = $allIssues | Group-Object number | ForEach-Object { $_.Group | Select-Object -First 1 }
|
||||
|
||||
Write-Host " ✓ Found $($uniqueIssues.Count) issues to analyze" -ForegroundColor Green
|
||||
Write-Host " - Open with activity: $(($uniqueIssues | Where-Object { $_.state -eq 'open' }).Count)" -ForegroundColor Gray
|
||||
Write-Host " - Closed with activity: $(($uniqueIssues | Where-Object { $_.state -eq 'closed' }).Count)" -ForegroundColor Gray
|
||||
#endregion
|
||||
|
||||
#region Filter for Analysis
|
||||
Write-Host ""
|
||||
Write-Host "[3/6] Filtering issues for analysis..." -ForegroundColor Yellow
|
||||
|
||||
$issuesToAnalyze = @()
|
||||
foreach ($issue in $uniqueIssues) {
|
||||
$issueNum = $issue.number
|
||||
$cached = $state.analysisResults[$issueNum.ToString()]
|
||||
|
||||
$needsAnalysis = $false
|
||||
$reason = ""
|
||||
|
||||
if ($Force) {
|
||||
$needsAnalysis = $true
|
||||
$reason = "forced"
|
||||
}
|
||||
elseif (-not $cached) {
|
||||
$needsAnalysis = $true
|
||||
$reason = "new"
|
||||
}
|
||||
elseif ($cached.analyzedAt) {
|
||||
$daysSinceAnalysis = ((Get-Date) - [datetime]$cached.analyzedAt).Days
|
||||
if ($daysSinceAnalysis -gt 7) {
|
||||
$needsAnalysis = $true
|
||||
$reason = "stale-cache"
|
||||
}
|
||||
elseif ($cached.commentCountAtAnalysis -and $state.issueSnapshots[$issueNum.ToString()]) {
|
||||
$previousCount = $state.issueSnapshots[$issueNum.ToString()].commentCount
|
||||
if ($cached.commentCountAtAnalysis -lt $previousCount) {
|
||||
$needsAnalysis = $true
|
||||
$reason = "new-comments"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($needsAnalysis) {
|
||||
$issuesToAnalyze += @{
|
||||
number = $issueNum
|
||||
title = $issue.title
|
||||
state = $issue.state
|
||||
reason = $reason
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host " ✓ $($issuesToAnalyze.Count) issues need analysis" -ForegroundColor Green
|
||||
Write-Host " ✓ $($uniqueIssues.Count - $issuesToAnalyze.Count) issues using cached results" -ForegroundColor Gray
|
||||
#endregion
|
||||
|
||||
#region Parallel Copilot Analysis
|
||||
Write-Host ""
|
||||
Write-Host "[4/6] Running parallel Copilot analysis..." -ForegroundColor Yellow
|
||||
Write-Host " Max parallel: $MaxParallel | Timeout: ${TimeoutMinutes}m | Max retries: $MaxRetries" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
|
||||
# Prepare the prompt template
|
||||
$promptTemplate = @"
|
||||
Analyze GitHub issue #ISSUE_NUMBER for PowerToys triage.
|
||||
|
||||
Use the review-issue prompt methodology from $_cfgDir/prompts/review-issue.prompt.md.
|
||||
|
||||
Output a JSON summary to stdout with this structure:
|
||||
{
|
||||
"issueNumber": ISSUE_NUMBER,
|
||||
"category": "trending|needs-label|ready-for-fix|needs-info|needs-clarification|closeable|stale-waiting|duplicate-candidate|review-needed",
|
||||
"categoryReason": "brief explanation",
|
||||
"priorityScore": 0-100,
|
||||
"suggestedAction": "what human should do",
|
||||
"suggestedLabels": ["label1", "label2"],
|
||||
"labelConfidence": 0-100,
|
||||
"missingInfo": ["item1", "item2"],
|
||||
"similarIssues": [12345, 12346],
|
||||
"potentialAssignees": ["@user1", "@user2"],
|
||||
"draftReply": "if needs-info or needs-clarification, draft the reply message here",
|
||||
"clarityScore": 0-100,
|
||||
"feasibilityScore": 0-100,
|
||||
"newCommentsSummary": "brief summary of recent discussion if trending"
|
||||
}
|
||||
|
||||
Focus on actionable triage. Be concise.
|
||||
"@
|
||||
|
||||
# Thread-safe collections for results
|
||||
$analysisResults = [System.Collections.Concurrent.ConcurrentDictionary[string, object]]::new()
|
||||
$analysisErrors = [System.Collections.Concurrent.ConcurrentBag[object]]::new()
|
||||
|
||||
# Progress tracking
|
||||
$totalIssues = $issuesToAnalyze.Count
|
||||
$completedCount = [ref]0
|
||||
$startTime = Get-Date
|
||||
|
||||
if ($totalIssues -gt 0) {
|
||||
$issuesToAnalyze | ForEach-Object -ThrottleLimit $MaxParallel -Parallel {
|
||||
$issue = $_
|
||||
$issueNum = $issue.number
|
||||
$results = $using:analysisResults
|
||||
$errors = $using:analysisErrors
|
||||
$completed = $using:completedCount
|
||||
$total = $using:totalIssues
|
||||
$timeoutMin = $using:TimeoutMinutes
|
||||
$maxRetry = $using:MaxRetries
|
||||
$model = $using:Model
|
||||
$mcpCfg = $using:McpConfig
|
||||
$template = $using:promptTemplate
|
||||
$root = $using:repoRoot
|
||||
$cachePath = $using:issueCachePath
|
||||
|
||||
$prompt = $template -replace 'ISSUE_NUMBER', $issueNum
|
||||
$logDir = Join-Path $cachePath $issueNum
|
||||
if (-not (Test-Path $logDir)) {
|
||||
New-Item -ItemType Directory -Path $logDir -Force | Out-Null
|
||||
}
|
||||
|
||||
$success = $false
|
||||
$lastError = $null
|
||||
$output = $null
|
||||
|
||||
for ($retry = 0; $retry -lt $maxRetry -and -not $success; $retry++) {
|
||||
if ($retry -gt 0) {
|
||||
Write-Host " ⟳ Retry $retry/$maxRetry for #$issueNum" -ForegroundColor Yellow
|
||||
Start-Sleep -Seconds 10
|
||||
}
|
||||
|
||||
try {
|
||||
# Build Copilot CLI arguments
|
||||
$copilotArgs = @()
|
||||
if ($mcpCfg) {
|
||||
$copilotArgs += @('--additional-mcp-config', $mcpCfg)
|
||||
}
|
||||
$copilotArgs += @('-p', $prompt, '--yolo', '--agent', 'ReviewIssue')
|
||||
if ($model) {
|
||||
$copilotArgs += @('--model', $model)
|
||||
}
|
||||
|
||||
# Run with timeout
|
||||
$job = Start-Job -ScriptBlock {
|
||||
param($args)
|
||||
& copilot @args 2>&1
|
||||
} -ArgumentList (,$copilotArgs)
|
||||
|
||||
$timeoutSec = $timeoutMin * 60
|
||||
$jobResult = $job | Wait-Job -Timeout $timeoutSec
|
||||
|
||||
if ($job.State -eq 'Running') {
|
||||
# Timeout - kill the job
|
||||
$job | Stop-Job -PassThru | Remove-Job -Force
|
||||
$lastError = "Timeout after ${timeoutMin} minutes"
|
||||
} else {
|
||||
$output = $job | Receive-Job
|
||||
$job | Remove-Job -Force
|
||||
|
||||
# Check for valid output
|
||||
if ($output) {
|
||||
$outputStr = $output -join "`n"
|
||||
# Try to extract JSON from output
|
||||
if ($outputStr -match '\{[\s\S]*"issueNumber"[\s\S]*\}') {
|
||||
$success = $true
|
||||
} else {
|
||||
$lastError = "No valid JSON in output"
|
||||
}
|
||||
} else {
|
||||
$lastError = "Empty output from Copilot"
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$lastError = $_.Exception.Message
|
||||
}
|
||||
}
|
||||
|
||||
# Update progress
|
||||
[System.Threading.Interlocked]::Increment($completed) | Out-Null
|
||||
$pct = [math]::Round(($completed.Value / $total) * 100)
|
||||
|
||||
if ($success) {
|
||||
# Save output and parse result
|
||||
$outputStr = $output -join "`n"
|
||||
$outputStr | Out-File -FilePath (Join-Path $logDir "analysis.log") -Force
|
||||
|
||||
# Try to extract JSON
|
||||
try {
|
||||
if ($outputStr -match '(\{[\s\S]*"issueNumber"[\s\S]*\})') {
|
||||
$jsonStr = $Matches[1]
|
||||
$parsed = $jsonStr | ConvertFrom-Json -AsHashtable
|
||||
$results[$issueNum.ToString()] = @{
|
||||
success = $true
|
||||
data = $parsed
|
||||
analyzedAt = (Get-Date).ToUniversalTime().ToString("o")
|
||||
}
|
||||
Write-Host " [$pct%] ✓ #$issueNum - $($parsed.category)" -ForegroundColor Green
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$errors.Add(@{ issueNumber = $issueNum; error = "JSON parse error: $_" })
|
||||
Write-Host " [$pct%] ⚠ #$issueNum - JSON parse failed" -ForegroundColor Yellow
|
||||
}
|
||||
} else {
|
||||
# Log error
|
||||
$lastError | Out-File -FilePath (Join-Path $logDir "error.log") -Force
|
||||
$errors.Add(@{ issueNumber = $issueNum; error = $lastError; retries = $maxRetry })
|
||||
Write-Host " [$pct%] ✗ #$issueNum - $lastError" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$elapsed = (Get-Date) - $startTime
|
||||
Write-Host ""
|
||||
Write-Host " Analysis complete in $([math]::Round($elapsed.TotalMinutes, 1)) minutes" -ForegroundColor Cyan
|
||||
Write-Host " ✓ Successful: $($analysisResults.Count)" -ForegroundColor Green
|
||||
Write-Host " ✗ Failed: $($analysisErrors.Count)" -ForegroundColor $(if ($analysisErrors.Count -gt 0) { 'Red' } else { 'Gray' })
|
||||
#endregion
|
||||
|
||||
#region Merge Results & Categorize
|
||||
Write-Host ""
|
||||
Write-Host "[5/6] Merging results and updating state..." -ForegroundColor Yellow
|
||||
|
||||
# Merge new analysis with cached results
|
||||
$allResults = @{}
|
||||
|
||||
# Add cached results
|
||||
foreach ($key in $state.analysisResults.Keys) {
|
||||
if (-not $analysisResults.ContainsKey($key)) {
|
||||
$allResults[$key] = $state.analysisResults[$key]
|
||||
}
|
||||
}
|
||||
|
||||
# Add new results
|
||||
foreach ($key in $analysisResults.Keys) {
|
||||
$allResults[$key] = $analysisResults[$key]
|
||||
}
|
||||
|
||||
# Categorize for reporting
|
||||
$categorized = @{
|
||||
trending = @()
|
||||
"needs-label" = @()
|
||||
"ready-for-fix" = @()
|
||||
"needs-info" = @()
|
||||
"needs-clarification" = @()
|
||||
closeable = @()
|
||||
"stale-waiting" = @()
|
||||
"duplicate-candidate" = @()
|
||||
"review-needed" = @()
|
||||
}
|
||||
|
||||
foreach ($key in $allResults.Keys) {
|
||||
$result = $allResults[$key]
|
||||
if ($result.success -and $result.data) {
|
||||
$data = $result.data
|
||||
$category = $data.category
|
||||
if ($categorized.ContainsKey($category)) {
|
||||
$categorized[$category] += $data
|
||||
} else {
|
||||
$categorized["review-needed"] += $data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Sort each category by priority
|
||||
foreach ($cat in $categorized.Keys) {
|
||||
$categorized[$cat] = $categorized[$cat] | Sort-Object { -[int]$_.priorityScore }
|
||||
}
|
||||
|
||||
Write-Host " Categorization complete:" -ForegroundColor Green
|
||||
foreach ($cat in $categorized.Keys | Sort-Object { $categorized[$_].Count } -Descending) {
|
||||
if ($categorized[$cat].Count -gt 0) {
|
||||
Write-Host " - $cat`: $($categorized[$cat].Count)" -ForegroundColor Gray
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Generate Reports
|
||||
Write-Host ""
|
||||
Write-Host "[6/6] Generating reports..." -ForegroundColor Yellow
|
||||
|
||||
# Archive previous run
|
||||
$archiveDate = Get-Date -Format "yyyy-MM-dd_HHmm"
|
||||
$archivePath = Join-Path $historyPath $archiveDate
|
||||
if (Test-Path "$currentRunPath/summary.md") {
|
||||
New-Item -ItemType Directory -Path $archivePath -Force | Out-Null
|
||||
Copy-Item -Path "$currentRunPath/*" -Destination $archivePath -Recurse -Force
|
||||
Write-Host " ✓ Archived previous run to: $archiveDate" -ForegroundColor Gray
|
||||
}
|
||||
|
||||
# Clean current run
|
||||
if (Test-Path $currentRunPath) {
|
||||
Remove-Item -Path "$currentRunPath/*" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
New-Item -ItemType Directory -Path "$currentRunPath/draft-replies" -Force | Out-Null
|
||||
|
||||
# Category info for display
|
||||
$categoryInfo = @{
|
||||
"trending" = @{ emoji = "🔥"; name = "Trending" }
|
||||
"needs-label" = @{ emoji = "🏷️"; name = "Needs-Label" }
|
||||
"ready-for-fix" = @{ emoji = "✅"; name = "Ready-for-Fix" }
|
||||
"needs-info" = @{ emoji = "❓"; name = "Needs-Info" }
|
||||
"needs-clarification" = @{ emoji = "💬"; name = "Needs-Clarification" }
|
||||
"closeable" = @{ emoji = "✔️"; name = "Closeable" }
|
||||
"stale-waiting" = @{ emoji = "⏳"; name = "Stale-Waiting" }
|
||||
"duplicate-candidate" = @{ emoji = "🔁"; name = "Duplicate-Candidate" }
|
||||
"review-needed" = @{ emoji = "👀"; name = "Review-Needed" }
|
||||
}
|
||||
|
||||
$repoUrl = "https://github.com/microsoft/PowerToys/issues"
|
||||
|
||||
# Generate summary.md
|
||||
$summary = @"
|
||||
# Issue Triage Summary - $(Get-Date -Format 'yyyy-MM-dd')
|
||||
|
||||
**Run Type**: $RunType | **Time**: $(Get-Date -Format 'HH:mm UTC') | **Duration**: $([math]::Round($elapsed.TotalMinutes, 1)) min
|
||||
|
||||
## 📊 Delta Since Last Run
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Issues with new activity | $($uniqueIssues.Count) |
|
||||
| Newly analyzed | $($analysisResults.Count) |
|
||||
| Using cached analysis | $($allResults.Count - $analysisResults.Count) |
|
||||
| Analysis failures | $($analysisErrors.Count) |
|
||||
|
||||
## ⚡ Action Required by Category
|
||||
|
||||
| Category | Count | Top Priority | Score |
|
||||
|----------|-------|--------------|-------|
|
||||
|
||||
"@
|
||||
|
||||
foreach ($cat in @("trending", "needs-label", "ready-for-fix", "needs-info", "needs-clarification", "closeable", "stale-waiting", "duplicate-candidate", "review-needed")) {
|
||||
$info = $categoryInfo[$cat]
|
||||
$issues = $categorized[$cat]
|
||||
if ($issues.Count -gt 0) {
|
||||
$top = $issues[0]
|
||||
$summary += "| $($info.emoji) $($info.name) | $($issues.Count) | [#$($top.issueNumber)]($repoUrl/$($top.issueNumber)) | $($top.priorityScore)/100 |`n"
|
||||
}
|
||||
}
|
||||
|
||||
$summary += @"
|
||||
|
||||
## 🎯 Top 10 Priority Actions
|
||||
|
||||
"@
|
||||
|
||||
# Get top 10 across all categories
|
||||
$allIssueData = @()
|
||||
foreach ($cat in $categorized.Keys) {
|
||||
$allIssueData += $categorized[$cat]
|
||||
}
|
||||
$topIssues = $allIssueData | Sort-Object { -[int]$_.priorityScore } | Select-Object -First 10
|
||||
|
||||
$priority = 1
|
||||
foreach ($issue in $topIssues) {
|
||||
$info = $categoryInfo[$issue.category]
|
||||
$urgency = if ([int]$issue.priorityScore -ge 80) { "**[Urgent]**" }
|
||||
elseif ([int]$issue.priorityScore -ge 60) { "**[High]**" }
|
||||
elseif ([int]$issue.priorityScore -ge 40) { "[Medium]" }
|
||||
else { "[Low]" }
|
||||
|
||||
$summary += "$priority. $urgency $($info.emoji) [#$($issue.issueNumber)]($repoUrl/$($issue.issueNumber)) - $($issue.categoryReason)`n"
|
||||
$priority++
|
||||
}
|
||||
|
||||
$summary += @"
|
||||
|
||||
## 📁 Detailed Reports
|
||||
|
||||
"@
|
||||
|
||||
foreach ($cat in @("trending", "needs-label", "ready-for-fix", "needs-info", "needs-clarification", "closeable", "stale-waiting", "duplicate-candidate")) {
|
||||
$info = $categoryInfo[$cat]
|
||||
if ($categorized[$cat].Count -gt 0) {
|
||||
$summary += "- [$($info.emoji) $($info.name)](./$cat.md) ($($categorized[$cat].Count) issues)`n"
|
||||
}
|
||||
}
|
||||
|
||||
$summary += @"
|
||||
|
||||
## 📝 Draft Replies Ready
|
||||
|
||||
"@
|
||||
|
||||
$draftsWritten = 0
|
||||
foreach ($cat in @("needs-info", "needs-clarification", "closeable", "stale-waiting")) {
|
||||
foreach ($issue in $categorized[$cat]) {
|
||||
if ($issue.draftReply) {
|
||||
$draftPath = Join-Path "$currentRunPath/draft-replies" "issue-$($issue.issueNumber).md"
|
||||
$draftContent = @"
|
||||
---
|
||||
issue: $($issue.issueNumber)
|
||||
category: $($issue.category)
|
||||
generated: $(Get-Date -Format "o")
|
||||
---
|
||||
|
||||
$($issue.draftReply)
|
||||
"@
|
||||
$draftContent | Out-File -FilePath $draftPath -Force
|
||||
$draftsWritten++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$summary += "**$draftsWritten** draft replies ready in ``draft-replies/```n`n"
|
||||
|
||||
if ($analysisErrors.Count -gt 0) {
|
||||
$summary += @"
|
||||
|
||||
## ⚠️ Analysis Failures
|
||||
|
||||
| Issue | Error |
|
||||
|-------|-------|
|
||||
|
||||
"@
|
||||
foreach ($err in $analysisErrors) {
|
||||
$summary += "| #$($err.issueNumber) | $($err.error) |`n"
|
||||
}
|
||||
}
|
||||
|
||||
$summary += @"
|
||||
|
||||
---
|
||||
*Generated by continuous-issue-triage skill*
|
||||
*Next suggested run: $(Get-Date (Get-Date).AddDays($(if ($RunType -eq 'daily') { 1 } elseif ($RunType -eq 'twice-weekly') { 3 } else { 7 })) -Format 'yyyy-MM-dd')*
|
||||
"@
|
||||
|
||||
$summary | Out-File -FilePath "$currentRunPath/summary.md" -Force
|
||||
Write-Host " ✓ Generated: summary.md" -ForegroundColor Green
|
||||
|
||||
# Generate category reports
|
||||
foreach ($cat in $categorized.Keys) {
|
||||
$issues = $categorized[$cat]
|
||||
if ($issues.Count -eq 0) { continue }
|
||||
|
||||
$info = $categoryInfo[$cat]
|
||||
$report = @"
|
||||
# $($info.emoji) $($info.name) Issues
|
||||
|
||||
**Total**: $($issues.Count) issues
|
||||
|
||||
## Overview
|
||||
|
||||
| # | Issue | Priority | Reason | Suggested Action |
|
||||
|---|-------|----------|--------|------------------|
|
||||
|
||||
"@
|
||||
|
||||
foreach ($issue in $issues) {
|
||||
$reason = if ($issue.categoryReason.Length -gt 40) { $issue.categoryReason.Substring(0, 37) + "..." } else { $issue.categoryReason }
|
||||
$action = if ($issue.suggestedAction.Length -gt 40) { $issue.suggestedAction.Substring(0, 37) + "..." } else { $issue.suggestedAction }
|
||||
$report += "| [#$($issue.issueNumber)]($repoUrl/$($issue.issueNumber)) | $($issue.priorityScore)/100 | $reason | $action |`n"
|
||||
}
|
||||
|
||||
$report += "`n## Detailed Breakdown`n`n"
|
||||
|
||||
foreach ($issue in $issues) {
|
||||
$report += @"
|
||||
### [#$($issue.issueNumber)]($repoUrl/$($issue.issueNumber))
|
||||
|
||||
- **Priority Score**: $($issue.priorityScore)/100
|
||||
- **Category Reason**: $($issue.categoryReason)
|
||||
- **Suggested Action**: $($issue.suggestedAction)
|
||||
- **Clarity Score**: $($issue.clarityScore)/100
|
||||
- **Feasibility Score**: $($issue.feasibilityScore)/100
|
||||
|
||||
"@
|
||||
if ($issue.suggestedLabels -and $issue.suggestedLabels.Count -gt 0) {
|
||||
$report += "- **Suggested Labels**: $($issue.suggestedLabels -join ', ') (confidence: $($issue.labelConfidence)%)`n"
|
||||
}
|
||||
if ($issue.missingInfo -and $issue.missingInfo.Count -gt 0) {
|
||||
$report += "- **Missing Info**: $($issue.missingInfo -join ', ')`n"
|
||||
}
|
||||
if ($issue.potentialAssignees -and $issue.potentialAssignees.Count -gt 0) {
|
||||
$report += "- **Potential Assignees**: $($issue.potentialAssignees -join ', ')`n"
|
||||
}
|
||||
if ($issue.similarIssues -and $issue.similarIssues.Count -gt 0) {
|
||||
$report += "- **Similar Issues**: #$($issue.similarIssues -join ', #')`n"
|
||||
}
|
||||
if ($issue.draftReply) {
|
||||
$report += "- **Draft Reply**: [View](./draft-replies/issue-$($issue.issueNumber).md)`n"
|
||||
}
|
||||
$report += "`n---`n`n"
|
||||
}
|
||||
|
||||
$report | Out-File -FilePath "$currentRunPath/$cat.md" -Force
|
||||
Write-Host " ✓ Generated: $cat.md ($($issues.Count) issues)" -ForegroundColor Green
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Save State
|
||||
Write-Host ""
|
||||
Write-Host "Saving state for next run..." -ForegroundColor Yellow
|
||||
|
||||
# Update issue snapshots
|
||||
foreach ($issue in $uniqueIssues) {
|
||||
$issueNum = $issue.number.ToString()
|
||||
$result = $allResults[$issueNum]
|
||||
|
||||
$state.issueSnapshots[$issueNum] = @{
|
||||
number = $issue.number
|
||||
title = $issue.title
|
||||
state = $issue.state
|
||||
lastSeenAt = (Get-Date).ToUniversalTime().ToString("o")
|
||||
category = if ($result.data) { $result.data.category } else { "unknown" }
|
||||
priorityScore = if ($result.data) { $result.data.priorityScore } else { 0 }
|
||||
}
|
||||
}
|
||||
|
||||
$state.lastRun = (Get-Date).ToUniversalTime().ToString("o")
|
||||
$state.lastRunType = $RunType
|
||||
$state.analysisResults = $allResults
|
||||
$state.statistics.totalRunCount++
|
||||
$state.statistics.issuesAnalyzed += $analysisResults.Count
|
||||
|
||||
$state | ConvertTo-Json -Depth 10 | Out-File -FilePath $statePath -Force
|
||||
Write-Host " ✓ State saved" -ForegroundColor Green
|
||||
#endregion
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "═══════════════════════════════════════════════════════════════" -ForegroundColor Cyan
|
||||
Write-Host " Triage complete!" -ForegroundColor Cyan
|
||||
Write-Host " Reports: $currentRunPath" -ForegroundColor Cyan
|
||||
Write-Host " Start with: summary.md" -ForegroundColor Cyan
|
||||
Write-Host "═══════════════════════════════════════════════════════════════" -ForegroundColor Cyan
|
||||
21
.github/skills/issue-fix/LICENSE.txt
vendored
21
.github/skills/issue-fix/LICENSE.txt
vendored
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
220
.github/skills/issue-fix/SKILL.md
vendored
220
.github/skills/issue-fix/SKILL.md
vendored
@@ -1,220 +0,0 @@
|
||||
---
|
||||
name: issue-fix
|
||||
description: Automatically fix GitHub issues and create PRs. Use when asked to fix an issue, implement a feature from an issue, auto-fix an issue, apply implementation plan, create code changes for an issue, resolve a GitHub issue, or submit a PR for an issue. Creates isolated git worktree, applies AI-generated fixes, commits changes, and creates pull requests.
|
||||
license: Complete terms in LICENSE.txt
|
||||
---
|
||||
|
||||
# Issue Fix Skill
|
||||
|
||||
Automatically fix GitHub issues by creating isolated worktrees, applying AI-generated code changes, and creating pull requests - the complete issue-to-PR workflow.
|
||||
|
||||
## Skill Contents
|
||||
|
||||
This skill is **self-contained** with all required resources:
|
||||
|
||||
```
|
||||
.github/skills/issue-fix/
|
||||
├── SKILL.md # This file
|
||||
├── LICENSE.txt # MIT License
|
||||
├── scripts/
|
||||
│ ├── Start-IssueAutoFix.ps1 # Main fix script (creates worktree, applies fix)
|
||||
│ ├── Start-IssueFixParallel.ps1 # Parallel runner (single terminal)
|
||||
│ ├── Get-WorktreeStatus.ps1 # Worktree status helper
|
||||
│ ├── Submit-IssueFix.ps1 # Commit and create PR
|
||||
│ └── IssueReviewLib.ps1 # Shared helpers
|
||||
└── references/
|
||||
├── fix-issue.prompt.md # AI prompt for fixing
|
||||
├── create-commit-title.prompt.md # AI prompt for commit messages
|
||||
├── create-pr-summary.prompt.md # AI prompt for PR descriptions
|
||||
└── mcp-config.json # MCP configuration
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
- **Worktrees**: Created at drive root level `Q:/PowerToys-xxxx/`
|
||||
- **PRs**: Created on GitHub linking to the original issue
|
||||
- **Signal file**: `Generated Files/issueFix/<issue>/.signal`
|
||||
|
||||
## Signal File
|
||||
|
||||
On completion, a `.signal` file is created for orchestrator coordination:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"issueNumber": 45363,
|
||||
"timestamp": "2026-02-04T10:05:23Z",
|
||||
"worktreePath": "Q:/PowerToys-ab12"
|
||||
}
|
||||
```
|
||||
|
||||
Status values: `success`, `failure`
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
- Fix a specific GitHub issue automatically
|
||||
- Implement a feature described in an issue
|
||||
- Apply an existing implementation plan
|
||||
- Create code changes and submit PR for an issue
|
||||
- Auto-fix high-confidence issues end-to-end
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- GitHub CLI (`gh`) installed and authenticated
|
||||
- Issue must be reviewed first (use `issue-review` skill)
|
||||
- PowerShell 7+ for running scripts
|
||||
- Copilot CLI or Claude CLI installed
|
||||
|
||||
## Required Variables
|
||||
|
||||
| Variable | Description | Example |
|
||||
|----------|-------------|---------|
|
||||
| `{{IssueNumber}}` | GitHub issue number to fix | `44044` |
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Ensure Issue is Reviewed
|
||||
|
||||
If not already reviewed, use the `issue-review` skill first.
|
||||
|
||||
### Step 2: Run Auto-Fix
|
||||
|
||||
```powershell
|
||||
# Create worktree and apply fix
|
||||
.github/skills/issue-fix/scripts/Start-IssueAutoFix.ps1 -IssueNumber {{IssueNumber}} -CLIType copilot -Force
|
||||
```
|
||||
|
||||
This will:
|
||||
1. Create a new git worktree with branch `issue/{{IssueNumber}}`
|
||||
2. Copy the review files to the worktree
|
||||
3. Launch Copilot CLI to implement the fix
|
||||
4. Build and verify the changes
|
||||
|
||||
### Step 3: Submit PR
|
||||
|
||||
```powershell
|
||||
# Commit changes and create PR
|
||||
.github/skills/issue-fix/scripts/Submit-IssueFix.ps1 -IssueNumber {{IssueNumber}} -CLIType copilot -Force
|
||||
```
|
||||
|
||||
This will:
|
||||
1. Generate AI commit message
|
||||
2. Commit all changes
|
||||
3. Push to origin
|
||||
4. Create PR with AI-generated description
|
||||
5. Link PR to issue with "Fixes #{{IssueNumber}}"
|
||||
|
||||
### One-Step Alternative
|
||||
|
||||
To fix AND submit in one command:
|
||||
|
||||
```powershell
|
||||
.github/skills/issue-fix/scripts/Start-IssueAutoFix.ps1 -IssueNumber {{IssueNumber}} -CLIType copilot -CreatePR -Force
|
||||
```
|
||||
|
||||
## CLI Options
|
||||
|
||||
### Start-IssueAutoFix.ps1
|
||||
|
||||
| Parameter | Description | Default |
|
||||
|-----------|-------------|---------|
|
||||
| `-IssueNumber` | Issue to fix | Required |
|
||||
| `-CLIType` | AI CLI: `copilot` or `claude` | `copilot` |
|
||||
| `-Model` | Copilot model (e.g., `gpt-5.2-codex`) | (optional) |
|
||||
| `-CreatePR` | Auto-create PR after fix | `false` |
|
||||
| `-SkipWorktree` | Fix in current repo (no worktree) | `false` |
|
||||
| `-Force` | Skip confirmation prompts | `false` |
|
||||
|
||||
### Submit-IssueFix.ps1
|
||||
|
||||
| Parameter | Description | Default |
|
||||
|-----------|-------------|---------|
|
||||
| `-IssueNumber` | Issue to submit | Required |
|
||||
| `-CLIType` | AI CLI: `copilot`, `claude`, `manual` | `copilot` |
|
||||
| `-Draft` | Create as draft PR | `false` |
|
||||
| `-SkipCommit` | Skip commit (changes already committed) | `false` |
|
||||
| `-Force` | Skip confirmation prompts | `false` |
|
||||
|
||||
## Batch Processing
|
||||
|
||||
Fix multiple issues:
|
||||
|
||||
```powershell
|
||||
# Fix multiple issues (creates worktrees, applies fixes)
|
||||
.github/skills/issue-fix/scripts/Start-IssueAutoFix.ps1 -IssueNumbers 44044, 32950 -CLIType copilot -Force
|
||||
|
||||
# Submit all fixed issues as PRs
|
||||
.github/skills/issue-fix/scripts/Submit-IssueFix.ps1 -CLIType copilot -Force
|
||||
```
|
||||
|
||||
## Parallel Execution (IMPORTANT)
|
||||
|
||||
**DO NOT** spawn separate terminals for each issue. Use the dedicated scripts:
|
||||
|
||||
```powershell
|
||||
# Run fixes in parallel (single terminal)
|
||||
.github/skills/issue-fix/scripts/Start-IssueFixParallel.ps1 -IssueNumbers 28726,13336,27507,3054,37800 -CLIType copilot -ThrottleLimit 5 -Force
|
||||
|
||||
# Check worktree status
|
||||
.github/skills/issue-fix/scripts/Get-WorktreeStatus.ps1
|
||||
```
|
||||
|
||||
This allows:
|
||||
- Tracking all jobs in one place
|
||||
- Waiting for completion with proper synchronization
|
||||
- Controlling parallelism with `-ThrottleLimit`
|
||||
- Combined output visibility
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Problem | Solution |
|
||||
|---------|----------|
|
||||
| Worktree already exists | Use existing worktree or `git worktree remove <path>` |
|
||||
| No implementation plan | Use `issue-review` skill first |
|
||||
| Build failures | Check build logs, may need manual intervention |
|
||||
| PR already exists | Script will skip, check existing PR |
|
||||
| CLI not found | Install Copilot CLI |
|
||||
|
||||
## PR Creation Requirements (CRITICAL)
|
||||
|
||||
**NEVER create PRs with placeholder/stub code.** Every PR must have:
|
||||
|
||||
1. **Real implementation** - Actual working code that addresses the issue
|
||||
2. **Proper title** - Follow `create-commit-title.prompt.md` (Conventional Commits)
|
||||
3. **Full description** - Follow `create-pr-summary.prompt.md` based on actual diff
|
||||
|
||||
### PR Title Format (Conventional Commits)
|
||||
```
|
||||
feat(module): add feature description
|
||||
fix(module): fix bug description
|
||||
docs(module): update documentation
|
||||
```
|
||||
|
||||
### PR Description Must Include
|
||||
- Summary of changes (from actual diff)
|
||||
- `Fixes #IssueNumber` link
|
||||
- Checklist items marked appropriately
|
||||
- Validation steps performed
|
||||
|
||||
**Example of BAD PR (never do this):**
|
||||
```
|
||||
Title: fix: address issue #12345
|
||||
Body: Fixes #12345
|
||||
Code: class Fix12345 { public void Apply() { } } // EMPTY STUB!
|
||||
```
|
||||
|
||||
**Example of GOOD PR:**
|
||||
```
|
||||
Title: feat(peek): add symbolic link resolution for PDF/HTML files
|
||||
Body: ## Summary
|
||||
Adds SymlinkResolver helper to resolve symlinks...
|
||||
[Full description based on create-pr-summary.prompt.md]
|
||||
```
|
||||
|
||||
## Related Skills
|
||||
|
||||
| Skill | Purpose |
|
||||
|-------|---------|
|
||||
| `issue-review` | Review issues, generate implementation plans |
|
||||
| `pr-review` | Review the created PR |
|
||||
| `pr-fix` | Fix PR review comments |
|
||||
@@ -1,49 +0,0 @@
|
||||
---
|
||||
agent: 'agent'
|
||||
description: 'Generate an 80-character git commit title for the local diff'
|
||||
---
|
||||
|
||||
# Generate Commit Title
|
||||
|
||||
## Purpose
|
||||
Provide a single-line, ready-to-paste git commit title (<= 80 characters) that reflects the most important local changes since `HEAD`.
|
||||
|
||||
## Input to collect
|
||||
- Run exactly one command to view the local diff:
|
||||
```@terminal
|
||||
git diff HEAD
|
||||
```
|
||||
|
||||
## How to decide the title
|
||||
1. From the diff, find the dominant area (e.g., `src/modules/*`, `doc/devdocs/**`) and the change type (bug fix, docs update, config tweak).
|
||||
2. Draft an imperative, plain-ASCII title that:
|
||||
- Mentions the primary component when obvious (e.g., `FancyZones:` or `Docs:`)
|
||||
- Stays within 80 characters and has no trailing punctuation
|
||||
|
||||
## Final output
|
||||
- Reply with only the commit title on a single line—no extra text.
|
||||
|
||||
## PR title convention (when asked)
|
||||
Use Conventional Commits style:
|
||||
|
||||
`<type>(<scope>): <summary>`
|
||||
|
||||
**Allowed types**
|
||||
- feat, fix, docs, refactor, perf, test, build, ci, chore
|
||||
|
||||
**Scope rules**
|
||||
- Use a short, PowerToys-focused scope (one word preferred). Common scopes:
|
||||
- Core: `runner`, `settings-ui`, `common`, `docs`, `build`, `ci`, `installer`, `gpo`, `dsc`
|
||||
- Modules: `fancyzones`, `powerrename`, `awake`, `colorpicker`, `imageresizer`, `keyboardmanager`, `mouseutils`, `peek`, `hosts`, `file-locksmith`, `screen-ruler`, `text-extractor`, `cropandlock`, `paste`, `powerlauncher`
|
||||
- If unclear, pick the closest module or subsystem; omit only if unavoidable
|
||||
|
||||
**Summary rules**
|
||||
- Imperative, present tense (“add”, “update”, “remove”, “fix”)
|
||||
- Keep it <= 72 characters when possible; be specific, avoid “misc changes”
|
||||
|
||||
**Examples**
|
||||
- `feat(fancyzones): add canvas template duplication`
|
||||
- `fix(mouseutils): guard crosshair toggle when dpi info missing`
|
||||
- `docs(runner): document tray icon states`
|
||||
- `build(installer): align wix v5 suffix flag`
|
||||
- `ci(ci): cache pipeline artifacts for x64`
|
||||
@@ -1,24 +0,0 @@
|
||||
---
|
||||
agent: 'agent'
|
||||
description: 'Generate a PowerToys-ready pull request description from the local diff'
|
||||
---
|
||||
|
||||
# Generate PR Summary
|
||||
|
||||
**Goal:** Produce a ready-to-paste PR title and description that follows PowerToys conventions by comparing the current branch against a user-selected target branch.
|
||||
|
||||
**Repo guardrails:**
|
||||
- Treat `.github/pull_request_template.md` as the single source of truth; load it at runtime instead of embedding hardcoded content in this prompt.
|
||||
- Preserve section order from the template but only surface checklist lines that are relevant for the detected changes, filling them with `[x]`/`[ ]` as appropriate.
|
||||
- Cite touched paths with inline backticks, matching the guidance in `.github/copilot-instructions.md`.
|
||||
- Call out test coverage explicitly: list automated tests run (unit/UI) or state why they are not applicable.
|
||||
|
||||
**Workflow:**
|
||||
1. Determine the target branch from user context; default to `main` when no branch is supplied.
|
||||
2. Run `git status --short` once to surface uncommitted files that may influence the summary.
|
||||
3. Run `git diff <target-branch>...HEAD` a single time to review the detailed changes. Only when confidence stays low dig deeper with focused calls such as `git diff <target-branch>...HEAD -- <path>`.
|
||||
4. From the diff, capture impacted areas, key file changes, behavioral risks, migrations, and noteworthy edge cases.
|
||||
5. Confirm validation: list tests executed with results or state why tests were skipped in line with repo guidance.
|
||||
6. Load `.github/pull_request_template.md`, mirror its section order, and populate it with the gathered facts. Include only relevant checklist entries, marking them `[x]/[ ]` and noting any intentional omissions as "N/A".
|
||||
7. Present the filled template inside a fenced ```markdown code block with no extra commentary so it is ready to paste into a PR, clearly flagging any placeholders that still need user input.
|
||||
8. Prepend the PR title above the filled template, applying the Conventional Commit type/scope rules from `.github/prompts/create-commit-title.prompt.md`; pick the dominant component from the diff and keep the title concise and imperative.
|
||||
@@ -1,72 +0,0 @@
|
||||
---
|
||||
agent: 'agent'
|
||||
description: 'Execute the fix for a GitHub issue using the previously generated implementation plan'
|
||||
---
|
||||
|
||||
# Fix GitHub Issue
|
||||
|
||||
## Dependencies
|
||||
Source review prompt (for generating the implementation plan if missing):
|
||||
- .github/prompts/review-issue.prompt.md
|
||||
|
||||
Required plan file (single source of truth):
|
||||
- Generated Files/issueReview/{{issue_number}}/implementation-plan.md
|
||||
|
||||
## Dependency Handling
|
||||
1) If `implementation-plan.md` exists → proceed.
|
||||
2) If missing → run the review prompt:
|
||||
- Invoke: `.github/prompts/review-issue.prompt.md`
|
||||
- Pass: `issue_number={{issue_number}}`
|
||||
- Then re-check for `implementation-plan.md`.
|
||||
3) If still missing → stop and generate:
|
||||
- `Generated Files/issueFix/{{issue_number}}/manual-steps.md` containing:
|
||||
“implementation-plan.md not found; please run .github/prompts/review-issue.prompt.md for #{{issue_number}}.”
|
||||
|
||||
# GOAL
|
||||
For **#{{issue_number}}**:
|
||||
- Use implementation-plan.md as the single authority.
|
||||
- Apply code and test changes directly in the repository.
|
||||
- Produce a PR-ready description.
|
||||
|
||||
# OUTPUT FILES
|
||||
1) Generated Files/issueFix/{{issue_number}}/pr-description.md
|
||||
2) Generated Files/issueFix/{{issue_number}}/manual-steps.md # only if human interaction or external setup is required
|
||||
|
||||
# EXECUTION RULES
|
||||
1) Read implementation-plan.md and execute:
|
||||
- Layers & Files → edit/create as listed
|
||||
- Pattern Choices → follow repository conventions
|
||||
- Fundamentals (perf, security, compatibility, accessibility)
|
||||
- Logging & Exceptions
|
||||
- Telemetry (only if explicitly included in the plan)
|
||||
- Risks & Mitigations
|
||||
- Tests to Add
|
||||
2) Locate affected files via `rg` or `git grep`.
|
||||
3) Add/update tests to enforce the fixed behavior.
|
||||
4) If any ambiguity exists, add:
|
||||
// TODO(Human input needed): <clarification needed>
|
||||
5) Verify locally: build & tests run successfully.
|
||||
|
||||
# pr-description.md should include:
|
||||
- Title: `Fix: <short summary> (#{{issue_number}})`
|
||||
- What changed and why the fix works
|
||||
- Files or modules touched
|
||||
- Risks & mitigations (implemented)
|
||||
- Tests added/updated and how to run them
|
||||
- Telemetry behavior (if applicable)
|
||||
- Validation / reproduction steps
|
||||
- `Closes #{{issue_number}}`
|
||||
|
||||
# manual-steps.md (only if needed)
|
||||
- List required human actions: secrets, config, approvals, missing info, or code comments requiring human decisions.
|
||||
|
||||
# IMPORTANT
|
||||
- Apply code and tests directly; do not produce patch files.
|
||||
- Follow implementation-plan.md as the source of truth.
|
||||
- Insert comments for human review where a decision or input is required.
|
||||
- Use repository conventions and deterministic, minimal changes.
|
||||
|
||||
# FINALIZE
|
||||
- Write pr-description.md
|
||||
- Write manual-steps.md only if needed
|
||||
- Print concise success message or note items requiring human interaction
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"github-artifacts": {
|
||||
"command": "cmd",
|
||||
"args": ["/c", "for /f %i in ('git rev-parse --show-toplevel') do node %i/tools/mcp/github-artifacts/launch.js"],
|
||||
"tools": ["*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Show commit/uncommitted status for issue/* worktrees.
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot '..\..\..\..')
|
||||
Set-Location $repoRoot
|
||||
|
||||
git worktree list | Select-String "issue/" | ForEach-Object {
|
||||
$path = ($_ -split "\s+")[0]
|
||||
$branch = ($_ -split "\s+")[2] -replace "\[|\]",""
|
||||
$ahead = (git -C $path rev-list main..HEAD --count 2>$null)
|
||||
$uncommitted = (git -C $path status --porcelain 2>$null | Measure-Object).Count
|
||||
[pscustomobject]@{
|
||||
Branch = $branch
|
||||
CommitsAhead = $ahead
|
||||
Uncommitted = $uncommitted
|
||||
Path = $path
|
||||
}
|
||||
}
|
||||
644
.github/skills/issue-fix/scripts/IssueReviewLib.ps1
vendored
644
.github/skills/issue-fix/scripts/IssueReviewLib.ps1
vendored
@@ -1,644 +0,0 @@
|
||||
# IssueReviewLib.ps1 - Helpers for issue auto-fix workflow
|
||||
# Part of the PowerToys GitHub Copilot/Claude Code issue review system
|
||||
# This is a trimmed version with only what issue-fix needs
|
||||
|
||||
#region Console Output Helpers
|
||||
function Info { param([string]$Message) Write-Host $Message -ForegroundColor Cyan }
|
||||
function Warn { param([string]$Message) Write-Host $Message -ForegroundColor Yellow }
|
||||
function Err { param([string]$Message) Write-Host $Message -ForegroundColor Red }
|
||||
function Success { param([string]$Message) Write-Host $Message -ForegroundColor Green }
|
||||
#endregion
|
||||
|
||||
#region Repository Helpers
|
||||
function Get-RepoRoot {
|
||||
$root = git rev-parse --show-toplevel 2>$null
|
||||
if (-not $root) { throw 'Not inside a git repository.' }
|
||||
return (Resolve-Path $root).Path
|
||||
}
|
||||
|
||||
function Get-GeneratedFilesPath {
|
||||
param([string]$RepoRoot)
|
||||
return Join-Path $RepoRoot 'Generated Files'
|
||||
}
|
||||
|
||||
function Get-IssueReviewPath {
|
||||
param(
|
||||
[string]$RepoRoot,
|
||||
[int]$IssueNumber
|
||||
)
|
||||
$genFiles = Get-GeneratedFilesPath -RepoRoot $RepoRoot
|
||||
return Join-Path $genFiles "issueReview/$IssueNumber"
|
||||
}
|
||||
|
||||
function Ensure-DirectoryExists {
|
||||
param([string]$Path)
|
||||
if (-not (Test-Path $Path)) {
|
||||
New-Item -ItemType Directory -Path $Path -Force | Out-Null
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region CLI Detection
|
||||
function Get-AvailableCLI {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Detect which AI CLI is available: GitHub Copilot CLI or Claude Code.
|
||||
#>
|
||||
|
||||
# Check for standalone GitHub Copilot CLI
|
||||
$copilotCLI = Get-Command 'copilot' -ErrorAction SilentlyContinue
|
||||
if ($copilotCLI) {
|
||||
return @{ Name = 'GitHub Copilot CLI'; Command = 'copilot'; Type = 'copilot' }
|
||||
}
|
||||
|
||||
# Check for Claude Code CLI
|
||||
$claudeCode = Get-Command 'claude' -ErrorAction SilentlyContinue
|
||||
if ($claudeCode) {
|
||||
return @{ Name = 'Claude Code CLI'; Command = 'claude'; Type = 'claude' }
|
||||
}
|
||||
|
||||
# Check for GitHub Copilot CLI via gh extension
|
||||
$ghCopilot = Get-Command 'gh' -ErrorAction SilentlyContinue
|
||||
if ($ghCopilot) {
|
||||
$copilotCheck = gh extension list 2>&1 | Select-String -Pattern 'copilot'
|
||||
if ($copilotCheck) {
|
||||
return @{ Name = 'GitHub Copilot CLI (gh extension)'; Command = 'gh'; Type = 'gh-copilot' }
|
||||
}
|
||||
}
|
||||
|
||||
# Check for VS Code CLI
|
||||
$code = Get-Command 'code' -ErrorAction SilentlyContinue
|
||||
if ($code) {
|
||||
return @{ Name = 'VS Code (Copilot Chat)'; Command = 'code'; Type = 'vscode' }
|
||||
}
|
||||
|
||||
return $null
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Issue Review Results Helpers
|
||||
function Get-IssueReviewResult {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Check if an issue has been reviewed and get its results.
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[int]$IssueNumber,
|
||||
[Parameter(Mandatory)]
|
||||
[string]$RepoRoot
|
||||
)
|
||||
|
||||
$reviewPath = Get-IssueReviewPath -RepoRoot $RepoRoot -IssueNumber $IssueNumber
|
||||
|
||||
$result = @{
|
||||
IssueNumber = $IssueNumber
|
||||
Path = $reviewPath
|
||||
HasOverview = $false
|
||||
HasImplementationPlan = $false
|
||||
OverviewPath = $null
|
||||
ImplementationPlanPath = $null
|
||||
}
|
||||
|
||||
$overviewPath = Join-Path $reviewPath 'overview.md'
|
||||
$implPlanPath = Join-Path $reviewPath 'implementation-plan.md'
|
||||
|
||||
if (Test-Path $overviewPath) {
|
||||
$result.HasOverview = $true
|
||||
$result.OverviewPath = $overviewPath
|
||||
}
|
||||
|
||||
if (Test-Path $implPlanPath) {
|
||||
$result.HasImplementationPlan = $true
|
||||
$result.ImplementationPlanPath = $implPlanPath
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
|
||||
function Get-HighConfidenceIssues {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Find issues with high confidence for auto-fix based on review results.
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$RepoRoot,
|
||||
[int]$MinFeasibilityScore = 70,
|
||||
[int]$MinClarityScore = 60,
|
||||
[int]$MaxEffortDays = 2,
|
||||
[int[]]$FilterIssueNumbers = @()
|
||||
)
|
||||
|
||||
$genFiles = Get-GeneratedFilesPath -RepoRoot $RepoRoot
|
||||
$reviewDir = Join-Path $genFiles 'issueReview'
|
||||
|
||||
if (-not (Test-Path $reviewDir)) {
|
||||
return @()
|
||||
}
|
||||
|
||||
$highConfidence = @()
|
||||
|
||||
Get-ChildItem -Path $reviewDir -Directory | ForEach-Object {
|
||||
$issueNum = [int]$_.Name
|
||||
|
||||
if ($FilterIssueNumbers.Count -gt 0 -and $issueNum -notin $FilterIssueNumbers) {
|
||||
return
|
||||
}
|
||||
|
||||
$overviewPath = Join-Path $_.FullName 'overview.md'
|
||||
$implPlanPath = Join-Path $_.FullName 'implementation-plan.md'
|
||||
|
||||
if (-not (Test-Path $overviewPath) -or -not (Test-Path $implPlanPath)) {
|
||||
return
|
||||
}
|
||||
|
||||
$overview = Get-Content $overviewPath -Raw
|
||||
|
||||
$feasibility = 0
|
||||
$clarity = 0
|
||||
$effortDays = 999
|
||||
|
||||
if ($overview -match 'Technical Feasibility[^\d]*(\d+)/100') {
|
||||
$feasibility = [int]$Matches[1]
|
||||
}
|
||||
if ($overview -match 'Requirement Clarity[^\d]*(\d+)/100') {
|
||||
$clarity = [int]$Matches[1]
|
||||
}
|
||||
if ($overview -match 'Effort Estimate[^|]*\|\s*[\d.]+(?:-(\d+))?\s*days?') {
|
||||
if ($Matches[1]) {
|
||||
$effortDays = [int]$Matches[1]
|
||||
} elseif ($overview -match 'Effort Estimate[^|]*\|\s*(\d+)\s*days?') {
|
||||
$effortDays = [int]$Matches[1]
|
||||
}
|
||||
}
|
||||
if ($overview -match 'Effort Estimate[^|]*\|[^|]*\|\s*(XS|S)\b') {
|
||||
if ($Matches[1] -eq 'XS') { $effortDays = 1 } else { $effortDays = 2 }
|
||||
} elseif ($overview -match 'Effort Estimate[^|]*\|[^|]*\(XS\)') {
|
||||
$effortDays = 1
|
||||
} elseif ($overview -match 'Effort Estimate[^|]*\|[^|]*\(S\)') {
|
||||
$effortDays = 2
|
||||
}
|
||||
|
||||
if ($feasibility -ge $MinFeasibilityScore -and
|
||||
$clarity -ge $MinClarityScore -and
|
||||
$effortDays -le $MaxEffortDays) {
|
||||
|
||||
$highConfidence += @{
|
||||
IssueNumber = $issueNum
|
||||
FeasibilityScore = $feasibility
|
||||
ClarityScore = $clarity
|
||||
EffortDays = $effortDays
|
||||
OverviewPath = $overviewPath
|
||||
ImplementationPlanPath = $implPlanPath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $highConfidence | Sort-Object -Property FeasibilityScore -Descending
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Release & PR Status Helpers
|
||||
function Get-PRReleaseStatus {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Check if a PR has been merged and released.
|
||||
.DESCRIPTION
|
||||
Queries GitHub to determine:
|
||||
1. If the PR is merged
|
||||
2. What release (if any) contains the merge commit
|
||||
.OUTPUTS
|
||||
@{
|
||||
PRNumber = <int>
|
||||
IsMerged = $true | $false
|
||||
MergeCommit = <commit sha or $null>
|
||||
ReleasedIn = <version string or $null> # e.g., "v0.90.0"
|
||||
IsReleased = $true | $false
|
||||
}
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[int]$PRNumber,
|
||||
[string]$Repo = 'microsoft/PowerToys'
|
||||
)
|
||||
|
||||
$result = @{
|
||||
PRNumber = $PRNumber
|
||||
IsMerged = $false
|
||||
MergeCommit = $null
|
||||
ReleasedIn = $null
|
||||
IsReleased = $false
|
||||
}
|
||||
|
||||
try {
|
||||
# Get PR details from GitHub
|
||||
$prJson = gh pr view $PRNumber --repo $Repo --json state,mergeCommit,mergedAt 2>$null
|
||||
if (-not $prJson) {
|
||||
return $result
|
||||
}
|
||||
|
||||
$pr = $prJson | ConvertFrom-Json
|
||||
|
||||
if ($pr.state -eq 'MERGED' -and $pr.mergeCommit) {
|
||||
$result.IsMerged = $true
|
||||
$result.MergeCommit = $pr.mergeCommit.oid
|
||||
|
||||
# Check which release tags contain this commit
|
||||
# Use git tag --contains to find tags that include the merge commit
|
||||
$tags = git tag --contains $result.MergeCommit 2>$null
|
||||
|
||||
if ($tags) {
|
||||
# Filter to release tags (v0.XX.X pattern) and get the earliest one
|
||||
$releaseTags = $tags | Where-Object { $_ -match '^v\d+\.\d+\.\d+$' } | Sort-Object
|
||||
if ($releaseTags) {
|
||||
$result.ReleasedIn = $releaseTags | Select-Object -First 1
|
||||
$result.IsReleased = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
# Silently fail - will return default "not merged" status
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
|
||||
function Get-LatestRelease {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Get the latest release version of PowerToys.
|
||||
#>
|
||||
param(
|
||||
[string]$Repo = 'microsoft/PowerToys'
|
||||
)
|
||||
|
||||
try {
|
||||
$releaseJson = gh release view --repo $Repo --json tagName 2>$null
|
||||
if ($releaseJson) {
|
||||
$release = $releaseJson | ConvertFrom-Json
|
||||
return $release.tagName
|
||||
}
|
||||
}
|
||||
catch {
|
||||
# Fallback: try to get from git tags
|
||||
$latestTag = git describe --tags --abbrev=0 2>$null
|
||||
if ($latestTag) {
|
||||
return $latestTag
|
||||
}
|
||||
}
|
||||
return $null
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Implementation Plan Analysis
|
||||
function Get-ImplementationPlanStatus {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Parse implementation-plan.md to determine the recommended action.
|
||||
.DESCRIPTION
|
||||
Reads the implementation plan and extracts the status/recommendation.
|
||||
For "already resolved" issues, also checks if the fix has been released.
|
||||
Returns an object indicating what action should be taken.
|
||||
.OUTPUTS
|
||||
@{
|
||||
Status = 'AlreadyResolved' | 'FixedButUnreleased' | 'NeedsClarification' | 'Duplicate' | 'WontFix' | 'ReadyToImplement' | 'Unknown'
|
||||
Action = 'CloseIssue' | 'AddComment' | 'LinkDuplicate' | 'ImplementFix' | 'Skip'
|
||||
Reason = <string explaining why>
|
||||
RelatedPR = <PR number if already fixed>
|
||||
ReleasedIn = <version if released, e.g., "v0.90.0">
|
||||
DuplicateOf = <issue number if duplicate>
|
||||
CommentText = <suggested comment if applicable>
|
||||
}
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$ImplementationPlanPath,
|
||||
[switch]$SkipReleaseCheck
|
||||
)
|
||||
|
||||
$result = @{
|
||||
Status = 'Unknown'
|
||||
Action = 'Skip'
|
||||
Reason = 'Could not determine status from implementation plan'
|
||||
RelatedPR = $null
|
||||
ReleasedIn = $null
|
||||
DuplicateOf = $null
|
||||
CommentText = $null
|
||||
}
|
||||
|
||||
if (-not (Test-Path $ImplementationPlanPath)) {
|
||||
$result.Reason = 'Implementation plan file not found'
|
||||
return $result
|
||||
}
|
||||
|
||||
$content = Get-Content $ImplementationPlanPath -Raw
|
||||
|
||||
# Check for ALREADY RESOLVED status
|
||||
if ($content -match '(?i)STATUS:\s*ALREADY\s+RESOLVED' -or
|
||||
$content -match '(?i)⚠️\s*STATUS:\s*ALREADY\s+RESOLVED' -or
|
||||
$content -match '(?i)This issue has been fixed by' -or
|
||||
$content -match '(?i)No implementation work is needed') {
|
||||
|
||||
# Try to extract the PR number
|
||||
$prNumber = $null
|
||||
if ($content -match '\[PR #(\d+)\]' -or $content -match 'PR #(\d+)' -or $content -match '/pull/(\d+)') {
|
||||
$prNumber = [int]$Matches[1]
|
||||
$result.RelatedPR = $prNumber
|
||||
}
|
||||
|
||||
# Check if the fix has been released
|
||||
if ($prNumber -and -not $SkipReleaseCheck) {
|
||||
$prStatus = Get-PRReleaseStatus -PRNumber $prNumber
|
||||
|
||||
if ($prStatus.IsReleased) {
|
||||
# Fix is released - safe to close
|
||||
$result.Status = 'AlreadyResolved'
|
||||
$result.Action = 'CloseIssue'
|
||||
$result.ReleasedIn = $prStatus.ReleasedIn
|
||||
$result.Reason = "Issue fixed by PR #$prNumber, released in $($prStatus.ReleasedIn)"
|
||||
$result.CommentText = @"
|
||||
This issue has been fixed by PR #$prNumber and is available in **$($prStatus.ReleasedIn)**.
|
||||
|
||||
Please update to the latest version. If you're still experiencing this issue after updating, please reopen with additional details.
|
||||
"@
|
||||
}
|
||||
elseif ($prStatus.IsMerged) {
|
||||
# PR merged but not yet released - add comment but don't close
|
||||
$result.Status = 'FixedButUnreleased'
|
||||
$result.Action = 'AddComment'
|
||||
$result.Reason = "Issue fixed by PR #$prNumber, but not yet released"
|
||||
$result.CommentText = @"
|
||||
This issue has been fixed by PR #$prNumber, which has been merged but **not yet released**.
|
||||
|
||||
The fix will be available in the next PowerToys release. You can:
|
||||
- Wait for the next official release
|
||||
- Build from source to get the fix immediately
|
||||
|
||||
We'll close this issue once the fix is released.
|
||||
"@
|
||||
}
|
||||
else {
|
||||
# PR exists but not merged - treat as ready to implement (PR might have been reverted)
|
||||
$result.Status = 'ReadyToImplement'
|
||||
$result.Action = 'ImplementFix'
|
||||
$result.Reason = "PR #$prNumber exists but is not merged - may need reimplementation"
|
||||
}
|
||||
}
|
||||
elseif ($prNumber) {
|
||||
# Skip release check requested or no PR number - assume it's resolved
|
||||
$result.Status = 'AlreadyResolved'
|
||||
$result.Action = 'CloseIssue'
|
||||
$result.Reason = 'Issue has already been fixed'
|
||||
$result.CommentText = "This issue has been fixed by PR #$prNumber. Closing as resolved."
|
||||
}
|
||||
else {
|
||||
# No PR number found - just mark as resolved with generic message
|
||||
$result.Status = 'AlreadyResolved'
|
||||
$result.Action = 'CloseIssue'
|
||||
$result.Reason = 'Issue appears to have been resolved'
|
||||
$result.CommentText = "Based on analysis, this issue appears to have already been resolved. Please verify and reopen if the issue persists."
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
|
||||
# Check for DUPLICATE status
|
||||
if ($content -match '(?i)STATUS:\s*DUPLICATE' -or
|
||||
$content -match '(?i)This is a duplicate of' -or
|
||||
$content -match '(?i)duplicate of #(\d+)') {
|
||||
|
||||
$result.Status = 'Duplicate'
|
||||
$result.Action = 'LinkDuplicate'
|
||||
$result.Reason = 'Issue is a duplicate'
|
||||
|
||||
# Try to extract the duplicate issue number
|
||||
if ($content -match 'duplicate of #(\d+)' -or $content -match '#(\d+)') {
|
||||
$result.DuplicateOf = [int]$Matches[1]
|
||||
$result.CommentText = "This appears to be a duplicate of #$($result.DuplicateOf)."
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
|
||||
# Check for NEEDS CLARIFICATION status
|
||||
if ($content -match '(?i)STATUS:\s*NEEDS?\s+CLARIFICATION' -or
|
||||
$content -match '(?i)STATUS:\s*NEEDS?\s+MORE\s+INFO' -or
|
||||
$content -match '(?i)cannot proceed without' -or
|
||||
$content -match '(?i)need(?:s)? more information') {
|
||||
|
||||
$result.Status = 'NeedsClarification'
|
||||
$result.Action = 'AddComment'
|
||||
$result.Reason = 'Issue needs more information from reporter'
|
||||
|
||||
# Try to extract what information is needed
|
||||
if ($content -match '(?i)(?:need(?:s)?|require(?:s)?|missing)[:\s]+([^\n]+)') {
|
||||
$result.CommentText = "Additional information is needed to proceed with this issue: $($Matches[1].Trim())"
|
||||
} else {
|
||||
$result.CommentText = "Could you please provide more details about this issue? Specifically, steps to reproduce and expected vs actual behavior would help."
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
|
||||
# Check for WONT FIX / NOT FEASIBLE status
|
||||
if ($content -match '(?i)STATUS:\s*(?:WONT?\s+FIX|NOT\s+FEASIBLE|REJECTED)' -or
|
||||
$content -match '(?i)(?:not|cannot be) (?:feasible|implemented)' -or
|
||||
$content -match '(?i)recommend(?:ed)?\s+(?:to\s+)?close') {
|
||||
|
||||
$result.Status = 'WontFix'
|
||||
$result.Action = 'AddComment'
|
||||
$result.Reason = 'Issue is not feasible or recommended to close'
|
||||
|
||||
# Try to extract the reason
|
||||
if ($content -match '(?i)(?:because|reason|due to)[:\s]+([^\n]+)') {
|
||||
$result.CommentText = "After analysis, this issue cannot be implemented: $($Matches[1].Trim())"
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
|
||||
# Check for external dependency / blocked status
|
||||
if ($content -match '(?i)STATUS:\s*BLOCKED' -or
|
||||
$content -match '(?i)blocked by' -or
|
||||
$content -match '(?i)depends on external' -or
|
||||
$content -match '(?i)waiting for upstream') {
|
||||
|
||||
$result.Status = 'Blocked'
|
||||
$result.Action = 'AddComment'
|
||||
$result.Reason = 'Issue is blocked by external dependency'
|
||||
|
||||
return $result
|
||||
}
|
||||
|
||||
# Check for READY TO IMPLEMENT (positive signals)
|
||||
if ($content -match '(?i)## \d+\)\s*Task Breakdown' -or
|
||||
$content -match '(?i)implementation steps' -or
|
||||
$content -match '(?i)## Layers & Files' -or
|
||||
($content -match '(?i)Feasibility' -and $content -notmatch '(?i)not\s+feasible')) {
|
||||
|
||||
$result.Status = 'ReadyToImplement'
|
||||
$result.Action = 'ImplementFix'
|
||||
$result.Reason = 'Implementation plan is ready'
|
||||
|
||||
return $result
|
||||
}
|
||||
|
||||
# Default: if we have a detailed plan, assume it's ready
|
||||
if ($content.Length -gt 500 -and $content -match '(?i)##') {
|
||||
$result.Status = 'ReadyToImplement'
|
||||
$result.Action = 'ImplementFix'
|
||||
$result.Reason = 'Implementation plan appears complete'
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
|
||||
function Invoke-ImplementationPlanAction {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Execute the recommended action from the implementation plan analysis.
|
||||
.DESCRIPTION
|
||||
Based on the status from Get-ImplementationPlanStatus, takes appropriate action:
|
||||
- CloseIssue: Closes the issue with a comment
|
||||
- AddComment: Adds a comment to the issue
|
||||
- LinkDuplicate: Marks as duplicate
|
||||
- ImplementFix: Returns $true to indicate code fix should proceed
|
||||
- Skip: Returns $false
|
||||
.OUTPUTS
|
||||
@{
|
||||
ActionTaken = <string describing what was done>
|
||||
ShouldProceedWithFix = $true | $false
|
||||
Success = $true | $false
|
||||
}
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[int]$IssueNumber,
|
||||
[Parameter(Mandatory)]
|
||||
[hashtable]$PlanStatus,
|
||||
[switch]$DryRun
|
||||
)
|
||||
|
||||
$result = @{
|
||||
ActionTaken = 'None'
|
||||
ShouldProceedWithFix = $false
|
||||
Success = $true
|
||||
}
|
||||
|
||||
switch ($PlanStatus.Action) {
|
||||
'ImplementFix' {
|
||||
$result.ActionTaken = 'Proceeding with code fix'
|
||||
$result.ShouldProceedWithFix = $true
|
||||
Info "[Issue #$IssueNumber] Status: $($PlanStatus.Status) - $($PlanStatus.Reason)"
|
||||
}
|
||||
|
||||
'CloseIssue' {
|
||||
$result.ActionTaken = "Closing issue: $($PlanStatus.Reason)"
|
||||
Info "[Issue #$IssueNumber] $($PlanStatus.Status): $($PlanStatus.Reason)"
|
||||
|
||||
if (-not $DryRun) {
|
||||
$comment = $PlanStatus.CommentText
|
||||
if (-not $comment) {
|
||||
$comment = "Closing based on automated analysis: $($PlanStatus.Reason)"
|
||||
}
|
||||
|
||||
try {
|
||||
# Check if issue is already closed
|
||||
$issueState = gh issue view $IssueNumber --json state 2>$null | ConvertFrom-Json
|
||||
if ($issueState.state -eq 'CLOSED') {
|
||||
Info "[Issue #$IssueNumber] Already closed, skipping"
|
||||
$result.ActionTaken = "Already closed"
|
||||
return $result
|
||||
}
|
||||
|
||||
# Close the issue with comment (single operation to avoid duplicates)
|
||||
gh issue close $IssueNumber --reason "completed" --comment $comment 2>&1 | Out-Null
|
||||
|
||||
Success "[Issue #$IssueNumber] ✓ Closed with comment"
|
||||
}
|
||||
catch {
|
||||
Err "[Issue #$IssueNumber] Failed to close: $($_.Exception.Message)"
|
||||
$result.Success = $false
|
||||
}
|
||||
} else {
|
||||
Info "[Issue #$IssueNumber] (DryRun) Would close with: $($PlanStatus.CommentText)"
|
||||
}
|
||||
}
|
||||
|
||||
'AddComment' {
|
||||
$result.ActionTaken = "Adding comment: $($PlanStatus.Reason)"
|
||||
Info "[Issue #$IssueNumber] $($PlanStatus.Status): $($PlanStatus.Reason)"
|
||||
|
||||
if (-not $DryRun -and $PlanStatus.CommentText) {
|
||||
try {
|
||||
gh issue comment $IssueNumber --body $PlanStatus.CommentText 2>&1 | Out-Null
|
||||
Success "[Issue #$IssueNumber] ✓ Comment added"
|
||||
}
|
||||
catch {
|
||||
Err "[Issue #$IssueNumber] Failed to add comment: $($_.Exception.Message)"
|
||||
$result.Success = $false
|
||||
}
|
||||
} else {
|
||||
Info "[Issue #$IssueNumber] (DryRun) Would comment: $($PlanStatus.CommentText)"
|
||||
}
|
||||
}
|
||||
|
||||
'LinkDuplicate' {
|
||||
$result.ActionTaken = "Marking as duplicate of #$($PlanStatus.DuplicateOf)"
|
||||
Info "[Issue #$IssueNumber] Duplicate of #$($PlanStatus.DuplicateOf)"
|
||||
|
||||
if (-not $DryRun -and $PlanStatus.DuplicateOf) {
|
||||
try {
|
||||
gh issue close $IssueNumber --reason "not_planned" --comment "Closing as duplicate of #$($PlanStatus.DuplicateOf)" 2>&1 | Out-Null
|
||||
Success "[Issue #$IssueNumber] ✓ Closed as duplicate"
|
||||
}
|
||||
catch {
|
||||
Err "[Issue #$IssueNumber] Failed to close as duplicate: $($_.Exception.Message)"
|
||||
$result.Success = $false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
'Skip' {
|
||||
$result.ActionTaken = "Skipped: $($PlanStatus.Reason)"
|
||||
Warn "[Issue #$IssueNumber] Skipping: $($PlanStatus.Reason)"
|
||||
}
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Worktree Integration
|
||||
function Copy-IssueReviewToWorktree {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Copy the Generated Files for an issue to a worktree.
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[int]$IssueNumber,
|
||||
[Parameter(Mandatory)]
|
||||
[string]$SourceRepoRoot,
|
||||
[Parameter(Mandatory)]
|
||||
[string]$WorktreePath
|
||||
)
|
||||
|
||||
$sourceReviewPath = Get-IssueReviewPath -RepoRoot $SourceRepoRoot -IssueNumber $IssueNumber
|
||||
$destReviewPath = Get-IssueReviewPath -RepoRoot $WorktreePath -IssueNumber $IssueNumber
|
||||
|
||||
if (-not (Test-Path $sourceReviewPath)) {
|
||||
throw "Issue review files not found at: $sourceReviewPath"
|
||||
}
|
||||
|
||||
Ensure-DirectoryExists -Path $destReviewPath
|
||||
|
||||
Copy-Item -Path "$sourceReviewPath\*" -Destination $destReviewPath -Recurse -Force
|
||||
|
||||
Info "Copied issue review files to: $destReviewPath"
|
||||
|
||||
return $destReviewPath
|
||||
}
|
||||
#endregion
|
||||
@@ -1,581 +0,0 @@
|
||||
<#!
|
||||
.SYNOPSIS
|
||||
Auto-fix high-confidence issues using worktrees and AI CLI.
|
||||
|
||||
.DESCRIPTION
|
||||
Finds issues with high confidence scores from the review results, creates worktrees
|
||||
for each, copies the Generated Files, and kicks off the FixIssue agent to implement fixes.
|
||||
|
||||
.PARAMETER IssueNumber
|
||||
Specific issue number to fix. If not specified, finds high-confidence issues automatically.
|
||||
|
||||
.PARAMETER MinFeasibilityScore
|
||||
Minimum Technical Feasibility score (0-100). Default: 70.
|
||||
|
||||
.PARAMETER MinClarityScore
|
||||
Minimum Requirement Clarity score (0-100). Default: 60.
|
||||
|
||||
.PARAMETER MaxEffortDays
|
||||
Maximum effort estimate in days. Default: 2 (Small fixes).
|
||||
|
||||
.PARAMETER MaxConcurrent
|
||||
Maximum parallel fix jobs. Default: 5 (worktrees are resource-intensive).
|
||||
|
||||
.PARAMETER CLIType
|
||||
AI CLI to use: claude, gh-copilot, or vscode. Auto-detected if not specified.
|
||||
|
||||
.PARAMETER Model
|
||||
Copilot CLI model to use (e.g., gpt-5.2-codex).
|
||||
|
||||
.PARAMETER DryRun
|
||||
List issues without starting fixes.
|
||||
|
||||
.PARAMETER SkipWorktree
|
||||
Fix in the current repository instead of creating worktrees (useful for single issue).
|
||||
|
||||
.PARAMETER VSCodeProfile
|
||||
VS Code profile to use when opening worktrees. Default: Default.
|
||||
|
||||
.PARAMETER AutoCommit
|
||||
Automatically commit changes after successful fix.
|
||||
|
||||
.PARAMETER CreatePR
|
||||
Automatically create a pull request after successful fix.
|
||||
|
||||
.EXAMPLE
|
||||
# Fix a specific issue
|
||||
./Start-IssueAutoFix.ps1 -IssueNumber 12345
|
||||
|
||||
.EXAMPLE
|
||||
# Find and fix all high-confidence issues (dry run)
|
||||
./Start-IssueAutoFix.ps1 -DryRun
|
||||
|
||||
.EXAMPLE
|
||||
# Fix issues with very high confidence
|
||||
./Start-IssueAutoFix.ps1 -MinFeasibilityScore 80 -MinClarityScore 70 -MaxEffortDays 1
|
||||
|
||||
.EXAMPLE
|
||||
# Fix single issue in current repo (no worktree)
|
||||
./Start-IssueAutoFix.ps1 -IssueNumber 12345 -SkipWorktree
|
||||
|
||||
.NOTES
|
||||
Prerequisites:
|
||||
- Run Start-BulkIssueReview.ps1 first to generate review files
|
||||
- GitHub CLI (gh) authenticated
|
||||
- Claude Code CLI or VS Code with Copilot
|
||||
|
||||
Results:
|
||||
- Worktrees created at ../<RepoName>-<hash>/
|
||||
- Generated Files copied to each worktree
|
||||
- Fix agent invoked in each worktree
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[int]$IssueNumber,
|
||||
|
||||
[int]$MinFeasibilityScore = 70,
|
||||
|
||||
[int]$MinClarityScore = 60,
|
||||
|
||||
[int]$MaxEffortDays = 2,
|
||||
|
||||
[int]$MaxConcurrent = 5,
|
||||
|
||||
[ValidateSet('claude', 'copilot', 'gh-copilot', 'vscode', 'auto')]
|
||||
[string]$CLIType = 'auto',
|
||||
|
||||
[string]$Model,
|
||||
|
||||
[switch]$DryRun,
|
||||
|
||||
[switch]$SkipWorktree,
|
||||
|
||||
[Alias('Profile')]
|
||||
[string]$VSCodeProfile = 'Default',
|
||||
|
||||
[switch]$AutoCommit,
|
||||
|
||||
[switch]$CreatePR,
|
||||
|
||||
[switch]$Force,
|
||||
|
||||
[switch]$Help
|
||||
)
|
||||
|
||||
# Load libraries
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
. "$scriptDir/IssueReviewLib.ps1"
|
||||
|
||||
# Resolve config directory name (.github or .claude) from script location
|
||||
$_cfgDir = if ($PSScriptRoot -match '[\\/](\.github|\.claude)[\\/]') { $Matches[1] } else { '.github' }
|
||||
|
||||
# Load worktree library from tools/build
|
||||
$repoRoot = Get-RepoRoot
|
||||
$worktreeLib = Join-Path $repoRoot 'tools/build/WorktreeLib.ps1'
|
||||
if (Test-Path $worktreeLib) {
|
||||
. $worktreeLib
|
||||
}
|
||||
|
||||
# Show help
|
||||
if ($Help) {
|
||||
Get-Help $MyInvocation.MyCommand.Path -Full
|
||||
return
|
||||
}
|
||||
|
||||
function Start-IssueFixInWorktree {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Analyze implementation plan and either take action or create worktree for fix.
|
||||
.DESCRIPTION
|
||||
First analyzes the implementation plan to determine if:
|
||||
- Issue is already resolved (close it)
|
||||
- Issue needs clarification (add comment)
|
||||
- Issue is a duplicate (close as duplicate)
|
||||
- Issue is ready to implement (create worktree and fix)
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[int]$IssueNumber,
|
||||
[Parameter(Mandatory)]
|
||||
[string]$SourceRepoRoot,
|
||||
[string]$CLIType = 'claude',
|
||||
[string]$Model,
|
||||
[string]$VSCodeProfile = 'Default',
|
||||
[switch]$SkipWorktree,
|
||||
[switch]$DryRun
|
||||
)
|
||||
|
||||
$issueReviewPath = Get-IssueReviewPath -RepoRoot $SourceRepoRoot -IssueNumber $IssueNumber
|
||||
$overviewPath = Join-Path $issueReviewPath 'overview.md'
|
||||
$implPlanPath = Join-Path $issueReviewPath 'implementation-plan.md'
|
||||
|
||||
# Verify review files exist
|
||||
if (-not (Test-Path $overviewPath)) {
|
||||
throw "No overview.md found for issue #$IssueNumber. Run Start-BulkIssueReview.ps1 first."
|
||||
}
|
||||
if (-not (Test-Path $implPlanPath)) {
|
||||
throw "No implementation-plan.md found for issue #$IssueNumber. Run Start-BulkIssueReview.ps1 first."
|
||||
}
|
||||
|
||||
# =====================================
|
||||
# STEP 1: Analyze the implementation plan
|
||||
# =====================================
|
||||
Info "Analyzing implementation plan for issue #$IssueNumber..."
|
||||
$planStatus = Get-ImplementationPlanStatus -ImplementationPlanPath $implPlanPath
|
||||
|
||||
# =====================================
|
||||
# STEP 2: Execute the recommended action
|
||||
# =====================================
|
||||
$actionResult = Invoke-ImplementationPlanAction -IssueNumber $IssueNumber -PlanStatus $planStatus -DryRun:$DryRun
|
||||
|
||||
# If we shouldn't proceed with fix, return early
|
||||
if (-not $actionResult.ShouldProceedWithFix) {
|
||||
return @{
|
||||
IssueNumber = $IssueNumber
|
||||
WorktreePath = $null
|
||||
Success = $actionResult.Success
|
||||
ActionTaken = $actionResult.ActionTaken
|
||||
SkippedCodeFix = $true
|
||||
}
|
||||
}
|
||||
|
||||
# =====================================
|
||||
# STEP 3: Proceed with code fix
|
||||
# =====================================
|
||||
|
||||
$workingDir = $SourceRepoRoot
|
||||
|
||||
if (-not $SkipWorktree) {
|
||||
# Use the simplified New-WorktreeFromIssue.cmd which only needs issue number
|
||||
$worktreeCmd = Join-Path $SourceRepoRoot 'tools/build/New-WorktreeFromIssue.cmd'
|
||||
|
||||
Info "Creating worktree for issue #$IssueNumber..."
|
||||
|
||||
# Call the cmd script with issue number and -NoVSCode for automation
|
||||
& cmd /c $worktreeCmd $IssueNumber -NoVSCode
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Failed to create worktree for issue #$IssueNumber"
|
||||
}
|
||||
|
||||
# Find the created worktree
|
||||
$entries = Get-WorktreeEntries
|
||||
$worktreeEntry = $entries | Where-Object { $_.Branch -like "issue/$IssueNumber*" } | Select-Object -First 1
|
||||
|
||||
if (-not $worktreeEntry) {
|
||||
throw "Failed to find worktree for issue #$IssueNumber"
|
||||
}
|
||||
|
||||
$workingDir = $worktreeEntry.Path
|
||||
Info "Worktree created at: $workingDir"
|
||||
|
||||
# Copy Generated Files to worktree
|
||||
Info "Copying review files to worktree..."
|
||||
$destReviewPath = Copy-IssueReviewToWorktree -IssueNumber $IssueNumber -SourceRepoRoot $SourceRepoRoot -WorktreePath $workingDir
|
||||
Info "Review files copied to: $destReviewPath"
|
||||
|
||||
# Copy config dirs to worktree (agents, skills, instructions, prompts, top-level md)
|
||||
# These aren't on the issue branch so the CLI can't find them without this.
|
||||
$sourceCfg = Join-Path $SourceRepoRoot $_cfgDir
|
||||
$destCfg = Join-Path $workingDir $_cfgDir
|
||||
if (Test-Path $sourceCfg) {
|
||||
if (-not (Test-Path $destCfg)) {
|
||||
New-Item -ItemType Directory -Path $destCfg -Force | Out-Null
|
||||
}
|
||||
foreach ($sub in @('agents', 'skills', 'instructions', 'prompts')) {
|
||||
$src = Join-Path $sourceCfg $sub
|
||||
$dst = Join-Path $destCfg $sub
|
||||
if ((Test-Path $src) -and -not (Test-Path $dst)) {
|
||||
Copy-Item -Path $src -Destination $dst -Recurse -Force
|
||||
Info "Copied $_cfgDir/$sub to worktree"
|
||||
}
|
||||
}
|
||||
foreach ($mdFile in @('copilot-instructions.md', 'CLAUDE.md')) {
|
||||
$src = Join-Path $sourceCfg $mdFile
|
||||
$dst = Join-Path $destCfg $mdFile
|
||||
if ((Test-Path $src) -and -not (Test-Path $dst)) {
|
||||
Copy-Item -Path $src -Destination $dst -Force
|
||||
Info "Copied $_cfgDir/$mdFile to worktree"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Build the prompt for the fix agent
|
||||
$prompt = @"
|
||||
You are the FixIssue agent. Fix GitHub issue #$IssueNumber.
|
||||
|
||||
The implementation plan is at: Generated Files/issueReview/$IssueNumber/implementation-plan.md
|
||||
The overview is at: Generated Files/issueReview/$IssueNumber/overview.md
|
||||
|
||||
Follow the implementation plan exactly. Build and verify after each change.
|
||||
"@
|
||||
|
||||
# Start the fix agent
|
||||
Info "Starting fix agent for issue #$IssueNumber in $workingDir..."
|
||||
|
||||
# MCP config for github-artifacts tools (relative to repo root)
|
||||
$mcpConfig = "@$_cfgDir/skills/issue-fix/references/mcp-config.json"
|
||||
|
||||
switch ($CLIType) {
|
||||
'copilot' {
|
||||
# GitHub Copilot CLI (standalone copilot command)
|
||||
# -p: Non-interactive prompt mode (exits after completion)
|
||||
# --yolo: Enable all permissions for automated execution
|
||||
# -s: Silent mode - output only agent response
|
||||
# --additional-mcp-config: Load github-artifacts MCP for image/attachment analysis
|
||||
$copilotArgs = @(
|
||||
'--additional-mcp-config', $mcpConfig,
|
||||
'-p', $prompt,
|
||||
'--yolo',
|
||||
'-s',
|
||||
'--agent', 'FixIssue'
|
||||
)
|
||||
if ($Model) {
|
||||
$copilotArgs += @('--model', $Model)
|
||||
}
|
||||
Info "Running: copilot $($copilotArgs -join ' ')"
|
||||
Push-Location $workingDir
|
||||
try {
|
||||
& copilot @copilotArgs
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Warn "Copilot exited with code $LASTEXITCODE"
|
||||
}
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
}
|
||||
'claude' {
|
||||
$claudeArgs = @(
|
||||
'--print',
|
||||
'--dangerously-skip-permissions',
|
||||
'--agent', 'FixIssue',
|
||||
'--prompt', $prompt
|
||||
)
|
||||
Start-Process -FilePath 'claude' -ArgumentList $claudeArgs -WorkingDirectory $workingDir -Wait -NoNewWindow
|
||||
}
|
||||
'gh-copilot' {
|
||||
# Use GitHub Copilot CLI via gh extension
|
||||
# gh copilot suggest requires interactive mode, so we open VS Code with the prompt
|
||||
Info "GitHub Copilot CLI detected. Opening VS Code with prompt..."
|
||||
|
||||
# Create a prompt file in the worktree for easy access
|
||||
$promptFile = Join-Path $workingDir "Generated Files/issueReview/$IssueNumber/fix-prompt.md"
|
||||
$promptContent = @"
|
||||
# Fix Issue #$IssueNumber
|
||||
|
||||
## Instructions
|
||||
|
||||
$prompt
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. Read the implementation plan: ``Generated Files/issueReview/$IssueNumber/implementation-plan.md``
|
||||
2. Read the overview: ``Generated Files/issueReview/$IssueNumber/overview.md``
|
||||
3. Follow the plan step by step
|
||||
4. Build and test after each change
|
||||
"@
|
||||
Set-Content -Path $promptFile -Value $promptContent -Force
|
||||
|
||||
# Open VS Code with the worktree
|
||||
code --new-window $workingDir --profile $VSCodeProfile
|
||||
Info "VS Code opened at $workingDir"
|
||||
Info "Prompt file created at: $promptFile"
|
||||
Info "Use GitHub Copilot in VS Code to implement the fix."
|
||||
}
|
||||
'vscode' {
|
||||
# Open VS Code and let user manually trigger the fix
|
||||
code --new-window $workingDir --profile $VSCodeProfile
|
||||
Info "VS Code opened at $workingDir. Use Copilot to implement the fix."
|
||||
}
|
||||
default {
|
||||
Warn "CLI type '$CLIType' not fully supported for auto-fix. Opening VS Code..."
|
||||
code --new-window $workingDir --profile $VSCodeProfile
|
||||
}
|
||||
}
|
||||
|
||||
# Check if any changes were actually made
|
||||
$hasChanges = $false
|
||||
Push-Location $workingDir
|
||||
try {
|
||||
$uncommitted = git status --porcelain 2>$null
|
||||
$commitsAhead = git rev-list main..HEAD --count 2>$null
|
||||
if ($uncommitted -or ($commitsAhead -gt 0)) {
|
||||
$hasChanges = $true
|
||||
}
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
return @{
|
||||
IssueNumber = $IssueNumber
|
||||
WorktreePath = $workingDir
|
||||
Success = $true
|
||||
ActionTaken = 'CodeFixAttempted'
|
||||
SkippedCodeFix = $false
|
||||
HasChanges = $hasChanges
|
||||
}
|
||||
}
|
||||
|
||||
#region Main Script
|
||||
try {
|
||||
Info "Repository root: $repoRoot"
|
||||
|
||||
# Detect or validate CLI
|
||||
if ($CLIType -eq 'auto') {
|
||||
$cli = Get-AvailableCLI
|
||||
if ($cli) {
|
||||
$CLIType = $cli.Type
|
||||
Info "Auto-detected CLI: $($cli.Name)"
|
||||
} else {
|
||||
$CLIType = 'vscode'
|
||||
Info "No CLI detected, will use VS Code"
|
||||
}
|
||||
}
|
||||
|
||||
# Find issues to fix
|
||||
$issuesToFix = @()
|
||||
|
||||
if ($IssueNumber) {
|
||||
# Single issue specified
|
||||
$reviewResult = Get-IssueReviewResult -IssueNumber $IssueNumber -RepoRoot $repoRoot
|
||||
if (-not $reviewResult.HasOverview -or -not $reviewResult.HasImplementationPlan) {
|
||||
throw "Issue #$IssueNumber does not have review files. Run Start-BulkIssueReview.ps1 first."
|
||||
}
|
||||
$issuesToFix += @{
|
||||
IssueNumber = $IssueNumber
|
||||
OverviewPath = $reviewResult.OverviewPath
|
||||
ImplementationPlanPath = $reviewResult.ImplementationPlanPath
|
||||
}
|
||||
} else {
|
||||
# Find high-confidence issues
|
||||
Info "`nSearching for high-confidence issues..."
|
||||
Info " Min Feasibility Score: $MinFeasibilityScore"
|
||||
Info " Min Clarity Score: $MinClarityScore"
|
||||
Info " Max Effort: $MaxEffortDays days"
|
||||
|
||||
$highConfidence = Get-HighConfidenceIssues `
|
||||
-RepoRoot $repoRoot `
|
||||
-MinFeasibilityScore $MinFeasibilityScore `
|
||||
-MinClarityScore $MinClarityScore `
|
||||
-MaxEffortDays $MaxEffortDays
|
||||
|
||||
if ($highConfidence.Count -eq 0) {
|
||||
Warn "No high-confidence issues found matching criteria."
|
||||
Info "Try lowering the score thresholds or increasing MaxEffortDays."
|
||||
return
|
||||
}
|
||||
|
||||
$issuesToFix = $highConfidence
|
||||
}
|
||||
|
||||
Info "`nIssues ready for auto-fix: $($issuesToFix.Count)"
|
||||
Info ("-" * 80)
|
||||
foreach ($issue in $issuesToFix) {
|
||||
$scores = ""
|
||||
if ($issue.FeasibilityScore) {
|
||||
$scores = " [Feasibility: $($issue.FeasibilityScore), Clarity: $($issue.ClarityScore), Effort: $($issue.EffortDays)d]"
|
||||
}
|
||||
Info ("#{0,-6}{1}" -f $issue.IssueNumber, $scores)
|
||||
}
|
||||
Info ("-" * 80)
|
||||
|
||||
# In DryRun mode, still analyze plans but don't take action
|
||||
if ($DryRun) {
|
||||
Info "`nAnalyzing implementation plans (dry run)..."
|
||||
foreach ($issue in $issuesToFix) {
|
||||
$implPlanPath = Join-Path (Get-IssueReviewPath -RepoRoot $repoRoot -IssueNumber $issue.IssueNumber) 'implementation-plan.md'
|
||||
if (Test-Path $implPlanPath) {
|
||||
$planStatus = Get-ImplementationPlanStatus -ImplementationPlanPath $implPlanPath
|
||||
$color = switch ($planStatus.Action) {
|
||||
'ImplementFix' { 'Green' }
|
||||
'CloseIssue' { 'Yellow' }
|
||||
'AddComment' { 'Cyan' }
|
||||
'LinkDuplicate' { 'Magenta' }
|
||||
default { 'Gray' }
|
||||
}
|
||||
Write-Host (" #{0,-6} [{1,-20}] -> {2}" -f $issue.IssueNumber, $planStatus.Status, $planStatus.Action) -ForegroundColor $color
|
||||
if ($planStatus.RelatedPR) {
|
||||
$prInfo = "PR #$($planStatus.RelatedPR)"
|
||||
if ($planStatus.ReleasedIn) {
|
||||
$prInfo += " (released in $($planStatus.ReleasedIn))"
|
||||
} elseif ($planStatus.Status -eq 'FixedButUnreleased') {
|
||||
$prInfo += " (merged, awaiting release)"
|
||||
}
|
||||
Write-Host " $prInfo" -ForegroundColor DarkGray
|
||||
}
|
||||
if ($planStatus.DuplicateOf) {
|
||||
Write-Host " Duplicate of #$($planStatus.DuplicateOf)" -ForegroundColor DarkGray
|
||||
}
|
||||
}
|
||||
}
|
||||
Warn "`nDry run mode - no actions taken."
|
||||
return
|
||||
}
|
||||
|
||||
# Confirm before proceeding (skip if -Force)
|
||||
if (-not $Force) {
|
||||
$confirm = Read-Host "`nProceed with fixing $($issuesToFix.Count) issues? (y/N)"
|
||||
if ($confirm -notmatch '^[yY]') {
|
||||
Info "Cancelled."
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
# Process issues
|
||||
$results = @{
|
||||
Succeeded = @()
|
||||
Failed = @()
|
||||
AlreadyResolved = @()
|
||||
AwaitingRelease = @()
|
||||
NeedsClarification = @()
|
||||
Duplicates = @()
|
||||
NoChanges = @()
|
||||
}
|
||||
|
||||
foreach ($issue in $issuesToFix) {
|
||||
try {
|
||||
Info "`n" + ("=" * 60)
|
||||
Info "PROCESSING ISSUE #$($issue.IssueNumber)"
|
||||
Info ("=" * 60)
|
||||
|
||||
$result = Start-IssueFixInWorktree `
|
||||
-IssueNumber $issue.IssueNumber `
|
||||
-SourceRepoRoot $repoRoot `
|
||||
-CLIType $CLIType `
|
||||
-Model $Model `
|
||||
-VSCodeProfile $VSCodeProfile `
|
||||
-SkipWorktree:$SkipWorktree `
|
||||
-DryRun:$DryRun
|
||||
|
||||
if ($result.SkippedCodeFix) {
|
||||
# Action was taken but no code fix (e.g., closed issue, added comment)
|
||||
switch -Wildcard ($result.ActionTaken) {
|
||||
'*Closing*' { $results.AlreadyResolved += $issue.IssueNumber }
|
||||
'*clarification*' { $results.NeedsClarification += $issue.IssueNumber }
|
||||
'*duplicate*' { $results.Duplicates += $issue.IssueNumber }
|
||||
'*merged*awaiting*' { $results.AwaitingRelease += $issue.IssueNumber }
|
||||
'*merged but not yet released*' { $results.AwaitingRelease += $issue.IssueNumber }
|
||||
default { $results.Succeeded += $issue.IssueNumber }
|
||||
}
|
||||
Success "✓ Issue #$($issue.IssueNumber) handled: $($result.ActionTaken)"
|
||||
}
|
||||
elseif ($result.HasChanges) {
|
||||
$results.Succeeded += $issue.IssueNumber
|
||||
Success "✓ Issue #$($issue.IssueNumber) fix completed with changes"
|
||||
}
|
||||
else {
|
||||
$results.NoChanges += $issue.IssueNumber
|
||||
Warn "⚠ Issue #$($issue.IssueNumber) fix ran but no code changes were made"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Err "✗ Issue #$($issue.IssueNumber) failed: $($_.Exception.Message)"
|
||||
$results.Failed += $issue.IssueNumber
|
||||
}
|
||||
}
|
||||
|
||||
# Summary
|
||||
Info "`n" + ("=" * 80)
|
||||
Info "AUTO-FIX COMPLETE"
|
||||
Info ("=" * 80)
|
||||
Info "Total issues: $($issuesToFix.Count)"
|
||||
if ($results.Succeeded.Count -gt 0) {
|
||||
Success "Code fixes: $($results.Succeeded.Count)"
|
||||
}
|
||||
if ($results.AlreadyResolved.Count -gt 0) {
|
||||
Success "Already resolved: $($results.AlreadyResolved.Count) (issues closed)"
|
||||
}
|
||||
if ($results.AwaitingRelease.Count -gt 0) {
|
||||
Info "Awaiting release: $($results.AwaitingRelease.Count) (fix merged, pending release)"
|
||||
}
|
||||
if ($results.NeedsClarification.Count -gt 0) {
|
||||
Warn "Need clarification: $($results.NeedsClarification.Count) (comments added)"
|
||||
}
|
||||
if ($results.Duplicates.Count -gt 0) {
|
||||
Warn "Duplicates: $($results.Duplicates.Count) (issues closed)"
|
||||
}
|
||||
if ($results.NoChanges.Count -gt 0) {
|
||||
Warn "No changes made: $($results.NoChanges.Count)"
|
||||
}
|
||||
if ($results.Failed.Count -gt 0) {
|
||||
Err "Failed: $($results.Failed.Count)"
|
||||
Err "Failed issues: $($results.Failed -join ', ')"
|
||||
}
|
||||
Info ("=" * 80)
|
||||
|
||||
if (-not $SkipWorktree -and ($results.Succeeded.Count -gt 0 -or $results.NoChanges.Count -gt 0)) {
|
||||
Info "`nWorktrees created. Use 'git worktree list' to see all worktrees."
|
||||
Info "To clean up: Delete-Worktree.ps1 -Branch issue/<number>"
|
||||
}
|
||||
|
||||
# Write signal files for orchestrator
|
||||
$genFiles = Get-GeneratedFilesPath -RepoRoot $repoRoot
|
||||
foreach ($issueNum in $results.Succeeded) {
|
||||
$signalDir = Join-Path $genFiles "issueFix/$issueNum"
|
||||
if (-not (Test-Path $signalDir)) { New-Item -ItemType Directory -Path $signalDir -Force | Out-Null }
|
||||
@{
|
||||
status = "success"
|
||||
issueNumber = $issueNum
|
||||
timestamp = (Get-Date).ToString("o")
|
||||
worktreePath = (git worktree list --porcelain | Select-String "worktree.*issue.$issueNum" | ForEach-Object { $_.Line -replace 'worktree ', '' })
|
||||
} | ConvertTo-Json | Set-Content "$signalDir/.signal" -Force
|
||||
}
|
||||
foreach ($issueNum in $results.Failed) {
|
||||
$signalDir = Join-Path $genFiles "issueFix/$issueNum"
|
||||
if (-not (Test-Path $signalDir)) { New-Item -ItemType Directory -Path $signalDir -Force | Out-Null }
|
||||
@{
|
||||
status = "failure"
|
||||
issueNumber = $issueNum
|
||||
timestamp = (Get-Date).ToString("o")
|
||||
} | ConvertTo-Json | Set-Content "$signalDir/.signal" -Force
|
||||
}
|
||||
|
||||
return $results
|
||||
}
|
||||
catch {
|
||||
Err "Error: $($_.Exception.Message)"
|
||||
exit 1
|
||||
}
|
||||
#endregion
|
||||
@@ -1,86 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Run issue-fix in parallel from a single terminal.
|
||||
|
||||
.PARAMETER IssueNumbers
|
||||
Issue numbers to fix.
|
||||
|
||||
.PARAMETER ThrottleLimit
|
||||
Maximum parallel tasks.
|
||||
|
||||
.PARAMETER CLIType
|
||||
AI CLI type (copilot/claude/gh-copilot/vscode/auto).
|
||||
|
||||
.PARAMETER Model
|
||||
Copilot CLI model to use (e.g., gpt-5.2-codex).
|
||||
|
||||
.PARAMETER Force
|
||||
Skip confirmation prompts in Start-IssueAutoFix.ps1.
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[int[]]$IssueNumbers,
|
||||
|
||||
[int]$ThrottleLimit = 5,
|
||||
|
||||
[ValidateSet('claude', 'copilot', 'gh-copilot', 'vscode', 'auto')]
|
||||
[string]$CLIType = 'copilot',
|
||||
|
||||
[string]$Model,
|
||||
|
||||
[switch]$Force
|
||||
)
|
||||
|
||||
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot '..\..\..\..')
|
||||
|
||||
# Resolve config directory name (.github or .claude) from script location
|
||||
$_cfgDir = if ($PSScriptRoot -match '[\\/](\.github|\.claude)[\\/]') { $Matches[1] } else { '.github' }
|
||||
$scriptPath = Join-Path $repoRoot "$_cfgDir\skills\issue-fix\scripts\Start-IssueAutoFix.ps1"
|
||||
|
||||
$results = $IssueNumbers | ForEach-Object -Parallel {
|
||||
$issue = $PSItem
|
||||
$repoRoot = $using:repoRoot
|
||||
$scriptPath = $using:scriptPath
|
||||
$cliType = $using:CLIType
|
||||
$model = $using:Model
|
||||
$force = $using:Force
|
||||
|
||||
Set-Location $repoRoot
|
||||
|
||||
if (-not $issue) {
|
||||
return [pscustomobject]@{
|
||||
IssueNumber = $issue
|
||||
ExitCode = 1
|
||||
Error = 'Issue number is empty.'
|
||||
}
|
||||
}
|
||||
|
||||
$params = @{
|
||||
IssueNumber = [int]$issue
|
||||
CLIType = $cliType
|
||||
}
|
||||
if ($model) {
|
||||
$params.Model = $model
|
||||
}
|
||||
if ($force) {
|
||||
$params.Force = $true
|
||||
}
|
||||
|
||||
try {
|
||||
& $scriptPath @params | Out-Default
|
||||
[pscustomobject]@{
|
||||
IssueNumber = $issue
|
||||
ExitCode = $LASTEXITCODE
|
||||
}
|
||||
}
|
||||
catch {
|
||||
[pscustomobject]@{
|
||||
IssueNumber = $issue
|
||||
ExitCode = 1
|
||||
Error = $_.Exception.Message
|
||||
}
|
||||
}
|
||||
} -ThrottleLimit $ThrottleLimit
|
||||
|
||||
$results
|
||||
562
.github/skills/issue-fix/scripts/Submit-IssueFix.ps1
vendored
562
.github/skills/issue-fix/scripts/Submit-IssueFix.ps1
vendored
@@ -1,562 +0,0 @@
|
||||
<#!
|
||||
.SYNOPSIS
|
||||
Commit and create PRs for completed issue fixes in worktrees.
|
||||
|
||||
.DESCRIPTION
|
||||
For each specified issue (or all issue worktrees), commits changes using AI-generated
|
||||
commit messages and creates PRs with AI-generated summaries, linking to the original issue.
|
||||
|
||||
.PARAMETER IssueNumbers
|
||||
Array of issue numbers to submit. If not specified, processes all issue/* worktrees.
|
||||
|
||||
.PARAMETER DryRun
|
||||
Show what would be done without actually committing or creating PRs.
|
||||
|
||||
.PARAMETER SkipCommit
|
||||
Skip the commit step (assume changes are already committed).
|
||||
|
||||
.PARAMETER SkipPush
|
||||
Skip pushing to remote (useful for testing).
|
||||
|
||||
.PARAMETER TargetBranch
|
||||
Target branch for the PR. Default: main.
|
||||
|
||||
.PARAMETER CLIType
|
||||
AI CLI to use for generating messages: copilot, claude, or manual. Default: copilot.
|
||||
|
||||
.PARAMETER Draft
|
||||
Create PRs as drafts.
|
||||
|
||||
.EXAMPLE
|
||||
# Submit all issue worktrees
|
||||
./Submit-IssueFixes.ps1
|
||||
|
||||
.EXAMPLE
|
||||
# Submit specific issues
|
||||
./Submit-IssueFixes.ps1 -IssueNumbers 44044, 44480
|
||||
|
||||
.EXAMPLE
|
||||
# Dry run to see what would happen
|
||||
./Submit-IssueFixes.ps1 -DryRun
|
||||
|
||||
.EXAMPLE
|
||||
# Create draft PRs
|
||||
./Submit-IssueFixes.ps1 -Draft
|
||||
|
||||
.NOTES
|
||||
Prerequisites:
|
||||
- Worktrees created by Start-IssueAutoFix.ps1
|
||||
- Changes made in the worktrees
|
||||
- GitHub CLI (gh) authenticated
|
||||
- Copilot CLI or Claude Code CLI
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[int[]]$IssueNumbers,
|
||||
|
||||
[switch]$DryRun,
|
||||
|
||||
[switch]$SkipCommit,
|
||||
|
||||
[switch]$SkipPush,
|
||||
|
||||
[string]$TargetBranch = 'main',
|
||||
|
||||
[ValidateSet('copilot', 'claude', 'manual')]
|
||||
[string]$CLIType = 'copilot',
|
||||
|
||||
[switch]$Draft,
|
||||
|
||||
[switch]$Force,
|
||||
|
||||
[switch]$Help
|
||||
)
|
||||
|
||||
# Load libraries
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
. "$scriptDir/IssueReviewLib.ps1"
|
||||
|
||||
# Load worktree library
|
||||
$repoRoot = Get-RepoRoot
|
||||
|
||||
# Resolve config directory name (.github or .claude) from script location
|
||||
$_cfgDir = if ($PSScriptRoot -match '[\\/](\.github|\.claude)[\\/]') { $Matches[1] } else { '.github' }
|
||||
$worktreeLib = Join-Path $repoRoot 'tools/build/WorktreeLib.ps1'
|
||||
if (Test-Path $worktreeLib) {
|
||||
. $worktreeLib
|
||||
}
|
||||
|
||||
if ($Help) {
|
||||
Get-Help $MyInvocation.MyCommand.Path -Full
|
||||
return
|
||||
}
|
||||
|
||||
function Get-AIGeneratedCommitTitle {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Generate commit title using AI CLI with create-commit-title prompt.
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$WorktreePath,
|
||||
[string]$CLIType = 'copilot'
|
||||
)
|
||||
|
||||
$promptFile = Join-Path $repoRoot "$_cfgDir/prompts/create-commit-title.prompt.md"
|
||||
if (-not (Test-Path $promptFile)) {
|
||||
throw "Prompt file not found: $promptFile"
|
||||
}
|
||||
|
||||
$prompt = "Follow the instructions in $_cfgDir/prompts/create-commit-title.prompt.md to generate a commit title for the current changes. Output ONLY the commit title, nothing else."
|
||||
|
||||
# MCP config for github-artifacts tools (relative to repo root)
|
||||
$mcpConfig = "@$_cfgDir/skills/issue-fix/references/mcp-config.json"
|
||||
|
||||
Push-Location $WorktreePath
|
||||
try {
|
||||
switch ($CLIType) {
|
||||
'copilot' {
|
||||
$result = & copilot --additional-mcp-config $mcpConfig -p $prompt --yolo -s --agent FixIssue 2>&1
|
||||
# Extract just the title line (last non-empty line that looks like a title)
|
||||
$lines = $result -split "`n" | Where-Object { $_.Trim() -and $_ -notmatch '^\s*```' -and $_ -notmatch '^\s*#' }
|
||||
$title = $lines | Select-Object -Last 1
|
||||
return $title.Trim()
|
||||
}
|
||||
'claude' {
|
||||
$result = & claude --print --dangerously-skip-permissions --agent FixIssue --prompt $prompt 2>&1
|
||||
$lines = $result -split "`n" | Where-Object { $_.Trim() -and $_ -notmatch '^\s*```' }
|
||||
$title = $lines | Select-Object -Last 1
|
||||
return $title.Trim()
|
||||
}
|
||||
'manual' {
|
||||
# Show diff and ask user for title
|
||||
git diff HEAD --stat
|
||||
return Read-Host "Enter commit title"
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
}
|
||||
|
||||
function Get-AIGeneratedPRSummary {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Generate PR summary using AI CLI with create-pr-summary prompt.
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$WorktreePath,
|
||||
[Parameter(Mandatory)]
|
||||
[int]$IssueNumber,
|
||||
[string]$TargetBranch = 'main',
|
||||
[string]$CLIType = 'copilot'
|
||||
)
|
||||
|
||||
$prompt = @"
|
||||
Follow the instructions in $_cfgDir/prompts/create-pr-summary.prompt.md to generate a PR summary.
|
||||
Target branch: $TargetBranch
|
||||
This PR fixes issue #$IssueNumber.
|
||||
|
||||
IMPORTANT:
|
||||
1. Output the PR title on the first line
|
||||
2. Then output the PR body in markdown format
|
||||
3. Make sure to include "Fixes #$IssueNumber" in the body to auto-link the issue
|
||||
"@
|
||||
|
||||
# MCP config for github-artifacts tools (relative to repo root)
|
||||
$mcpConfig = "@$_cfgDir/skills/issue-fix/references/mcp-config.json"
|
||||
|
||||
Push-Location $WorktreePath
|
||||
try {
|
||||
switch ($CLIType) {
|
||||
'copilot' {
|
||||
$result = & copilot --additional-mcp-config $mcpConfig -p $prompt --yolo -s --agent FixIssue 2>&1
|
||||
return $result -join "`n"
|
||||
}
|
||||
'claude' {
|
||||
$result = & claude --print --dangerously-skip-permissions --agent FixIssue --prompt $prompt 2>&1
|
||||
return $result -join "`n"
|
||||
}
|
||||
'manual' {
|
||||
git diff "$TargetBranch...HEAD" --stat
|
||||
$title = Read-Host "Enter PR title"
|
||||
$body = Read-Host "Enter PR body (or press Enter for default)"
|
||||
if (-not $body) {
|
||||
$body = "Fixes #$IssueNumber"
|
||||
}
|
||||
return "$title`n`n$body"
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
}
|
||||
|
||||
function Parse-PRContent {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Parse AI output to extract PR title and body.
|
||||
Expected format:
|
||||
Line 1: feat(scope): title text
|
||||
Line 2+: ```markdown
|
||||
## Summary...
|
||||
```
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$Content,
|
||||
[int]$IssueNumber
|
||||
)
|
||||
|
||||
$lines = $Content -split "`n"
|
||||
|
||||
# Title is the FIRST line that looks like a conventional commit
|
||||
# Body is the content INSIDE the ```markdown ... ``` block
|
||||
$title = $null
|
||||
$body = $null
|
||||
|
||||
# Find title - first line matching conventional commit format
|
||||
foreach ($line in $lines) {
|
||||
$trimmed = $line.Trim()
|
||||
if ($trimmed -match '^(feat|fix|docs|refactor|perf|test|build|ci|chore)(\([^)]+\))?:') {
|
||||
$title = $trimmed -replace '^#+\s*', ''
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
# Fallback title
|
||||
if (-not $title) {
|
||||
$title = "fix: address issue #$IssueNumber"
|
||||
}
|
||||
|
||||
# Extract body from markdown code block
|
||||
$fullContent = $Content
|
||||
if ($fullContent -match '```markdown\r?\n([\s\S]*?)\r?\n```') {
|
||||
$body = $Matches[1].Trim()
|
||||
} else {
|
||||
# No markdown block - use everything after the title line
|
||||
$titleIndex = [array]::IndexOf($lines, ($lines | Where-Object { $_.Trim() -eq $title } | Select-Object -First 1))
|
||||
if ($titleIndex -ge 0 -and $titleIndex -lt $lines.Count - 1) {
|
||||
$body = ($lines[($titleIndex + 1)..($lines.Count - 1)] -join "`n").Trim()
|
||||
# Clean up any remaining code fences
|
||||
$body = $body -replace '^```\w*\r?\n', '' -replace '\r?\n```\s*$', ''
|
||||
} else {
|
||||
$body = ""
|
||||
}
|
||||
}
|
||||
|
||||
# Ensure issue link is present
|
||||
if ($body -notmatch "Fixes\s*#$IssueNumber" -and $body -notmatch "Closes\s*#$IssueNumber" -and $body -notmatch "Resolves\s*#$IssueNumber") {
|
||||
$body = "$body`n`nFixes #$IssueNumber"
|
||||
}
|
||||
|
||||
return @{
|
||||
Title = $title
|
||||
Body = $body
|
||||
}
|
||||
}
|
||||
|
||||
function Submit-IssueFix {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Commit changes, push, and create PR for a single issue.
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[int]$IssueNumber,
|
||||
[Parameter(Mandatory)]
|
||||
[string]$WorktreePath,
|
||||
[Parameter(Mandatory)]
|
||||
[string]$Branch,
|
||||
[string]$TargetBranch = 'main',
|
||||
[string]$CLIType = 'copilot',
|
||||
[switch]$DryRun,
|
||||
[switch]$SkipCommit,
|
||||
[switch]$SkipPush,
|
||||
[switch]$Draft
|
||||
)
|
||||
|
||||
Push-Location $WorktreePath
|
||||
try {
|
||||
# Check for changes
|
||||
$status = git status --porcelain
|
||||
$hasUncommitted = $status.Count -gt 0
|
||||
|
||||
# Check for commits ahead of target
|
||||
git fetch origin $TargetBranch 2>$null
|
||||
$commitsAhead = git rev-list --count "origin/$TargetBranch..$Branch" 2>$null
|
||||
if (-not $commitsAhead) { $commitsAhead = 0 }
|
||||
|
||||
Info "Issue #$IssueNumber in $WorktreePath"
|
||||
Info " Branch: $Branch"
|
||||
Info " Uncommitted changes: $hasUncommitted"
|
||||
Info " Commits ahead of $TargetBranch`: $commitsAhead"
|
||||
|
||||
if (-not $hasUncommitted -and $commitsAhead -eq 0) {
|
||||
Warn " No changes to submit for issue #$IssueNumber"
|
||||
return @{ IssueNumber = $IssueNumber; Status = 'NoChanges' }
|
||||
}
|
||||
|
||||
# Step 1: Commit if there are uncommitted changes
|
||||
if ($hasUncommitted -and -not $SkipCommit) {
|
||||
Info " Generating commit title..."
|
||||
|
||||
if ($DryRun) {
|
||||
Info " [DRY RUN] Would generate commit title and commit changes"
|
||||
} else {
|
||||
$commitTitle = Get-AIGeneratedCommitTitle -WorktreePath $WorktreePath -CLIType $CLIType
|
||||
|
||||
if (-not $commitTitle) {
|
||||
throw "Failed to generate commit title"
|
||||
}
|
||||
|
||||
Info " Commit title: $commitTitle"
|
||||
|
||||
# Stage all changes and commit
|
||||
git add -A
|
||||
git commit -m $commitTitle
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Git commit failed"
|
||||
}
|
||||
|
||||
Success " ✓ Changes committed"
|
||||
}
|
||||
}
|
||||
|
||||
# Step 2: Push to remote
|
||||
if (-not $SkipPush) {
|
||||
if ($DryRun) {
|
||||
Info " [DRY RUN] Would push branch $Branch to origin"
|
||||
} else {
|
||||
Info " Pushing to origin..."
|
||||
git push -u origin $Branch 2>&1 | Out-Null
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
# Try force push if normal push fails (branch might have been reset)
|
||||
Warn " Normal push failed, trying force push..."
|
||||
git push -u origin $Branch --force-with-lease 2>&1 | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Git push failed"
|
||||
}
|
||||
}
|
||||
|
||||
Success " ✓ Pushed to origin"
|
||||
}
|
||||
}
|
||||
|
||||
# Step 3: Create PR
|
||||
Info " Generating PR summary..."
|
||||
|
||||
if ($DryRun) {
|
||||
Info " [DRY RUN] Would generate PR summary and create PR"
|
||||
Info " [DRY RUN] PR would link to issue #$IssueNumber"
|
||||
return @{ IssueNumber = $IssueNumber; Status = 'DryRun' }
|
||||
}
|
||||
|
||||
# Check if PR already exists
|
||||
$existingPR = gh pr list --head $Branch --json number,url 2>$null | ConvertFrom-Json
|
||||
if ($existingPR -and $existingPR.Count -gt 0) {
|
||||
Warn " PR already exists: $($existingPR[0].url)"
|
||||
return @{ IssueNumber = $IssueNumber; Status = 'PRExists'; PRUrl = $existingPR[0].url }
|
||||
}
|
||||
|
||||
$prContent = Get-AIGeneratedPRSummary -WorktreePath $WorktreePath -IssueNumber $IssueNumber -TargetBranch $TargetBranch -CLIType $CLIType
|
||||
$parsed = Parse-PRContent -Content $prContent -IssueNumber $IssueNumber
|
||||
|
||||
if (-not $parsed.Title) {
|
||||
throw "Failed to generate PR title"
|
||||
}
|
||||
|
||||
Info " PR Title: $($parsed.Title)"
|
||||
|
||||
# Create PR using gh CLI
|
||||
$ghArgs = @(
|
||||
'pr', 'create',
|
||||
'--base', $TargetBranch,
|
||||
'--head', $Branch,
|
||||
'--title', $parsed.Title,
|
||||
'--body', $parsed.Body
|
||||
)
|
||||
|
||||
if ($Draft) {
|
||||
$ghArgs += '--draft'
|
||||
}
|
||||
|
||||
$prResult = & gh @ghArgs 2>&1
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Failed to create PR: $prResult"
|
||||
}
|
||||
|
||||
# Extract PR URL from result
|
||||
$prUrl = $prResult | Select-String -Pattern 'https://github.com/[^\s]+' | ForEach-Object { $_.Matches[0].Value }
|
||||
|
||||
Success " ✓ PR created: $prUrl"
|
||||
|
||||
return @{
|
||||
IssueNumber = $IssueNumber
|
||||
Status = 'Success'
|
||||
PRUrl = $prUrl
|
||||
CommitTitle = $commitTitle
|
||||
PRTitle = $parsed.Title
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Err " ✗ Failed: $($_.Exception.Message)"
|
||||
return @{
|
||||
IssueNumber = $IssueNumber
|
||||
Status = 'Failed'
|
||||
Error = $_.Exception.Message
|
||||
}
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
}
|
||||
}
|
||||
|
||||
#region Main Script
|
||||
try {
|
||||
Info "Repository root: $repoRoot"
|
||||
Info "Target branch: $TargetBranch"
|
||||
Info "CLI type: $CLIType"
|
||||
|
||||
# Get all issue worktrees
|
||||
$allWorktrees = Get-WorktreeEntries | Where-Object { $_.Branch -like 'issue/*' }
|
||||
|
||||
if ($allWorktrees.Count -eq 0) {
|
||||
Warn "No issue worktrees found. Run Start-IssueAutoFix.ps1 first."
|
||||
return
|
||||
}
|
||||
|
||||
# Filter to specified issues if provided
|
||||
$worktreesToProcess = @()
|
||||
|
||||
if ($IssueNumbers -and $IssueNumbers.Count -gt 0) {
|
||||
foreach ($issueNum in $IssueNumbers) {
|
||||
$wt = $allWorktrees | Where-Object { $_.Branch -match "issue/$issueNum\b" }
|
||||
if ($wt) {
|
||||
$worktreesToProcess += $wt
|
||||
} else {
|
||||
Warn "No worktree found for issue #$issueNum"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$worktreesToProcess = $allWorktrees
|
||||
}
|
||||
|
||||
if ($worktreesToProcess.Count -eq 0) {
|
||||
Warn "No worktrees to process."
|
||||
return
|
||||
}
|
||||
|
||||
# Display worktrees to process
|
||||
Info "`nWorktrees to submit:"
|
||||
Info ("-" * 80)
|
||||
foreach ($wt in $worktreesToProcess) {
|
||||
# Extract issue number from branch name
|
||||
if ($wt.Branch -match 'issue/(\d+)') {
|
||||
$issueNum = $Matches[1]
|
||||
Info " #$issueNum -> $($wt.Path) [$($wt.Branch)]"
|
||||
}
|
||||
}
|
||||
Info ("-" * 80)
|
||||
|
||||
if ($DryRun) {
|
||||
Warn "`nDry run mode - no changes will be made."
|
||||
}
|
||||
|
||||
# Confirm before proceeding
|
||||
if (-not $Force -and -not $DryRun) {
|
||||
$confirm = Read-Host "`nProceed with submitting $($worktreesToProcess.Count) fixes? (y/N)"
|
||||
if ($confirm -notmatch '^[yY]') {
|
||||
Info "Cancelled."
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
# Process each worktree
|
||||
$results = @{
|
||||
Success = @()
|
||||
Failed = @()
|
||||
NoChanges = @()
|
||||
PRExists = @()
|
||||
DryRun = @()
|
||||
}
|
||||
|
||||
foreach ($wt in $worktreesToProcess) {
|
||||
if ($wt.Branch -match 'issue/(\d+)') {
|
||||
$issueNum = [int]$Matches[1]
|
||||
|
||||
Info "`n" + ("=" * 60)
|
||||
Info "SUBMITTING ISSUE #$issueNum"
|
||||
Info ("=" * 60)
|
||||
|
||||
$result = Submit-IssueFix `
|
||||
-IssueNumber $issueNum `
|
||||
-WorktreePath $wt.Path `
|
||||
-Branch $wt.Branch `
|
||||
-TargetBranch $TargetBranch `
|
||||
-CLIType $CLIType `
|
||||
-DryRun:$DryRun `
|
||||
-SkipCommit:$SkipCommit `
|
||||
-SkipPush:$SkipPush `
|
||||
-Draft:$Draft
|
||||
|
||||
switch ($result.Status) {
|
||||
'Success' { $results.Success += $result }
|
||||
'Failed' { $results.Failed += $result }
|
||||
'NoChanges' { $results.NoChanges += $result }
|
||||
'PRExists' { $results.PRExists += $result }
|
||||
'DryRun' { $results.DryRun += $result }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Summary
|
||||
Info "`n" + ("=" * 80)
|
||||
Info "SUBMISSION COMPLETE"
|
||||
Info ("=" * 80)
|
||||
Info "Total worktrees: $($worktreesToProcess.Count)"
|
||||
|
||||
if ($results.Success.Count -gt 0) {
|
||||
Success "PRs created: $($results.Success.Count)"
|
||||
foreach ($r in $results.Success) {
|
||||
Success " #$($r.IssueNumber): $($r.PRUrl)"
|
||||
}
|
||||
}
|
||||
|
||||
if ($results.PRExists.Count -gt 0) {
|
||||
Warn "PRs already exist: $($results.PRExists.Count)"
|
||||
foreach ($r in $results.PRExists) {
|
||||
Warn " #$($r.IssueNumber): $($r.PRUrl)"
|
||||
}
|
||||
}
|
||||
|
||||
if ($results.NoChanges.Count -gt 0) {
|
||||
Warn "No changes: $($results.NoChanges.Count)"
|
||||
Warn " Issues: $($results.NoChanges.IssueNumber -join ', ')"
|
||||
}
|
||||
|
||||
if ($results.Failed.Count -gt 0) {
|
||||
Err "Failed: $($results.Failed.Count)"
|
||||
foreach ($r in $results.Failed) {
|
||||
Err " #$($r.IssueNumber): $($r.Error)"
|
||||
}
|
||||
}
|
||||
|
||||
if ($results.DryRun.Count -gt 0) {
|
||||
Info "Dry run: $($results.DryRun.Count)"
|
||||
}
|
||||
|
||||
Info ("=" * 80)
|
||||
|
||||
return $results
|
||||
}
|
||||
catch {
|
||||
Err "Error: $($_.Exception.Message)"
|
||||
exit 1
|
||||
}
|
||||
#endregion
|
||||
21
.github/skills/issue-review-review/LICENSE.txt
vendored
21
.github/skills/issue-review-review/LICENSE.txt
vendored
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
184
.github/skills/issue-review-review/SKILL.md
vendored
184
.github/skills/issue-review-review/SKILL.md
vendored
@@ -1,184 +0,0 @@
|
||||
---
|
||||
name: issue-review-review
|
||||
description: Meta-review of issue-review outputs to validate scoring accuracy and implementation plan quality. Use when asked to verify an issue review, validate review scores, check if implementation plan is sound, audit issue analysis quality, second-opinion on issue feasibility, or ensure review consistency. Outputs a quality score (0-100) and corrective feedback that feeds back into issue-review for re-analysis.
|
||||
license: Complete terms in LICENSE.txt
|
||||
---
|
||||
|
||||
# Issue Review Review Skill
|
||||
|
||||
Validate the quality of `issue-review` outputs by cross-checking scores against evidence, verifying implementation plan correctness, and producing actionable feedback. When the quality score is below 90, the feedback is fed back into `issue-review` to re-run the analysis with corrections.
|
||||
|
||||
## Skill Contents
|
||||
|
||||
This skill is **self-contained** with all required resources:
|
||||
|
||||
```
|
||||
.github/skills/issue-review-review/
|
||||
├── SKILL.md # This file
|
||||
├── LICENSE.txt # MIT License
|
||||
├── scripts/
|
||||
│ ├── Start-IssueReviewReview.ps1 # Main review-review script
|
||||
│ ├── Start-IssueReviewReviewParallel.ps1 # Parallel runner
|
||||
│ └── IssueReviewLib.ps1 # Shared library functions
|
||||
└── references/
|
||||
├── review-the-review.prompt.md # AI prompt for meta-review
|
||||
└── mcp-config.json # MCP configuration
|
||||
```
|
||||
|
||||
## Output Directory
|
||||
|
||||
All generated artifacts are placed under `Generated Files/issueReviewReview/<issue-number>/` at the repository root (gitignored).
|
||||
|
||||
```
|
||||
Generated Files/issueReviewReview/
|
||||
└── <issue-number>/
|
||||
├── reviewTheReview.md # Meta-review with quality score and feedback
|
||||
├── .signal # Completion signal for orchestrator
|
||||
└── iteration-<N>/ # Previous iteration outputs (if looped)
|
||||
└── reviewTheReview.md
|
||||
```
|
||||
|
||||
## Signal File
|
||||
|
||||
On completion, a `.signal` file is created for orchestrator coordination:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"issueNumber": 45363,
|
||||
"timestamp": "2026-02-04T10:05:23Z",
|
||||
"qualityScore": 85,
|
||||
"iteration": 1,
|
||||
"outputs": ["reviewTheReview.md"],
|
||||
"needsReReview": true
|
||||
}
|
||||
```
|
||||
|
||||
Status values: `success`, `failure`
|
||||
|
||||
Key fields:
|
||||
- `qualityScore` (0-100): Overall quality of the original review
|
||||
- `iteration`: Which review-review pass this is (1, 2, 3...)
|
||||
- `needsReReview`: `true` if score < 90, meaning `issue-review` should re-run with feedback
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
- Validate that an issue review's scores match the evidence
|
||||
- Check if an implementation plan is technically sound
|
||||
- Verify that short-term and long-term fix strategies are correct
|
||||
- Audit review quality before sending issues to `issue-fix`
|
||||
- Second-opinion on feasibility and clarity assessments
|
||||
- Quality gate in the issue-to-PR cycle automation
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- GitHub CLI (`gh`) installed and authenticated
|
||||
- PowerShell 7+ for running scripts
|
||||
- Issue must be reviewed first (use `issue-review` skill)
|
||||
- Copilot CLI or Claude CLI installed
|
||||
|
||||
## Required Variables
|
||||
|
||||
⚠️ **Before starting**, confirm `{{IssueNumber}}` with the user. If not provided, **ASK**: "What issue number should I review-review?"
|
||||
|
||||
| Variable | Description | Example |
|
||||
|----------|-------------|---------|
|
||||
| `{{IssueNumber}}` | GitHub issue number whose review to validate | `44044` |
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Ensure Issue Is Reviewed
|
||||
|
||||
The issue must already have `Generated Files/issueReview/{{IssueNumber}}/overview.md` and `implementation-plan.md`. If not, run `issue-review` first.
|
||||
|
||||
### Step 2: Run Review-Review
|
||||
|
||||
```powershell
|
||||
# From repo root
|
||||
.github/skills/issue-review-review/scripts/Start-IssueReviewReview.ps1 -IssueNumber {{IssueNumber}}
|
||||
```
|
||||
|
||||
This will:
|
||||
1. Read the original issue from GitHub
|
||||
2. Read the existing `overview.md` and `implementation-plan.md`
|
||||
3. Cross-check scores against evidence in the issue
|
||||
4. Validate implementation plan against codebase
|
||||
5. Generate `reviewTheReview.md` with quality score and feedback
|
||||
|
||||
### Step 3: Check Quality Score
|
||||
|
||||
Read the signal file at `Generated Files/issueReviewReview/{{IssueNumber}}/.signal`:
|
||||
|
||||
| Quality Score | Action |
|
||||
|---------------|--------|
|
||||
| 90-100 | ✅ Review is high quality — proceed to `issue-fix` |
|
||||
| 70-89 | ⚠️ Review needs improvement — re-run `issue-review` with feedback |
|
||||
| 50-69 | 🔶 Review has significant issues — re-run with feedback, may need 2 iterations |
|
||||
| 0-49 | 🔴 Review is poor — re-run with feedback, consider manual review |
|
||||
|
||||
### Step 4: Feed Back to Issue-Review (if score < 90)
|
||||
|
||||
If `needsReReview` is `true`, re-run issue-review with the feedback file:
|
||||
|
||||
```powershell
|
||||
# Re-run issue-review with feedback from review-review
|
||||
.github/skills/issue-review/scripts/Start-BulkIssueReview.ps1 -IssueNumber {{IssueNumber}} -FeedbackFile "Generated Files/issueReviewReview/{{IssueNumber}}/reviewTheReview.md" -Force
|
||||
```
|
||||
|
||||
Then re-run the review-review to check if quality improved:
|
||||
|
||||
```powershell
|
||||
.github/skills/issue-review-review/scripts/Start-IssueReviewReview.ps1 -IssueNumber {{IssueNumber}} -Force
|
||||
```
|
||||
|
||||
### Step 5: Loop Until Quality ≥ 90
|
||||
|
||||
The orchestrator (`issue-to-pr-cycle`) will loop Steps 2-4 until either:
|
||||
- Quality score ≥ 90, OR
|
||||
- Maximum iterations reached (default: 3)
|
||||
|
||||
## Batch Review-Review
|
||||
|
||||
To review-review multiple issues at once:
|
||||
|
||||
```powershell
|
||||
.github/skills/issue-review-review/scripts/Start-IssueReviewReviewParallel.ps1 -IssueNumbers 44044,32950,45029 -ThrottleLimit 5 -Force
|
||||
```
|
||||
|
||||
## CLI Options
|
||||
|
||||
### Start-IssueReviewReview.ps1
|
||||
|
||||
| Parameter | Description | Default |
|
||||
|-----------|-------------|---------|
|
||||
| `-IssueNumber` | Issue number to review-review | (required) |
|
||||
| `-CLIType` | AI CLI: `copilot` or `claude` | `copilot` |
|
||||
| `-Model` | Copilot model to use | (auto) |
|
||||
| `-Force` | Skip confirmation prompts | `$false` |
|
||||
| `-DryRun` | Show what would be done | `$false` |
|
||||
|
||||
### Start-IssueReviewReviewParallel.ps1
|
||||
|
||||
| Parameter | Description | Default |
|
||||
|-----------|-------------|---------|
|
||||
| `-IssueNumbers` | Array of issue numbers | (required) |
|
||||
| `-ThrottleLimit` | Max parallel tasks | `5` |
|
||||
| `-CLIType` | AI CLI type | `copilot` |
|
||||
| `-Model` | Copilot model to use | (auto) |
|
||||
| `-Force` | Skip confirmation prompts | `$false` |
|
||||
|
||||
## Quality Dimensions Checked
|
||||
|
||||
The meta-review evaluates these dimensions:
|
||||
|
||||
| Dimension | What It Checks | Weight |
|
||||
|-----------|---------------|--------|
|
||||
| Score Accuracy | Do scores match the evidence cited? | 30% |
|
||||
| Implementation Correctness | Are the right files/patterns identified? | 25% |
|
||||
| Risk Assessment | Are risks properly identified and mitigated? | 15% |
|
||||
| Completeness | Are all aspects covered (perf, security, a11y, i18n)? | 15% |
|
||||
| Actionability | Can an AI agent execute the plan as written? | 15% |
|
||||
|
||||
## AI Prompt Reference
|
||||
|
||||
The full prompt template is at [references/review-the-review.prompt.md](./references/review-the-review.prompt.md).
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"github-artifacts": {
|
||||
"command": "cmd",
|
||||
"args": ["/c", "for /f %i in ('git rev-parse --show-toplevel') do node %i/tools/mcp/github-artifacts/launch.js"],
|
||||
"tools": ["*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,194 +0,0 @@
|
||||
---
|
||||
agent: 'agent'
|
||||
description: 'Meta-review of issue-review outputs: validate scores, check implementation plan quality, produce feedback'
|
||||
---
|
||||
|
||||
# Review the Review — Meta-Analysis of Issue Review Quality
|
||||
|
||||
## Goal
|
||||
For issue **#{{issue_number}}**, validate the existing `issue-review` outputs and produce:
|
||||
1) `Generated Files/issueReviewReview/{{issue_number}}/reviewTheReview.md`
|
||||
|
||||
## Inputs
|
||||
|
||||
You MUST have these files available before starting:
|
||||
- `Generated Files/issueReview/{{issue_number}}/overview.md` — The original review scores and assessment
|
||||
- `Generated Files/issueReview/{{issue_number}}/implementation-plan.md` — The original implementation plan
|
||||
- The original GitHub issue data (fetch via `gh issue view {{issue_number}}`)
|
||||
|
||||
If a feedback file from a previous iteration exists, also read it:
|
||||
- `Generated Files/issueReviewReview/{{issue_number}}/reviewTheReview.md` — Previous meta-review feedback (check if iteration > 1)
|
||||
|
||||
## Process
|
||||
|
||||
### Step 1: Gather Context
|
||||
|
||||
1. **Read the original issue**: `gh issue view {{issue_number}} --json number,title,body,author,createdAt,updatedAt,state,labels,milestone,reactions,comments,linkedPullRequests`
|
||||
2. **Read overview.md**: Parse all scores (Business Importance, Community Excitement, Technical Feasibility, Requirement Clarity, Overall Priority, Effort Estimate)
|
||||
3. **Read implementation-plan.md**: Parse all sections (Problem Framing, Layers & Files, Pattern Choices, Fundamentals, Task Breakdown)
|
||||
4. **Examine the actual codebase**: Use `rg`/`git grep`/`find` to verify file paths mentioned in the implementation plan actually exist
|
||||
5. **Check for similar past fixes**: Search for related PRs and how they were implemented
|
||||
|
||||
### Step 2: Validate Scores
|
||||
|
||||
For EACH score dimension, evaluate whether the score matches the evidence:
|
||||
|
||||
#### A) Business Importance Score Validation
|
||||
- Does the score align with the issue's labels (priority/security/regression)?
|
||||
- Is the milestone/roadmap impact correctly assessed?
|
||||
- Are customer/contract impacts properly weighted?
|
||||
|
||||
#### B) Community Excitement Score Validation
|
||||
- Count actual 👍/❤️ reactions and compare against the score
|
||||
- Verify comment count and unique participant count
|
||||
- Check if recent activity assessment is accurate
|
||||
- Verify duplicate/related issue count
|
||||
|
||||
#### C) Technical Feasibility Score Validation
|
||||
- **CRITICAL**: Verify that files mentioned in the plan actually exist in the repo
|
||||
- Check if the proposed changes follow existing patterns (use `rg` to find similar patterns)
|
||||
- Assess whether risk factors (perf/security/compat) are properly identified
|
||||
- Verify testability claims by checking if test infrastructure exists for the affected module
|
||||
|
||||
#### D) Requirement Clarity Score Validation
|
||||
- Does the issue actually contain clear repro steps?
|
||||
- Are non-functional requirements (perf/security/i18n/a11y) addressed?
|
||||
- Are acceptance criteria defined or at least inferable?
|
||||
|
||||
### Step 3: Validate Implementation Plan
|
||||
|
||||
For EACH section of the implementation plan:
|
||||
|
||||
#### Problem Framing
|
||||
- Is the problem correctly understood?
|
||||
- Are scope boundaries reasonable?
|
||||
- Is current vs expected behavior accurately described?
|
||||
|
||||
#### Layers & Files
|
||||
- **CRITICAL**: Do ALL referenced files/directories exist? Run `test -f <path>` or `ls <path>` for each one
|
||||
- Are the file paths using correct casing and separators?
|
||||
- Are all affected layers identified (UI/domain/data/infra/build)?
|
||||
- Are any files missing that should be modified?
|
||||
|
||||
#### Pattern Choices
|
||||
- Do the suggested patterns match what the repo actually uses?
|
||||
- Use `rg` to find 2-3 examples of the suggested pattern in the codebase
|
||||
- If a new pattern is suggested, is the justification sound?
|
||||
|
||||
#### Fundamentals
|
||||
- Are performance concerns addressed for the specific module?
|
||||
- Are security implications properly assessed?
|
||||
- Is i18n/l10n handled (check for hardcoded strings)?
|
||||
- Is accessibility considered (keyboard nav, screen readers)?
|
||||
|
||||
#### Task Breakdown
|
||||
- Can an AI agent actually execute each task as written?
|
||||
- Are the steps in the right order (dependencies respected)?
|
||||
- Are test requirements specified for each task?
|
||||
- Is the human-vs-agent ownership realistic?
|
||||
|
||||
### Step 4: Check for Red Flags
|
||||
|
||||
Flag these issues if found:
|
||||
- 🔴 **Ghost files**: Implementation plan references files that don't exist
|
||||
- 🔴 **Wrong patterns**: Suggested approach contradicts existing codebase patterns
|
||||
- 🔴 **Missing tests**: No test plan for behavior changes
|
||||
- 🔴 **Score inflation**: Scores are ≥20 points higher than evidence supports
|
||||
- 🔴 **Score deflation**: Scores are ≥20 points lower than evidence supports
|
||||
- 🟡 **Incomplete coverage**: Missing fundamentals (security, i18n, a11y)
|
||||
- 🟡 **Vague tasks**: Task breakdown has steps that are too broad to execute
|
||||
- 🟡 **Missing dependencies**: Task order doesn't respect build/import dependencies
|
||||
|
||||
## Output: reviewTheReview.md
|
||||
|
||||
Generate the following structure:
|
||||
|
||||
```markdown
|
||||
# Review-Review: Issue #{{issue_number}}
|
||||
|
||||
**Review Quality Score: X/100**
|
||||
**Iteration: N**
|
||||
**Verdict: PASS / NEEDS_IMPROVEMENT / FAIL**
|
||||
|
||||
## Executive Summary
|
||||
|
||||
Brief (2-3 sentences) on whether the original review is trustworthy and actionable.
|
||||
|
||||
## Score Validation
|
||||
|
||||
| Dimension | Original Score | Validated Score | Delta | Assessment |
|
||||
|-----------|---------------|-----------------|-------|------------|
|
||||
| Business Importance | X/100 | Y/100 | ±Z | ✅ Accurate / ⚠️ Inflated / ⚠️ Deflated |
|
||||
| Community Excitement | X/100 | Y/100 | ±Z | ✅ / ⚠️ |
|
||||
| Technical Feasibility | X/100 | Y/100 | ±Z | ✅ / ⚠️ |
|
||||
| Requirement Clarity | X/100 | Y/100 | ±Z | ✅ / ⚠️ |
|
||||
| Overall Priority | X/100 | Y/100 | ±Z | ✅ / ⚠️ |
|
||||
|
||||
### Score Details
|
||||
|
||||
For each dimension where delta ≥ 10 points:
|
||||
- What evidence was missed or misinterpreted
|
||||
- What the correct assessment should be
|
||||
- Specific data points supporting the correction
|
||||
|
||||
## Implementation Plan Validation
|
||||
|
||||
### Files Verification
|
||||
|
||||
| File Path | Exists? | Correct? | Notes |
|
||||
|-----------|---------|----------|-------|
|
||||
| `src/modules/...` | ✅/❌ | ✅/⚠️ | ... |
|
||||
|
||||
### Pattern Verification
|
||||
|
||||
| Suggested Pattern | Used in Repo? | Examples Found | Assessment |
|
||||
|-------------------|---------------|----------------|------------|
|
||||
| ... | ✅/❌ | `src/...`, `src/...` | ✅ Correct / ⚠️ Wrong pattern |
|
||||
|
||||
### Task Breakdown Assessment
|
||||
|
||||
| Task # | Executable by Agent? | Issues | Corrective Action |
|
||||
|--------|---------------------|--------|-------------------|
|
||||
| 1 | ✅/⚠️/❌ | ... | ... |
|
||||
|
||||
## Red Flags Found
|
||||
|
||||
List any 🔴 or 🟡 flags with evidence.
|
||||
|
||||
## Corrective Feedback for Re-Review
|
||||
|
||||
**IF quality score < 90, provide specific instructions for issue-review to fix:**
|
||||
|
||||
### Scores to Adjust
|
||||
- Dimension X: Change from Y to Z because [evidence]
|
||||
|
||||
### Implementation Plan Corrections
|
||||
- File path corrections: [list]
|
||||
- Missing files to add: [list]
|
||||
- Pattern corrections: [list]
|
||||
- Task breakdown fixes: [list]
|
||||
|
||||
### Missing Coverage
|
||||
- Add section on: [topic]
|
||||
- Expand analysis of: [topic]
|
||||
|
||||
## Quality Score Breakdown
|
||||
|
||||
| Dimension | Score | Weight | Weighted |
|
||||
|-----------|-------|--------|----------|
|
||||
| Score Accuracy | X/100 | 30% | X |
|
||||
| Implementation Correctness | X/100 | 25% | X |
|
||||
| Risk Assessment | X/100 | 15% | X |
|
||||
| Completeness | X/100 | 15% | X |
|
||||
| Actionability | X/100 | 15% | X |
|
||||
| **Total** | | | **X/100** |
|
||||
```
|
||||
|
||||
## Important Rules
|
||||
|
||||
1. **Be evidence-based**: Every correction must cite specific files, lines, or data
|
||||
2. **Verify file existence**: ALWAYS run `test -f` or `ls` for paths in the implementation plan
|
||||
3. **Check patterns**: Use `rg` to find at least 2 examples of any suggested pattern
|
||||
4. **Don't be a rubber stamp**: If the review looks perfect, still verify the top 3 most impactful claims
|
||||
5. **Actionable feedback**: Every issue found must include a specific correction, not just "this is wrong"
|
||||
6. **Score honestly**: The quality score should reflect real issues found, not just gut feeling
|
||||
@@ -1,777 +0,0 @@
|
||||
# IssueReviewLib.ps1 - Shared helpers for bulk issue review automation
|
||||
# Part of the PowerToys GitHub Copilot/Claude Code issue review system
|
||||
|
||||
# Resolve config directory name (.github or .claude) from this script's location
|
||||
$_cfgDir = if ($PSScriptRoot -match '[\\/](\.github|\.claude)[\\/]') { $Matches[1] } else { '.github' }
|
||||
|
||||
#region Console Output Helpers
|
||||
function Info { param([string]$Message) Write-Host $Message -ForegroundColor Cyan }
|
||||
function Warn { param([string]$Message) Write-Host $Message -ForegroundColor Yellow }
|
||||
function Err { param([string]$Message) Write-Host $Message -ForegroundColor Red }
|
||||
function Success { param([string]$Message) Write-Host $Message -ForegroundColor Green }
|
||||
#endregion
|
||||
|
||||
#region Repository Helpers
|
||||
function Get-RepoRoot {
|
||||
$root = git rev-parse --show-toplevel 2>$null
|
||||
if (-not $root) { throw 'Not inside a git repository.' }
|
||||
return (Resolve-Path $root).Path
|
||||
}
|
||||
|
||||
function Get-GeneratedFilesPath {
|
||||
param([string]$RepoRoot)
|
||||
return Join-Path $RepoRoot 'Generated Files'
|
||||
}
|
||||
|
||||
function Get-IssueReviewPath {
|
||||
param(
|
||||
[string]$RepoRoot,
|
||||
[int]$IssueNumber
|
||||
)
|
||||
$genFiles = Get-GeneratedFilesPath -RepoRoot $RepoRoot
|
||||
return Join-Path $genFiles "issueReview/$IssueNumber"
|
||||
}
|
||||
|
||||
function Get-IssueTitleFromOverview {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Extract issue title from existing overview.md file.
|
||||
.DESCRIPTION
|
||||
Parses the overview.md to get the issue title without requiring GitHub CLI.
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$OverviewPath
|
||||
)
|
||||
|
||||
if (-not (Test-Path $OverviewPath)) {
|
||||
return $null
|
||||
}
|
||||
|
||||
$content = Get-Content $OverviewPath -Raw
|
||||
|
||||
# Try to match title from Summary table: | **Title** | <title> |
|
||||
if ($content -match '\*\*Title\*\*\s*\|\s*([^|]+)\s*\|') {
|
||||
return $Matches[1].Trim()
|
||||
}
|
||||
|
||||
# Try to match from header: # Issue #XXXX: <title>
|
||||
if ($content -match '# Issue #\d+[:\s]+(.+)$' ) {
|
||||
return $Matches[1].Trim()
|
||||
}
|
||||
|
||||
# Try to match: # Issue #XXXX Review: <title>
|
||||
if ($content -match '# Issue #\d+ Review[:\s]+(.+)$') {
|
||||
return $Matches[1].Trim()
|
||||
}
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
function Ensure-DirectoryExists {
|
||||
param([string]$Path)
|
||||
if (-not (Test-Path $Path)) {
|
||||
New-Item -ItemType Directory -Path $Path -Force | Out-Null
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region GitHub Issue Query Helpers
|
||||
function Get-GitHubIssues {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Query GitHub issues by label, state, and sort order.
|
||||
.PARAMETER Labels
|
||||
Comma-separated list of labels to filter by (e.g., "bug,help wanted").
|
||||
.PARAMETER State
|
||||
Issue state: open, closed, or all. Default: open.
|
||||
.PARAMETER Sort
|
||||
Sort field: created, updated, comments, reactions. Default: created.
|
||||
.PARAMETER Order
|
||||
Sort order: asc or desc. Default: desc.
|
||||
.PARAMETER Limit
|
||||
Maximum number of issues to return. Default: 100.
|
||||
.PARAMETER Repository
|
||||
Repository in owner/repo format. Default: microsoft/PowerToys.
|
||||
#>
|
||||
param(
|
||||
[string]$Labels,
|
||||
[ValidateSet('open', 'closed', 'all')]
|
||||
[string]$State = 'open',
|
||||
[ValidateSet('created', 'updated', 'comments', 'reactions')]
|
||||
[string]$Sort = 'created',
|
||||
[ValidateSet('asc', 'desc')]
|
||||
[string]$Order = 'desc',
|
||||
[int]$Limit = 100,
|
||||
[string]$Repository = 'microsoft/PowerToys'
|
||||
)
|
||||
|
||||
$ghArgs = @('issue', 'list', '--repo', $Repository, '--state', $State, '--limit', $Limit)
|
||||
|
||||
if ($Labels) {
|
||||
foreach ($label in ($Labels -split ',')) {
|
||||
$ghArgs += @('--label', $label.Trim())
|
||||
}
|
||||
}
|
||||
|
||||
# Build JSON fields (use reactionGroups instead of reactions)
|
||||
$jsonFields = 'number,title,state,labels,createdAt,updatedAt,author,reactionGroups,comments'
|
||||
$ghArgs += @('--json', $jsonFields)
|
||||
|
||||
Info "Querying issues: gh $($ghArgs -join ' ')"
|
||||
$result = & gh @ghArgs 2>&1
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Failed to query issues: $result"
|
||||
}
|
||||
|
||||
$issues = $result | ConvertFrom-Json
|
||||
|
||||
# Sort by reactions if requested (gh CLI doesn't support this natively)
|
||||
if ($Sort -eq 'reactions') {
|
||||
$issues = $issues | ForEach-Object {
|
||||
# reactionGroups is an array of {content, users} - sum up user counts
|
||||
$totalReactions = ($_.reactionGroups | ForEach-Object { $_.users.totalCount } | Measure-Object -Sum).Sum
|
||||
if (-not $totalReactions) { $totalReactions = 0 }
|
||||
$_ | Add-Member -NotePropertyName 'totalReactions' -NotePropertyValue $totalReactions -PassThru
|
||||
}
|
||||
if ($Order -eq 'desc') {
|
||||
$issues = $issues | Sort-Object -Property totalReactions -Descending
|
||||
} else {
|
||||
$issues = $issues | Sort-Object -Property totalReactions
|
||||
}
|
||||
}
|
||||
|
||||
return $issues
|
||||
}
|
||||
|
||||
function Get-IssueDetails {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Get detailed information about a specific issue.
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[int]$IssueNumber,
|
||||
[string]$Repository = 'microsoft/PowerToys'
|
||||
)
|
||||
|
||||
$jsonFields = 'number,title,body,state,labels,createdAt,updatedAt,author,reactions,comments,linkedPullRequests,milestone'
|
||||
$result = gh issue view $IssueNumber --repo $Repository --json $jsonFields 2>&1
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Failed to get issue #$IssueNumber`: $result"
|
||||
}
|
||||
|
||||
return $result | ConvertFrom-Json
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region CLI Detection and Execution
|
||||
function Get-AvailableCLI {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Detect which AI CLI is available: GitHub Copilot CLI or Claude Code.
|
||||
.OUTPUTS
|
||||
Returns object with: Name, Command, PromptArg
|
||||
#>
|
||||
|
||||
# Check for standalone GitHub Copilot CLI (copilot command)
|
||||
$copilotCLI = Get-Command 'copilot' -ErrorAction SilentlyContinue
|
||||
if ($copilotCLI) {
|
||||
return @{
|
||||
Name = 'GitHub Copilot CLI'
|
||||
Command = 'copilot'
|
||||
Args = @('-p') # Non-interactive prompt mode
|
||||
Type = 'copilot'
|
||||
}
|
||||
}
|
||||
|
||||
# Check for Claude Code CLI
|
||||
$claudeCode = Get-Command 'claude' -ErrorAction SilentlyContinue
|
||||
if ($claudeCode) {
|
||||
return @{
|
||||
Name = 'Claude Code CLI'
|
||||
Command = 'claude'
|
||||
Args = @()
|
||||
Type = 'claude'
|
||||
}
|
||||
}
|
||||
|
||||
# Check for GitHub Copilot CLI via gh extension
|
||||
$ghCopilot = Get-Command 'gh' -ErrorAction SilentlyContinue
|
||||
if ($ghCopilot) {
|
||||
$copilotCheck = gh extension list 2>&1 | Select-String -Pattern 'copilot'
|
||||
if ($copilotCheck) {
|
||||
return @{
|
||||
Name = 'GitHub Copilot CLI (gh extension)'
|
||||
Command = 'gh'
|
||||
Args = @('copilot', 'suggest')
|
||||
Type = 'gh-copilot'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Check for VS Code CLI with Copilot
|
||||
$code = Get-Command 'code' -ErrorAction SilentlyContinue
|
||||
if ($code) {
|
||||
return @{
|
||||
Name = 'VS Code (Copilot Chat)'
|
||||
Command = 'code'
|
||||
Args = @()
|
||||
Type = 'vscode'
|
||||
}
|
||||
}
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
function Invoke-AIReview {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Invoke AI CLI to review a single issue.
|
||||
.PARAMETER IssueNumber
|
||||
The issue number to review.
|
||||
.PARAMETER RepoRoot
|
||||
Repository root path.
|
||||
.PARAMETER CLIType
|
||||
CLI type: 'claude', 'copilot', 'gh-copilot', or 'vscode'.
|
||||
.PARAMETER WorkingDirectory
|
||||
Working directory for the CLI command.
|
||||
.PARAMETER FeedbackContext
|
||||
Optional feedback from review-the-review to incorporate into the re-review.
|
||||
.PARAMETER Model
|
||||
Optional model override for Copilot CLI (e.g., claude-sonnet-4).
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[int]$IssueNumber,
|
||||
[Parameter(Mandatory)]
|
||||
[string]$RepoRoot,
|
||||
[ValidateSet('claude', 'copilot', 'gh-copilot', 'vscode')]
|
||||
[string]$CLIType = 'copilot',
|
||||
[string]$WorkingDirectory,
|
||||
[string]$FeedbackContext,
|
||||
[string]$Model
|
||||
)
|
||||
|
||||
if (-not $WorkingDirectory) {
|
||||
$WorkingDirectory = $RepoRoot
|
||||
}
|
||||
|
||||
$promptFile = Join-Path $RepoRoot "$_cfgDir/prompts/review-issue.prompt.md"
|
||||
if (-not (Test-Path $promptFile)) {
|
||||
throw "Prompt file not found: $promptFile"
|
||||
}
|
||||
|
||||
# Prepare the prompt with issue number substitution
|
||||
$promptContent = Get-Content $promptFile -Raw
|
||||
$promptContent = $promptContent -replace '\{\{issue_number\}\}', $IssueNumber
|
||||
|
||||
# Create temp prompt file
|
||||
$tempPromptDir = Join-Path $env:TEMP "issue-review-$IssueNumber"
|
||||
Ensure-DirectoryExists -Path $tempPromptDir
|
||||
$tempPromptFile = Join-Path $tempPromptDir "prompt.md"
|
||||
$promptContent | Set-Content -Path $tempPromptFile -Encoding UTF8
|
||||
|
||||
# Build the prompt text for CLI
|
||||
$promptText = "Review GitHub issue #$IssueNumber following the template in $_cfgDir/prompts/review-issue.prompt.md. Generate overview.md and implementation-plan.md in 'Generated Files/issueReview/$IssueNumber/'"
|
||||
|
||||
# Inject feedback from review-the-review if available
|
||||
if ($FeedbackContext) {
|
||||
$promptText += @"
|
||||
|
||||
IMPORTANT: This is a RE-REVIEW. A previous review was rejected by the quality gate. You MUST address ALL the corrective feedback below. Read the feedback carefully and fix every issue identified.
|
||||
|
||||
=== CORRECTIVE FEEDBACK FROM REVIEW-THE-REVIEW ===
|
||||
$FeedbackContext
|
||||
=== END FEEDBACK ===
|
||||
|
||||
Pay special attention to:
|
||||
1. Score corrections — adjust scores to match the evidence cited in the feedback
|
||||
2. File path corrections — verify all paths exist before including them
|
||||
3. Pattern corrections — use the patterns identified as correct in the feedback
|
||||
4. Missing coverage — add any sections flagged as missing
|
||||
5. Task breakdown fixes — make tasks specific and executable
|
||||
"@
|
||||
}
|
||||
|
||||
switch ($CLIType) {
|
||||
'copilot' {
|
||||
# GitHub Copilot CLI (standalone copilot command)
|
||||
# Use --yolo for full permissions (--allow-all-tools --allow-all-paths --allow-all-urls)
|
||||
# Use -s (silent) for cleaner output in batch mode
|
||||
# Enable ALL GitHub MCP tools (issues, PRs, repos, etc.) + github-artifacts for images/attachments
|
||||
# MCP config path relative to repo root for github-artifacts tools
|
||||
$mcpConfig = "@$_cfgDir/skills/issue-review/references/mcp-config.json"
|
||||
$args = @(
|
||||
'--additional-mcp-config', $mcpConfig, # Load github-artifacts MCP for image/attachment analysis
|
||||
'-p', $promptText, # Non-interactive prompt mode (exits after completion)
|
||||
'--yolo', # Enable all permissions for automated execution
|
||||
'-s', # Silent mode - output only agent response
|
||||
'--enable-all-github-mcp-tools', # Enable ALL GitHub MCP tools (issues, PRs, search, etc.)
|
||||
'--allow-tool', 'github-artifacts', # Also enable our custom github-artifacts MCP
|
||||
'--agent', 'ReviewIssue'
|
||||
)
|
||||
if ($Model) {
|
||||
$args += @('--model', $Model)
|
||||
}
|
||||
|
||||
return @{
|
||||
Command = 'copilot'
|
||||
Arguments = $args
|
||||
WorkingDirectory = $WorkingDirectory
|
||||
IssueNumber = $IssueNumber
|
||||
}
|
||||
}
|
||||
'claude' {
|
||||
# Claude Code CLI
|
||||
$args = @(
|
||||
'--print', # Non-interactive mode
|
||||
'--dangerously-skip-permissions',
|
||||
'--agent', 'ReviewIssue',
|
||||
'--prompt', $promptText
|
||||
)
|
||||
|
||||
return @{
|
||||
Command = 'claude'
|
||||
Arguments = $args
|
||||
WorkingDirectory = $WorkingDirectory
|
||||
IssueNumber = $IssueNumber
|
||||
}
|
||||
}
|
||||
'gh-copilot' {
|
||||
# GitHub Copilot CLI via gh
|
||||
$args = @(
|
||||
'copilot', 'suggest',
|
||||
'-t', 'shell',
|
||||
"Review GitHub issue #$IssueNumber and generate analysis files"
|
||||
)
|
||||
|
||||
return @{
|
||||
Command = 'gh'
|
||||
Arguments = $args
|
||||
WorkingDirectory = $WorkingDirectory
|
||||
IssueNumber = $IssueNumber
|
||||
}
|
||||
}
|
||||
'vscode' {
|
||||
# VS Code with Copilot - open with prompt
|
||||
$args = @(
|
||||
'--new-window',
|
||||
$WorkingDirectory,
|
||||
'--goto', $tempPromptFile
|
||||
)
|
||||
|
||||
return @{
|
||||
Command = 'code'
|
||||
Arguments = $args
|
||||
WorkingDirectory = $WorkingDirectory
|
||||
IssueNumber = $IssueNumber
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Parallel Job Management
|
||||
function Start-ParallelIssueReviews {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Start parallel issue reviews with throttling.
|
||||
.PARAMETER Issues
|
||||
Array of issue objects to review.
|
||||
.PARAMETER MaxConcurrent
|
||||
Maximum number of parallel jobs. Default: 20.
|
||||
.PARAMETER CLIType
|
||||
CLI type to use for reviews.
|
||||
.PARAMETER RepoRoot
|
||||
Repository root path.
|
||||
.PARAMETER TimeoutMinutes
|
||||
Timeout per issue in minutes. Default: 30.
|
||||
.PARAMETER MaxRetryCount
|
||||
Maximum number of retries for failed issues. Default: 2.
|
||||
.PARAMETER RetryDelaySeconds
|
||||
Delay between retries in seconds. Default: 10.
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[array]$Issues,
|
||||
[int]$MaxConcurrent = 20,
|
||||
[ValidateSet('claude', 'copilot', 'gh-copilot', 'vscode')]
|
||||
[string]$CLIType = 'copilot',
|
||||
[Parameter(Mandatory)]
|
||||
[string]$RepoRoot,
|
||||
[int]$TimeoutMinutes = 30,
|
||||
[int]$MaxRetryCount = 2,
|
||||
[int]$RetryDelaySeconds = 10,
|
||||
[string]$FeedbackContext,
|
||||
[string]$Model
|
||||
)
|
||||
|
||||
$totalIssues = $Issues.Count
|
||||
$completed = 0
|
||||
$failed = @()
|
||||
$succeeded = @()
|
||||
$retryQueue = [System.Collections.Queue]::new()
|
||||
|
||||
Info "Starting parallel review of $totalIssues issues (max $MaxConcurrent concurrent, $MaxRetryCount retries)"
|
||||
|
||||
# Use PowerShell jobs for parallelization
|
||||
$jobs = @()
|
||||
$issueQueue = [System.Collections.Queue]::new($Issues)
|
||||
|
||||
while ($issueQueue.Count -gt 0 -or $jobs.Count -gt 0 -or $retryQueue.Count -gt 0) {
|
||||
# Process retry queue when main queue is empty
|
||||
if ($issueQueue.Count -eq 0 -and $retryQueue.Count -gt 0 -and $jobs.Count -lt $MaxConcurrent) {
|
||||
$retryItem = $retryQueue.Dequeue()
|
||||
Warn "🔄 Retrying issue #$($retryItem.IssueNumber) (attempt $($retryItem.Attempt + 1)/$($MaxRetryCount + 1))"
|
||||
Start-Sleep -Seconds $RetryDelaySeconds
|
||||
$issueQueue.Enqueue(@{ number = $retryItem.IssueNumber; _retryAttempt = $retryItem.Attempt + 1 })
|
||||
}
|
||||
|
||||
# Start new jobs up to MaxParallel
|
||||
while ($jobs.Count -lt $MaxConcurrent -and $issueQueue.Count -gt 0) {
|
||||
$issue = $issueQueue.Dequeue()
|
||||
$issueNum = $issue.number
|
||||
$retryAttempt = if ($issue._retryAttempt) { $issue._retryAttempt } else { 0 }
|
||||
|
||||
$attemptInfo = if ($retryAttempt -gt 0) { " (retry $retryAttempt)" } else { "" }
|
||||
Info "Starting review for issue #$issueNum$attemptInfo ($($totalIssues - $issueQueue.Count)/$totalIssues)"
|
||||
|
||||
$job = Start-Job -Name "Issue-$issueNum" -ScriptBlock {
|
||||
param($IssueNumber, $RepoRoot, $CLIType, $FeedbackCtx, $ModelOverride)
|
||||
|
||||
Set-Location $RepoRoot
|
||||
|
||||
# Import the library in the job context
|
||||
. "$RepoRoot/.github/review-tools/IssueReviewLib.ps1"
|
||||
|
||||
try {
|
||||
$reviewParams = @{
|
||||
IssueNumber = $IssueNumber
|
||||
RepoRoot = $RepoRoot
|
||||
CLIType = $CLIType
|
||||
}
|
||||
if ($FeedbackCtx) {
|
||||
$reviewParams.FeedbackContext = $FeedbackCtx
|
||||
}
|
||||
if ($ModelOverride) {
|
||||
$reviewParams.Model = $ModelOverride
|
||||
}
|
||||
$reviewCmd = Invoke-AIReview @reviewParams
|
||||
|
||||
# Execute the command using invocation operator (works for .ps1 scripts and executables)
|
||||
Set-Location $reviewCmd.WorkingDirectory
|
||||
$argList = $reviewCmd.Arguments
|
||||
|
||||
# Capture both stdout and stderr for better error reporting
|
||||
$output = & $reviewCmd.Command @argList 2>&1
|
||||
$exitCode = $LASTEXITCODE
|
||||
|
||||
# Get last 20 lines of output for error context
|
||||
$outputLines = $output | Out-String
|
||||
$lastLines = ($outputLines -split "`n" | Select-Object -Last 20) -join "`n"
|
||||
|
||||
# Check if output files were created (success indicator)
|
||||
$overviewPath = Join-Path $RepoRoot "Generated Files/issueReview/$IssueNumber/overview.md"
|
||||
$implPlanPath = Join-Path $RepoRoot "Generated Files/issueReview/$IssueNumber/implementation-plan.md"
|
||||
$filesCreated = (Test-Path $overviewPath) -and (Test-Path $implPlanPath)
|
||||
|
||||
return @{
|
||||
IssueNumber = $IssueNumber
|
||||
Success = ($exitCode -eq 0) -or $filesCreated
|
||||
ExitCode = $exitCode
|
||||
FilesCreated = $filesCreated
|
||||
Output = $lastLines
|
||||
Error = if ($exitCode -ne 0 -and -not $filesCreated) { "Exit code: $exitCode`n$lastLines" } else { $null }
|
||||
}
|
||||
}
|
||||
catch {
|
||||
return @{
|
||||
IssueNumber = $IssueNumber
|
||||
Success = $false
|
||||
ExitCode = -1
|
||||
FilesCreated = $false
|
||||
Output = $null
|
||||
Error = $_.Exception.Message
|
||||
}
|
||||
}
|
||||
} -ArgumentList $issueNum, $RepoRoot, $CLIType, $FeedbackContext, $Model
|
||||
|
||||
$jobs += @{
|
||||
Job = $job
|
||||
IssueNumber = $issueNum
|
||||
StartTime = Get-Date
|
||||
RetryAttempt = $retryAttempt
|
||||
}
|
||||
}
|
||||
|
||||
# Check for completed jobs
|
||||
$completedJobs = @()
|
||||
foreach ($jobInfo in $jobs) {
|
||||
$job = $jobInfo.Job
|
||||
$issueNum = $jobInfo.IssueNumber
|
||||
$startTime = $jobInfo.StartTime
|
||||
$retryAttempt = $jobInfo.RetryAttempt
|
||||
|
||||
if ($job.State -eq 'Completed') {
|
||||
$result = Receive-Job -Job $job
|
||||
Remove-Job -Job $job -Force
|
||||
|
||||
if ($result.Success) {
|
||||
Success "✓ Issue #$issueNum completed (files created: $($result.FilesCreated))"
|
||||
$succeeded += $issueNum
|
||||
$completed++
|
||||
} else {
|
||||
# Check if we should retry
|
||||
if ($retryAttempt -lt $MaxRetryCount) {
|
||||
$errorPreview = if ($result.Error) { ($result.Error -split "`n" | Select-Object -First 3) -join " | " } else { "Unknown error" }
|
||||
Warn "⚠ Issue #$issueNum failed (will retry): $errorPreview"
|
||||
$retryQueue.Enqueue(@{ IssueNumber = $issueNum; Attempt = $retryAttempt; LastError = $result.Error })
|
||||
} else {
|
||||
$errorMsg = if ($result.Error) { $result.Error } else { "Exit code: $($result.ExitCode)" }
|
||||
Err "✗ Issue #$issueNum failed after $($retryAttempt + 1) attempts:"
|
||||
Err " Error: $errorMsg"
|
||||
$failed += @{ IssueNumber = $issueNum; Error = $errorMsg; Attempts = $retryAttempt + 1 }
|
||||
$completed++
|
||||
}
|
||||
}
|
||||
$completedJobs += $jobInfo
|
||||
}
|
||||
elseif ($job.State -eq 'Failed') {
|
||||
$jobError = $job.ChildJobs[0].JobStateInfo.Reason.Message
|
||||
Remove-Job -Job $job -Force
|
||||
|
||||
if ($retryAttempt -lt $MaxRetryCount) {
|
||||
Warn "⚠ Issue #$issueNum job crashed (will retry): $jobError"
|
||||
$retryQueue.Enqueue(@{ IssueNumber = $issueNum; Attempt = $retryAttempt; LastError = $jobError })
|
||||
} else {
|
||||
Err "✗ Issue #$issueNum job failed after $($retryAttempt + 1) attempts: $jobError"
|
||||
$failed += @{ IssueNumber = $issueNum; Error = $jobError; Attempts = $retryAttempt + 1 }
|
||||
$completed++
|
||||
}
|
||||
$completedJobs += $jobInfo
|
||||
}
|
||||
elseif ((Get-Date) - $startTime -gt [TimeSpan]::FromMinutes($TimeoutMinutes)) {
|
||||
Stop-Job -Job $job -ErrorAction SilentlyContinue
|
||||
Remove-Job -Job $job -Force
|
||||
|
||||
if ($retryAttempt -lt $MaxRetryCount) {
|
||||
Warn "⏱ Issue #$issueNum timed out after $TimeoutMinutes min (will retry)"
|
||||
$retryQueue.Enqueue(@{ IssueNumber = $issueNum; Attempt = $retryAttempt; LastError = "Timeout after $TimeoutMinutes minutes" })
|
||||
} else {
|
||||
Err "⏱ Issue #$issueNum timed out after $($retryAttempt + 1) attempts"
|
||||
$failed += @{ IssueNumber = $issueNum; Error = "Timeout after $TimeoutMinutes minutes"; Attempts = $retryAttempt + 1 }
|
||||
$completed++
|
||||
}
|
||||
$completedJobs += $jobInfo
|
||||
}
|
||||
}
|
||||
|
||||
# Remove completed jobs from active list
|
||||
$jobs = $jobs | Where-Object { $_ -notin $completedJobs }
|
||||
|
||||
# Brief pause to avoid tight loop
|
||||
if ($jobs.Count -gt 0) {
|
||||
Start-Sleep -Seconds 2
|
||||
}
|
||||
}
|
||||
|
||||
# Extract just issue numbers for the failed list
|
||||
$failedNumbers = $failed | ForEach-Object { $_.IssueNumber }
|
||||
|
||||
return @{
|
||||
Total = $totalIssues
|
||||
Succeeded = $succeeded
|
||||
Failed = $failedNumbers
|
||||
FailedDetails = $failed
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Issue Review Results Helpers
|
||||
function Get-IssueReviewResult {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Check if an issue has been reviewed and get its results.
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[int]$IssueNumber,
|
||||
[Parameter(Mandatory)]
|
||||
[string]$RepoRoot
|
||||
)
|
||||
|
||||
$reviewPath = Get-IssueReviewPath -RepoRoot $RepoRoot -IssueNumber $IssueNumber
|
||||
|
||||
$result = @{
|
||||
IssueNumber = $IssueNumber
|
||||
Path = $reviewPath
|
||||
HasOverview = $false
|
||||
HasImplementationPlan = $false
|
||||
OverviewPath = $null
|
||||
ImplementationPlanPath = $null
|
||||
}
|
||||
|
||||
$overviewPath = Join-Path $reviewPath 'overview.md'
|
||||
$implPlanPath = Join-Path $reviewPath 'implementation-plan.md'
|
||||
|
||||
if (Test-Path $overviewPath) {
|
||||
$result.HasOverview = $true
|
||||
$result.OverviewPath = $overviewPath
|
||||
}
|
||||
|
||||
if (Test-Path $implPlanPath) {
|
||||
$result.HasImplementationPlan = $true
|
||||
$result.ImplementationPlanPath = $implPlanPath
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
|
||||
function Get-HighConfidenceIssues {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Find issues with high confidence for auto-fix based on review results.
|
||||
.PARAMETER RepoRoot
|
||||
Repository root path.
|
||||
.PARAMETER MinFeasibilityScore
|
||||
Minimum Technical Feasibility score (0-100). Default: 70.
|
||||
.PARAMETER MinClarityScore
|
||||
Minimum Requirement Clarity score (0-100). Default: 60.
|
||||
.PARAMETER MaxEffortDays
|
||||
Maximum effort estimate in days. Default: 2 (S = Small).
|
||||
.PARAMETER FilterIssueNumbers
|
||||
Optional array of issue numbers to filter to. If specified, only these issues are considered.
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$RepoRoot,
|
||||
[int]$MinFeasibilityScore = 70,
|
||||
[int]$MinClarityScore = 60,
|
||||
[int]$MaxEffortDays = 2,
|
||||
[int[]]$FilterIssueNumbers = @()
|
||||
)
|
||||
|
||||
$genFiles = Get-GeneratedFilesPath -RepoRoot $RepoRoot
|
||||
$reviewDir = Join-Path $genFiles 'issueReview'
|
||||
|
||||
if (-not (Test-Path $reviewDir)) {
|
||||
return @()
|
||||
}
|
||||
|
||||
$highConfidence = @()
|
||||
|
||||
Get-ChildItem -Path $reviewDir -Directory | ForEach-Object {
|
||||
$issueNum = [int]$_.Name
|
||||
|
||||
# Skip if filter is specified and this issue is not in the filter list
|
||||
if ($FilterIssueNumbers.Count -gt 0 -and $issueNum -notin $FilterIssueNumbers) {
|
||||
return
|
||||
}
|
||||
|
||||
$overviewPath = Join-Path $_.FullName 'overview.md'
|
||||
$implPlanPath = Join-Path $_.FullName 'implementation-plan.md'
|
||||
|
||||
if (-not (Test-Path $overviewPath) -or -not (Test-Path $implPlanPath)) {
|
||||
return
|
||||
}
|
||||
|
||||
# Parse overview.md to extract scores
|
||||
$overview = Get-Content $overviewPath -Raw
|
||||
|
||||
# Extract scores using regex (looking for score table or inline scores)
|
||||
$feasibility = 0
|
||||
$clarity = 0
|
||||
$effortDays = 999
|
||||
|
||||
# Try to extract from At-a-Glance Score Table
|
||||
if ($overview -match 'Technical Feasibility[^\d]*(\d+)/100') {
|
||||
$feasibility = [int]$Matches[1]
|
||||
}
|
||||
if ($overview -match 'Requirement Clarity[^\d]*(\d+)/100') {
|
||||
$clarity = [int]$Matches[1]
|
||||
}
|
||||
# Match effort formats like "0.5-1 day", "1-2 days", "2-3 days" - extract the upper bound
|
||||
if ($overview -match 'Effort Estimate[^|]*\|\s*[\d.]+(?:-(\d+))?\s*days?') {
|
||||
if ($Matches[1]) {
|
||||
$effortDays = [int]$Matches[1]
|
||||
} elseif ($overview -match 'Effort Estimate[^|]*\|\s*(\d+)\s*days?') {
|
||||
$effortDays = [int]$Matches[1]
|
||||
}
|
||||
}
|
||||
# Also check for XS/S sizing in the table (e.g., "| XS |" or "| S |" or "(XS)" or "(S)")
|
||||
if ($overview -match 'Effort Estimate[^|]*\|[^|]*\|\s*(XS|S)\b') {
|
||||
# XS = 1 day, S = 2 days
|
||||
if ($Matches[1] -eq 'XS') {
|
||||
$effortDays = 1
|
||||
} else {
|
||||
$effortDays = 2
|
||||
}
|
||||
} elseif ($overview -match 'Effort Estimate[^|]*\|[^|]*\(XS\)') {
|
||||
$effortDays = 1
|
||||
} elseif ($overview -match 'Effort Estimate[^|]*\|[^|]*\(S\)') {
|
||||
$effortDays = 2
|
||||
}
|
||||
|
||||
if ($feasibility -ge $MinFeasibilityScore -and
|
||||
$clarity -ge $MinClarityScore -and
|
||||
$effortDays -le $MaxEffortDays) {
|
||||
|
||||
$highConfidence += @{
|
||||
IssueNumber = $issueNum
|
||||
FeasibilityScore = $feasibility
|
||||
ClarityScore = $clarity
|
||||
EffortDays = $effortDays
|
||||
OverviewPath = $overviewPath
|
||||
ImplementationPlanPath = $implPlanPath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $highConfidence | Sort-Object -Property FeasibilityScore -Descending
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Worktree Integration
|
||||
function Copy-IssueReviewToWorktree {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Copy the Generated Files for an issue to a worktree.
|
||||
.PARAMETER IssueNumber
|
||||
The issue number.
|
||||
.PARAMETER SourceRepoRoot
|
||||
Source repository root (main repo).
|
||||
.PARAMETER WorktreePath
|
||||
Destination worktree path.
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[int]$IssueNumber,
|
||||
[Parameter(Mandatory)]
|
||||
[string]$SourceRepoRoot,
|
||||
[Parameter(Mandatory)]
|
||||
[string]$WorktreePath
|
||||
)
|
||||
|
||||
$sourceReviewPath = Get-IssueReviewPath -RepoRoot $SourceRepoRoot -IssueNumber $IssueNumber
|
||||
$destReviewPath = Get-IssueReviewPath -RepoRoot $WorktreePath -IssueNumber $IssueNumber
|
||||
|
||||
if (-not (Test-Path $sourceReviewPath)) {
|
||||
throw "Issue review files not found at: $sourceReviewPath"
|
||||
}
|
||||
|
||||
Ensure-DirectoryExists -Path $destReviewPath
|
||||
|
||||
# Copy all files from the issue review folder
|
||||
Copy-Item -Path "$sourceReviewPath\*" -Destination $destReviewPath -Recurse -Force
|
||||
|
||||
Info "Copied issue review files to: $destReviewPath"
|
||||
|
||||
return $destReviewPath
|
||||
}
|
||||
#endregion
|
||||
|
||||
# Note: This script is dot-sourced, not imported as a module.
|
||||
# All functions above are available after: . "path/to/IssueReviewLib.ps1"
|
||||
@@ -1,298 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Orchestrate the feedback loop: re-run issue-review with corrections, then re-review.
|
||||
|
||||
.DESCRIPTION
|
||||
For each issue whose review-review score is below the threshold:
|
||||
1. Re-run issue-review with the corrective feedback from reviewTheReview.md
|
||||
2. Re-run review-review on the updated review files
|
||||
3. Repeat up to MaxIterations times or until the score passes
|
||||
|
||||
.PARAMETER ThrottleLimit
|
||||
Maximum parallel tasks. Default: 3.
|
||||
|
||||
.PARAMETER QualityThreshold
|
||||
Score threshold for PASS. Default: 90.
|
||||
|
||||
.PARAMETER MaxIterations
|
||||
Maximum feedback loop iterations per issue. Default: 3.
|
||||
|
||||
.PARAMETER CLIType
|
||||
AI CLI type (copilot/claude). Default: copilot.
|
||||
|
||||
.PARAMETER Model
|
||||
Copilot CLI model override (e.g., claude-sonnet-4).
|
||||
|
||||
.PARAMETER IssueNumbers
|
||||
Optional: specific issue numbers to process. If omitted, processes all issues with needsReReview=true.
|
||||
|
||||
.PARAMETER Force
|
||||
Skip confirmation prompts.
|
||||
|
||||
.EXAMPLE
|
||||
./Start-FeedbackLoop.ps1 -CLIType copilot -Model claude-sonnet-4 -ThrottleLimit 3 -Force
|
||||
|
||||
.EXAMPLE
|
||||
# Process specific issues only
|
||||
./Start-FeedbackLoop.ps1 -IssueNumbers @(1929, 1934) -CLIType copilot -Model claude-sonnet-4 -Force
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[int]$ThrottleLimit = 3,
|
||||
|
||||
[int]$QualityThreshold = 90,
|
||||
|
||||
[int]$MaxIterations = 3,
|
||||
|
||||
[ValidateSet('copilot', 'claude')]
|
||||
[string]$CLIType = 'copilot',
|
||||
|
||||
[string]$Model,
|
||||
|
||||
[int[]]$IssueNumbers,
|
||||
|
||||
[switch]$Force
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Continue'
|
||||
|
||||
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot '..\..\..\..')
|
||||
|
||||
# Resolve config directory name (.github or .claude) from script location
|
||||
$_cfgDir = if ($PSScriptRoot -match '[\\/](\.github|\.claude)[\\/]') { $Matches[1] } else { '.github' }
|
||||
$genFiles = Join-Path $repoRoot 'Generated Files'
|
||||
$reviewReviewDir = Join-Path $genFiles 'issueReviewReview'
|
||||
$issueReviewDir = Join-Path $genFiles 'issueReview'
|
||||
|
||||
$bulkReviewScript = Join-Path $repoRoot "$_cfgDir\skills\issue-review\scripts\Start-BulkIssueReview.ps1"
|
||||
$reviewReviewScript = Join-Path $repoRoot "$_cfgDir\skills\issue-review-review\scripts\Start-IssueReviewReview.ps1"
|
||||
|
||||
Write-Host "=== FEEDBACK LOOP ORCHESTRATOR ===" -ForegroundColor Cyan
|
||||
Write-Host "Repository root: $repoRoot"
|
||||
Write-Host "Quality threshold: $QualityThreshold"
|
||||
Write-Host "Max iterations: $MaxIterations"
|
||||
Write-Host "Throttle limit: $ThrottleLimit"
|
||||
Write-Host "CLI: $CLIType $(if ($Model) { "(model: $Model)" })"
|
||||
Write-Host ""
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Step 1: Identify issues that need re-review
|
||||
# ------------------------------------------------------------------
|
||||
if ($IssueNumbers -and $IssueNumbers.Count -gt 0) {
|
||||
# Use explicit list
|
||||
$needsWork = $IssueNumbers | ForEach-Object {
|
||||
$signalPath = Join-Path $reviewReviewDir "$_\.signal"
|
||||
if (Test-Path $signalPath) {
|
||||
$signal = Get-Content $signalPath -Raw | ConvertFrom-Json
|
||||
[PSCustomObject]@{
|
||||
IssueNumber = $_
|
||||
CurrentScore = [int]$signal.qualityScore
|
||||
Iteration = [int]$signal.iteration
|
||||
FeedbackFile = Join-Path $reviewReviewDir "$_\reviewTheReview.md"
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Host " Warning: No signal for issue #$_ — skipping" -ForegroundColor Yellow
|
||||
}
|
||||
} | Where-Object { $_ }
|
||||
}
|
||||
else {
|
||||
# Auto-discover from signals with needsReReview = true
|
||||
$needsWork = Get-ChildItem $reviewReviewDir -Directory -ErrorAction SilentlyContinue |
|
||||
Where-Object { Test-Path (Join-Path $_.FullName '.signal') } |
|
||||
ForEach-Object {
|
||||
$signal = Get-Content (Join-Path $_.FullName '.signal') -Raw | ConvertFrom-Json
|
||||
if ($signal.needsReReview -eq $true -and [int]$signal.iteration -lt $MaxIterations) {
|
||||
[PSCustomObject]@{
|
||||
IssueNumber = [int]$signal.issueNumber
|
||||
CurrentScore = [int]$signal.qualityScore
|
||||
Iteration = [int]$signal.iteration
|
||||
FeedbackFile = Join-Path $_.FullName 'reviewTheReview.md'
|
||||
}
|
||||
}
|
||||
} | Sort-Object IssueNumber
|
||||
}
|
||||
|
||||
if (-not $needsWork -or $needsWork.Count -eq 0) {
|
||||
Write-Host "No issues need re-review. All passed or reached max iterations." -ForegroundColor Green
|
||||
return
|
||||
}
|
||||
|
||||
Write-Host "Issues needing feedback loop: $($needsWork.Count)" -ForegroundColor Yellow
|
||||
Write-Host ("-" * 70)
|
||||
$needsWork | Format-Table IssueNumber, CurrentScore, Iteration -AutoSize | Out-String | Write-Host
|
||||
Write-Host ("-" * 70)
|
||||
|
||||
if (-not $Force) {
|
||||
$confirm = Read-Host "Proceed with feedback loop for $($needsWork.Count) issues? (y/N)"
|
||||
if ($confirm -notmatch '^[yY]') {
|
||||
Write-Host "Cancelled."
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Step 2: Run feedback loop in parallel
|
||||
# ------------------------------------------------------------------
|
||||
$startTime = Get-Date
|
||||
|
||||
$results = $needsWork | ForEach-Object -Parallel {
|
||||
$item = $PSItem
|
||||
$repoRoot = $using:repoRoot
|
||||
$bulkScript = $using:bulkReviewScript
|
||||
$reviewScript = $using:reviewReviewScript
|
||||
$cliType = $using:CLIType
|
||||
$model = $using:Model
|
||||
$qualityThreshold = $using:QualityThreshold
|
||||
$maxIter = $using:MaxIterations
|
||||
|
||||
Set-Location $repoRoot
|
||||
|
||||
$issueNum = $item.IssueNumber
|
||||
$currentScore = $item.CurrentScore
|
||||
$currentIter = $item.Iteration
|
||||
$feedbackFile = $item.FeedbackFile
|
||||
|
||||
Write-Host "[#$issueNum] Starting feedback loop (current score: $currentScore, iteration: $currentIter)" -ForegroundColor Cyan
|
||||
|
||||
# Phase A: Re-run issue-review with corrective feedback
|
||||
Write-Host "[#$issueNum] Phase A: Re-running issue-review with feedback..." -ForegroundColor Yellow
|
||||
$bulkParams = @{
|
||||
IssueNumber = $issueNum
|
||||
CLIType = $cliType
|
||||
Force = $true
|
||||
}
|
||||
if ($model) { $bulkParams.Model = $model }
|
||||
if (Test-Path $feedbackFile) {
|
||||
$bulkParams.FeedbackFile = $feedbackFile
|
||||
}
|
||||
|
||||
try {
|
||||
& $bulkScript @bulkParams 2>&1 | ForEach-Object { Write-Host "[#$issueNum] $_" }
|
||||
}
|
||||
catch {
|
||||
Write-Host "[#$issueNum] Phase A error: $($_.Exception.Message)" -ForegroundColor Red
|
||||
return [PSCustomObject]@{
|
||||
IssueNumber = $issueNum
|
||||
OldScore = $currentScore
|
||||
NewScore = 0
|
||||
Iteration = $currentIter
|
||||
Status = 'FAILED_REVIEW'
|
||||
Error = $_.Exception.Message
|
||||
}
|
||||
}
|
||||
|
||||
# Phase B: Re-run review-review on the updated files
|
||||
Write-Host "[#$issueNum] Phase B: Re-running review-review..." -ForegroundColor Yellow
|
||||
$rrParams = @{
|
||||
IssueNumber = $issueNum
|
||||
CLIType = $cliType
|
||||
Force = $true
|
||||
}
|
||||
if ($model) { $rrParams.Model = $model }
|
||||
|
||||
try {
|
||||
& $reviewScript @rrParams 2>&1 | ForEach-Object { Write-Host "[#$issueNum] $_" }
|
||||
}
|
||||
catch {
|
||||
Write-Host "[#$issueNum] Phase B error: $($_.Exception.Message)" -ForegroundColor Red
|
||||
return [PSCustomObject]@{
|
||||
IssueNumber = $issueNum
|
||||
OldScore = $currentScore
|
||||
NewScore = 0
|
||||
Iteration = $currentIter + 1
|
||||
Status = 'FAILED_REVIEW_REVIEW'
|
||||
Error = $_.Exception.Message
|
||||
}
|
||||
}
|
||||
|
||||
# Read updated signal
|
||||
$signalPath = Join-Path $using:reviewReviewDir "$issueNum\.signal"
|
||||
if (Test-Path $signalPath) {
|
||||
$newSignal = Get-Content $signalPath -Raw | ConvertFrom-Json
|
||||
$newScore = [int]$newSignal.qualityScore
|
||||
$newIter = [int]$newSignal.iteration
|
||||
$verdict = $newSignal.verdict
|
||||
|
||||
$status = if ($newScore -ge $qualityThreshold) { 'IMPROVED_TO_PASS' }
|
||||
elseif ($newScore -gt $currentScore) { 'IMPROVED' }
|
||||
elseif ($newScore -eq $currentScore) { 'NO_CHANGE' }
|
||||
else { 'REGRESSED' }
|
||||
|
||||
Write-Host "[#$issueNum] Done: $currentScore → $newScore ($status)" -ForegroundColor $(
|
||||
if ($status -eq 'IMPROVED_TO_PASS') { 'Green' }
|
||||
elseif ($status -eq 'IMPROVED') { 'Yellow' }
|
||||
else { 'Red' }
|
||||
)
|
||||
|
||||
[PSCustomObject]@{
|
||||
IssueNumber = $issueNum
|
||||
OldScore = $currentScore
|
||||
NewScore = $newScore
|
||||
Iteration = $newIter
|
||||
Status = $status
|
||||
Verdict = $verdict
|
||||
}
|
||||
}
|
||||
else {
|
||||
[PSCustomObject]@{
|
||||
IssueNumber = $issueNum
|
||||
OldScore = $currentScore
|
||||
NewScore = 0
|
||||
Iteration = $currentIter + 1
|
||||
Status = 'NO_SIGNAL'
|
||||
Error = 'No signal file after review-review'
|
||||
}
|
||||
}
|
||||
} -ThrottleLimit $ThrottleLimit
|
||||
|
||||
$duration = (Get-Date) - $startTime
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Step 3: Summary
|
||||
# ------------------------------------------------------------------
|
||||
Write-Host ""
|
||||
Write-Host ("=" * 70) -ForegroundColor Cyan
|
||||
Write-Host " FEEDBACK LOOP SUMMARY" -ForegroundColor Cyan
|
||||
Write-Host ("=" * 70) -ForegroundColor Cyan
|
||||
|
||||
$improved = @($results | Where-Object Status -eq 'IMPROVED_TO_PASS')
|
||||
$partial = @($results | Where-Object Status -eq 'IMPROVED')
|
||||
$noChange = @($results | Where-Object Status -eq 'NO_CHANGE')
|
||||
$regressed = @($results | Where-Object Status -eq 'REGRESSED')
|
||||
$errors = @($results | Where-Object { $_.Status -like 'FAILED*' -or $_.Status -eq 'NO_SIGNAL' })
|
||||
|
||||
Write-Host "Total processed: $($results.Count)"
|
||||
Write-Host "Improved to PASS: $($improved.Count)" -ForegroundColor Green
|
||||
Write-Host "Improved (below): $($partial.Count)" -ForegroundColor Yellow
|
||||
Write-Host "No change: $($noChange.Count)" -ForegroundColor DarkYellow
|
||||
Write-Host "Regressed: $($regressed.Count)" -ForegroundColor Red
|
||||
Write-Host "Errors: $($errors.Count)" -ForegroundColor Red
|
||||
Write-Host "Duration: $($duration.ToString('hh\:mm\:ss'))"
|
||||
Write-Host ("=" * 70) -ForegroundColor Cyan
|
||||
|
||||
# Show details
|
||||
if ($results.Count -gt 0) {
|
||||
Write-Host ""
|
||||
Write-Host "Details:" -ForegroundColor White
|
||||
$results | Sort-Object NewScore -Descending | Format-Table IssueNumber, OldScore, NewScore, Status, Iteration -AutoSize | Out-String | Write-Host
|
||||
}
|
||||
|
||||
# Count remaining issues that still need work
|
||||
$stillNeedsWork = Get-ChildItem $reviewReviewDir -Directory -ErrorAction SilentlyContinue |
|
||||
Where-Object { Test-Path (Join-Path $_.FullName '.signal') } |
|
||||
ForEach-Object {
|
||||
$signal = Get-Content (Join-Path $_.FullName '.signal') -Raw | ConvertFrom-Json
|
||||
if ($signal.needsReReview -eq $true -and [int]$signal.iteration -lt $MaxIterations) { $signal }
|
||||
}
|
||||
|
||||
if ($stillNeedsWork.Count -gt 0) {
|
||||
Write-Host "`nStill needs improvement: $($stillNeedsWork.Count) issues" -ForegroundColor Yellow
|
||||
Write-Host "Run this script again for another iteration." -ForegroundColor Yellow
|
||||
}
|
||||
else {
|
||||
Write-Host "`nAll issues have either passed or reached max iterations!" -ForegroundColor Green
|
||||
}
|
||||
|
||||
# Return results for pipeline
|
||||
return $results
|
||||
@@ -1,327 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Meta-review of issue-review outputs to validate scoring and implementation plan quality.
|
||||
|
||||
.DESCRIPTION
|
||||
Reads the existing overview.md and implementation-plan.md from issue-review,
|
||||
cross-checks scores against evidence, validates file paths and patterns,
|
||||
and produces a reviewTheReview.md with a quality score (0-100).
|
||||
|
||||
If the quality score is < 90, the signal file indicates that issue-review
|
||||
should re-run with the feedback.
|
||||
|
||||
.PARAMETER IssueNumber
|
||||
GitHub issue number whose review to validate.
|
||||
|
||||
.PARAMETER CLIType
|
||||
AI CLI to use: copilot or claude. Default: copilot.
|
||||
|
||||
.PARAMETER Model
|
||||
Copilot CLI model to use (e.g., gpt-5.2-codex).
|
||||
|
||||
.PARAMETER Force
|
||||
Skip confirmation prompts.
|
||||
|
||||
.PARAMETER DryRun
|
||||
Show what would be done without executing.
|
||||
|
||||
.EXAMPLE
|
||||
./Start-IssueReviewReview.ps1 -IssueNumber 44044
|
||||
|
||||
.EXAMPLE
|
||||
./Start-IssueReviewReview.ps1 -IssueNumber 44044 -CLIType copilot -Model gpt-5.2-codex -Force
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[int]$IssueNumber,
|
||||
|
||||
[ValidateSet('copilot', 'claude')]
|
||||
[string]$CLIType = 'copilot',
|
||||
|
||||
[string]$Model,
|
||||
|
||||
[switch]$Force,
|
||||
|
||||
[switch]$DryRun,
|
||||
|
||||
[switch]$Help
|
||||
)
|
||||
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
. (Join-Path $scriptDir 'IssueReviewLib.ps1')
|
||||
|
||||
if ($Help) {
|
||||
Get-Help $MyInvocation.MyCommand.Path -Full
|
||||
return
|
||||
}
|
||||
|
||||
#region Main
|
||||
try {
|
||||
$repoRoot = Get-RepoRoot
|
||||
$genFiles = Get-GeneratedFilesPath -RepoRoot $repoRoot
|
||||
Info "Repository root: $repoRoot"
|
||||
|
||||
#region Validate prerequisites
|
||||
$reviewDir = Join-Path $genFiles "issueReview/$IssueNumber"
|
||||
$overviewPath = Join-Path $reviewDir 'overview.md'
|
||||
$implPlanPath = Join-Path $reviewDir 'implementation-plan.md'
|
||||
|
||||
if (-not (Test-Path $overviewPath)) {
|
||||
throw "overview.md not found for issue #$IssueNumber at: $overviewPath. Run issue-review first."
|
||||
}
|
||||
if (-not (Test-Path $implPlanPath)) {
|
||||
throw "implementation-plan.md not found for issue #$IssueNumber at: $implPlanPath. Run issue-review first."
|
||||
}
|
||||
|
||||
Info "Found review files for issue #$IssueNumber"
|
||||
Info " Overview: $overviewPath"
|
||||
Info " Implementation plan: $implPlanPath"
|
||||
#endregion
|
||||
|
||||
#region Determine iteration
|
||||
$outputDir = Join-Path $genFiles "issueReviewReview/$IssueNumber"
|
||||
Ensure-DirectoryExists -Path $outputDir
|
||||
|
||||
$existingSignalPath = Join-Path $outputDir '.signal'
|
||||
$iteration = 1
|
||||
if (Test-Path $existingSignalPath) {
|
||||
try {
|
||||
$existingSignal = Get-Content $existingSignalPath -Raw | ConvertFrom-Json
|
||||
$iteration = ([int]$existingSignal.iteration) + 1
|
||||
Info "Previous review-review found (iteration $($existingSignal.iteration), score: $($existingSignal.qualityScore))"
|
||||
|
||||
# Archive previous output
|
||||
$archiveDir = Join-Path $outputDir "iteration-$($existingSignal.iteration)"
|
||||
Ensure-DirectoryExists -Path $archiveDir
|
||||
$prevReviewPath = Join-Path $outputDir 'reviewTheReview.md'
|
||||
if (Test-Path $prevReviewPath) {
|
||||
Copy-Item $prevReviewPath (Join-Path $archiveDir 'reviewTheReview.md') -Force
|
||||
Info "Archived previous review to: $archiveDir"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Warn "Could not parse existing signal, starting fresh"
|
||||
}
|
||||
}
|
||||
|
||||
Info "Starting review-review iteration $iteration for issue #$IssueNumber"
|
||||
#endregion
|
||||
|
||||
if ($DryRun) {
|
||||
Warn "Dry run mode - would review-review issue #$IssueNumber (iteration $iteration)"
|
||||
return
|
||||
}
|
||||
|
||||
if (-not $Force) {
|
||||
$confirm = Read-Host "Proceed with review-review for issue #$IssueNumber? (y/N)"
|
||||
if ($confirm -notmatch '^[yY]') {
|
||||
Info "Cancelled."
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
#region Build and run AI prompt
|
||||
$promptText = @"
|
||||
TASK: Write a meta-review file to 'Generated Files/issueReviewReview/$IssueNumber/reviewTheReview.md'.
|
||||
|
||||
You MUST create this file before finishing. This is your primary deliverable.
|
||||
|
||||
Issue number: $IssueNumber
|
||||
Iteration: $iteration
|
||||
|
||||
STEP 1 - Read these inputs:
|
||||
- Run: gh issue view $IssueNumber --json number,title,body,state,labels,comments
|
||||
- Read file: Generated Files/issueReview/$IssueNumber/overview.md
|
||||
- Read file: Generated Files/issueReview/$IssueNumber/implementation-plan.md
|
||||
$(if ($iteration -gt 1) { "- Read file: Generated Files/issueReviewReview/$IssueNumber/iteration-$($iteration - 1)/reviewTheReview.md" })
|
||||
|
||||
STEP 2 - Verify file paths from the implementation plan exist using test -f or ls.
|
||||
STEP 3 - Verify code patterns from the implementation plan using rg.
|
||||
|
||||
STEP 4 - Write the file 'Generated Files/issueReviewReview/$IssueNumber/reviewTheReview.md' with this structure:
|
||||
|
||||
# Meta-Review: Issue #$IssueNumber
|
||||
|
||||
## Score Validation
|
||||
| Dimension | Original Score | Verified Score | Evidence |
|
||||
|-----------|---------------|----------------|----------|
|
||||
(validate each score dimension from overview.md against actual codebase evidence)
|
||||
|
||||
## Implementation Plan Verification
|
||||
- File paths: which exist, which don't
|
||||
- Patterns: which are correct, which are wrong
|
||||
- Task breakdown: are tasks specific and executable?
|
||||
|
||||
## Quality Score Breakdown
|
||||
| Dimension | Weight | Score | Weighted |
|
||||
|-----------|--------|-------|----------|
|
||||
| Score Accuracy | 30% | X/100 | X |
|
||||
| Implementation Correctness | 25% | X/100 | X |
|
||||
| Risk Assessment | 15% | X/100 | X |
|
||||
| Completeness | 15% | X/100 | X |
|
||||
| Actionability | 15% | X/100 | X |
|
||||
| **Total** | | | **X/100** |
|
||||
|
||||
## Review Quality Score: X/100
|
||||
|
||||
## Verdict: PASS/NEEDS_IMPROVEMENT/FAIL
|
||||
|
||||
## Corrective Feedback
|
||||
(specific items the review should fix, if any)
|
||||
|
||||
CRITICAL: You MUST write the output file. Do NOT just describe what you would do. Actually create the file.
|
||||
"@
|
||||
|
||||
$mcpConfig = "@$_cfgDir/skills/issue-review-review/references/mcp-config.json"
|
||||
|
||||
switch ($CLIType) {
|
||||
'copilot' {
|
||||
$cliArgs = @(
|
||||
'--additional-mcp-config', $mcpConfig,
|
||||
'-p', $promptText,
|
||||
'--yolo',
|
||||
'-s',
|
||||
'--enable-all-github-mcp-tools',
|
||||
'--allow-tool', 'github-artifacts',
|
||||
'--agent', 'ReviewTheReview'
|
||||
)
|
||||
if ($Model) {
|
||||
$cliArgs += @('--model', $Model)
|
||||
}
|
||||
|
||||
Info "Running Copilot CLI for review-review..."
|
||||
& copilot @cliArgs 2>&1 | Out-Default
|
||||
$exitCode = $LASTEXITCODE
|
||||
}
|
||||
'claude' {
|
||||
$cliArgs = @(
|
||||
'--print',
|
||||
'--dangerously-skip-permissions',
|
||||
'--agent', 'ReviewTheReview',
|
||||
'--prompt', $promptText
|
||||
)
|
||||
|
||||
Info "Running Claude CLI for review-review..."
|
||||
& claude @cliArgs 2>&1 | Out-Default
|
||||
$exitCode = $LASTEXITCODE
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Parse result and write signal
|
||||
$reviewTheReviewPath = Join-Path $outputDir 'reviewTheReview.md'
|
||||
|
||||
if (-not (Test-Path $reviewTheReviewPath)) {
|
||||
# CLI may have failed
|
||||
Err "reviewTheReview.md was not generated for issue #$IssueNumber"
|
||||
|
||||
@{
|
||||
status = 'failure'
|
||||
issueNumber = $IssueNumber
|
||||
timestamp = (Get-Date).ToString('o')
|
||||
qualityScore = 0
|
||||
iteration = $iteration
|
||||
outputs = @()
|
||||
needsReReview = $true
|
||||
error = "Output file not generated (exit code: $exitCode)"
|
||||
} | ConvertTo-Json | Set-Content $existingSignalPath -Force
|
||||
|
||||
return @{
|
||||
IssueNumber = $IssueNumber
|
||||
Status = 'failure'
|
||||
QualityScore = 0
|
||||
Iteration = $iteration
|
||||
NeedsReReview = $true
|
||||
Error = "Output file not generated"
|
||||
}
|
||||
}
|
||||
|
||||
# Parse quality score from the generated reviewTheReview.md
|
||||
$content = Get-Content $reviewTheReviewPath -Raw
|
||||
$qualityScore = 0
|
||||
|
||||
# Try to extract "Review Quality Score: X/100"
|
||||
if ($content -match 'Review Quality Score:\s*(\d+)/100') {
|
||||
$qualityScore = [int]$Matches[1]
|
||||
}
|
||||
# Also try total from breakdown table: "| **Total** | | | **X/100** |"
|
||||
elseif ($content -match '\*\*Total\*\*[^|]*\|[^|]*\|[^|]*\|\s*\*\*(\d+)/100\*\*') {
|
||||
$qualityScore = [int]$Matches[1]
|
||||
}
|
||||
# Fallback: any line with "Quality Score" and a number
|
||||
elseif ($content -match 'Quality Score[^\d]*(\d+)') {
|
||||
$qualityScore = [int]$Matches[1]
|
||||
}
|
||||
|
||||
$needsReReview = $qualityScore -lt 90
|
||||
|
||||
# Determine verdict
|
||||
$verdict = if ($qualityScore -ge 90) { 'PASS' }
|
||||
elseif ($qualityScore -ge 50) { 'NEEDS_IMPROVEMENT' }
|
||||
else { 'FAIL' }
|
||||
|
||||
# Write signal
|
||||
$signal = @{
|
||||
status = 'success'
|
||||
issueNumber = $IssueNumber
|
||||
timestamp = (Get-Date).ToString('o')
|
||||
qualityScore = $qualityScore
|
||||
iteration = $iteration
|
||||
verdict = $verdict
|
||||
outputs = @('reviewTheReview.md')
|
||||
needsReReview = $needsReReview
|
||||
}
|
||||
$signal | ConvertTo-Json | Set-Content $existingSignalPath -Force
|
||||
|
||||
if ($needsReReview) {
|
||||
Warn "Review-review score: $qualityScore/100 (iteration $iteration) — NEEDS RE-REVIEW"
|
||||
Warn "Feedback written to: $reviewTheReviewPath"
|
||||
Warn "Re-run issue-review with: -FeedbackFile `"$reviewTheReviewPath`""
|
||||
}
|
||||
else {
|
||||
Success "Review-review score: $qualityScore/100 (iteration $iteration) — PASS"
|
||||
Success "Review quality is sufficient. Proceed to issue-fix."
|
||||
}
|
||||
|
||||
Info "Signal: $existingSignalPath"
|
||||
#endregion
|
||||
|
||||
return @{
|
||||
IssueNumber = $IssueNumber
|
||||
Status = 'success'
|
||||
QualityScore = $qualityScore
|
||||
Iteration = $iteration
|
||||
Verdict = $verdict
|
||||
NeedsReReview = $needsReReview
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Err "Error: $($_.Exception.Message)"
|
||||
|
||||
# Write failure signal
|
||||
$outputDir = Join-Path (Get-GeneratedFilesPath -RepoRoot (Get-RepoRoot)) "issueReviewReview/$IssueNumber"
|
||||
Ensure-DirectoryExists -Path $outputDir
|
||||
$signalPath = Join-Path $outputDir '.signal'
|
||||
@{
|
||||
status = 'failure'
|
||||
issueNumber = $IssueNumber
|
||||
timestamp = (Get-Date).ToString('o')
|
||||
qualityScore = 0
|
||||
iteration = 1
|
||||
outputs = @()
|
||||
needsReReview = $true
|
||||
error = $_.Exception.Message
|
||||
} | ConvertTo-Json | Set-Content $signalPath -Force
|
||||
|
||||
return @{
|
||||
IssueNumber = $IssueNumber
|
||||
Status = 'failure'
|
||||
QualityScore = 0
|
||||
Iteration = 1
|
||||
NeedsReReview = $true
|
||||
Error = $_.Exception.Message
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
@@ -1,111 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Run issue-review-review in parallel from a single terminal.
|
||||
|
||||
.PARAMETER IssueNumbers
|
||||
Issue numbers to review-review.
|
||||
|
||||
.PARAMETER ThrottleLimit
|
||||
Maximum parallel tasks.
|
||||
|
||||
.PARAMETER CLIType
|
||||
AI CLI type (copilot/claude).
|
||||
|
||||
.PARAMETER Model
|
||||
Copilot CLI model to use (e.g., gpt-5.2-codex).
|
||||
|
||||
.PARAMETER Force
|
||||
Skip confirmation prompts.
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[int[]]$IssueNumbers,
|
||||
|
||||
[int]$ThrottleLimit = 5,
|
||||
|
||||
[ValidateSet('copilot', 'claude')]
|
||||
[string]$CLIType = 'copilot',
|
||||
|
||||
[string]$Model,
|
||||
|
||||
[switch]$Force
|
||||
)
|
||||
|
||||
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot '..\..\..\..')
|
||||
|
||||
# Resolve config directory name (.github or .claude) from script location
|
||||
$_cfgDir = if ($PSScriptRoot -match '[\\/](\.github|\.claude)[\\/]') { $Matches[1] } else { '.github' }
|
||||
$scriptPath = Join-Path $repoRoot "$_cfgDir\skills\issue-review-review\scripts\Start-IssueReviewReview.ps1"
|
||||
|
||||
$results = $IssueNumbers | ForEach-Object -Parallel {
|
||||
$issue = $PSItem
|
||||
$repoRoot = $using:repoRoot
|
||||
$scriptPath = $using:scriptPath
|
||||
$cliType = $using:CLIType
|
||||
$model = $using:Model
|
||||
$force = $using:Force
|
||||
|
||||
Set-Location $repoRoot
|
||||
|
||||
if (-not $issue) {
|
||||
return [pscustomobject]@{
|
||||
IssueNumber = $issue
|
||||
ExitCode = 1
|
||||
QualityScore = 0
|
||||
Error = 'Issue number is empty.'
|
||||
}
|
||||
}
|
||||
|
||||
$params = @{
|
||||
IssueNumber = [int]$issue
|
||||
CLIType = $cliType
|
||||
}
|
||||
if ($model) {
|
||||
$params.Model = $model
|
||||
}
|
||||
if ($force) {
|
||||
$params.Force = $true
|
||||
}
|
||||
|
||||
try {
|
||||
$result = & $scriptPath @params
|
||||
[pscustomobject]@{
|
||||
IssueNumber = $issue
|
||||
ExitCode = $LASTEXITCODE
|
||||
QualityScore = $result.QualityScore
|
||||
NeedsReReview = $result.NeedsReReview
|
||||
Iteration = $result.Iteration
|
||||
Verdict = $result.Verdict
|
||||
}
|
||||
}
|
||||
catch {
|
||||
[pscustomobject]@{
|
||||
IssueNumber = $issue
|
||||
ExitCode = 1
|
||||
QualityScore = 0
|
||||
NeedsReReview = $true
|
||||
Error = $_.Exception.Message
|
||||
}
|
||||
}
|
||||
} -ThrottleLimit $ThrottleLimit
|
||||
|
||||
# Summary
|
||||
$passed = @($results | Where-Object { $_.QualityScore -ge 90 })
|
||||
$needsWork = @($results | Where-Object { $_.QualityScore -gt 0 -and $_.QualityScore -lt 90 })
|
||||
$failed = @($results | Where-Object { $_.QualityScore -eq 0 -or $_.Error })
|
||||
|
||||
Write-Host "`n=== REVIEW-REVIEW SUMMARY ===" -ForegroundColor Cyan
|
||||
Write-Host "Total: $($results.Count)"
|
||||
Write-Host "Passed (>=90): $($passed.Count)" -ForegroundColor Green
|
||||
Write-Host "Needs work: $($needsWork.Count)" -ForegroundColor Yellow
|
||||
Write-Host "Failed: $($failed.Count)" -ForegroundColor Red
|
||||
|
||||
if ($needsWork.Count -gt 0) {
|
||||
Write-Host "`nIssues needing re-review:" -ForegroundColor Yellow
|
||||
foreach ($r in $needsWork) {
|
||||
Write-Host " #$($r.IssueNumber) — score: $($r.QualityScore)/100 (iteration $($r.Iteration))"
|
||||
}
|
||||
}
|
||||
|
||||
$results
|
||||
21
.github/skills/issue-review/LICENSE.txt
vendored
21
.github/skills/issue-review/LICENSE.txt
vendored
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
148
.github/skills/issue-review/SKILL.md
vendored
148
.github/skills/issue-review/SKILL.md
vendored
@@ -1,148 +0,0 @@
|
||||
---
|
||||
name: issue-review
|
||||
description: Analyze GitHub issues for feasibility and implementation planning. Use when asked to review an issue, analyze if an issue is fixable, evaluate issue complexity, create implementation plan for an issue, triage issues, assess technical feasibility, or estimate effort for an issue. Outputs structured analysis including feasibility score, clarity score, effort estimate, and detailed implementation plan.
|
||||
license: Complete terms in LICENSE.txt
|
||||
---
|
||||
|
||||
# Issue Review Skill
|
||||
|
||||
Analyze GitHub issues to determine technical feasibility, requirement clarity, and create detailed implementation plans for PowerToys.
|
||||
|
||||
## Skill Contents
|
||||
|
||||
This skill is **self-contained** with all required resources:
|
||||
|
||||
```
|
||||
.github/skills/issue-review/
|
||||
├── SKILL.md # This file
|
||||
├── LICENSE.txt # MIT License
|
||||
├── scripts/
|
||||
│ ├── IssueReviewLib.ps1 # Shared library functions
|
||||
│ └── Start-BulkIssueReview.ps1 # Main review script
|
||||
└── references/
|
||||
└── review-issue.prompt.md # Full AI prompt template
|
||||
```
|
||||
|
||||
## Output Directory
|
||||
|
||||
All generated artifacts are placed under `Generated Files/issueReview/<issue-number>/` at the repository root (gitignored).
|
||||
|
||||
```
|
||||
Generated Files/issueReview/
|
||||
└── <issue-number>/
|
||||
├── overview.md # High-level assessment with scores
|
||||
├── implementation-plan.md # Detailed step-by-step fix plan
|
||||
├── _raw-issue.json # Cached issue data from GitHub
|
||||
└── .signal # Completion signal for orchestrator
|
||||
```
|
||||
|
||||
## Signal File
|
||||
|
||||
On completion, a `.signal` file is created for orchestrator coordination:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"issueNumber": 45363,
|
||||
"timestamp": "2026-02-04T10:05:23Z",
|
||||
"outputs": ["overview.md", "implementation-plan.md"]
|
||||
}
|
||||
```
|
||||
|
||||
Status values: `success`, `failure`
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
- Review a specific GitHub issue for feasibility
|
||||
- Analyze whether an issue can be fixed by AI
|
||||
- Create an implementation plan for an issue
|
||||
- Triage issues by complexity and clarity
|
||||
- Estimate effort for fixing an issue
|
||||
- Evaluate technical requirements of an issue
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- GitHub CLI (`gh`) installed and authenticated
|
||||
- PowerShell 7+ for running scripts
|
||||
|
||||
## Required Variables
|
||||
|
||||
⚠️ **Before starting**, confirm `{{IssueNumber}}` with the user. If not provided, **ASK**: "What issue number should I review?"
|
||||
|
||||
| Variable | Description | Example |
|
||||
|----------|-------------|---------|
|
||||
| `{{IssueNumber}}` | GitHub issue number to analyze | `44044` |
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Run Issue Review
|
||||
|
||||
Execute the review script (use paths relative to this skill folder):
|
||||
|
||||
```powershell
|
||||
# From repo root
|
||||
.github/skills/issue-review/scripts/Start-BulkIssueReview.ps1 -IssueNumber {{IssueNumber}}
|
||||
```
|
||||
|
||||
This will:
|
||||
1. Fetch issue details from GitHub
|
||||
2. Analyze the codebase for relevant files
|
||||
3. Generate `overview.md` with feasibility assessment
|
||||
4. Generate `implementation-plan.md` with detailed steps
|
||||
|
||||
### Step 2: Review Output
|
||||
|
||||
Check the generated files at `Generated Files/issueReview/{{IssueNumber}}/`:
|
||||
|
||||
| File | Contains |
|
||||
|------|----------|
|
||||
| `overview.md` | Feasibility score (0-100), Clarity score (0-100), Effort estimate, Risk assessment |
|
||||
| `implementation-plan.md` | Step-by-step implementation with file paths, code snippets, test requirements |
|
||||
|
||||
### Step 3: Interpret Scores
|
||||
|
||||
| Score Range | Interpretation |
|
||||
|-------------|----------------|
|
||||
| 80-100 | High confidence - straightforward fix |
|
||||
| 60-79 | Medium confidence - some complexity |
|
||||
| 40-59 | Low confidence - significant challenges |
|
||||
| 0-39 | Very low - may need human intervention |
|
||||
|
||||
## Batch Review
|
||||
|
||||
To review multiple issues at once:
|
||||
|
||||
```powershell
|
||||
.github/skills/issue-review/scripts/Start-BulkIssueReview.ps1 -IssueNumbers 44044, 32950, 45029
|
||||
```
|
||||
|
||||
## AI Prompt Reference
|
||||
|
||||
For manual AI invocation, the full prompt is at:
|
||||
- `references/review-issue.prompt.md` (relative to this skill folder)
|
||||
|
||||
## Re-Review with Feedback
|
||||
|
||||
When the `issue-review-review` skill identifies quality issues, re-run with feedback:
|
||||
|
||||
```powershell
|
||||
.github/skills/issue-review/scripts/Start-BulkIssueReview.ps1 -IssueNumber {{IssueNumber}} -FeedbackFile "Generated Files/issueReviewReview/{{IssueNumber}}/reviewTheReview.md" -Force
|
||||
```
|
||||
|
||||
The `-FeedbackFile` parameter injects corrective feedback into the AI prompt so the review addresses specific issues found by the meta-review.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Problem | Solution |
|
||||
|---------|----------|
|
||||
| Issue not found | Verify issue number exists: `gh issue view {{IssueNumber}}` |
|
||||
| No implementation plan | Issue may be unclear - check `overview.md` for clarity score |
|
||||
| Script errors | Ensure you're in the PowerToys repo root |
|
||||
|
||||
## Related Skills
|
||||
|
||||
| Skill | Purpose |
|
||||
|-------|---------|
|
||||
| `issue-review-review` | Validate review quality, loop until score ≥ 90 |
|
||||
| `issue-fix` | Fix issues after review, create PRs |
|
||||
| `issue-to-pr-cycle` | Full orchestration (review → fix → PR → review loop) |
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"github-artifacts": {
|
||||
"command": "cmd",
|
||||
"args": ["/c", "for /f %i in ('git rev-parse --show-toplevel') do node %i/tools/mcp/github-artifacts/launch.js"],
|
||||
"tools": ["*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
---
|
||||
agent: 'agent'
|
||||
description: 'Review a GitHub issue, score it (0-100), and generate an implementation plan'
|
||||
---
|
||||
|
||||
# Review GitHub Issue
|
||||
|
||||
## Goal
|
||||
For **#{{issue_number}}** produce:
|
||||
1) `Generated Files/issueReview/{{issue_number}}/overview.md`
|
||||
2) `Generated Files/issueReview/{{issue_number}}/implementation-plan.md`
|
||||
|
||||
## Inputs
|
||||
Figure out required inputs {{issue_number}} from the invocation context; if anything is missing, ask for the value or note it as a gap.
|
||||
|
||||
# CONTEXT (brief)
|
||||
Ground evidence using `gh issue view {{issue_number}} --json number,title,body,author,createdAt,updatedAt,state,labels,milestone,reactions,comments,linkedPullRequests`, download images via MCP `github_issue_images` to better understand the issue context. Finally, use MCP `github_issue_attachments` to download logs with parameter `extractFolder` as `Generated Files/issueReview/{{issue_number}}/logs`, and analyze the downloaded logs if available to identify relevant issues. Locate the source code in the current workspace (use `rg`/`git grep` as needed). Link related issues and PRs.
|
||||
|
||||
## When to call MCP tools
|
||||
If the following MCP "github-artifacts" tools are available in the environment, use them:
|
||||
- `github_issue_images`: use when the issue/PR likely contains screenshots or other visual evidence (UI bugs, glitches, design problems).
|
||||
- `github_issue_attachments`: use when the issue/PR mentions attached ZIPs (PowerToysReport_*.zip, logs.zip, debug.zip) or asks to analyze logs/diagnostics. Always provide `extractFolder` as `Generated Files/issueReview/{{issue_number}}/logs`
|
||||
|
||||
If these tools are not available (not listed by the runtime), start the MCP server "github-artifacts" first.
|
||||
|
||||
# OVERVIEW.MD
|
||||
## Summary
|
||||
Issue, state, milestone, labels. **Signals**: 👍/❤️/👎, comment count, last activity, linked PRs.
|
||||
|
||||
## At-a-Glance Score Table
|
||||
Present all ratings in a compact table for quick scanning:
|
||||
|
||||
| Dimension | Score | Assessment | Key Drivers |
|
||||
|-----------|-------|------------|-------------|
|
||||
| **A) Business Importance** | X/100 | Low/Medium/High | Top 2 factors with scores |
|
||||
| **B) Community Excitement** | X/100 | Low/Medium/High | Top 2 factors with scores |
|
||||
| **C) Technical Feasibility** | X/100 | Low/Medium/High | Top 2 factors with scores |
|
||||
| **D) Requirement Clarity** | X/100 | Low/Medium/High | Top 2 factors with scores |
|
||||
| **Overall Priority** | X/100 | Low/Medium/High/Critical | Average or weighted summary |
|
||||
| **Effort Estimate** | X days (T-shirt) | XS/S/M/L/XL/XXL/Epic | Type: bug/feature/chore |
|
||||
| **Similar Issues Found** | X open, Y closed | — | Quick reference to related work |
|
||||
| **Potential Assignees** | @username, @username | — | Top contributors to module |
|
||||
|
||||
**Assessment bands**: 0-25 Low, 26-50 Medium, 51-75 High, 76-100 Critical
|
||||
|
||||
## Ratings (0–100) — add evidence & short rationale
|
||||
### A) Business Importance
|
||||
- Labels (priority/security/regression): **≤35**
|
||||
- Milestone/roadmap: **≤25**
|
||||
- Customer/contract impact: **≤20**
|
||||
- Unblocks/platform leverage: **≤20**
|
||||
### B) Community Excitement
|
||||
- 👍+❤️ normalized: **≤45**
|
||||
- Comment volume & unique participants: **≤25**
|
||||
- Recent activity (≤30d): **≤15**
|
||||
- Duplicates/related issues: **≤15**
|
||||
### C) Technical Feasibility
|
||||
- Contained surface/clear seams: **≤30**
|
||||
- Existing patterns/utilities: **≤25**
|
||||
- Risk (perf/sec/compat) manageable: **≤25**
|
||||
- Testability & CI support: **≤20**
|
||||
### D) Requirement Clarity
|
||||
- Behavior/repro/constraints: **≤60**
|
||||
- Non-functionals (perf/sec/i18n/a11y): **≤25**
|
||||
- Decision owners/acceptance signals: **≤15**
|
||||
|
||||
## Effort
|
||||
Days + **T-shirt** (XS 0.5–1d, S 1–2, M 2–4, L 4–7, XL 7–14, XXL 14–30, Epic >30).
|
||||
Type/level: bug/feature/chore/docs/refactor/test-only; severity/value tier.
|
||||
|
||||
## Suggested Actions
|
||||
Provide actionable recommendations for issue triage and assignment:
|
||||
|
||||
### A) Requirement Clarification (if Clarity score <50)
|
||||
**When Requirement Clarity (Dimension D) is Medium or Low:**
|
||||
- Identify specific gaps in issue description: missing repro steps, unclear expected behavior, undefined acceptance criteria, missing non-functional requirements
|
||||
- Draft 3-5 clarifying questions to post as issue comment
|
||||
- Suggest additional information needed: screenshots, logs, environment details, OS version, PowerToys version, error messages
|
||||
- If behavior is ambiguous, propose 2-3 interpretation scenarios and ask reporter to confirm
|
||||
- Example questions:
|
||||
- "Can you provide exact steps to reproduce this issue?"
|
||||
- "What is the expected behavior vs. what you're actually seeing?"
|
||||
- "Does this happen on Windows 10, 11, or both?"
|
||||
- "Can you attach a screenshot or screen recording?"
|
||||
|
||||
### B) Correct Label Suggestions
|
||||
- Analyze issue type, module, and severity to suggest missing or incorrect labels
|
||||
- Recommend labels from: `Issue-Bug`, `Issue-Feature`, `Issue-Docs`, `Issue-Task`, `Priority-High`, `Priority-Medium`, `Priority-Low`, `Needs-Triage`, `Needs-Author-Feedback`, `Product-<ModuleName>`, etc.
|
||||
- If Requirement Clarity is low (<50), add `Needs-Author-Feedback` label
|
||||
- If current labels are incorrect or incomplete, provide specific label changes with rationale
|
||||
|
||||
### C) Find Similar Issues & Past Fixes
|
||||
- Search for similar issues using `gh issue list --search "keywords" --state all --json number,title,state,closedAt`
|
||||
- Identify patterns: duplicate issues, related bugs, or similar feature requests
|
||||
- For closed issues, find linked PRs that fixed them: check `linkedPullRequests` in issue data
|
||||
- Provide 3-5 examples of similar issues with format: `#<number> - <title> (closed by PR #<pr>)` or `(still open)`
|
||||
|
||||
### D) Identify Subject Matter Experts
|
||||
- Use git blame/log to find who fixed similar issues in the past
|
||||
- Search for PR authors who touched relevant files: `git log --all --format='%aN' -- <file_paths> | sort | uniq -c | sort -rn | head -5`
|
||||
- Check issue/PR history for frequent contributors to the affected module
|
||||
- Suggest 2-3 potential assignees with context: `@<username> - <reason>` (e.g., "fixed similar rendering bug in #12345", "maintains FancyZones module")
|
||||
|
||||
### E) Semantic Search for Related Work
|
||||
- Use semantic_search tool to find similar issues, code patterns, or past discussions
|
||||
- Search queries should include: issue keywords, module names, error messages, feature descriptions
|
||||
- Cross-reference semantic results with GitHub issue search for comprehensive coverage
|
||||
|
||||
**Output format for Suggested Actions section in overview.md:**
|
||||
```markdown
|
||||
## Suggested Actions
|
||||
|
||||
### Clarifying Questions (if Clarity <50)
|
||||
Post these questions as issue comment to gather missing information:
|
||||
1. <question>
|
||||
2. <question>
|
||||
3. <question>
|
||||
|
||||
**Recommended label**: `Needs-Author-Feedback`
|
||||
|
||||
### Label Recommendations
|
||||
- Add: `<label>` - <reason>
|
||||
- Remove: `<label>` - <reason>
|
||||
- Current labels are appropriate ✓
|
||||
|
||||
### Similar Issues Found
|
||||
1. #<number> - <title> (<state>, closed by PR #<pr> on <date>)
|
||||
2. #<number> - <title> (<state>)
|
||||
...
|
||||
|
||||
### Potential Assignees
|
||||
- @<username> - <reason>
|
||||
- @<username> - <reason>
|
||||
|
||||
### Related Code/Discussions
|
||||
- <semantic search findings>
|
||||
```
|
||||
|
||||
# IMPLEMENTATION-PLAN.MD
|
||||
1) **Problem Framing** — restate problem; current vs expected; scope boundaries.
|
||||
2) **Layers & Files** — layers (UI/domain/data/infra/build). For each, list **files/dirs to modify** and **new files** (exact paths + why). Prefer repo patterns; cite examples/PRs.
|
||||
3) **Pattern Choices** — reuse existing; if new, justify trade-offs & transition.
|
||||
4) **Fundamentals** (brief plan or N/A + reason):
|
||||
- Performance (hot paths, allocs, caching/streaming)
|
||||
- Security (validation, authN/Z, secrets, SSRF/XSS/CSRF)
|
||||
- G11N/L10N (resources, number/date, pluralization)
|
||||
- Compatibility (public APIs, formats, OS/runtime/toolchain)
|
||||
- Extensibility (DI seams, options/flags, plugin points)
|
||||
- Accessibility (roles, labels, focus, keyboard, contrast)
|
||||
- SOLID & repo conventions (naming, folders, dependency direction)
|
||||
5) **Logging & Exception Handling**
|
||||
- Where to log; levels; structured fields; correlation/traces.
|
||||
- What to catch vs rethrow; retries/backoff; user-visible errors.
|
||||
- **Privacy**: never log secrets/PII; redaction policy.
|
||||
6) **Telemetry (optional — business metrics only)**
|
||||
- Events/metrics (name, when, props); success signal; privacy/sampling; dashboards/alerts.
|
||||
7) **Risks & Mitigations** — flags/canary/shadow-write/config guards.
|
||||
8) **Task Breakdown (agent-ready)** — table (leave a blank line before the header so Markdown renders correctly):
|
||||
|
||||
| Task | Intent | Files/Areas | Steps | Tests (brief) | Owner (Agent/Human) | Human interaction needed? (why) |
|
||||
|---|---|---|---|---|---|---|
|
||||
|
||||
9) **Tests to Add (only)**
|
||||
- **Unit**: targets, cases (success/edge/error), mocks/fixtures, path, notes.
|
||||
- **UI** (if applicable): flows, locator strategy, env/data/flags, path, flake mitigation.
|
||||
@@ -1,777 +0,0 @@
|
||||
# IssueReviewLib.ps1 - Shared helpers for bulk issue review automation
|
||||
# Part of the PowerToys GitHub Copilot/Claude Code issue review system
|
||||
|
||||
# Resolve config directory name (.github or .claude) from this script's location
|
||||
$_cfgDir = if ($PSScriptRoot -match '[\\/](\.github|\.claude)[\\/]') { $Matches[1] } else { '.github' }
|
||||
|
||||
#region Console Output Helpers
|
||||
function Info { param([string]$Message) Write-Host $Message -ForegroundColor Cyan }
|
||||
function Warn { param([string]$Message) Write-Host $Message -ForegroundColor Yellow }
|
||||
function Err { param([string]$Message) Write-Host $Message -ForegroundColor Red }
|
||||
function Success { param([string]$Message) Write-Host $Message -ForegroundColor Green }
|
||||
#endregion
|
||||
|
||||
#region Repository Helpers
|
||||
function Get-RepoRoot {
|
||||
$root = git rev-parse --show-toplevel 2>$null
|
||||
if (-not $root) { throw 'Not inside a git repository.' }
|
||||
return (Resolve-Path $root).Path
|
||||
}
|
||||
|
||||
function Get-GeneratedFilesPath {
|
||||
param([string]$RepoRoot)
|
||||
return Join-Path $RepoRoot 'Generated Files'
|
||||
}
|
||||
|
||||
function Get-IssueReviewPath {
|
||||
param(
|
||||
[string]$RepoRoot,
|
||||
[int]$IssueNumber
|
||||
)
|
||||
$genFiles = Get-GeneratedFilesPath -RepoRoot $RepoRoot
|
||||
return Join-Path $genFiles "issueReview/$IssueNumber"
|
||||
}
|
||||
|
||||
function Get-IssueTitleFromOverview {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Extract issue title from existing overview.md file.
|
||||
.DESCRIPTION
|
||||
Parses the overview.md to get the issue title without requiring GitHub CLI.
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$OverviewPath
|
||||
)
|
||||
|
||||
if (-not (Test-Path $OverviewPath)) {
|
||||
return $null
|
||||
}
|
||||
|
||||
$content = Get-Content $OverviewPath -Raw
|
||||
|
||||
# Try to match title from Summary table: | **Title** | <title> |
|
||||
if ($content -match '\*\*Title\*\*\s*\|\s*([^|]+)\s*\|') {
|
||||
return $Matches[1].Trim()
|
||||
}
|
||||
|
||||
# Try to match from header: # Issue #XXXX: <title>
|
||||
if ($content -match '# Issue #\d+[:\s]+(.+)$' ) {
|
||||
return $Matches[1].Trim()
|
||||
}
|
||||
|
||||
# Try to match: # Issue #XXXX Review: <title>
|
||||
if ($content -match '# Issue #\d+ Review[:\s]+(.+)$') {
|
||||
return $Matches[1].Trim()
|
||||
}
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
function Ensure-DirectoryExists {
|
||||
param([string]$Path)
|
||||
if (-not (Test-Path $Path)) {
|
||||
New-Item -ItemType Directory -Path $Path -Force | Out-Null
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region GitHub Issue Query Helpers
|
||||
function Get-GitHubIssues {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Query GitHub issues by label, state, and sort order.
|
||||
.PARAMETER Labels
|
||||
Comma-separated list of labels to filter by (e.g., "bug,help wanted").
|
||||
.PARAMETER State
|
||||
Issue state: open, closed, or all. Default: open.
|
||||
.PARAMETER Sort
|
||||
Sort field: created, updated, comments, reactions. Default: created.
|
||||
.PARAMETER Order
|
||||
Sort order: asc or desc. Default: desc.
|
||||
.PARAMETER Limit
|
||||
Maximum number of issues to return. Default: 100.
|
||||
.PARAMETER Repository
|
||||
Repository in owner/repo format. Default: microsoft/PowerToys.
|
||||
#>
|
||||
param(
|
||||
[string]$Labels,
|
||||
[ValidateSet('open', 'closed', 'all')]
|
||||
[string]$State = 'open',
|
||||
[ValidateSet('created', 'updated', 'comments', 'reactions')]
|
||||
[string]$Sort = 'created',
|
||||
[ValidateSet('asc', 'desc')]
|
||||
[string]$Order = 'desc',
|
||||
[int]$Limit = 100,
|
||||
[string]$Repository = 'microsoft/PowerToys'
|
||||
)
|
||||
|
||||
$ghArgs = @('issue', 'list', '--repo', $Repository, '--state', $State, '--limit', $Limit)
|
||||
|
||||
if ($Labels) {
|
||||
foreach ($label in ($Labels -split ',')) {
|
||||
$ghArgs += @('--label', $label.Trim())
|
||||
}
|
||||
}
|
||||
|
||||
# Build JSON fields (use reactionGroups instead of reactions)
|
||||
$jsonFields = 'number,title,state,labels,createdAt,updatedAt,author,reactionGroups,comments'
|
||||
$ghArgs += @('--json', $jsonFields)
|
||||
|
||||
Info "Querying issues: gh $($ghArgs -join ' ')"
|
||||
$result = & gh @ghArgs 2>&1
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Failed to query issues: $result"
|
||||
}
|
||||
|
||||
$issues = $result | ConvertFrom-Json
|
||||
|
||||
# Sort by reactions if requested (gh CLI doesn't support this natively)
|
||||
if ($Sort -eq 'reactions') {
|
||||
$issues = $issues | ForEach-Object {
|
||||
# reactionGroups is an array of {content, users} - sum up user counts
|
||||
$totalReactions = ($_.reactionGroups | ForEach-Object { $_.users.totalCount } | Measure-Object -Sum).Sum
|
||||
if (-not $totalReactions) { $totalReactions = 0 }
|
||||
$_ | Add-Member -NotePropertyName 'totalReactions' -NotePropertyValue $totalReactions -PassThru
|
||||
}
|
||||
if ($Order -eq 'desc') {
|
||||
$issues = $issues | Sort-Object -Property totalReactions -Descending
|
||||
} else {
|
||||
$issues = $issues | Sort-Object -Property totalReactions
|
||||
}
|
||||
}
|
||||
|
||||
return $issues
|
||||
}
|
||||
|
||||
function Get-IssueDetails {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Get detailed information about a specific issue.
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[int]$IssueNumber,
|
||||
[string]$Repository = 'microsoft/PowerToys'
|
||||
)
|
||||
|
||||
$jsonFields = 'number,title,body,state,labels,createdAt,updatedAt,author,reactions,comments,linkedPullRequests,milestone'
|
||||
$result = gh issue view $IssueNumber --repo $Repository --json $jsonFields 2>&1
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Failed to get issue #$IssueNumber`: $result"
|
||||
}
|
||||
|
||||
return $result | ConvertFrom-Json
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region CLI Detection and Execution
|
||||
function Get-AvailableCLI {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Detect which AI CLI is available: GitHub Copilot CLI or Claude Code.
|
||||
.OUTPUTS
|
||||
Returns object with: Name, Command, PromptArg
|
||||
#>
|
||||
|
||||
# Check for standalone GitHub Copilot CLI (copilot command)
|
||||
$copilotCLI = Get-Command 'copilot' -ErrorAction SilentlyContinue
|
||||
if ($copilotCLI) {
|
||||
return @{
|
||||
Name = 'GitHub Copilot CLI'
|
||||
Command = 'copilot'
|
||||
Args = @('-p') # Non-interactive prompt mode
|
||||
Type = 'copilot'
|
||||
}
|
||||
}
|
||||
|
||||
# Check for Claude Code CLI
|
||||
$claudeCode = Get-Command 'claude' -ErrorAction SilentlyContinue
|
||||
if ($claudeCode) {
|
||||
return @{
|
||||
Name = 'Claude Code CLI'
|
||||
Command = 'claude'
|
||||
Args = @()
|
||||
Type = 'claude'
|
||||
}
|
||||
}
|
||||
|
||||
# Check for GitHub Copilot CLI via gh extension
|
||||
$ghCopilot = Get-Command 'gh' -ErrorAction SilentlyContinue
|
||||
if ($ghCopilot) {
|
||||
$copilotCheck = gh extension list 2>&1 | Select-String -Pattern 'copilot'
|
||||
if ($copilotCheck) {
|
||||
return @{
|
||||
Name = 'GitHub Copilot CLI (gh extension)'
|
||||
Command = 'gh'
|
||||
Args = @('copilot', 'suggest')
|
||||
Type = 'gh-copilot'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Check for VS Code CLI with Copilot
|
||||
$code = Get-Command 'code' -ErrorAction SilentlyContinue
|
||||
if ($code) {
|
||||
return @{
|
||||
Name = 'VS Code (Copilot Chat)'
|
||||
Command = 'code'
|
||||
Args = @()
|
||||
Type = 'vscode'
|
||||
}
|
||||
}
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
function Invoke-AIReview {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Invoke AI CLI to review a single issue.
|
||||
.PARAMETER IssueNumber
|
||||
The issue number to review.
|
||||
.PARAMETER RepoRoot
|
||||
Repository root path.
|
||||
.PARAMETER CLIType
|
||||
CLI type: 'claude', 'copilot', 'gh-copilot', or 'vscode'.
|
||||
.PARAMETER WorkingDirectory
|
||||
Working directory for the CLI command.
|
||||
.PARAMETER FeedbackContext
|
||||
Optional feedback from review-the-review to incorporate into the re-review.
|
||||
.PARAMETER Model
|
||||
Optional model override for Copilot CLI (e.g., claude-sonnet-4).
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[int]$IssueNumber,
|
||||
[Parameter(Mandatory)]
|
||||
[string]$RepoRoot,
|
||||
[ValidateSet('claude', 'copilot', 'gh-copilot', 'vscode')]
|
||||
[string]$CLIType = 'copilot',
|
||||
[string]$WorkingDirectory,
|
||||
[string]$FeedbackContext,
|
||||
[string]$Model
|
||||
)
|
||||
|
||||
if (-not $WorkingDirectory) {
|
||||
$WorkingDirectory = $RepoRoot
|
||||
}
|
||||
|
||||
$promptFile = Join-Path $RepoRoot "$_cfgDir/prompts/review-issue.prompt.md"
|
||||
if (-not (Test-Path $promptFile)) {
|
||||
throw "Prompt file not found: $promptFile"
|
||||
}
|
||||
|
||||
# Prepare the prompt with issue number substitution
|
||||
$promptContent = Get-Content $promptFile -Raw
|
||||
$promptContent = $promptContent -replace '\{\{issue_number\}\}', $IssueNumber
|
||||
|
||||
# Create temp prompt file
|
||||
$tempPromptDir = Join-Path $env:TEMP "issue-review-$IssueNumber"
|
||||
Ensure-DirectoryExists -Path $tempPromptDir
|
||||
$tempPromptFile = Join-Path $tempPromptDir "prompt.md"
|
||||
$promptContent | Set-Content -Path $tempPromptFile -Encoding UTF8
|
||||
|
||||
# Build the prompt text for CLI
|
||||
$promptText = "Review GitHub issue #$IssueNumber following the template in $_cfgDir/prompts/review-issue.prompt.md. Generate overview.md and implementation-plan.md in 'Generated Files/issueReview/$IssueNumber/'"
|
||||
|
||||
# Inject feedback from review-the-review if available
|
||||
if ($FeedbackContext) {
|
||||
$promptText += @"
|
||||
|
||||
IMPORTANT: This is a RE-REVIEW. A previous review was rejected by the quality gate. You MUST address ALL the corrective feedback below. Read the feedback carefully and fix every issue identified.
|
||||
|
||||
=== CORRECTIVE FEEDBACK FROM REVIEW-THE-REVIEW ===
|
||||
$FeedbackContext
|
||||
=== END FEEDBACK ===
|
||||
|
||||
Pay special attention to:
|
||||
1. Score corrections — adjust scores to match the evidence cited in the feedback
|
||||
2. File path corrections — verify all paths exist before including them
|
||||
3. Pattern corrections — use the patterns identified as correct in the feedback
|
||||
4. Missing coverage — add any sections flagged as missing
|
||||
5. Task breakdown fixes — make tasks specific and executable
|
||||
"@
|
||||
}
|
||||
|
||||
switch ($CLIType) {
|
||||
'copilot' {
|
||||
# GitHub Copilot CLI (standalone copilot command)
|
||||
# Use --yolo for full permissions (--allow-all-tools --allow-all-paths --allow-all-urls)
|
||||
# Use -s (silent) for cleaner output in batch mode
|
||||
# Enable ALL GitHub MCP tools (issues, PRs, repos, etc.) + github-artifacts for images/attachments
|
||||
# MCP config path relative to repo root for github-artifacts tools
|
||||
$mcpConfig = "@$_cfgDir/skills/issue-review/references/mcp-config.json"
|
||||
$args = @(
|
||||
'--additional-mcp-config', $mcpConfig, # Load github-artifacts MCP for image/attachment analysis
|
||||
'-p', $promptText, # Non-interactive prompt mode (exits after completion)
|
||||
'--yolo', # Enable all permissions for automated execution
|
||||
'-s', # Silent mode - output only agent response
|
||||
'--enable-all-github-mcp-tools', # Enable ALL GitHub MCP tools (issues, PRs, search, etc.)
|
||||
'--allow-tool', 'github-artifacts', # Also enable our custom github-artifacts MCP
|
||||
'--agent', 'ReviewIssue'
|
||||
)
|
||||
if ($Model) {
|
||||
$args += @('--model', $Model)
|
||||
}
|
||||
|
||||
return @{
|
||||
Command = 'copilot'
|
||||
Arguments = $args
|
||||
WorkingDirectory = $WorkingDirectory
|
||||
IssueNumber = $IssueNumber
|
||||
}
|
||||
}
|
||||
'claude' {
|
||||
# Claude Code CLI
|
||||
$args = @(
|
||||
'--print', # Non-interactive mode
|
||||
'--dangerously-skip-permissions',
|
||||
'--agent', 'ReviewIssue',
|
||||
'--prompt', $promptText
|
||||
)
|
||||
|
||||
return @{
|
||||
Command = 'claude'
|
||||
Arguments = $args
|
||||
WorkingDirectory = $WorkingDirectory
|
||||
IssueNumber = $IssueNumber
|
||||
}
|
||||
}
|
||||
'gh-copilot' {
|
||||
# GitHub Copilot CLI via gh
|
||||
$args = @(
|
||||
'copilot', 'suggest',
|
||||
'-t', 'shell',
|
||||
"Review GitHub issue #$IssueNumber and generate analysis files"
|
||||
)
|
||||
|
||||
return @{
|
||||
Command = 'gh'
|
||||
Arguments = $args
|
||||
WorkingDirectory = $WorkingDirectory
|
||||
IssueNumber = $IssueNumber
|
||||
}
|
||||
}
|
||||
'vscode' {
|
||||
# VS Code with Copilot - open with prompt
|
||||
$args = @(
|
||||
'--new-window',
|
||||
$WorkingDirectory,
|
||||
'--goto', $tempPromptFile
|
||||
)
|
||||
|
||||
return @{
|
||||
Command = 'code'
|
||||
Arguments = $args
|
||||
WorkingDirectory = $WorkingDirectory
|
||||
IssueNumber = $IssueNumber
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Parallel Job Management
|
||||
function Start-ParallelIssueReviews {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Start parallel issue reviews with throttling.
|
||||
.PARAMETER Issues
|
||||
Array of issue objects to review.
|
||||
.PARAMETER MaxConcurrent
|
||||
Maximum number of parallel jobs. Default: 20.
|
||||
.PARAMETER CLIType
|
||||
CLI type to use for reviews.
|
||||
.PARAMETER RepoRoot
|
||||
Repository root path.
|
||||
.PARAMETER TimeoutMinutes
|
||||
Timeout per issue in minutes. Default: 30.
|
||||
.PARAMETER MaxRetryCount
|
||||
Maximum number of retries for failed issues. Default: 2.
|
||||
.PARAMETER RetryDelaySeconds
|
||||
Delay between retries in seconds. Default: 10.
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[array]$Issues,
|
||||
[int]$MaxConcurrent = 20,
|
||||
[ValidateSet('claude', 'copilot', 'gh-copilot', 'vscode')]
|
||||
[string]$CLIType = 'copilot',
|
||||
[Parameter(Mandatory)]
|
||||
[string]$RepoRoot,
|
||||
[int]$TimeoutMinutes = 30,
|
||||
[int]$MaxRetryCount = 2,
|
||||
[int]$RetryDelaySeconds = 10,
|
||||
[string]$FeedbackContext,
|
||||
[string]$Model
|
||||
)
|
||||
|
||||
$totalIssues = $Issues.Count
|
||||
$completed = 0
|
||||
$failed = @()
|
||||
$succeeded = @()
|
||||
$retryQueue = [System.Collections.Queue]::new()
|
||||
|
||||
Info "Starting parallel review of $totalIssues issues (max $MaxConcurrent concurrent, $MaxRetryCount retries)"
|
||||
|
||||
# Use PowerShell jobs for parallelization
|
||||
$jobs = @()
|
||||
$issueQueue = [System.Collections.Queue]::new($Issues)
|
||||
|
||||
while ($issueQueue.Count -gt 0 -or $jobs.Count -gt 0 -or $retryQueue.Count -gt 0) {
|
||||
# Process retry queue when main queue is empty
|
||||
if ($issueQueue.Count -eq 0 -and $retryQueue.Count -gt 0 -and $jobs.Count -lt $MaxConcurrent) {
|
||||
$retryItem = $retryQueue.Dequeue()
|
||||
Warn "🔄 Retrying issue #$($retryItem.IssueNumber) (attempt $($retryItem.Attempt + 1)/$($MaxRetryCount + 1))"
|
||||
Start-Sleep -Seconds $RetryDelaySeconds
|
||||
$issueQueue.Enqueue(@{ number = $retryItem.IssueNumber; _retryAttempt = $retryItem.Attempt + 1 })
|
||||
}
|
||||
|
||||
# Start new jobs up to MaxParallel
|
||||
while ($jobs.Count -lt $MaxConcurrent -and $issueQueue.Count -gt 0) {
|
||||
$issue = $issueQueue.Dequeue()
|
||||
$issueNum = $issue.number
|
||||
$retryAttempt = if ($issue._retryAttempt) { $issue._retryAttempt } else { 0 }
|
||||
|
||||
$attemptInfo = if ($retryAttempt -gt 0) { " (retry $retryAttempt)" } else { "" }
|
||||
Info "Starting review for issue #$issueNum$attemptInfo ($($totalIssues - $issueQueue.Count)/$totalIssues)"
|
||||
|
||||
$job = Start-Job -Name "Issue-$issueNum" -ScriptBlock {
|
||||
param($IssueNumber, $RepoRoot, $CLIType, $FeedbackCtx, $ModelOverride)
|
||||
|
||||
Set-Location $RepoRoot
|
||||
|
||||
# Import the library in the job context
|
||||
. "$RepoRoot/.github/review-tools/IssueReviewLib.ps1"
|
||||
|
||||
try {
|
||||
$reviewParams = @{
|
||||
IssueNumber = $IssueNumber
|
||||
RepoRoot = $RepoRoot
|
||||
CLIType = $CLIType
|
||||
}
|
||||
if ($FeedbackCtx) {
|
||||
$reviewParams.FeedbackContext = $FeedbackCtx
|
||||
}
|
||||
if ($ModelOverride) {
|
||||
$reviewParams.Model = $ModelOverride
|
||||
}
|
||||
$reviewCmd = Invoke-AIReview @reviewParams
|
||||
|
||||
# Execute the command using invocation operator (works for .ps1 scripts and executables)
|
||||
Set-Location $reviewCmd.WorkingDirectory
|
||||
$argList = $reviewCmd.Arguments
|
||||
|
||||
# Capture both stdout and stderr for better error reporting
|
||||
$output = & $reviewCmd.Command @argList 2>&1
|
||||
$exitCode = $LASTEXITCODE
|
||||
|
||||
# Get last 20 lines of output for error context
|
||||
$outputLines = $output | Out-String
|
||||
$lastLines = ($outputLines -split "`n" | Select-Object -Last 20) -join "`n"
|
||||
|
||||
# Check if output files were created (success indicator)
|
||||
$overviewPath = Join-Path $RepoRoot "Generated Files/issueReview/$IssueNumber/overview.md"
|
||||
$implPlanPath = Join-Path $RepoRoot "Generated Files/issueReview/$IssueNumber/implementation-plan.md"
|
||||
$filesCreated = (Test-Path $overviewPath) -and (Test-Path $implPlanPath)
|
||||
|
||||
return @{
|
||||
IssueNumber = $IssueNumber
|
||||
Success = ($exitCode -eq 0) -or $filesCreated
|
||||
ExitCode = $exitCode
|
||||
FilesCreated = $filesCreated
|
||||
Output = $lastLines
|
||||
Error = if ($exitCode -ne 0 -and -not $filesCreated) { "Exit code: $exitCode`n$lastLines" } else { $null }
|
||||
}
|
||||
}
|
||||
catch {
|
||||
return @{
|
||||
IssueNumber = $IssueNumber
|
||||
Success = $false
|
||||
ExitCode = -1
|
||||
FilesCreated = $false
|
||||
Output = $null
|
||||
Error = $_.Exception.Message
|
||||
}
|
||||
}
|
||||
} -ArgumentList $issueNum, $RepoRoot, $CLIType, $FeedbackContext, $Model
|
||||
|
||||
$jobs += @{
|
||||
Job = $job
|
||||
IssueNumber = $issueNum
|
||||
StartTime = Get-Date
|
||||
RetryAttempt = $retryAttempt
|
||||
}
|
||||
}
|
||||
|
||||
# Check for completed jobs
|
||||
$completedJobs = @()
|
||||
foreach ($jobInfo in $jobs) {
|
||||
$job = $jobInfo.Job
|
||||
$issueNum = $jobInfo.IssueNumber
|
||||
$startTime = $jobInfo.StartTime
|
||||
$retryAttempt = $jobInfo.RetryAttempt
|
||||
|
||||
if ($job.State -eq 'Completed') {
|
||||
$result = Receive-Job -Job $job
|
||||
Remove-Job -Job $job -Force
|
||||
|
||||
if ($result.Success) {
|
||||
Success "✓ Issue #$issueNum completed (files created: $($result.FilesCreated))"
|
||||
$succeeded += $issueNum
|
||||
$completed++
|
||||
} else {
|
||||
# Check if we should retry
|
||||
if ($retryAttempt -lt $MaxRetryCount) {
|
||||
$errorPreview = if ($result.Error) { ($result.Error -split "`n" | Select-Object -First 3) -join " | " } else { "Unknown error" }
|
||||
Warn "⚠ Issue #$issueNum failed (will retry): $errorPreview"
|
||||
$retryQueue.Enqueue(@{ IssueNumber = $issueNum; Attempt = $retryAttempt; LastError = $result.Error })
|
||||
} else {
|
||||
$errorMsg = if ($result.Error) { $result.Error } else { "Exit code: $($result.ExitCode)" }
|
||||
Err "✗ Issue #$issueNum failed after $($retryAttempt + 1) attempts:"
|
||||
Err " Error: $errorMsg"
|
||||
$failed += @{ IssueNumber = $issueNum; Error = $errorMsg; Attempts = $retryAttempt + 1 }
|
||||
$completed++
|
||||
}
|
||||
}
|
||||
$completedJobs += $jobInfo
|
||||
}
|
||||
elseif ($job.State -eq 'Failed') {
|
||||
$jobError = $job.ChildJobs[0].JobStateInfo.Reason.Message
|
||||
Remove-Job -Job $job -Force
|
||||
|
||||
if ($retryAttempt -lt $MaxRetryCount) {
|
||||
Warn "⚠ Issue #$issueNum job crashed (will retry): $jobError"
|
||||
$retryQueue.Enqueue(@{ IssueNumber = $issueNum; Attempt = $retryAttempt; LastError = $jobError })
|
||||
} else {
|
||||
Err "✗ Issue #$issueNum job failed after $($retryAttempt + 1) attempts: $jobError"
|
||||
$failed += @{ IssueNumber = $issueNum; Error = $jobError; Attempts = $retryAttempt + 1 }
|
||||
$completed++
|
||||
}
|
||||
$completedJobs += $jobInfo
|
||||
}
|
||||
elseif ((Get-Date) - $startTime -gt [TimeSpan]::FromMinutes($TimeoutMinutes)) {
|
||||
Stop-Job -Job $job -ErrorAction SilentlyContinue
|
||||
Remove-Job -Job $job -Force
|
||||
|
||||
if ($retryAttempt -lt $MaxRetryCount) {
|
||||
Warn "⏱ Issue #$issueNum timed out after $TimeoutMinutes min (will retry)"
|
||||
$retryQueue.Enqueue(@{ IssueNumber = $issueNum; Attempt = $retryAttempt; LastError = "Timeout after $TimeoutMinutes minutes" })
|
||||
} else {
|
||||
Err "⏱ Issue #$issueNum timed out after $($retryAttempt + 1) attempts"
|
||||
$failed += @{ IssueNumber = $issueNum; Error = "Timeout after $TimeoutMinutes minutes"; Attempts = $retryAttempt + 1 }
|
||||
$completed++
|
||||
}
|
||||
$completedJobs += $jobInfo
|
||||
}
|
||||
}
|
||||
|
||||
# Remove completed jobs from active list
|
||||
$jobs = $jobs | Where-Object { $_ -notin $completedJobs }
|
||||
|
||||
# Brief pause to avoid tight loop
|
||||
if ($jobs.Count -gt 0) {
|
||||
Start-Sleep -Seconds 2
|
||||
}
|
||||
}
|
||||
|
||||
# Extract just issue numbers for the failed list
|
||||
$failedNumbers = $failed | ForEach-Object { $_.IssueNumber }
|
||||
|
||||
return @{
|
||||
Total = $totalIssues
|
||||
Succeeded = $succeeded
|
||||
Failed = $failedNumbers
|
||||
FailedDetails = $failed
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Issue Review Results Helpers
|
||||
function Get-IssueReviewResult {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Check if an issue has been reviewed and get its results.
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[int]$IssueNumber,
|
||||
[Parameter(Mandatory)]
|
||||
[string]$RepoRoot
|
||||
)
|
||||
|
||||
$reviewPath = Get-IssueReviewPath -RepoRoot $RepoRoot -IssueNumber $IssueNumber
|
||||
|
||||
$result = @{
|
||||
IssueNumber = $IssueNumber
|
||||
Path = $reviewPath
|
||||
HasOverview = $false
|
||||
HasImplementationPlan = $false
|
||||
OverviewPath = $null
|
||||
ImplementationPlanPath = $null
|
||||
}
|
||||
|
||||
$overviewPath = Join-Path $reviewPath 'overview.md'
|
||||
$implPlanPath = Join-Path $reviewPath 'implementation-plan.md'
|
||||
|
||||
if (Test-Path $overviewPath) {
|
||||
$result.HasOverview = $true
|
||||
$result.OverviewPath = $overviewPath
|
||||
}
|
||||
|
||||
if (Test-Path $implPlanPath) {
|
||||
$result.HasImplementationPlan = $true
|
||||
$result.ImplementationPlanPath = $implPlanPath
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
|
||||
function Get-HighConfidenceIssues {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Find issues with high confidence for auto-fix based on review results.
|
||||
.PARAMETER RepoRoot
|
||||
Repository root path.
|
||||
.PARAMETER MinFeasibilityScore
|
||||
Minimum Technical Feasibility score (0-100). Default: 70.
|
||||
.PARAMETER MinClarityScore
|
||||
Minimum Requirement Clarity score (0-100). Default: 60.
|
||||
.PARAMETER MaxEffortDays
|
||||
Maximum effort estimate in days. Default: 2 (S = Small).
|
||||
.PARAMETER FilterIssueNumbers
|
||||
Optional array of issue numbers to filter to. If specified, only these issues are considered.
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$RepoRoot,
|
||||
[int]$MinFeasibilityScore = 70,
|
||||
[int]$MinClarityScore = 60,
|
||||
[int]$MaxEffortDays = 2,
|
||||
[int[]]$FilterIssueNumbers = @()
|
||||
)
|
||||
|
||||
$genFiles = Get-GeneratedFilesPath -RepoRoot $RepoRoot
|
||||
$reviewDir = Join-Path $genFiles 'issueReview'
|
||||
|
||||
if (-not (Test-Path $reviewDir)) {
|
||||
return @()
|
||||
}
|
||||
|
||||
$highConfidence = @()
|
||||
|
||||
Get-ChildItem -Path $reviewDir -Directory | ForEach-Object {
|
||||
$issueNum = [int]$_.Name
|
||||
|
||||
# Skip if filter is specified and this issue is not in the filter list
|
||||
if ($FilterIssueNumbers.Count -gt 0 -and $issueNum -notin $FilterIssueNumbers) {
|
||||
return
|
||||
}
|
||||
|
||||
$overviewPath = Join-Path $_.FullName 'overview.md'
|
||||
$implPlanPath = Join-Path $_.FullName 'implementation-plan.md'
|
||||
|
||||
if (-not (Test-Path $overviewPath) -or -not (Test-Path $implPlanPath)) {
|
||||
return
|
||||
}
|
||||
|
||||
# Parse overview.md to extract scores
|
||||
$overview = Get-Content $overviewPath -Raw
|
||||
|
||||
# Extract scores using regex (looking for score table or inline scores)
|
||||
$feasibility = 0
|
||||
$clarity = 0
|
||||
$effortDays = 999
|
||||
|
||||
# Try to extract from At-a-Glance Score Table
|
||||
if ($overview -match 'Technical Feasibility[^\d]*(\d+)/100') {
|
||||
$feasibility = [int]$Matches[1]
|
||||
}
|
||||
if ($overview -match 'Requirement Clarity[^\d]*(\d+)/100') {
|
||||
$clarity = [int]$Matches[1]
|
||||
}
|
||||
# Match effort formats like "0.5-1 day", "1-2 days", "2-3 days" - extract the upper bound
|
||||
if ($overview -match 'Effort Estimate[^|]*\|\s*[\d.]+(?:-(\d+))?\s*days?') {
|
||||
if ($Matches[1]) {
|
||||
$effortDays = [int]$Matches[1]
|
||||
} elseif ($overview -match 'Effort Estimate[^|]*\|\s*(\d+)\s*days?') {
|
||||
$effortDays = [int]$Matches[1]
|
||||
}
|
||||
}
|
||||
# Also check for XS/S sizing in the table (e.g., "| XS |" or "| S |" or "(XS)" or "(S)")
|
||||
if ($overview -match 'Effort Estimate[^|]*\|[^|]*\|\s*(XS|S)\b') {
|
||||
# XS = 1 day, S = 2 days
|
||||
if ($Matches[1] -eq 'XS') {
|
||||
$effortDays = 1
|
||||
} else {
|
||||
$effortDays = 2
|
||||
}
|
||||
} elseif ($overview -match 'Effort Estimate[^|]*\|[^|]*\(XS\)') {
|
||||
$effortDays = 1
|
||||
} elseif ($overview -match 'Effort Estimate[^|]*\|[^|]*\(S\)') {
|
||||
$effortDays = 2
|
||||
}
|
||||
|
||||
if ($feasibility -ge $MinFeasibilityScore -and
|
||||
$clarity -ge $MinClarityScore -and
|
||||
$effortDays -le $MaxEffortDays) {
|
||||
|
||||
$highConfidence += @{
|
||||
IssueNumber = $issueNum
|
||||
FeasibilityScore = $feasibility
|
||||
ClarityScore = $clarity
|
||||
EffortDays = $effortDays
|
||||
OverviewPath = $overviewPath
|
||||
ImplementationPlanPath = $implPlanPath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $highConfidence | Sort-Object -Property FeasibilityScore -Descending
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Worktree Integration
|
||||
function Copy-IssueReviewToWorktree {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Copy the Generated Files for an issue to a worktree.
|
||||
.PARAMETER IssueNumber
|
||||
The issue number.
|
||||
.PARAMETER SourceRepoRoot
|
||||
Source repository root (main repo).
|
||||
.PARAMETER WorktreePath
|
||||
Destination worktree path.
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[int]$IssueNumber,
|
||||
[Parameter(Mandatory)]
|
||||
[string]$SourceRepoRoot,
|
||||
[Parameter(Mandatory)]
|
||||
[string]$WorktreePath
|
||||
)
|
||||
|
||||
$sourceReviewPath = Get-IssueReviewPath -RepoRoot $SourceRepoRoot -IssueNumber $IssueNumber
|
||||
$destReviewPath = Get-IssueReviewPath -RepoRoot $WorktreePath -IssueNumber $IssueNumber
|
||||
|
||||
if (-not (Test-Path $sourceReviewPath)) {
|
||||
throw "Issue review files not found at: $sourceReviewPath"
|
||||
}
|
||||
|
||||
Ensure-DirectoryExists -Path $destReviewPath
|
||||
|
||||
# Copy all files from the issue review folder
|
||||
Copy-Item -Path "$sourceReviewPath\*" -Destination $destReviewPath -Recurse -Force
|
||||
|
||||
Info "Copied issue review files to: $destReviewPath"
|
||||
|
||||
return $destReviewPath
|
||||
}
|
||||
#endregion
|
||||
|
||||
# Note: This script is dot-sourced, not imported as a module.
|
||||
# All functions above are available after: . "path/to/IssueReviewLib.ps1"
|
||||
@@ -1,291 +0,0 @@
|
||||
<#!
|
||||
.SYNOPSIS
|
||||
Bulk review GitHub issues using AI CLI (Claude Code or GitHub Copilot).
|
||||
|
||||
.DESCRIPTION
|
||||
Queries GitHub issues by labels, state, and sort order, then kicks off parallel
|
||||
AI-powered reviews for each issue. Results are stored in Generated Files/issueReview/<number>/.
|
||||
|
||||
.PARAMETER Labels
|
||||
Comma-separated list of labels to filter issues (e.g., "bug,help wanted").
|
||||
|
||||
.PARAMETER State
|
||||
Issue state: open, closed, or all. Default: open.
|
||||
|
||||
.PARAMETER Sort
|
||||
Sort field: created, updated, comments, reactions. Default: created.
|
||||
|
||||
.PARAMETER Order
|
||||
Sort order: asc or desc. Default: desc.
|
||||
|
||||
.PARAMETER Limit
|
||||
Maximum number of issues to process. Default: 100.
|
||||
|
||||
.PARAMETER MaxConcurrent
|
||||
Maximum parallel review jobs. Default: 20.
|
||||
|
||||
.PARAMETER CLIType
|
||||
AI CLI to use: claude, gh-copilot, or vscode. Auto-detected if not specified.
|
||||
|
||||
.PARAMETER DryRun
|
||||
List issues without starting reviews.
|
||||
|
||||
.PARAMETER SkipExisting
|
||||
Skip issues that already have review files.
|
||||
|
||||
.PARAMETER Repository
|
||||
Repository in owner/repo format. Default: microsoft/PowerToys.
|
||||
|
||||
.PARAMETER TimeoutMinutes
|
||||
Timeout per issue review in minutes. Default: 30.
|
||||
|
||||
.EXAMPLE
|
||||
# Review all open bugs sorted by reactions
|
||||
./Start-BulkIssueReview.ps1 -Labels "bug" -Sort reactions -Order desc
|
||||
|
||||
.EXAMPLE
|
||||
# Dry run to see which issues would be reviewed
|
||||
./Start-BulkIssueReview.ps1 -Labels "help wanted" -DryRun
|
||||
|
||||
.EXAMPLE
|
||||
# Review top 50 issues with Claude Code, max 10 parallel
|
||||
./Start-BulkIssueReview.ps1 -Labels "Issue-Bug" -Limit 50 -MaxConcurrent 10 -CLIType claude
|
||||
|
||||
.EXAMPLE
|
||||
# Skip already-reviewed issues
|
||||
./Start-BulkIssueReview.ps1 -Labels "Issue-Feature" -SkipExisting
|
||||
|
||||
.NOTES
|
||||
Requires: GitHub CLI (gh) authenticated, and either Claude Code CLI or VS Code with Copilot.
|
||||
Results: Generated Files/issueReview/<issue_number>/overview.md and implementation-plan.md
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Position = 0)]
|
||||
[string]$Labels,
|
||||
|
||||
[ValidateSet('open', 'closed', 'all')]
|
||||
[string]$State = 'open',
|
||||
|
||||
[ValidateSet('created', 'updated', 'comments', 'reactions')]
|
||||
[string]$Sort = 'created',
|
||||
|
||||
[ValidateSet('asc', 'desc')]
|
||||
[string]$Order = 'desc',
|
||||
|
||||
[int]$Limit = 1000,
|
||||
|
||||
[int]$MaxConcurrent = 20,
|
||||
|
||||
[ValidateSet('claude', 'copilot', 'gh-copilot', 'vscode', 'auto')]
|
||||
[string]$CLIType = 'auto',
|
||||
|
||||
[switch]$DryRun,
|
||||
|
||||
[switch]$SkipExisting,
|
||||
|
||||
[string]$Repository = 'microsoft/PowerToys',
|
||||
|
||||
[int]$TimeoutMinutes = 30,
|
||||
|
||||
[int]$MaxRetryCount = 2,
|
||||
|
||||
[int]$RetryDelaySeconds = 10,
|
||||
|
||||
[switch]$Force,
|
||||
|
||||
[int]$IssueNumber,
|
||||
|
||||
[int[]]$IssueNumbers,
|
||||
|
||||
[string]$FeedbackFile,
|
||||
|
||||
[string]$Model,
|
||||
|
||||
[switch]$Help
|
||||
)
|
||||
|
||||
# Load library
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
. "$scriptDir/IssueReviewLib.ps1"
|
||||
|
||||
# Show help
|
||||
if ($Help) {
|
||||
Get-Help $MyInvocation.MyCommand.Path -Full
|
||||
return
|
||||
}
|
||||
|
||||
#region Main Script
|
||||
try {
|
||||
# Get repo root
|
||||
$repoRoot = Get-RepoRoot
|
||||
Info "Repository root: $repoRoot"
|
||||
|
||||
# Detect or validate CLI
|
||||
if ($CLIType -eq 'auto') {
|
||||
$cli = Get-AvailableCLI
|
||||
if (-not $cli) {
|
||||
throw "No AI CLI found. Please install Claude Code CLI or GitHub Copilot CLI extension."
|
||||
}
|
||||
$CLIType = $cli.Type
|
||||
Info "Auto-detected CLI: $($cli.Name)"
|
||||
}
|
||||
|
||||
# Load feedback context if provided
|
||||
$feedbackContext = $null
|
||||
if ($FeedbackFile -and (Test-Path $FeedbackFile)) {
|
||||
$feedbackContext = Get-Content $FeedbackFile -Raw
|
||||
Info "Loaded feedback from: $FeedbackFile"
|
||||
}
|
||||
elseif ($FeedbackFile) {
|
||||
Warn "Feedback file not found: $FeedbackFile (proceeding without feedback)"
|
||||
}
|
||||
|
||||
# Determine issue list: explicit IssueNumber(s) take priority over label query
|
||||
if ($IssueNumber -gt 0) {
|
||||
Info "`nUsing single issue: #$IssueNumber"
|
||||
$issues = @(@{ number = $IssueNumber })
|
||||
}
|
||||
elseif ($IssueNumbers -and $IssueNumbers.Count -gt 0) {
|
||||
Info "`nUsing explicit issue list: $($IssueNumbers -join ', ')"
|
||||
$issues = $IssueNumbers | ForEach-Object { @{ number = $_ } }
|
||||
}
|
||||
else {
|
||||
# Query issues from GitHub
|
||||
Info "`nQuerying issues with filters:"
|
||||
Info " Labels: $(if ($Labels) { $Labels } else { '(none)' })"
|
||||
Info " State: $State"
|
||||
Info " Sort: $Sort $Order"
|
||||
Info " Limit: $Limit"
|
||||
|
||||
$issues = Get-GitHubIssues -Labels $Labels -State $State -Sort $Sort -Order $Order -Limit $Limit -Repository $Repository
|
||||
}
|
||||
|
||||
if ($issues.Count -eq 0) {
|
||||
Warn "No issues found matching the criteria."
|
||||
return
|
||||
}
|
||||
|
||||
Info "`nFound $($issues.Count) issues"
|
||||
|
||||
# Filter out existing reviews if requested
|
||||
if ($SkipExisting) {
|
||||
$originalCount = $issues.Count
|
||||
$issues = $issues | Where-Object {
|
||||
$result = Get-IssueReviewResult -IssueNumber $_.number -RepoRoot $repoRoot
|
||||
-not ($result.HasOverview -and $result.HasImplementationPlan)
|
||||
}
|
||||
$skipped = $originalCount - $issues.Count
|
||||
if ($skipped -gt 0) {
|
||||
Info "Skipping $skipped issues with existing reviews"
|
||||
}
|
||||
}
|
||||
|
||||
if ($issues.Count -eq 0) {
|
||||
Warn "All issues already have reviews. Nothing to do."
|
||||
return
|
||||
}
|
||||
|
||||
# Display issue list
|
||||
Info "`nIssues to review:"
|
||||
Info ("-" * 80)
|
||||
foreach ($issue in $issues) {
|
||||
$labels = ($issue.labels | ForEach-Object { $_.name }) -join ', '
|
||||
$reactions = if ($issue.reactions) { $issue.reactions.totalCount } else { 0 }
|
||||
Info ("#{0,-6} {1,-50} [👍{2}] [{3}]" -f $issue.number, ($issue.title.Substring(0, [Math]::Min(50, $issue.title.Length))), $reactions, $labels)
|
||||
}
|
||||
Info ("-" * 80)
|
||||
|
||||
if ($DryRun) {
|
||||
Warn "`nDry run mode - no reviews started."
|
||||
Info "Would review $($issues.Count) issues with CLI: $CLIType"
|
||||
return
|
||||
}
|
||||
|
||||
# Confirm before proceeding (skip if -Force)
|
||||
if (-not $Force) {
|
||||
$confirm = Read-Host "`nProceed with reviewing $($issues.Count) issues using $CLIType? (y/N)"
|
||||
if ($confirm -notmatch '^[yY]') {
|
||||
Info "Cancelled."
|
||||
return
|
||||
}
|
||||
} else {
|
||||
Info "`nProceeding with $($issues.Count) issues (Force mode)"
|
||||
}
|
||||
|
||||
# Create output directory
|
||||
$genFiles = Get-GeneratedFilesPath -RepoRoot $repoRoot
|
||||
Ensure-DirectoryExists -Path (Join-Path $genFiles 'issueReview')
|
||||
|
||||
# Start parallel reviews
|
||||
Info "`nStarting bulk review..."
|
||||
Info " Max retries: $MaxRetryCount (delay: ${RetryDelaySeconds}s)"
|
||||
$startTime = Get-Date
|
||||
|
||||
$results = Start-ParallelIssueReviews `
|
||||
-Issues $issues `
|
||||
-MaxConcurrent $MaxConcurrent `
|
||||
-CLIType $CLIType `
|
||||
-RepoRoot $repoRoot `
|
||||
-TimeoutMinutes $TimeoutMinutes `
|
||||
-MaxRetryCount $MaxRetryCount `
|
||||
-RetryDelaySeconds $RetryDelaySeconds `
|
||||
-FeedbackContext $feedbackContext `
|
||||
-Model $Model
|
||||
|
||||
$duration = (Get-Date) - $startTime
|
||||
|
||||
# Summary
|
||||
Info "`n" + ("=" * 80)
|
||||
Info "BULK REVIEW COMPLETE"
|
||||
Info ("=" * 80)
|
||||
Info "Total issues: $($results.Total)"
|
||||
Success "Succeeded: $($results.Succeeded.Count)"
|
||||
if ($results.Failed.Count -gt 0) {
|
||||
Err "Failed: $($results.Failed.Count)"
|
||||
Err "Failed issues: $($results.Failed -join ', ')"
|
||||
Info ""
|
||||
Info "Failed Issue Details:"
|
||||
Info ("-" * 40)
|
||||
foreach ($failedItem in $results.FailedDetails) {
|
||||
Err " #$($failedItem.IssueNumber) (attempts: $($failedItem.Attempts)):"
|
||||
$errorLines = ($failedItem.Error -split "`n" | Select-Object -First 5) -join "`n "
|
||||
Err " $errorLines"
|
||||
}
|
||||
Info ("-" * 40)
|
||||
}
|
||||
Info "Duration: $($duration.ToString('hh\:mm\:ss'))"
|
||||
Info "Output: $genFiles/issueReview/"
|
||||
Info ("=" * 80)
|
||||
|
||||
# Write signal file for each issue processed
|
||||
foreach ($issueNum in $results.Succeeded) {
|
||||
$signalPath = Join-Path $genFiles "issueReview/$issueNum/.signal"
|
||||
@{
|
||||
status = "success"
|
||||
issueNumber = $issueNum
|
||||
timestamp = (Get-Date).ToString("o")
|
||||
outputs = @("overview.md", "implementation-plan.md")
|
||||
} | ConvertTo-Json | Set-Content $signalPath -Force
|
||||
Info "Signal: $signalPath"
|
||||
}
|
||||
foreach ($issueNum in $results.Failed) {
|
||||
$signalPath = Join-Path $genFiles "issueReview/$issueNum/.signal"
|
||||
$failDetail = $results.FailedDetails | Where-Object { $_.IssueNumber -eq $issueNum }
|
||||
@{
|
||||
status = "failure"
|
||||
issueNumber = $issueNum
|
||||
timestamp = (Get-Date).ToString("o")
|
||||
error = $failDetail.Error
|
||||
} | ConvertTo-Json | Set-Content $signalPath -Force
|
||||
}
|
||||
|
||||
# Return results for pipeline
|
||||
return $results
|
||||
}
|
||||
catch {
|
||||
Err "Error: $($_.Exception.Message)"
|
||||
exit 1
|
||||
}
|
||||
#endregion
|
||||
21
.github/skills/issue-to-pr-cycle/LICENSE.txt
vendored
21
.github/skills/issue-to-pr-cycle/LICENSE.txt
vendored
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
287
.github/skills/issue-to-pr-cycle/SKILL.md
vendored
287
.github/skills/issue-to-pr-cycle/SKILL.md
vendored
@@ -1,287 +0,0 @@
|
||||
---
|
||||
name: issue-to-pr-cycle
|
||||
description: End-to-end orchestration from issue analysis to PR creation and review. This skill is the ORCHESTRATION BRAIN that invokes other skills via CLI and performs VS Code MCP operations directly.
|
||||
license: Complete terms in LICENSE.txt
|
||||
---
|
||||
|
||||
# Issue-to-PR Full Cycle Skill
|
||||
|
||||
**ORCHESTRATION BRAIN** - coordinates other skills and performs VS Code MCP operations.
|
||||
|
||||
## Skill Contents
|
||||
|
||||
```
|
||||
.github/skills/issue-to-pr-cycle/
|
||||
├── SKILL.md # This file (orchestration brain)
|
||||
├── LICENSE.txt # MIT License
|
||||
└── scripts/
|
||||
├── Get-CycleStatus.ps1 # Check status of issues/PRs
|
||||
├── IssueReviewLib.ps1 # Shared helpers
|
||||
└── Start-FullIssueCycle.ps1 # Legacy script (phases A-C)
|
||||
```
|
||||
|
||||
**Orchestrates these skills:**
|
||||
| Skill | Purpose |
|
||||
|-------|---------|
|
||||
| `issue-review` | Analyze issues, generate implementation plans |
|
||||
| `issue-review-review` | Validate review quality, loop until score ≥ 90 |
|
||||
| `issue-fix` | Create worktrees, apply fixes, create PRs |
|
||||
| `pr-review` | Comprehensive PR review (13 steps) |
|
||||
| `pr-fix` | Fix review comments, resolve threads |
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- GitHub CLI (`gh`) installed and authenticated
|
||||
- Copilot CLI or Claude CLI installed
|
||||
- PowerShell 7+
|
||||
- VS Code with MCP tools (for write operations)
|
||||
|
||||
## Required Variables
|
||||
|
||||
| Variable | Description | Example |
|
||||
|----------|-------------|---------|
|
||||
| `{{IssueNumbers}}` | Issue numbers to process | `45363, 45364` |
|
||||
| (or) `{{PRNumbers}}` | PR numbers for review/fix loop | `45365, 45366` |
|
||||
|
||||
## How This Skill Works
|
||||
|
||||
The orchestrator:
|
||||
1. **Invokes skills via CLI** - kicks off `copilot` CLI (not `gh copilot`) to run each skill
|
||||
2. **Runs in parallel** - use PowerShell 7 `ForEach-Object -Parallel` in SINGLE terminal
|
||||
3. **Waits for signals** - polls for `.signal` files indicating completion
|
||||
4. **Performs VS Code MCP directly** - for operations that require write access (request reviewer, resolve threads)
|
||||
|
||||
## Quality Gates (CRITICAL)
|
||||
|
||||
**Every PR must pass these quality checks before creation:**
|
||||
|
||||
1. **Real Implementation** - NO placeholder/stub code
|
||||
- Files must contain actual working code
|
||||
- Empty classes like `class FixXXX { }` are FORBIDDEN
|
||||
|
||||
2. **Proper PR Title** - Follow Conventional Commits
|
||||
- Use `.github/prompts/create-commit-title.prompt.md`
|
||||
- Format: `feat(module): description` or `fix(module): description`
|
||||
- NEVER use generic titles like "fix: address issue #12345"
|
||||
|
||||
3. **Full PR Description** - Based on actual diff
|
||||
- Use `.github/prompts/create-pr-summary.prompt.md`
|
||||
- Run `git diff main...HEAD` to analyze changes
|
||||
- Fill PR template with real information
|
||||
|
||||
4. **Build Verification** - Code must compile
|
||||
- Run `tools/build/build.cmd` in worktree
|
||||
- Exit code 0 = success
|
||||
|
||||
### Checking Worktree Quality
|
||||
|
||||
```powershell
|
||||
# Check if worktree has real implementation (not stubs)
|
||||
$files = git diff main --name-only
|
||||
foreach ($file in $files) {
|
||||
if ($file -match "src/common/fixes/Fix\d+\.cs") {
|
||||
Write-Error "STUB FILE DETECTED: $file - Need real implementation"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Signal Files
|
||||
|
||||
Each skill produces a `.signal` file when complete:
|
||||
|
||||
| Skill | Signal Location | Status Values |
|
||||
|-------|-----------------|---------------|
|
||||
| `issue-review` | `Generated Files/issueReview/<issue>/.signal` | `success`, `failure` |
|
||||
| `issue-review-review` | `Generated Files/issueReviewReview/<issue>/.signal` | `success`, `failure` |
|
||||
| `issue-fix` | `Generated Files/issueFix/<issue>/.signal` | `success`, `failure` |
|
||||
| `pr-review` | `Generated Files/prReview/<pr>/.signal` | `success`, `failure` |
|
||||
| `pr-fix` | `Generated Files/prFix/<pr>/.signal` | `success`, `partial`, `failure` |
|
||||
|
||||
Signal format:
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"issueNumber": 45363,
|
||||
"timestamp": "2026-02-04T10:05:23Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ ORCHESTRATOR (this skill, VS Code agent) │
|
||||
│ │
|
||||
│ ┌─────────────┐ ┌──────────────────┐ ┌─────────────┐ │
|
||||
│ │ issue-review│◄─┤issue-review- │ │ issue-fix │ │
|
||||
│ │ (CLI) │ │review (CLI) │ │ (CLI) │ │
|
||||
│ └──────┬──────┘ │ loop until ≥90 │ └──────┬──────┘ │
|
||||
│ │ └────────┬─────────┘ │ │
|
||||
│ └────────►─────────┘ │ │
|
||||
│ │ │
|
||||
│ ┌─────────────┐ ┌─────────────┐ │ │
|
||||
│ │ pr-review │ │ pr-fix │ │ │
|
||||
│ │ (CLI) │ │ (CLI) │ │ │
|
||||
│ └──────┬──────┘ └──────┬──────┘ │ │
|
||||
│ │ │ │ │
|
||||
│ ▼ ▼ ▼ │
|
||||
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Signal Files (Generated Files/*/.signal) │ │
|
||||
│ └─────────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ VS Code MCP Operations (orchestrator executes directly): │
|
||||
│ - mcp_github_request_copilot_review │
|
||||
│ - gh api graphql (resolve threads) │
|
||||
│ - Post review comments │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Workflow
|
||||
|
||||
### Phase A: Issue Review
|
||||
|
||||
Use the orchestration script instead of inline commands:
|
||||
|
||||
```powershell
|
||||
.github/skills/issue-to-pr-cycle/scripts/Start-FullIssueCycle.ps1 -IssueNumbers 45363,45364
|
||||
```
|
||||
|
||||
### Phase A2: Review-Review Loop (Quality Gate)
|
||||
|
||||
After issue-review completes, validate the review quality. Loop until quality score ≥ 90 or max iterations reached.
|
||||
|
||||
**A2.1: Run review-review**
|
||||
```powershell
|
||||
.github/skills/issue-review-review/scripts/Start-IssueReviewReviewParallel.ps1 -IssueNumbers 45363,45364 -CLIType copilot -ThrottleLimit 5 -Force
|
||||
```
|
||||
|
||||
**A2.2: Check signals**
|
||||
```powershell
|
||||
# For each issue, check the review-review signal
|
||||
$signal = Get-Content "Generated Files/issueReviewReview/45363/.signal" | ConvertFrom-Json
|
||||
# If signal.needsReReview is true (qualityScore < 90), re-run issue-review with feedback
|
||||
```
|
||||
|
||||
**A2.3: Re-run issue-review with feedback (if needed)**
|
||||
```powershell
|
||||
# Re-run issue-review, passing the reviewTheReview.md feedback file
|
||||
.github/skills/issue-review/scripts/Start-BulkIssueReview.ps1 -IssueNumber 45363 -FeedbackFile "Generated Files/issueReviewReview/45363/reviewTheReview.md" -Force
|
||||
```
|
||||
|
||||
**A2.4: Loop** — Go back to A2.1 until:
|
||||
- All issues have quality score ≥ 90, OR
|
||||
- Maximum 3 iterations reached per issue
|
||||
|
||||
### Phase B: Issue Fix
|
||||
|
||||
Use the parallel runner script:
|
||||
|
||||
```powershell
|
||||
.github/skills/issue-fix/scripts/Start-IssueFixParallel.ps1 -IssueNumbers 45363,45364 -CLIType copilot -ThrottleLimit 5 -Force
|
||||
```
|
||||
|
||||
### Phase C: PR Review
|
||||
|
||||
Use the pr-review script for each PR, or run the full cycle script to orchestrate:
|
||||
|
||||
```powershell
|
||||
.github/skills/pr-review/scripts/Start-PRReviewWorkflow.ps1 -PRNumber 45392
|
||||
```
|
||||
|
||||
### Phase D: Review/Fix Loop (VS Code Agent Orchestrated)
|
||||
|
||||
This phase requires the VS Code agent to:
|
||||
|
||||
**D1: Request Copilot review (VS Code MCP)**
|
||||
```
|
||||
mcp_github_request_copilot_review:
|
||||
owner: microsoft
|
||||
repo: PowerToys
|
||||
pullNumber: {{PRNumber}}
|
||||
```
|
||||
|
||||
**D2: Invoke pr-review skill (CLI, parallel)**
|
||||
```powershell
|
||||
gh copilot -p "Run skill pr-review for PR #{{PRNumber}}"
|
||||
# Wait for: Generated Files/prReview/{{PRNumber}}/.signal
|
||||
```
|
||||
|
||||
**D3: Check results**
|
||||
- Read `Generated Files/prReview/{{PRNumber}}/00-OVERVIEW.md`
|
||||
- Query unresolved threads via GraphQL
|
||||
|
||||
**D4: Post comments (VS Code MCP) - if medium+ severity**
|
||||
|
||||
**D5: Invoke pr-fix skill in WORKTREE (CLI)**
|
||||
```powershell
|
||||
# Find worktree for this PR's branch
|
||||
$branch = (gh pr view {{PRNumber}} --json headRefName -q .headRefName)
|
||||
$worktree = git worktree list --porcelain | Select-String "worktree.*$branch" | ...
|
||||
|
||||
# Run fix in worktree
|
||||
cd $worktreePath
|
||||
gh copilot -p "Run skill pr-fix for PR #{{PRNumber}}"
|
||||
# Wait for: Generated Files/prFix/{{PRNumber}}/.signal
|
||||
```
|
||||
|
||||
**D6: Resolve threads (VS Code MCP)**
|
||||
```powershell
|
||||
# Get thread IDs
|
||||
gh api graphql -f query='query { repository(owner:"microsoft",name:"PowerToys") {
|
||||
pullRequest(number:{{PRNumber}}) { reviewThreads(first:50) { nodes { id isResolved } } }
|
||||
} }'
|
||||
|
||||
# Resolve each (VS Code agent executes this)
|
||||
gh api graphql -f query='mutation { resolveReviewThread(input:{threadId:"{{ID}}"}) { thread { isResolved } } }'
|
||||
```
|
||||
|
||||
**D7: Loop**
|
||||
- If unresolved issues remain → go to D2
|
||||
- If all clear → done
|
||||
|
||||
## Timeout Handling
|
||||
|
||||
Default timeout: 10 minutes per skill invocation.
|
||||
|
||||
If no signal file appears within timeout:
|
||||
1. Check if the skill process is still running
|
||||
2. If hung, terminate and mark as `timeout`
|
||||
3. Log failure and continue with other items
|
||||
|
||||
## Parallel Execution (CRITICAL)
|
||||
|
||||
**DO NOT spawn separate terminals for each operation.** Use the dedicated scripts to run parallel work from a single terminal:
|
||||
|
||||
```powershell
|
||||
# Issue fixes in parallel
|
||||
.github/skills/issue-fix/scripts/Start-IssueFixParallel.ps1 -IssueNumbers 28726,13336,27507,3054,37800 -CLIType copilot -Model gpt-5.2-codex -ThrottleLimit 5 -Force
|
||||
|
||||
# PR fixes in parallel
|
||||
.github/skills/pr-fix/scripts/Start-PRFixParallel.ps1 -PRNumbers 45256,45257,45285,45286 -CLIType copilot -Model gpt-5.2-codex -ThrottleLimit 3 -Force
|
||||
```
|
||||
|
||||
## Worktree Mapping
|
||||
|
||||
The orchestrator must track which worktree belongs to which issue/PR:
|
||||
|
||||
```powershell
|
||||
# Get all worktrees
|
||||
$worktrees = git worktree list --porcelain | Select-String "worktree|branch" |
|
||||
ForEach-Object { $_.Line }
|
||||
|
||||
# Parse into mapping
|
||||
# Q:\PowerToys-ab12 → issue/44044
|
||||
# Q:\PowerToys-cd34 → issue/32950
|
||||
|
||||
# Find worktree for issue
|
||||
$issueNum = 45363
|
||||
$worktreeLine = git worktree list | Select-String "issue/$issueNum"
|
||||
$worktreePath = ($worktreeLine -split '\s+')[0]
|
||||
```
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
- Process multiple issues end-to-end
|
||||
- Automate the full issue → PR → review → fix cycle
|
||||
- Batch process high-confidence issues
|
||||
- Run continuous review/fix loops until clean
|
||||
@@ -1,370 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Get the current status of issues/PRs in the issue-to-PR cycle.
|
||||
|
||||
.DESCRIPTION
|
||||
Checks the status of:
|
||||
- Issue review completion (has overview.md + implementation-plan.md)
|
||||
- Issue fix completion (has worktree + commits)
|
||||
- PR creation status (has open PR)
|
||||
- PR review status (has review files)
|
||||
- PR active comments count
|
||||
|
||||
.PARAMETER IssueNumbers
|
||||
Array of issue numbers to check status for.
|
||||
|
||||
.PARAMETER PRNumbers
|
||||
Array of PR numbers to check status for.
|
||||
|
||||
.PARAMETER CheckAll
|
||||
Check all issues with review data and all open PRs with issue/* branches.
|
||||
|
||||
.EXAMPLE
|
||||
./Get-CycleStatus.ps1 -IssueNumbers 44044, 32950
|
||||
|
||||
.EXAMPLE
|
||||
./Get-CycleStatus.ps1 -PRNumbers 45234, 45235
|
||||
|
||||
.EXAMPLE
|
||||
./Get-CycleStatus.ps1 -CheckAll
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[int[]]$IssueNumbers = @(),
|
||||
[int[]]$PRNumbers = @(),
|
||||
[switch]$CheckAll,
|
||||
[switch]$JsonOutput
|
||||
)
|
||||
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
. (Join-Path $scriptDir 'IssueReviewLib.ps1')
|
||||
|
||||
$repoRoot = Get-RepoRoot
|
||||
$genFiles = Get-GeneratedFilesPath -RepoRoot $repoRoot
|
||||
$worktreeLib = Join-Path $repoRoot 'tools/build/WorktreeLib.ps1'
|
||||
if (Test-Path $worktreeLib) {
|
||||
. $worktreeLib
|
||||
}
|
||||
|
||||
function Get-IssueStatus {
|
||||
param([int]$IssueNumber)
|
||||
|
||||
$status = @{
|
||||
IssueNumber = $IssueNumber
|
||||
HasReview = $false
|
||||
HasImplementationPlan = $false
|
||||
FeasibilityScore = 0
|
||||
ClarityScore = 0
|
||||
EffortDays = 0
|
||||
HasWorktree = $false
|
||||
WorktreePath = $null
|
||||
HasCommits = $false
|
||||
CommitCount = 0
|
||||
HasPR = $false
|
||||
PRNumber = 0
|
||||
PRState = $null
|
||||
PRUrl = $null
|
||||
ReviewSignalStatus = $null
|
||||
ReviewSignalTimestamp = $null
|
||||
ReviewReviewSignalStatus = $null
|
||||
ReviewReviewQualityScore = 0
|
||||
ReviewReviewIteration = 0
|
||||
ReviewReviewNeedsReReview = $false
|
||||
FixSignalStatus = $null
|
||||
FixSignalTimestamp = $null
|
||||
}
|
||||
|
||||
# Check review status
|
||||
$reviewDir = Join-Path $genFiles "issueReview/$IssueNumber"
|
||||
$overviewPath = Join-Path $reviewDir 'overview.md'
|
||||
$implPlanPath = Join-Path $reviewDir 'implementation-plan.md'
|
||||
|
||||
if (Test-Path $overviewPath) {
|
||||
$status.HasReview = $true
|
||||
$overview = Get-Content $overviewPath -Raw
|
||||
|
||||
if ($overview -match 'Technical Feasibility[^\d]*(\d+)/100') {
|
||||
$status.FeasibilityScore = [int]$Matches[1]
|
||||
}
|
||||
if ($overview -match 'Requirement Clarity[^\d]*(\d+)/100') {
|
||||
$status.ClarityScore = [int]$Matches[1]
|
||||
}
|
||||
if ($overview -match 'Effort Estimate[^|]*\|\s*[\d.]+(?:-(\d+))?\s*days?') {
|
||||
$status.EffortDays = if ($Matches[1]) { [int]$Matches[1] } else { 1 }
|
||||
}
|
||||
}
|
||||
|
||||
if (Test-Path $implPlanPath) {
|
||||
$status.HasImplementationPlan = $true
|
||||
}
|
||||
|
||||
# Check review signal
|
||||
$reviewSignalPath = Join-Path $reviewDir '.signal'
|
||||
if (Test-Path $reviewSignalPath) {
|
||||
try {
|
||||
$reviewSignal = Get-Content $reviewSignalPath -Raw | ConvertFrom-Json
|
||||
$status.ReviewSignalStatus = $reviewSignal.status
|
||||
$status.ReviewSignalTimestamp = $reviewSignal.timestamp
|
||||
}
|
||||
catch {}
|
||||
}
|
||||
|
||||
# Check review-review signal
|
||||
$reviewReviewSignalPath = Join-Path $genFiles "issueReviewReview/$IssueNumber/.signal"
|
||||
if (Test-Path $reviewReviewSignalPath) {
|
||||
try {
|
||||
$rrSignal = Get-Content $reviewReviewSignalPath -Raw | ConvertFrom-Json
|
||||
$status.ReviewReviewSignalStatus = $rrSignal.status
|
||||
$status.ReviewReviewQualityScore = [int]$rrSignal.qualityScore
|
||||
$status.ReviewReviewIteration = [int]$rrSignal.iteration
|
||||
$status.ReviewReviewNeedsReReview = [bool]$rrSignal.needsReReview
|
||||
}
|
||||
catch {}
|
||||
}
|
||||
|
||||
# Check worktree status
|
||||
$worktrees = Get-WorktreeEntries | Where-Object { $_.Branch -like "issue/$IssueNumber*" }
|
||||
if ($worktrees) {
|
||||
$status.HasWorktree = $true
|
||||
$status.WorktreePath = $worktrees[0].Path
|
||||
|
||||
# Check for commits
|
||||
Push-Location $status.WorktreePath
|
||||
try {
|
||||
$commits = git log --oneline "main..HEAD" 2>$null
|
||||
if ($commits) {
|
||||
$status.HasCommits = $true
|
||||
$status.CommitCount = @($commits).Count
|
||||
}
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
}
|
||||
}
|
||||
|
||||
# Check fix signal
|
||||
$fixSignalPath = Join-Path $genFiles "issueFix/$IssueNumber/.signal"
|
||||
if (Test-Path $fixSignalPath) {
|
||||
try {
|
||||
$fixSignal = Get-Content $fixSignalPath -Raw | ConvertFrom-Json
|
||||
$status.FixSignalStatus = $fixSignal.status
|
||||
$status.FixSignalTimestamp = $fixSignal.timestamp
|
||||
}
|
||||
catch {}
|
||||
}
|
||||
|
||||
# Check PR status
|
||||
$prs = gh pr list --head "issue/$IssueNumber" --state all --json number,url,state 2>$null | ConvertFrom-Json
|
||||
if (-not $prs -or $prs.Count -eq 0) {
|
||||
# Try searching by issue reference
|
||||
$prs = gh pr list --search "fixes #$IssueNumber OR closes #$IssueNumber" --state all --json number,url,state --limit 1 2>$null | ConvertFrom-Json
|
||||
}
|
||||
if ($prs -and $prs.Count -gt 0) {
|
||||
$status.HasPR = $true
|
||||
$status.PRNumber = $prs[0].number
|
||||
$status.PRState = $prs[0].state
|
||||
$status.PRUrl = $prs[0].url
|
||||
}
|
||||
|
||||
return $status
|
||||
}
|
||||
|
||||
function Get-PRStatus {
|
||||
param([int]$PRNumber)
|
||||
|
||||
$status = @{
|
||||
PRNumber = $PRNumber
|
||||
State = $null
|
||||
IssueNumber = 0
|
||||
Branch = $null
|
||||
HasReviewFiles = $false
|
||||
ReviewStepCount = 0
|
||||
HighSeverityCount = 0
|
||||
MediumSeverityCount = 0
|
||||
ActiveCommentCount = 0
|
||||
UnresolvedThreadCount = 0
|
||||
CopilotReviewRequested = $false
|
||||
ReviewSignalStatus = $null
|
||||
ReviewSignalTimestamp = $null
|
||||
FixSignalStatus = $null
|
||||
FixSignalTimestamp = $null
|
||||
}
|
||||
|
||||
# Get PR info
|
||||
$prInfo = gh pr view $PRNumber --json state,headRefName,number 2>$null | ConvertFrom-Json
|
||||
if (-not $prInfo) {
|
||||
return $status
|
||||
}
|
||||
|
||||
$status.State = $prInfo.state
|
||||
$status.Branch = $prInfo.headRefName
|
||||
|
||||
# Extract issue number from branch
|
||||
if ($status.Branch -match 'issue/(\d+)') {
|
||||
$status.IssueNumber = [int]$Matches[1]
|
||||
}
|
||||
|
||||
# Check review files
|
||||
$reviewDir = Join-Path $genFiles "prReview/$PRNumber"
|
||||
if (Test-Path $reviewDir) {
|
||||
$status.HasReviewFiles = $true
|
||||
$stepFiles = Get-ChildItem -Path $reviewDir -Filter "*.md" -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.Name -match '^\d{2}-' }
|
||||
$status.ReviewStepCount = $stepFiles.Count
|
||||
|
||||
# Count severity issues
|
||||
foreach ($stepFile in $stepFiles) {
|
||||
$content = Get-Content $stepFile.FullName -Raw -ErrorAction SilentlyContinue
|
||||
if ($content) {
|
||||
$status.HighSeverityCount += ([regex]::Matches($content, '\*\*Severity:\s*high\*\*', 'IgnoreCase')).Count
|
||||
$status.HighSeverityCount += ([regex]::Matches($content, '🔴\s*High', 'IgnoreCase')).Count
|
||||
$status.MediumSeverityCount += ([regex]::Matches($content, '\*\*Severity:\s*medium\*\*', 'IgnoreCase')).Count
|
||||
$status.MediumSeverityCount += ([regex]::Matches($content, '🟡\s*Medium', 'IgnoreCase')).Count
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Check review signal
|
||||
$reviewSignalPath = Join-Path $reviewDir '.signal'
|
||||
if (Test-Path $reviewSignalPath) {
|
||||
try {
|
||||
$reviewSignal = Get-Content $reviewSignalPath -Raw | ConvertFrom-Json
|
||||
$status.ReviewSignalStatus = $reviewSignal.status
|
||||
$status.ReviewSignalTimestamp = $reviewSignal.timestamp
|
||||
}
|
||||
catch {}
|
||||
}
|
||||
|
||||
# Check fix signal
|
||||
$fixSignalPath = Join-Path $genFiles "prFix/$PRNumber/.signal"
|
||||
if (Test-Path $fixSignalPath) {
|
||||
try {
|
||||
$fixSignal = Get-Content $fixSignalPath -Raw | ConvertFrom-Json
|
||||
$status.FixSignalStatus = $fixSignal.status
|
||||
$status.FixSignalTimestamp = $fixSignal.timestamp
|
||||
}
|
||||
catch {}
|
||||
}
|
||||
|
||||
# Get active comments (not in reply to another comment)
|
||||
try {
|
||||
$commentCount = gh api "repos/microsoft/PowerToys/pulls/$PRNumber/comments" --jq '[.[] | select(.in_reply_to_id == null)] | length' 2>$null
|
||||
$status.ActiveCommentCount = [int]$commentCount
|
||||
}
|
||||
catch {
|
||||
$status.ActiveCommentCount = 0
|
||||
}
|
||||
|
||||
# Get unresolved thread count
|
||||
try {
|
||||
$threads = gh api graphql -f query="query { repository(owner: `"microsoft`", name: `"PowerToys`") { pullRequest(number: $PRNumber) { reviewThreads(first: 100) { nodes { isResolved } } } } }" --jq '.data.repository.pullRequest.reviewThreads.nodes | map(select(.isResolved == false)) | length' 2>$null
|
||||
$status.UnresolvedThreadCount = [int]$threads
|
||||
}
|
||||
catch {
|
||||
$status.UnresolvedThreadCount = 0
|
||||
}
|
||||
|
||||
# Check if Copilot review was requested
|
||||
try {
|
||||
$reviewers = gh pr view $PRNumber --json reviewRequests --jq '.reviewRequests[].login' 2>$null
|
||||
if ($reviewers -contains 'copilot' -or $reviewers -contains 'github-copilot') {
|
||||
$status.CopilotReviewRequested = $true
|
||||
}
|
||||
}
|
||||
catch {}
|
||||
|
||||
return $status
|
||||
}
|
||||
|
||||
# Main execution
|
||||
$results = @{
|
||||
Issues = @()
|
||||
PRs = @()
|
||||
Timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
|
||||
}
|
||||
|
||||
# Gather issue numbers to check
|
||||
$issuesToCheck = @()
|
||||
$prsToCheck = @()
|
||||
|
||||
if ($CheckAll) {
|
||||
# Get all reviewed issues
|
||||
$reviewDir = Join-Path $genFiles 'issueReview'
|
||||
if (Test-Path $reviewDir) {
|
||||
$issuesToCheck = Get-ChildItem -Path $reviewDir -Directory |
|
||||
Where-Object { $_.Name -match '^\d+$' } |
|
||||
ForEach-Object { [int]$_.Name }
|
||||
}
|
||||
|
||||
# Get all open PRs with issue/* branches
|
||||
$openPRs = gh pr list --state open --json number,headRefName 2>$null | ConvertFrom-Json |
|
||||
Where-Object { $_.headRefName -like 'issue/*' }
|
||||
$prsToCheck = @($openPRs | ForEach-Object { $_.number })
|
||||
}
|
||||
else {
|
||||
$issuesToCheck = $IssueNumbers
|
||||
$prsToCheck = $PRNumbers
|
||||
}
|
||||
|
||||
# Get issue statuses
|
||||
foreach ($issueNum in $issuesToCheck) {
|
||||
$status = Get-IssueStatus -IssueNumber $issueNum
|
||||
$results.Issues += $status
|
||||
}
|
||||
|
||||
# Get PR statuses
|
||||
foreach ($prNum in $prsToCheck) {
|
||||
$status = Get-PRStatus -PRNumber $prNum
|
||||
$results.PRs += $status
|
||||
}
|
||||
|
||||
# Output
|
||||
if ($JsonOutput) {
|
||||
$results | ConvertTo-Json -Depth 5
|
||||
return
|
||||
}
|
||||
else {
|
||||
if ($results.Issues.Count -gt 0) {
|
||||
Write-Host "`n=== ISSUE STATUS ===" -ForegroundColor Cyan
|
||||
Write-Host ("-" * 120)
|
||||
Write-Host ("{0,-8} {1,-8} {2,-8} {3,-5} {4,-5} {5,-8} {6,-8} {7,-8} {8,-8} {9,-8} {10,-8}" -f "Issue", "Review", "Plan", "Feas", "Clar", "RR Scr", "Worktree", "PR", "RevSig", "RRSig", "FixSig")
|
||||
Write-Host ("-" * 120)
|
||||
foreach ($issue in $results.Issues | Sort-Object IssueNumber) {
|
||||
$reviewMark = if ($issue.HasReview) { "✓" } else { "-" }
|
||||
$planMark = if ($issue.HasImplementationPlan) { "✓" } else { "-" }
|
||||
$wtMark = if ($issue.HasWorktree) { "✓" } else { "-" }
|
||||
$commitMark = if ($issue.HasCommits) { $issue.CommitCount } else { "-" }
|
||||
$prMark = if ($issue.HasPR) { "#$($issue.PRNumber) ($($issue.PRState))" } else { "-" }
|
||||
$reviewSignalMark = if ($issue.ReviewSignalStatus) { $issue.ReviewSignalStatus } else { "-" }
|
||||
$fixSignalMark = if ($issue.FixSignalStatus) { $issue.FixSignalStatus } else { "-" }
|
||||
$rrScoreMark = if ($issue.ReviewReviewSignalStatus) { "$($issue.ReviewReviewQualityScore)" } else { "-" }
|
||||
$rrSignalMark = if ($issue.ReviewReviewSignalStatus) {
|
||||
if ($issue.ReviewReviewNeedsReReview) { "redo" } else { "pass" }
|
||||
} else { "-" }
|
||||
|
||||
Write-Host ("{0,-8} {1,-8} {2,-8} {3,-5} {4,-5} {5,-8} {6,-8} {7,-8} {8,-8} {9,-8} {10,-8}" -f
|
||||
"#$($issue.IssueNumber)", $reviewMark, $planMark, $issue.FeasibilityScore, $issue.ClarityScore, $rrScoreMark, $wtMark, $prMark, $reviewSignalMark, $rrSignalMark, $fixSignalMark)
|
||||
}
|
||||
}
|
||||
|
||||
if ($results.PRs.Count -gt 0) {
|
||||
Write-Host "`n=== PR STATUS ===" -ForegroundColor Cyan
|
||||
Write-Host ("-" * 120)
|
||||
Write-Host ("{0,-8} {1,-10} {2,-10} {3,-8} {4,-8} {5,-10} {6,-12} {7,-10} {8,-8} {9,-8}" -f "PR", "State", "Issue", "Reviews", "High", "Medium", "Comments", "Unresolved", "RevSig", "FixSig")
|
||||
Write-Host ("-" * 120)
|
||||
foreach ($pr in $results.PRs | Sort-Object PRNumber) {
|
||||
$reviewMark = if ($pr.HasReviewFiles) { "$($pr.ReviewStepCount) steps" } else { "-" }
|
||||
$issueMark = if ($pr.IssueNumber -gt 0) { "#$($pr.IssueNumber)" } else { "-" }
|
||||
$reviewSignalMark = if ($pr.ReviewSignalStatus) { $pr.ReviewSignalStatus } else { "-" }
|
||||
$fixSignalMark = if ($pr.FixSignalStatus) { $pr.FixSignalStatus } else { "-" }
|
||||
|
||||
Write-Host ("{0,-8} {1,-10} {2,-10} {3,-8} {4,-8} {5,-10} {6,-12} {7,-10} {8,-8} {9,-8}" -f
|
||||
"#$($pr.PRNumber)", $pr.State, $issueMark, $reviewMark, $pr.HighSeverityCount, $pr.MediumSeverityCount, $pr.ActiveCommentCount, $pr.UnresolvedThreadCount, $reviewSignalMark, $fixSignalMark)
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "`nTimestamp: $($results.Timestamp)" -ForegroundColor Gray
|
||||
}
|
||||
|
||||
return $results
|
||||
@@ -1,123 +0,0 @@
|
||||
# IssueReviewLib.ps1 - Helpers for full issue-to-PR cycle workflow
|
||||
# Part of the PowerToys GitHub Copilot/Claude Code issue review system
|
||||
# This is a trimmed version with only what issue-to-pr-cycle needs
|
||||
|
||||
#region Console Output Helpers
|
||||
function Info { param([string]$Message) Write-Host $Message -ForegroundColor Cyan }
|
||||
function Warn { param([string]$Message) Write-Host $Message -ForegroundColor Yellow }
|
||||
function Err { param([string]$Message) Write-Host $Message -ForegroundColor Red }
|
||||
function Success { param([string]$Message) Write-Host $Message -ForegroundColor Green }
|
||||
#endregion
|
||||
|
||||
#region Repository Helpers
|
||||
function Get-RepoRoot {
|
||||
$root = git rev-parse --show-toplevel 2>$null
|
||||
if (-not $root) { throw 'Not inside a git repository.' }
|
||||
return (Resolve-Path $root).Path
|
||||
}
|
||||
|
||||
function Get-GeneratedFilesPath {
|
||||
param([string]$RepoRoot)
|
||||
return Join-Path $RepoRoot 'Generated Files'
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Issue Review Results Helpers
|
||||
function Get-HighConfidenceIssues {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Find issues with high confidence for auto-fix based on review results.
|
||||
.PARAMETER RepoRoot
|
||||
Repository root path.
|
||||
.PARAMETER MinFeasibilityScore
|
||||
Minimum Technical Feasibility score (0-100). Default: 70.
|
||||
.PARAMETER MinClarityScore
|
||||
Minimum Requirement Clarity score (0-100). Default: 60.
|
||||
.PARAMETER MaxEffortDays
|
||||
Maximum effort estimate in days. Default: 2 (S = Small).
|
||||
.PARAMETER FilterIssueNumbers
|
||||
Optional array of issue numbers to filter to. If specified, only these issues are considered.
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$RepoRoot,
|
||||
[int]$MinFeasibilityScore = 70,
|
||||
[int]$MinClarityScore = 60,
|
||||
[int]$MaxEffortDays = 2,
|
||||
[int[]]$FilterIssueNumbers = @()
|
||||
)
|
||||
|
||||
$genFiles = Get-GeneratedFilesPath -RepoRoot $RepoRoot
|
||||
$reviewDir = Join-Path $genFiles 'issueReview'
|
||||
|
||||
if (-not (Test-Path $reviewDir)) {
|
||||
return @()
|
||||
}
|
||||
|
||||
$highConfidence = @()
|
||||
|
||||
Get-ChildItem -Path $reviewDir -Directory | ForEach-Object {
|
||||
$issueNum = [int]$_.Name
|
||||
|
||||
# Skip if filter is specified and this issue is not in the filter list
|
||||
if ($FilterIssueNumbers.Count -gt 0 -and $issueNum -notin $FilterIssueNumbers) {
|
||||
return
|
||||
}
|
||||
|
||||
$overviewPath = Join-Path $_.FullName 'overview.md'
|
||||
$implPlanPath = Join-Path $_.FullName 'implementation-plan.md'
|
||||
|
||||
if (-not (Test-Path $overviewPath) -or -not (Test-Path $implPlanPath)) {
|
||||
return
|
||||
}
|
||||
|
||||
# Parse overview.md to extract scores
|
||||
$overview = Get-Content $overviewPath -Raw
|
||||
|
||||
# Extract scores using regex (looking for score table or inline scores)
|
||||
$feasibility = 0
|
||||
$clarity = 0
|
||||
$effortDays = 999
|
||||
|
||||
# Try to extract from At-a-Glance Score Table
|
||||
if ($overview -match 'Technical Feasibility[^\d]*(\d+)/100') {
|
||||
$feasibility = [int]$Matches[1]
|
||||
}
|
||||
if ($overview -match 'Requirement Clarity[^\d]*(\d+)/100') {
|
||||
$clarity = [int]$Matches[1]
|
||||
}
|
||||
# Match effort formats like "0.5-1 day", "1-2 days", "2-3 days" - extract the upper bound
|
||||
if ($overview -match 'Effort Estimate[^|]*\|\s*[\d.]+(?:-(\d+))?\s*days?') {
|
||||
if ($Matches[1]) {
|
||||
$effortDays = [int]$Matches[1]
|
||||
} elseif ($overview -match 'Effort Estimate[^|]*\|\s*(\d+)\s*days?') {
|
||||
$effortDays = [int]$Matches[1]
|
||||
}
|
||||
}
|
||||
# Also check for XS/S sizing in the table
|
||||
if ($overview -match 'Effort Estimate[^|]*\|[^|]*\|\s*(XS|S)\b') {
|
||||
if ($Matches[1] -eq 'XS') { $effortDays = 1 } else { $effortDays = 2 }
|
||||
} elseif ($overview -match 'Effort Estimate[^|]*\|[^|]*\(XS\)') {
|
||||
$effortDays = 1
|
||||
} elseif ($overview -match 'Effort Estimate[^|]*\|[^|]*\(S\)') {
|
||||
$effortDays = 2
|
||||
}
|
||||
|
||||
if ($feasibility -ge $MinFeasibilityScore -and
|
||||
$clarity -ge $MinClarityScore -and
|
||||
$effortDays -le $MaxEffortDays) {
|
||||
|
||||
$highConfidence += @{
|
||||
IssueNumber = $issueNum
|
||||
FeasibilityScore = $feasibility
|
||||
ClarityScore = $clarity
|
||||
EffortDays = $effortDays
|
||||
OverviewPath = $overviewPath
|
||||
ImplementationPlanPath = $implPlanPath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $highConfidence | Sort-Object -Property FeasibilityScore -Descending
|
||||
}
|
||||
#endregion
|
||||
@@ -1,679 +0,0 @@
|
||||
<#!
|
||||
.SYNOPSIS
|
||||
Run the complete issue-to-PR cycle: fix issues, create PRs, review, and fix comments.
|
||||
|
||||
.DESCRIPTION
|
||||
Orchestrates the full workflow:
|
||||
1. Find high-confidence issues matching criteria
|
||||
2. Create worktrees and run auto-fix for each issue
|
||||
3. Commit changes and create PRs
|
||||
4. Run PR review workflow in a loop until no issues remain:
|
||||
a. Review PR and post comments
|
||||
b. Fix PR comments
|
||||
c. Re-review to check for remaining issues
|
||||
d. Repeat until clean or max iterations reached
|
||||
|
||||
.PARAMETER MinFeasibilityScore
|
||||
Minimum Technical Feasibility score. Default: 70.
|
||||
|
||||
.PARAMETER MinClarityScore
|
||||
Minimum Requirement Clarity score. Default: 70.
|
||||
|
||||
.PARAMETER MaxEffortDays
|
||||
Maximum effort in days. Default: 10.
|
||||
|
||||
.PARAMETER MaxReviewIterations
|
||||
Maximum review/fix iterations per PR before giving up. Default: 3.
|
||||
|
||||
.PARAMETER ExcludeIssues
|
||||
Array of issue numbers to exclude (already processed).
|
||||
|
||||
.PARAMETER CLIType
|
||||
AI CLI to use: copilot or claude. Default: copilot.
|
||||
|
||||
.PARAMETER DryRun
|
||||
Show what would be done without executing.
|
||||
|
||||
.PARAMETER SkipExisting
|
||||
Skip issues that already have worktrees or PRs.
|
||||
|
||||
.EXAMPLE
|
||||
./Start-FullIssueCycle.ps1 -MinFeasibilityScore 70 -MinClarityScore 70 -MaxEffortDays 10
|
||||
|
||||
.EXAMPLE
|
||||
./Start-FullIssueCycle.ps1 -ExcludeIssues 44044,45029,32950,35703,44480 -DryRun
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$Labels = '',
|
||||
[int]$Limit = 500, # GitHub API max is 1000, default to 500 to get most issues
|
||||
[int]$MinFeasibilityScore = 70,
|
||||
[int]$MinClarityScore = 70,
|
||||
[int]$MaxEffortDays = 10,
|
||||
[int]$MaxReviewIterations = 3,
|
||||
[int[]]$ExcludeIssues = @(),
|
||||
[ValidateSet('copilot', 'claude')]
|
||||
[string]$CLIType = 'copilot',
|
||||
[int]$FixThrottleLimit = 5,
|
||||
[int]$PRThrottleLimit = 5,
|
||||
[int]$ReviewMaxConcurrent = 3,
|
||||
[ValidateSet('high', 'medium', 'low', 'info')]
|
||||
[string]$MinSeverityForLoop = 'medium',
|
||||
[switch]$DryRun,
|
||||
[switch]$SkipExisting,
|
||||
[switch]$SkipReview,
|
||||
[switch]$Force,
|
||||
[switch]$Help
|
||||
)
|
||||
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$skillsDir = Split-Path -Parent (Split-Path -Parent $scriptDir) # <configRoot>/skills (e.g. .github/skills or .claude/skills)
|
||||
. (Join-Path $scriptDir 'IssueReviewLib.ps1')
|
||||
|
||||
# Paths to other skills' scripts
|
||||
$issueFixScript = Join-Path $skillsDir 'issue-fix/scripts/Start-IssueAutoFix.ps1'
|
||||
$submitPRScript = Join-Path $skillsDir 'issue-fix/scripts/Submit-IssueFix.ps1'
|
||||
$prReviewScript = Join-Path $skillsDir 'pr-review/scripts/Start-PRReviewWorkflow.ps1'
|
||||
$prFixScript = Join-Path $skillsDir 'pr-fix/scripts/Start-PRFix.ps1'
|
||||
|
||||
$repoRoot = Get-RepoRoot
|
||||
$worktreeLib = Join-Path $repoRoot 'tools/build/WorktreeLib.ps1'
|
||||
if (Test-Path $worktreeLib) {
|
||||
. $worktreeLib
|
||||
}
|
||||
|
||||
if ($Help) {
|
||||
Get-Help $MyInvocation.MyCommand.Path -Full
|
||||
return
|
||||
}
|
||||
|
||||
#region Helper Functions
|
||||
function Get-ExistingIssuePRs {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Get ALL issues that already have PRs (open, closed, or merged) - checking GitHub directly.
|
||||
#>
|
||||
param(
|
||||
[int[]]$IssueNumbers
|
||||
)
|
||||
|
||||
$existingPRs = @{}
|
||||
|
||||
foreach ($issueNum in $IssueNumbers) {
|
||||
# Check if there's a PR that mentions this issue (any state: open, closed, merged)
|
||||
$prs = gh pr list --search "fixes #$issueNum OR closes #$issueNum OR resolves #$issueNum" --state all --json number,url,headRefName,state 2>$null | ConvertFrom-Json
|
||||
if ($prs -and $prs.Count -gt 0) {
|
||||
$existingPRs[$issueNum] = @{
|
||||
PRNumber = $prs[0].number
|
||||
PRUrl = $prs[0].url
|
||||
Branch = $prs[0].headRefName
|
||||
State = $prs[0].state
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
# Also check for branch pattern issue/<number>* (any state)
|
||||
$branchPrs = gh pr list --head "issue/$issueNum" --state all --json number,url,headRefName,state 2>$null | ConvertFrom-Json
|
||||
if (-not $branchPrs -or $branchPrs.Count -eq 0) {
|
||||
# Try with wildcard search via gh api
|
||||
$branchPrs = gh pr list --state all --json number,url,headRefName,state 2>$null | ConvertFrom-Json | Where-Object { $_.headRefName -like "issue/$issueNum*" }
|
||||
}
|
||||
if ($branchPrs -and $branchPrs.Count -gt 0) {
|
||||
$existingPRs[$issueNum] = @{
|
||||
PRNumber = $branchPrs[0].number
|
||||
PRUrl = $branchPrs[0].url
|
||||
Branch = $branchPrs[0].headRefName
|
||||
State = $branchPrs[0].state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $existingPRs
|
||||
}
|
||||
|
||||
function Get-ExistingWorktrees {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Get issues that already have worktrees.
|
||||
#>
|
||||
$existingWorktrees = @{}
|
||||
$worktrees = Get-WorktreeEntries | Where-Object { $_.Branch -like 'issue/*' }
|
||||
|
||||
foreach ($wt in $worktrees) {
|
||||
if ($wt.Branch -match 'issue/(\d+)') {
|
||||
$issueNum = [int]$Matches[1]
|
||||
$existingWorktrees[$issueNum] = $wt.Path
|
||||
}
|
||||
}
|
||||
|
||||
return $existingWorktrees
|
||||
}
|
||||
|
||||
function Get-PRReviewIssueCount {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Count high/medium severity issues from the review overview file.
|
||||
#>
|
||||
param(
|
||||
[int]$PRNumber,
|
||||
[string]$MinSeverity = 'medium'
|
||||
)
|
||||
|
||||
$overviewPath = Join-Path $repoRoot "Generated Files/prReview/$PRNumber/00-OVERVIEW.md"
|
||||
|
||||
if (-not (Test-Path $overviewPath)) {
|
||||
return -1 # No review yet
|
||||
}
|
||||
|
||||
$content = Get-Content $overviewPath -Raw
|
||||
|
||||
# Parse "High severity issues: <count>" from the overview
|
||||
$highCount = 0
|
||||
$mediumCount = 0
|
||||
|
||||
if ($content -match 'High severity issues:\s*(\d+)') {
|
||||
$highCount = [int]$Matches[1]
|
||||
}
|
||||
|
||||
# Also check step files for medium severity
|
||||
$stepFiles = Get-ChildItem -Path (Split-Path $overviewPath) -Filter "*.md" | Where-Object { $_.Name -match '^\d{2}-' }
|
||||
foreach ($stepFile in $stepFiles) {
|
||||
$stepContent = Get-Content $stepFile.FullName -Raw
|
||||
# Count severity markers
|
||||
$mediumCount += ([regex]::Matches($stepContent, '\*\*Severity:\s*medium\*\*', 'IgnoreCase')).Count
|
||||
$mediumCount += ([regex]::Matches($stepContent, '🟡\s*Medium', 'IgnoreCase')).Count
|
||||
}
|
||||
|
||||
switch ($MinSeverity) {
|
||||
'high' { return $highCount }
|
||||
'medium' { return $highCount + $mediumCount }
|
||||
default { return $highCount + $mediumCount }
|
||||
}
|
||||
}
|
||||
|
||||
function Get-PRActiveCommentCount {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Count active (unresolved) review comments on a PR.
|
||||
#>
|
||||
param(
|
||||
[int]$PRNumber
|
||||
)
|
||||
|
||||
try {
|
||||
# Get all review comments
|
||||
$comments = gh api "repos/microsoft/PowerToys/pulls/$PRNumber/comments" --jq '[.[] | select(.in_reply_to_id == null)] | length' 2>$null
|
||||
if ($comments) {
|
||||
return [int]$comments
|
||||
}
|
||||
return 0
|
||||
}
|
||||
catch {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
function Clear-PRReviewCache {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Clear the review cache to force a fresh review.
|
||||
#>
|
||||
param(
|
||||
[int]$PRNumber
|
||||
)
|
||||
|
||||
$reviewPath = Join-Path $repoRoot "Generated Files/prReview/$PRNumber"
|
||||
if (Test-Path $reviewPath) {
|
||||
# Keep logs but remove review files
|
||||
Get-ChildItem $reviewPath -Filter "*.md" | Remove-Item -Force
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-PRReviewFixLoop {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Run the review/fix loop until no issues remain or max iterations reached.
|
||||
#>
|
||||
param(
|
||||
[int]$PRNumber,
|
||||
[int]$IssueNumber,
|
||||
[string]$WorktreePath,
|
||||
[string]$CLIType = 'copilot',
|
||||
[string]$MinSeverity = 'medium',
|
||||
[int]$MaxIterations = 3
|
||||
)
|
||||
|
||||
$iteration = 0
|
||||
$issuesRemaining = $true
|
||||
|
||||
while ($issuesRemaining -and $iteration -lt $MaxIterations) {
|
||||
$iteration++
|
||||
Info " [PR #$PRNumber] Review/Fix iteration $iteration of $MaxIterations"
|
||||
|
||||
# Step 1: Run PR review (assign Copilot, review, post comments)
|
||||
Info " [PR #$PRNumber] Running review..."
|
||||
try {
|
||||
# Clear previous review to force fresh analysis
|
||||
if ($iteration -gt 1) {
|
||||
Clear-PRReviewCache -PRNumber $PRNumber
|
||||
}
|
||||
|
||||
& $prReviewScript -PRNumbers $PRNumber -CLIType $CLIType -Force 2>&1 | Out-Null
|
||||
}
|
||||
catch {
|
||||
Warn " [PR #$PRNumber] Review failed: $($_.Exception.Message)"
|
||||
break
|
||||
}
|
||||
|
||||
# Step 2: Check if there are issues found
|
||||
$issueCount = Get-PRReviewIssueCount -PRNumber $PRNumber -MinSeverity $MinSeverity
|
||||
$activeComments = Get-PRActiveCommentCount -PRNumber $PRNumber
|
||||
|
||||
Info " [PR #$PRNumber] Found $issueCount issues (severity >= $MinSeverity), $activeComments active comments"
|
||||
|
||||
if ($issueCount -le 0 -and $activeComments -le 0) {
|
||||
Info " [PR #$PRNumber] ✓ No issues remaining!"
|
||||
$issuesRemaining = $false
|
||||
break
|
||||
}
|
||||
|
||||
# Step 3: Run fix for active comments
|
||||
if ($activeComments -gt 0 -or $issueCount -gt 0) {
|
||||
Info " [PR #$PRNumber] Fixing $activeComments active comments..."
|
||||
try {
|
||||
# Run fix via pr-fix skill (review script only does reviews)
|
||||
& $prFixScript -PRNumber $PRNumber -CLIType $CLIType -Force 2>&1 | Out-Null
|
||||
}
|
||||
catch {
|
||||
Warn " [PR #$PRNumber] Fix failed: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
# Brief pause to let GitHub sync
|
||||
Start-Sleep -Seconds 2
|
||||
}
|
||||
|
||||
if ($issuesRemaining) {
|
||||
Warn " [PR #$PRNumber] Max iterations reached, some issues may remain"
|
||||
}
|
||||
|
||||
return @{
|
||||
PRNumber = $PRNumber
|
||||
IssueNumber = $IssueNumber
|
||||
Iterations = $iteration
|
||||
IssuesRemaining = $issuesRemaining
|
||||
FinalIssueCount = (Get-PRReviewIssueCount -PRNumber $PRNumber -MinSeverity $MinSeverity)
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Main Script
|
||||
try {
|
||||
$startTime = Get-Date
|
||||
|
||||
Info "=" * 80
|
||||
Info "FULL ISSUE-TO-PR CYCLE"
|
||||
Info "=" * 80
|
||||
Info "Repository root: $repoRoot"
|
||||
Info "CLI type: $CLIType"
|
||||
if ($Labels) {
|
||||
Info "Labels filter: $Labels"
|
||||
}
|
||||
Info "Criteria: Feasibility >= $MinFeasibilityScore, Clarity >= $MinClarityScore, Effort <= $MaxEffortDays days"
|
||||
|
||||
# Step 0: Review issues first (if labels specified and not skipping review)
|
||||
if ($Labels -and -not $SkipReview) {
|
||||
Info "`n" + ("=" * 60)
|
||||
Info "STEP 0: Reviewing issues with label '$Labels'"
|
||||
Info ("=" * 60)
|
||||
|
||||
$reviewScript = Join-Path $scriptDir '../../issue-review/scripts/Start-BulkIssueReview.ps1'
|
||||
if (Test-Path $reviewScript) {
|
||||
$reviewArgs = @{
|
||||
Labels = $Labels
|
||||
Limit = $Limit
|
||||
CLIType = $CLIType
|
||||
Force = $Force
|
||||
}
|
||||
if ($DryRun) {
|
||||
Info "[DRY RUN] Would run: Start-BulkIssueReview.ps1 -Labels '$Labels' -Limit $Limit -CLIType $CLIType -Force"
|
||||
} else {
|
||||
Info "Running bulk issue review..."
|
||||
& $reviewScript @reviewArgs
|
||||
}
|
||||
} else {
|
||||
Warn "Review script not found at: $reviewScript"
|
||||
Warn "Proceeding with existing review data..."
|
||||
}
|
||||
}
|
||||
|
||||
# Step 1: Find high-confidence issues
|
||||
Info "`n" + ("=" * 60)
|
||||
Info "STEP 1: Finding high-confidence issues"
|
||||
Info ("=" * 60)
|
||||
|
||||
# If labels specified, get the list of issue numbers with that label first
|
||||
# This ensures we ONLY look at issues with the specified label, not all reviewed issues
|
||||
$filterIssueNumbers = @()
|
||||
if ($Labels) {
|
||||
Info "Fetching issues with label '$Labels' from GitHub..."
|
||||
$labeledIssues = gh issue list --repo microsoft/PowerToys --label "$Labels" --state open --limit $Limit --json number 2>$null | ConvertFrom-Json
|
||||
$filterIssueNumbers = @($labeledIssues | ForEach-Object { $_.number })
|
||||
Info "Found $($filterIssueNumbers.Count) issues with label '$Labels'"
|
||||
}
|
||||
|
||||
$highConfidence = Get-HighConfidenceIssues `
|
||||
-RepoRoot $repoRoot `
|
||||
-MinFeasibilityScore $MinFeasibilityScore `
|
||||
-MinClarityScore $MinClarityScore `
|
||||
-MaxEffortDays $MaxEffortDays `
|
||||
-FilterIssueNumbers $filterIssueNumbers
|
||||
|
||||
Info "Found $($highConfidence.Count) high-confidence issues matching criteria"
|
||||
|
||||
if ($highConfidence.Count -eq 0) {
|
||||
Warn "No issues found matching criteria."
|
||||
return
|
||||
}
|
||||
|
||||
# Get issue numbers for checking
|
||||
$issueNumbers = $highConfidence | ForEach-Object { $_.IssueNumber }
|
||||
|
||||
# Get existing PRs to skip (check GitHub directly)
|
||||
Info "Checking for existing PRs..."
|
||||
$existingPRs = Get-ExistingIssuePRs -IssueNumbers $issueNumbers
|
||||
Info "Found $($existingPRs.Count) issues with existing PRs"
|
||||
|
||||
# Filter out excluded issues and those with existing PRs
|
||||
$issuesToProcess = $highConfidence | Where-Object {
|
||||
$issueNum = $_.IssueNumber
|
||||
$excluded = $issueNum -in $ExcludeIssues
|
||||
$hasPR = $existingPRs.ContainsKey($issueNum)
|
||||
|
||||
if ($excluded) {
|
||||
Info " Excluding #$issueNum (in exclude list)"
|
||||
}
|
||||
if ($hasPR -and $SkipExisting) {
|
||||
$prState = $existingPRs[$issueNum].State
|
||||
Info " Skipping #$issueNum (has $prState PR #$($existingPRs[$issueNum].PRNumber))"
|
||||
}
|
||||
|
||||
-not $excluded -and (-not $hasPR -or -not $SkipExisting)
|
||||
}
|
||||
|
||||
if ($issuesToProcess.Count -eq 0) {
|
||||
Warn "No new issues to process after filtering."
|
||||
return
|
||||
}
|
||||
|
||||
Info "`nIssues to process: $($issuesToProcess.Count)"
|
||||
Info ("-" * 80)
|
||||
foreach ($issue in $issuesToProcess) {
|
||||
$prInfo = if ($existingPRs.ContainsKey($issue.IssueNumber)) {
|
||||
$state = $existingPRs[$issue.IssueNumber].State
|
||||
" [has $state PR #$($existingPRs[$issue.IssueNumber].PRNumber)]"
|
||||
} else { "" }
|
||||
Info ("#{0,-6} [F:{1}, C:{2}, E:{3}d]{4}" -f $issue.IssueNumber, $issue.FeasibilityScore, $issue.ClarityScore, $issue.EffortDays, $prInfo)
|
||||
}
|
||||
Info ("-" * 80)
|
||||
|
||||
if ($DryRun) {
|
||||
Warn "`nDry run mode - showing what would be done:"
|
||||
Info " 1. Create worktrees for $($issuesToProcess.Count) issues (parallel)"
|
||||
Info " 2. Run Copilot auto-fix in each worktree (parallel)"
|
||||
Info " 3. Commit and create PRs (parallel)"
|
||||
Info " 4. Run PR review/fix loop (up to $MaxReviewIterations iterations per PR)"
|
||||
Info " - Review PR and post comments (severity >= $MinSeverityForLoop)"
|
||||
Info " - Fix active comments"
|
||||
Info " - Repeat until clean or max iterations"
|
||||
return
|
||||
}
|
||||
|
||||
# Confirm
|
||||
if (-not $Force) {
|
||||
$confirm = Read-Host "`nProceed with full cycle for $($issuesToProcess.Count) issues? (y/N)"
|
||||
if ($confirm -notmatch '^[yY]') {
|
||||
Info "Cancelled."
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
# Track results
|
||||
$results = @{
|
||||
FixSucceeded = [System.Collections.Concurrent.ConcurrentBag[object]]::new()
|
||||
FixFailed = [System.Collections.Concurrent.ConcurrentBag[object]]::new()
|
||||
PRCreated = [System.Collections.Concurrent.ConcurrentBag[object]]::new()
|
||||
PRFailed = [System.Collections.Concurrent.ConcurrentBag[object]]::new()
|
||||
PRSkipped = [System.Collections.Concurrent.ConcurrentBag[object]]::new()
|
||||
ReviewSucceeded = [System.Collections.Concurrent.ConcurrentBag[object]]::new()
|
||||
ReviewFailed = [System.Collections.Concurrent.ConcurrentBag[object]]::new()
|
||||
}
|
||||
|
||||
# ========================================
|
||||
# PHASE 1: Create worktrees and fix issues (PARALLEL)
|
||||
# ========================================
|
||||
Info "`n" + ("=" * 60)
|
||||
Info "PHASE 1: Auto-Fix Issues (Parallel)"
|
||||
Info ("=" * 60)
|
||||
|
||||
$issuesNeedingFix = $issuesToProcess | Where-Object { -not $existingPRs.ContainsKey($_.IssueNumber) }
|
||||
$issuesWithPR = $issuesToProcess | Where-Object { $existingPRs.ContainsKey($_.IssueNumber) }
|
||||
|
||||
Info "Issues needing fix: $($issuesNeedingFix.Count)"
|
||||
Info "Issues with existing PR (skip to review): $($issuesWithPR.Count)"
|
||||
|
||||
if ($issuesNeedingFix.Count -gt 0) {
|
||||
$issuesNeedingFix | ForEach-Object -ThrottleLimit $FixThrottleLimit -Parallel {
|
||||
$issue = $_
|
||||
$issueNum = $issue.IssueNumber
|
||||
$issueFixScript = $using:issueFixScript
|
||||
$CLIType = $using:CLIType
|
||||
$results = $using:results
|
||||
|
||||
try {
|
||||
Write-Host "[Issue #$issueNum] Starting auto-fix..." -ForegroundColor Cyan
|
||||
& $issueFixScript -IssueNumber $issueNum -CLIType $CLIType -Force 2>&1 | Out-Null
|
||||
$results.FixSucceeded.Add($issueNum)
|
||||
Write-Host "[Issue #$issueNum] ✓ Fix completed" -ForegroundColor Green
|
||||
}
|
||||
catch {
|
||||
$results.FixFailed.Add(@{ IssueNumber = $issueNum; Error = $_.Exception.Message })
|
||||
Write-Host "[Issue #$issueNum] ✗ Fix failed: $($_.Exception.Message)" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Info "`nPhase 1 complete: $($results.FixSucceeded.Count) succeeded, $($results.FixFailed.Count) failed"
|
||||
|
||||
# ========================================
|
||||
# PHASE 2: Commit and create PRs (PARALLEL)
|
||||
# ========================================
|
||||
Info "`n" + ("=" * 60)
|
||||
Info "PHASE 2: Submit PRs (Parallel)"
|
||||
Info ("=" * 60)
|
||||
|
||||
$fixedIssues = $results.FixSucceeded.ToArray()
|
||||
|
||||
if ($fixedIssues.Count -gt 0) {
|
||||
$fixedIssues | ForEach-Object -ThrottleLimit $PRThrottleLimit -Parallel {
|
||||
$issueNum = $_
|
||||
$submitPRScript = $using:submitPRScript
|
||||
$CLIType = $using:CLIType
|
||||
$results = $using:results
|
||||
|
||||
try {
|
||||
Write-Host "[Issue #$issueNum] Creating PR..." -ForegroundColor Cyan
|
||||
$submitResult = & $submitPRScript -IssueNumbers $issueNum -CLIType $CLIType -Force 2>&1
|
||||
|
||||
# Parse output to find PR URL
|
||||
$prUrl = $null
|
||||
$prNum = 0
|
||||
|
||||
if ($submitResult -match 'https://github.com/[^/]+/[^/]+/pull/(\d+)') {
|
||||
$prUrl = $Matches[0]
|
||||
$prNum = [int]$Matches[1]
|
||||
}
|
||||
|
||||
if ($prNum -gt 0) {
|
||||
$results.PRCreated.Add(@{ IssueNumber = $issueNum; PRNumber = $prNum; PRUrl = $prUrl })
|
||||
Write-Host "[Issue #$issueNum] ✓ PR #$prNum created" -ForegroundColor Green
|
||||
} else {
|
||||
# Check if PR was already created
|
||||
$existingPr = gh pr list --head "issue/$issueNum" --state open --json number,url 2>$null | ConvertFrom-Json
|
||||
if ($existingPr -and $existingPr.Count -gt 0) {
|
||||
$results.PRSkipped.Add(@{ IssueNumber = $issueNum; PRNumber = $existingPr[0].number; PRUrl = $existingPr[0].url; Reason = "Already exists" })
|
||||
Write-Host "[Issue #$issueNum] PR already exists: #$($existingPr[0].number)" -ForegroundColor Yellow
|
||||
} else {
|
||||
$results.PRFailed.Add(@{ IssueNumber = $issueNum; Error = "No PR created" })
|
||||
Write-Host "[Issue #$issueNum] ✗ PR creation failed" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$results.PRFailed.Add(@{ IssueNumber = $issueNum; Error = $_.Exception.Message })
|
||||
Write-Host "[Issue #$issueNum] ✗ PR failed: $($_.Exception.Message)" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Info "`nPhase 2 complete: $($results.PRCreated.Count) created, $($results.PRSkipped.Count) skipped, $($results.PRFailed.Count) failed"
|
||||
|
||||
# ========================================
|
||||
# PHASE 3: Review and Fix PRs (ITERATIVE LOOP)
|
||||
# ========================================
|
||||
Info "`n" + ("=" * 60)
|
||||
Info "PHASE 3: Review & Fix PRs (Iterative Loop)"
|
||||
Info ("=" * 60)
|
||||
Info "Max iterations per PR: $MaxReviewIterations"
|
||||
Info "Min severity to fix: $MinSeverityForLoop"
|
||||
|
||||
# Collect all PRs to review (newly created + existing)
|
||||
$prsToReview = @()
|
||||
|
||||
foreach ($pr in $results.PRCreated.ToArray()) {
|
||||
$prsToReview += @{ IssueNumber = $pr.IssueNumber; PRNumber = $pr.PRNumber }
|
||||
}
|
||||
foreach ($pr in $results.PRSkipped.ToArray()) {
|
||||
$prsToReview += @{ IssueNumber = $pr.IssueNumber; PRNumber = $pr.PRNumber }
|
||||
}
|
||||
foreach ($issue in $issuesWithPR) {
|
||||
$prInfo = $existingPRs[$issue.IssueNumber]
|
||||
# Only include open PRs
|
||||
if ($prInfo.State -eq 'OPEN') {
|
||||
$prsToReview += @{ IssueNumber = $issue.IssueNumber; PRNumber = $prInfo.PRNumber }
|
||||
}
|
||||
}
|
||||
|
||||
Info "PRs to review: $($prsToReview.Count)"
|
||||
|
||||
# Track review loop results
|
||||
$reviewLoopResults = [System.Collections.Concurrent.ConcurrentBag[object]]::new()
|
||||
|
||||
if ($prsToReview.Count -gt 0) {
|
||||
# Process sequentially to avoid overwhelming the AI CLI
|
||||
foreach ($pr in $prsToReview) {
|
||||
$issueNum = $pr.IssueNumber
|
||||
$prNum = $pr.PRNumber
|
||||
|
||||
Info "`n [PR #$prNum for Issue #$issueNum] Starting review/fix loop..."
|
||||
|
||||
try {
|
||||
$loopResult = Invoke-PRReviewFixLoop `
|
||||
-PRNumber $prNum `
|
||||
-IssueNumber $issueNum `
|
||||
-CLIType $CLIType `
|
||||
-MinSeverity $MinSeverityForLoop `
|
||||
-MaxIterations $MaxReviewIterations
|
||||
|
||||
$reviewLoopResults.Add($loopResult)
|
||||
|
||||
if (-not $loopResult.IssuesRemaining) {
|
||||
$results.ReviewSucceeded.Add(@{ IssueNumber = $issueNum; PRNumber = $prNum; Iterations = $loopResult.Iterations })
|
||||
Success " [PR #$prNum] ✓ Clean after $($loopResult.Iterations) iteration(s)"
|
||||
} else {
|
||||
$results.ReviewFailed.Add(@{ IssueNumber = $issueNum; PRNumber = $prNum; Iterations = $loopResult.Iterations; RemainingIssues = $loopResult.FinalIssueCount })
|
||||
Warn " [PR #$prNum] ⚠ $($loopResult.FinalIssueCount) issues remain after $($loopResult.Iterations) iterations"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$results.ReviewFailed.Add(@{ IssueNumber = $issueNum; PRNumber = $prNum; Error = $_.Exception.Message })
|
||||
Err " [PR #$prNum] ✗ Review loop failed: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Info "`nPhase 3 complete: $($results.ReviewSucceeded.Count) clean, $($results.ReviewFailed.Count) with remaining issues"
|
||||
|
||||
# Final Summary
|
||||
$duration = (Get-Date) - $startTime
|
||||
|
||||
Info "`n" + ("=" * 80)
|
||||
Info "FULL CYCLE COMPLETE"
|
||||
Info ("=" * 80)
|
||||
Info "Duration: $($duration.ToString('hh\:mm\:ss'))"
|
||||
Info ""
|
||||
Info "Issues processed: $($issuesToProcess.Count)"
|
||||
Success "Fixes succeeded: $($results.FixSucceeded.Count)"
|
||||
if ($results.FixFailed.Count -gt 0) {
|
||||
Err "Fixes failed: $($results.FixFailed.Count)"
|
||||
}
|
||||
Success "PRs created: $($results.PRCreated.Count)"
|
||||
if ($results.PRSkipped.Count -gt 0) {
|
||||
Warn "PRs skipped: $($results.PRSkipped.Count) (already existed)"
|
||||
}
|
||||
if ($results.PRFailed.Count -gt 0) {
|
||||
Err "PRs failed: $($results.PRFailed.Count)"
|
||||
}
|
||||
Success "PRs clean (no issues): $($results.ReviewSucceeded.Count)"
|
||||
if ($results.ReviewFailed.Count -gt 0) {
|
||||
Warn "PRs with remaining issues: $($results.ReviewFailed.Count)"
|
||||
}
|
||||
|
||||
Info ""
|
||||
Info "Summary by issue:"
|
||||
foreach ($issue in $issuesToProcess) {
|
||||
$issueNum = $issue.IssueNumber
|
||||
$prInfo = $results.PRCreated.ToArray() | Where-Object { $_.IssueNumber -eq $issueNum } | Select-Object -First 1
|
||||
if (-not $prInfo) {
|
||||
$prInfo = $results.PRSkipped.ToArray() | Where-Object { $_.IssueNumber -eq $issueNum } | Select-Object -First 1
|
||||
}
|
||||
if (-not $prInfo -and $existingPRs.ContainsKey($issueNum)) {
|
||||
$prInfo = @{ PRNumber = $existingPRs[$issueNum].PRNumber }
|
||||
}
|
||||
|
||||
$prNum = if ($prInfo) { "PR #$($prInfo.PRNumber)" } else { "No PR" }
|
||||
$fixStatus = if ($results.FixSucceeded.ToArray() -contains $issueNum) { "✓" } elseif ($results.FixFailed.ToArray().IssueNumber -contains $issueNum) { "✗" } else { "-" }
|
||||
|
||||
# Check review status with iteration count
|
||||
$reviewResult = $results.ReviewSucceeded.ToArray() | Where-Object { $_.IssueNumber -eq $issueNum -or $_.PRNumber -eq $prInfo.PRNumber } | Select-Object -First 1
|
||||
$reviewFailResult = $results.ReviewFailed.ToArray() | Where-Object { $_.IssueNumber -eq $issueNum -or $_.PRNumber -eq $prInfo.PRNumber } | Select-Object -First 1
|
||||
|
||||
if ($reviewResult) {
|
||||
$reviewStatus = "✓($($reviewResult.Iterations))"
|
||||
} elseif ($reviewFailResult) {
|
||||
$reviewStatus = "⚠($($reviewFailResult.RemainingIssues) left)"
|
||||
} else {
|
||||
$reviewStatus = "-"
|
||||
}
|
||||
|
||||
Info (" Issue #{0,-6} [{1}Fix] [{2}Review] -> {3}" -f $issueNum, $fixStatus, $reviewStatus, $prNum)
|
||||
}
|
||||
|
||||
Info ("=" * 80)
|
||||
|
||||
return @{
|
||||
FixSucceeded = $results.FixSucceeded.ToArray()
|
||||
FixFailed = $results.FixFailed.ToArray()
|
||||
PRCreated = $results.PRCreated.ToArray()
|
||||
PRSkipped = $results.PRSkipped.ToArray()
|
||||
PRFailed = $results.PRFailed.ToArray()
|
||||
ReviewSucceeded = $results.ReviewSucceeded.ToArray()
|
||||
ReviewFailed = $results.ReviewFailed.ToArray()
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Err "Error: $($_.Exception.Message)"
|
||||
exit 1
|
||||
}
|
||||
#endregion
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
151
.github/skills/parallel-job-orchestrator/SKILL.md
vendored
151
.github/skills/parallel-job-orchestrator/SKILL.md
vendored
@@ -1,151 +0,0 @@
|
||||
---
|
||||
name: parallel-job-orchestrator
|
||||
description: Generic parallel job orchestrator for running copilot, claude, or any CLI tool concurrently with queuing, monitoring, retry, and cleanup. Use when asked to run multiple jobs in parallel, batch process PRs or issues with copilot/claude, orchestrate concurrent CLI executions, run parallel reviews, run parallel triage, or execute any batch of shell commands concurrently. ALL skills that need parallel execution MUST use this orchestrator — do NOT use Start-Job, ForEach-Object -Parallel, or Start-Process directly.
|
||||
license: Complete terms in LICENSE.txt
|
||||
---
|
||||
|
||||
# Parallel Job Orchestrator
|
||||
|
||||
The **single, canonical way** to run multiple jobs concurrently in this repository. Every skill that needs to run copilot, claude, or any CLI tool in parallel **MUST** use this orchestrator. Do NOT use `Start-Job`, `ForEach-Object -Parallel`, or `Start-Process` directly — those approaches have known PowerShell 7 crash bugs that took 48 hours to diagnose and fix.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
- Running copilot or claude CLI on multiple PRs/issues simultaneously
|
||||
- Any batch processing that spawns multiple CLI processes
|
||||
- Parallel review, triage, fix, or rework workflows
|
||||
- Any skill that needs concurrent execution with retry and monitoring
|
||||
|
||||
## Why This Orchestrator Exists
|
||||
|
||||
PowerShell 7 has **silent host-process crash bugs** triggered by:
|
||||
|
||||
1. `[CmdletBinding()]`, `[Parameter(Mandatory)]`, `[ValidateSet()]` attributes propagating `ErrorActionPreference='Stop'` through child scopes
|
||||
2. `Start-Job` called from within functions inside `while` loops — crashes after ~10-15 jobs
|
||||
3. Accumulated completed `Job` objects consuming runspace resources
|
||||
4. `ForEach-Object -Parallel` swallowing errors and losing context
|
||||
|
||||
This orchestrator avoids all of these by:
|
||||
|
||||
- **No advanced-function attributes** on the script itself
|
||||
- **Inlined** all `Start-Job`/`Stop-Job`/`Remove-Job` calls (never in functions)
|
||||
- **Immediately** `Receive-Job` + `Remove-Job` on completion
|
||||
- **`$ErrorActionPreference = 'Continue'`** in the monitoring loop
|
||||
- **Write-Host on every iteration** (PS7 kills the host if no output for ~8s in child-script loops)
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Step 1: Build Job Definitions
|
||||
|
||||
Each job is a hashtable with this exact structure:
|
||||
|
||||
```powershell
|
||||
$jobDef = @{
|
||||
Label = 'copilot-pr-12345' # unique human-readable label
|
||||
ExecutionParameters = @{
|
||||
JobName = 'copilot-pr-12345' # PS job name
|
||||
Command = 'copilot' # executable to run
|
||||
Arguments = @('-p', 'Review PR #12345', '--yolo') # argument array
|
||||
WorkingDir = 'C:\repo' # working directory
|
||||
OutputDir = 'C:\repo\output\copilot\12345' # output directory (auto-created)
|
||||
LogPath = 'C:\repo\output\copilot\12345\review.log' # stdout+stderr log
|
||||
}
|
||||
MonitorFiles = @('C:\repo\output\copilot\12345\review.log') # files to watch for activity
|
||||
CleanupTask = $null # optional scriptblock: { param($Tracker) ... }
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: Call the Orchestrator
|
||||
|
||||
```powershell
|
||||
# CRITICAL: Set ErrorActionPreference to Continue before calling
|
||||
$savedEAP = $ErrorActionPreference
|
||||
$ErrorActionPreference = 'Continue'
|
||||
|
||||
$results = & '.github/skills/parallel-job-orchestrator/scripts/Invoke-SimpleJobOrchestrator.ps1' `
|
||||
-JobDefinitions $jobDefs `
|
||||
-MaxConcurrent 4 `
|
||||
-InactivityTimeoutSeconds 60 `
|
||||
-MaxRetryCount 3 `
|
||||
-PollIntervalSeconds 5 `
|
||||
-LogDir 'C:\repo\output'
|
||||
|
||||
$ErrorActionPreference = $savedEAP
|
||||
```
|
||||
|
||||
### Step 3: Process Results
|
||||
|
||||
The orchestrator returns an array of result objects:
|
||||
|
||||
```powershell
|
||||
$results | Format-Table Label, Status, JobState, ExitCode, RetryCount -AutoSize
|
||||
```
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `Label` | string | Job label from definition |
|
||||
| `JobId` | int | Last PowerShell job ID |
|
||||
| `Status` | string | `Completed`, `Failed`, `Abandoned` |
|
||||
| `JobState` | string | PowerShell job state |
|
||||
| `ExitCode` | int | Process exit code |
|
||||
| `RetryCount` | int | Number of retries performed |
|
||||
| `OutputDir` | string | Output directory path |
|
||||
| `LogPath` | string | Log file path |
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| `-JobDefinitions` | hashtable[] | **(required)** | Array of job definition hashtables |
|
||||
| `-MaxConcurrent` | int | 4 | Maximum simultaneous jobs |
|
||||
| `-InactivityTimeoutSeconds` | int | 60 | Seconds of zero log-file growth before stale |
|
||||
| `-MaxRetryCount` | int | 3 | Retry attempts before abandoning |
|
||||
| `-PollIntervalSeconds` | int | 5 | Health-check interval |
|
||||
| `-LogDir` | string | `$env:TEMP` | Directory for orchestrator's own log |
|
||||
|
||||
## Job Definition Schema
|
||||
|
||||
See [references/job-definition-schema.md](./references/job-definition-schema.md) for the complete schema, copilot/claude examples, and the CleanupTask API.
|
||||
|
||||
## Critical Rules for Callers
|
||||
|
||||
1. **Set `$ErrorActionPreference = 'Continue'`** before calling the orchestrator
|
||||
2. **Do NOT** wrap the orchestrator call in a `try/catch` that re-throws
|
||||
3. **Do NOT** use `[CmdletBinding()]` or `[Parameter(Mandatory)]` on your runner script
|
||||
4. **Do NOT** use `Start-Job`, `ForEach-Object -Parallel`, or `Start-Process` for parallel work — use this orchestrator
|
||||
5. **Do** use manual validation (`if (-not $param) { Write-Error ...; return }`) instead of parameter attributes
|
||||
|
||||
## Scripts
|
||||
|
||||
| Script | Purpose |
|
||||
|--------|---------|
|
||||
| [Invoke-SimpleJobOrchestrator.ps1](./scripts/Invoke-SimpleJobOrchestrator.ps1) | The orchestrator — the ONLY parallel execution engine |
|
||||
| [Test-OrchestratorEdgeCases.ps1](./scripts/Test-OrchestratorEdgeCases.ps1) | 28-scenario stress test suite |
|
||||
|
||||
## Execution & Monitoring Rules
|
||||
|
||||
The orchestrator is a long-running poll loop. The agent calling it MUST:
|
||||
|
||||
1. **Never exit early** — monitor the orchestrator log until it prints "All N jobs finished."
|
||||
2. **For VS Code terminal usage**, launch the parent script as a detached process (`Start-Process -WindowStyle Hidden`) with `Tee-Object` to a log file. VS Code kills idle background terminals after ~60s.
|
||||
3. **Poll the log every 30–120 seconds** and report concise progress (done/total, running jobs, retries).
|
||||
4. **On unexpected termination**, check the orchestrator log's last entries, diagnose the failure, and relaunch.
|
||||
5. **Only report done** after the orchestrator returns results and all downstream processing is complete.
|
||||
|
||||
## Post-Execution Review
|
||||
|
||||
After using the orchestrator:
|
||||
|
||||
1. Check the orchestrator log in `$LogDir/orchestrator-*.log` for errors
|
||||
2. Verify all expected jobs show `Completed` status in results
|
||||
3. Check `RetryCount` — high retries may indicate CLI instability
|
||||
4. Review `Abandoned` jobs — these hit `MaxRetryCount` and need manual attention
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Symptom | Cause | Fix |
|
||||
|---------|-------|-----|
|
||||
| PS7 crashes silently | Advanced-function attributes on caller | Remove `[CmdletBinding()]`, `[Parameter()]` from runner script |
|
||||
| PS7 crashes after ~10 jobs | `Start-Job` inside functions in while loops | Already fixed in orchestrator; don't re-introduce functions |
|
||||
| Jobs stuck as "Running" | `InactivityTimeoutSeconds` too high | Lower timeout or check CLI isn't hanging |
|
||||
| All jobs `Abandoned` | CLI tool not installed or auth expired | Test CLI manually: `copilot -p "hello" --yolo` |
|
||||
| Orchestrator itself crashes at iter ~9 | Too many VS Code terminals open | Kill all terminals, restart VS Code, run in single terminal |
|
||||
@@ -1,166 +0,0 @@
|
||||
# Job Definition Schema
|
||||
|
||||
This document defines the exact hashtable structure required by the
|
||||
`Invoke-SimpleJobOrchestrator.ps1` script. Every skill that needs parallel
|
||||
execution builds an array of these hashtables and passes them to the
|
||||
orchestrator.
|
||||
|
||||
## Schema
|
||||
|
||||
```powershell
|
||||
@{
|
||||
Label = [string] # REQUIRED: unique human-readable label (e.g. 'copilot-pr-12345')
|
||||
ExecutionParameters = @{
|
||||
JobName = [string] # REQUIRED: PowerShell background job name
|
||||
Command = [string] # REQUIRED: executable to run (e.g. 'copilot', 'claude', 'gh')
|
||||
Arguments = [string[]] # REQUIRED: argument array splatted to Command
|
||||
WorkingDir = [string] # REQUIRED: working directory for the job
|
||||
OutputDir = [string] # REQUIRED: output directory (auto-created by orchestrator)
|
||||
LogPath = [string] # REQUIRED: path for stdout+stderr capture
|
||||
}
|
||||
MonitorFiles = [string[]] # REQUIRED: files to watch for activity (typically LogPath or a debug log)
|
||||
CleanupTask = [scriptblock] # OPTIONAL: runs after job finishes or is abandoned
|
||||
}
|
||||
```
|
||||
|
||||
## Field Details
|
||||
|
||||
### Label
|
||||
A unique string identifying the job in logs and results. Convention:
|
||||
`{cli-type}-{skill}-{id}` — e.g. `copilot-pr-45601`, `claude-issue-1234`.
|
||||
|
||||
### ExecutionParameters
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `JobName` | Name for `Start-Job -Name`. Should match `Label`. |
|
||||
| `Command` | The executable. Must be in `$PATH` or an absolute path. |
|
||||
| `Arguments` | Array of arguments. Splatted via `@ArgList`. |
|
||||
| `WorkingDir` | The job sets `Set-Location` to this before running. |
|
||||
| `OutputDir` | The orchestrator creates this directory automatically. |
|
||||
| `LogPath` | All stdout+stderr is redirected here via `*> $LogFile`. |
|
||||
|
||||
### MonitorFiles
|
||||
Array of file paths the orchestrator watches for growth. If none of these
|
||||
files grow for `InactivityTimeoutSeconds`, the job is considered stale and
|
||||
retried.
|
||||
|
||||
**For copilot CLI**: Monitor the `LogPath` (stdout/stderr).
|
||||
**For claude CLI**: Monitor the debug log (`--debug-file` path) — claude
|
||||
writes progress there more frequently than to stdout.
|
||||
|
||||
### CleanupTask
|
||||
Optional scriptblock that receives the tracker hashtable as its single
|
||||
parameter. Runs after the job completes, fails, or is abandoned. Use for
|
||||
cleaning up large temporary files.
|
||||
|
||||
```powershell
|
||||
CleanupTask = {
|
||||
param($Tracker)
|
||||
$debugLog = Join-Path $Tracker.ExecutionParameters.OutputDir '_debug.log'
|
||||
if (Test-Path $debugLog) { Remove-Item $debugLog -Force }
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Copilot CLI Job
|
||||
|
||||
```powershell
|
||||
@{
|
||||
Label = 'copilot-pr-45601'
|
||||
ExecutionParameters = @{
|
||||
JobName = 'copilot-pr-45601'
|
||||
Command = 'copilot'
|
||||
Arguments = @('-p', 'Review PR #45601 in microsoft/PowerToys...', '--yolo')
|
||||
WorkingDir = 'C:\s\PowerToys'
|
||||
OutputDir = 'C:\s\PowerToys\output\copilot\45601'
|
||||
LogPath = 'C:\s\PowerToys\output\copilot\45601\_copilot-review.log'
|
||||
}
|
||||
MonitorFiles = @('C:\s\PowerToys\output\copilot\45601\_copilot-review.log')
|
||||
CleanupTask = $null
|
||||
}
|
||||
```
|
||||
|
||||
### Claude CLI Job
|
||||
|
||||
```powershell
|
||||
@{
|
||||
Label = 'claude-pr-45601'
|
||||
ExecutionParameters = @{
|
||||
JobName = 'claude-pr-45601'
|
||||
Command = 'claude'
|
||||
Arguments = @('-p', 'Review PR #45601 in microsoft/PowerToys...',
|
||||
'--dangerously-skip-permissions',
|
||||
'--debug', 'all', '--debug-file', 'C:\output\claude\45601\_claude-debug.log')
|
||||
WorkingDir = 'C:\s\PowerToys'
|
||||
OutputDir = 'C:\s\PowerToys\output\claude\45601'
|
||||
LogPath = 'C:\s\PowerToys\output\claude\45601\_claude-review.log'
|
||||
}
|
||||
MonitorFiles = @('C:\s\PowerToys\output\claude\45601\_claude-debug.log')
|
||||
CleanupTask = {
|
||||
param($Tracker)
|
||||
$dbg = Join-Path $Tracker.ExecutionParameters.OutputDir '_claude-debug.log'
|
||||
if (Test-Path $dbg) {
|
||||
$fi = [System.IO.FileInfo]::new($dbg)
|
||||
if ($fi.Length -gt 0) {
|
||||
$sizeMB = [math]::Round($fi.Length / 1MB, 1)
|
||||
Remove-Item $dbg -Force
|
||||
Write-Host "[$($Tracker.Label)] Cleaned debug log (${sizeMB} MB)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Generic Shell Command Job
|
||||
|
||||
```powershell
|
||||
@{
|
||||
Label = 'lint-module-fancyzones'
|
||||
ExecutionParameters = @{
|
||||
JobName = 'lint-fancyzones'
|
||||
Command = 'dotnet'
|
||||
Arguments = @('build', '--no-restore', '-warnaserror')
|
||||
WorkingDir = 'C:\s\PowerToys\src\modules\fancyzones'
|
||||
OutputDir = 'C:\s\PowerToys\output\lint\fancyzones'
|
||||
LogPath = 'C:\s\PowerToys\output\lint\fancyzones\build.log'
|
||||
}
|
||||
MonitorFiles = @('C:\s\PowerToys\output\lint\fancyzones\build.log')
|
||||
CleanupTask = $null
|
||||
}
|
||||
```
|
||||
|
||||
## Caller Template
|
||||
|
||||
Every skill that builds job definitions and calls the orchestrator should
|
||||
follow this pattern:
|
||||
|
||||
```powershell
|
||||
# Build definitions
|
||||
$jobDefs = @(foreach ($item in $items) {
|
||||
@{
|
||||
Label = "myskill-$($item.Id)"
|
||||
ExecutionParameters = @{ ... }
|
||||
MonitorFiles = @(...)
|
||||
CleanupTask = $null
|
||||
}
|
||||
})
|
||||
|
||||
# Resolve orchestrator path
|
||||
$orchestratorPath = Join-Path $PSScriptRoot '..\..\parallel-job-orchestrator\scripts\Invoke-SimpleJobOrchestrator.ps1'
|
||||
|
||||
# CRITICAL: Lower ErrorActionPreference before calling
|
||||
$savedEAP = $ErrorActionPreference
|
||||
$ErrorActionPreference = 'Continue'
|
||||
|
||||
$results = & $orchestratorPath `
|
||||
-JobDefinitions $jobDefs `
|
||||
-MaxConcurrent 4 `
|
||||
-LogDir $outputPath
|
||||
|
||||
$ErrorActionPreference = $savedEAP
|
||||
|
||||
# Process results
|
||||
$results | Format-Table Label, Status, ExitCode, RetryCount -AutoSize
|
||||
```
|
||||
@@ -1,358 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Generic job orchestrator: queues, starts, monitors, retries, and cleans up
|
||||
PowerShell background jobs with configurable concurrency.
|
||||
|
||||
.DESCRIPTION
|
||||
Accepts an array of job definitions (created via New-JobDefinition), queues
|
||||
them in memory, and runs up to MaxConcurrent at a time. Jobs are retried
|
||||
up to MaxRetryCount times when they:
|
||||
- Exit with a non-zero exit code
|
||||
- Finish with a Failed or NotFound job state
|
||||
- Stall (log-file inactivity exceeds InactivityTimeoutSeconds)
|
||||
When a job finishes or is abandoned, its optional CleanupTask scriptblock
|
||||
runs.
|
||||
|
||||
Returns an array of result objects with final state, exit code, retry count,
|
||||
and output directory for every definition.
|
||||
|
||||
This is the CANONICAL parallel execution engine for this repository.
|
||||
ALL skills that need to run copilot, claude, or any CLI tool in parallel
|
||||
MUST use this orchestrator. Do NOT use Start-Job, ForEach-Object -Parallel,
|
||||
or Start-Process directly — those approaches have known PowerShell 7 crash
|
||||
bugs.
|
||||
|
||||
Part of the parallel-job-orchestrator skill:
|
||||
<configRoot>/skills/parallel-job-orchestrator/SKILL.md
|
||||
|
||||
.PARAMETER JobDefinitions
|
||||
Array of job-definition hashtables created by New-JobDefinition.
|
||||
|
||||
.PARAMETER MaxConcurrent
|
||||
Maximum number of jobs running simultaneously. Default 4.
|
||||
|
||||
.PARAMETER InactivityTimeoutSeconds
|
||||
Seconds of zero log-file growth before a job is considered stale. Default 60.
|
||||
|
||||
.PARAMETER MaxRetryCount
|
||||
How many times to restart a stale job before giving up. Default 3.
|
||||
|
||||
.PARAMETER PollIntervalSeconds
|
||||
How often (seconds) to check job health. Default 5.
|
||||
|
||||
.PARAMETER LogDir
|
||||
Directory for the orchestrator's own progress log. Default: TEMP.
|
||||
#>
|
||||
# NOTE: Do NOT use [CmdletBinding()] here. When a caller sets
|
||||
# $ErrorActionPreference='Stop', CmdletBinding propagates that as the implicit
|
||||
# -ErrorAction common parameter, overriding any local assignment. A monitoring
|
||||
# loop must be resilient, so we intentionally stay as a simple script.
|
||||
# IMPORTANT: Do not use [Parameter()], [ValidateSet()] or any attribute on params
|
||||
# either — those ALSO implicitly enable advanced-script behaviour.
|
||||
param(
|
||||
[hashtable[]]$JobDefinitions,
|
||||
|
||||
[int]$MaxConcurrent = 4,
|
||||
|
||||
[int]$InactivityTimeoutSeconds = 60,
|
||||
|
||||
[int]$MaxRetryCount = 3,
|
||||
|
||||
[int]$PollIntervalSeconds = 5,
|
||||
|
||||
[string]$LogDir
|
||||
)
|
||||
|
||||
# Manual mandatory check (replacing [Parameter(Mandatory)] which makes this
|
||||
# an advanced script and re-enables ErrorActionPreference propagation).
|
||||
if (-not $JobDefinitions -or $JobDefinitions.Count -eq 0) {
|
||||
Write-Error 'Invoke-SimpleJobOrchestrator: -JobDefinitions is required and must not be empty.'
|
||||
return @()
|
||||
}
|
||||
|
||||
# Orchestrator must be resilient — individual operations handle their own errors.
|
||||
$ErrorActionPreference = 'Continue'
|
||||
|
||||
# ── logging ──────────────────────────────────────────────────────────────
|
||||
# Verbose progress goes to a log file to avoid terminal-output issues that
|
||||
# can silently terminate the script when run inside VS Code / IDE terminals.
|
||||
# Only summary-level messages go to Write-Host (console).
|
||||
|
||||
if (-not $LogDir) { $LogDir = $env:TEMP }
|
||||
New-Item -ItemType Directory -Path $LogDir -Force -ErrorAction SilentlyContinue | Out-Null
|
||||
$script:_orchestratorLog = Join-Path $LogDir "orchestrator-$(Get-Date -Format 'yyyyMMdd-HHmmss').log"
|
||||
|
||||
function Write-Log {
|
||||
param([string]$Message)
|
||||
$ts = Get-Date -Format 'HH:mm:ss'
|
||||
$line = "[$ts] $Message"
|
||||
try { Add-Content -Path $script:_orchestratorLog -Value $line -ErrorAction SilentlyContinue }
|
||||
catch { }
|
||||
}
|
||||
|
||||
function Write-ProgressMessage {
|
||||
<# Write to both console and log file. Use sparingly. #>
|
||||
param([string]$Message)
|
||||
Write-Log $Message
|
||||
Write-Host $Message
|
||||
}
|
||||
|
||||
# ── helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
# IMPORTANT: Start-TrackedJob is deliberately NOT a function. PowerShell 7
|
||||
# silently crashes the host process when Start-Job is called from within a
|
||||
# function that is invoked inside a while loop in a .ps1 script file (~10-15
|
||||
# jobs triggers it). Inline the Start-Job call at every call site instead.
|
||||
|
||||
# Shared scriptblock for all tracked jobs (defined once, reused).
|
||||
$_jobScriptBlock = {
|
||||
param($Cmd, $ArgList, $WorkDir, $LogFile)
|
||||
Set-Location $WorkDir
|
||||
if (Test-Path $LogFile) { Remove-Item $LogFile -Force }
|
||||
& $Cmd @ArgList *> $LogFile
|
||||
[PSCustomObject]@{
|
||||
Command = $Cmd
|
||||
ExitCode = $LASTEXITCODE
|
||||
LogPath = $LogFile
|
||||
}
|
||||
}
|
||||
|
||||
# NOTE: Test-MonitorFilesActive, Stop-TrackedJob, and Invoke-CleanupTask
|
||||
# are deliberately NOT functions. PowerShell 7 silently crashes the host when
|
||||
# certain cmdlets (Stop-Job, Remove-Job, Get-Job, Get-Item) are called from
|
||||
# within a function in a while loop inside a .ps1 script. Their logic is
|
||||
# inlined at every call site below.
|
||||
|
||||
function Get-TrackerResult {
|
||||
param([hashtable]$Tracker)
|
||||
|
||||
# Job output was collected and stored in _ReceivedOutput / _FinalJobState
|
||||
# at completion time (before Remove-Job). Fall back to live query only if
|
||||
# the tracker somehow missed the collection step.
|
||||
$received = $Tracker._ReceivedOutput
|
||||
$state = $Tracker._FinalJobState
|
||||
|
||||
if (-not $state) {
|
||||
$jobObj = Get-Job -Id $Tracker.JobId -ErrorAction SilentlyContinue
|
||||
$received = if ($jobObj) {
|
||||
Receive-Job -Id $Tracker.JobId -Keep -ErrorAction SilentlyContinue
|
||||
}
|
||||
else { $null }
|
||||
$state = if ($jobObj) { $jobObj.State } else { 'Removed' }
|
||||
}
|
||||
|
||||
[PSCustomObject]@{
|
||||
Label = $Tracker.Label
|
||||
JobId = $Tracker.JobId
|
||||
Status = $Tracker.Status
|
||||
JobState = $state
|
||||
ExitCode = if ($received) { $received.ExitCode } else { $null }
|
||||
RetryCount = $Tracker.RetryCount
|
||||
OutputDir = $Tracker.ExecutionParameters.OutputDir
|
||||
LogPath = $Tracker.ExecutionParameters.LogPath
|
||||
}
|
||||
}
|
||||
|
||||
# ── build tracker list ───────────────────────────────────────────────────
|
||||
|
||||
$queue = [System.Collections.Generic.Queue[hashtable]]::new()
|
||||
$running = [System.Collections.Generic.List[hashtable]]::new()
|
||||
$finished = [System.Collections.Generic.List[hashtable]]::new()
|
||||
|
||||
foreach ($def in $JobDefinitions) {
|
||||
$tracker = @{
|
||||
Label = $def.Label
|
||||
ExecutionParameters = $def.ExecutionParameters
|
||||
MonitorFiles = $def.MonitorFiles
|
||||
CleanupTask = $def.CleanupTask
|
||||
Status = 'Queued'
|
||||
JobId = $null
|
||||
RetryCount = 0
|
||||
LastFileSizes = @{}
|
||||
LastChangeTime = [DateTime]::UtcNow
|
||||
}
|
||||
$queue.Enqueue($tracker)
|
||||
}
|
||||
|
||||
Write-ProgressMessage "Orchestrator: $($queue.Count) jobs queued, max $MaxConcurrent concurrent. Log: $script:_orchestratorLog"
|
||||
|
||||
# ── main loop ────────────────────────────────────────────────────────────
|
||||
|
||||
$loopIteration = 0
|
||||
|
||||
while ($queue.Count -gt 0 -or $running.Count -gt 0) {
|
||||
$loopIteration++
|
||||
|
||||
try {
|
||||
$ErrorActionPreference = 'Continue'
|
||||
|
||||
# fill slots from queue
|
||||
while ($running.Count -lt $MaxConcurrent -and $queue.Count -gt 0) {
|
||||
$t = $queue.Dequeue()
|
||||
Write-Log "Dequeued $($t.Label); about to start job (running=$($running.Count), queue=$($queue.Count))"
|
||||
try {
|
||||
# ── inline Start-TrackedJob (see note above about PS7 crash) ──
|
||||
$ep = $t.ExecutionParameters
|
||||
New-Item -ItemType Directory -Path $ep.OutputDir -Force | Out-Null
|
||||
$job = Start-Job -Name $ep.JobName -ScriptBlock $_jobScriptBlock `
|
||||
-ArgumentList $ep.Command, $ep.Arguments, $ep.WorkingDir, $ep.LogPath
|
||||
$t.JobId = $job.Id
|
||||
$t.Status = 'Running'
|
||||
$t.LastFileSizes = @{}
|
||||
$t.LastChangeTime = [DateTime]::UtcNow
|
||||
foreach ($f in $t.MonitorFiles) { $t.LastFileSizes[$f] = 0L }
|
||||
Write-Log "[$($t.Label)] Started job $($job.Id)"
|
||||
}
|
||||
catch {
|
||||
Write-Log "Start job FAILED for $($t.Label): $_"
|
||||
$t.Status = 'Failed'
|
||||
$finished.Add($t)
|
||||
continue
|
||||
}
|
||||
$running.Add($t)
|
||||
}
|
||||
|
||||
Write-Log "Sleeping ${PollIntervalSeconds}s..."
|
||||
|
||||
Start-Sleep -Seconds $PollIntervalSeconds
|
||||
|
||||
# evaluate every running tracker
|
||||
$toRemove = [System.Collections.Generic.List[hashtable]]::new()
|
||||
|
||||
foreach ($t in $running) {
|
||||
$jobObj = Get-Job -Id $t.JobId -ErrorAction SilentlyContinue
|
||||
$jobState = if ($jobObj) { $jobObj.State } else { 'NotFound' }
|
||||
|
||||
# ── finished naturally ────────────────────────────────────
|
||||
if ($jobState -in 'Completed', 'Failed', 'NotFound') {
|
||||
# Collect job output before deciding whether to retry.
|
||||
$received = $null
|
||||
if ($jobObj) {
|
||||
$received = Receive-Job -Id $t.JobId -ErrorAction SilentlyContinue
|
||||
}
|
||||
Remove-Job -Id $t.JobId -Force -ErrorAction SilentlyContinue
|
||||
|
||||
$exitCode = if ($received) { $received.ExitCode } else { $null }
|
||||
$isFailedExit = ($jobState -in 'Failed', 'NotFound') -or
|
||||
($null -ne $exitCode -and $exitCode -ne 0)
|
||||
|
||||
# ── retry if the process exited with failure ──────────
|
||||
if ($isFailedExit -and $t.RetryCount -lt $MaxRetryCount) {
|
||||
$t.RetryCount++
|
||||
Write-Log "[$($t.Label)] Exited with failure (state=$jobState, exit=$exitCode) — retry $($t.RetryCount)/$MaxRetryCount"
|
||||
# ── inline cleanup before retry (no function — see PS7 crash note) ──
|
||||
if ($t.CleanupTask) {
|
||||
try { & $t.CleanupTask $t }
|
||||
catch { Write-Log "[$($t.Label)] Cleanup failed: $_" }
|
||||
}
|
||||
# ── inline Start-TrackedJob for retry (see note about PS7 crash) ──
|
||||
$ep = $t.ExecutionParameters
|
||||
New-Item -ItemType Directory -Path $ep.OutputDir -Force | Out-Null
|
||||
$job = Start-Job -Name $ep.JobName -ScriptBlock $_jobScriptBlock `
|
||||
-ArgumentList $ep.Command, $ep.Arguments, $ep.WorkingDir, $ep.LogPath
|
||||
$t.JobId = $job.Id
|
||||
$t.Status = 'Running'
|
||||
$t.LastFileSizes = @{}
|
||||
$t.LastChangeTime = [DateTime]::UtcNow
|
||||
foreach ($f in $t.MonitorFiles) { $t.LastFileSizes[$f] = 0L }
|
||||
Write-Log "[$($t.Label)] Retry started job $($job.Id)"
|
||||
continue
|
||||
}
|
||||
|
||||
$t.Status = if ($isFailedExit) { 'Failed' } else { $jobState }
|
||||
Write-Log "[$($t.Label)] Finished (state=$jobState, exit=$exitCode) after $($t.RetryCount) retries."
|
||||
|
||||
$t._ReceivedOutput = $received
|
||||
$t._FinalJobState = $jobState
|
||||
|
||||
# ── inline cleanup (no function — see PS7 crash note) ──
|
||||
if ($t.CleanupTask) {
|
||||
try { & $t.CleanupTask $t }
|
||||
catch { Write-Log "[$($t.Label)] Cleanup failed: $_" }
|
||||
}
|
||||
$toRemove.Add($t)
|
||||
continue
|
||||
}
|
||||
|
||||
# ── still running — check monitor files ──────────────────
|
||||
$active = $false
|
||||
try {
|
||||
# ── inline file-activity check (no function — see PS7 crash note) ──
|
||||
$_anyGrew = $false
|
||||
foreach ($_f in $t.MonitorFiles) {
|
||||
$_sz = 0L
|
||||
if (Test-Path $_f) { $_sz = ([System.IO.FileInfo]::new($_f)).Length }
|
||||
if ($_sz -ne $t.LastFileSizes[$_f]) {
|
||||
$t.LastFileSizes[$_f] = $_sz
|
||||
$_anyGrew = $true
|
||||
}
|
||||
}
|
||||
$active = $_anyGrew
|
||||
}
|
||||
catch { $active = $true }
|
||||
|
||||
if ($active) {
|
||||
$t.LastChangeTime = [DateTime]::UtcNow
|
||||
continue
|
||||
}
|
||||
|
||||
$staleSecs = [math]::Round(([DateTime]::UtcNow - $t.LastChangeTime).TotalSeconds)
|
||||
if ($staleSecs -lt $InactivityTimeoutSeconds) { continue }
|
||||
|
||||
# ── stale — retry or give up ─────────────────────────────
|
||||
if ($t.RetryCount -ge $MaxRetryCount) {
|
||||
Write-Log "[$($t.Label)] Max retries ($MaxRetryCount) after ${staleSecs}s stale. Giving up."
|
||||
$t.Status = 'Abandoned'
|
||||
# ── inline stop + cleanup (no function — see PS7 crash note) ──
|
||||
Stop-Job -Id $t.JobId -ErrorAction SilentlyContinue
|
||||
Remove-Job -Id $t.JobId -Force -ErrorAction SilentlyContinue
|
||||
if ($t.CleanupTask) {
|
||||
try { & $t.CleanupTask $t }
|
||||
catch { Write-Log "[$($t.Label)] Cleanup failed: $_" }
|
||||
}
|
||||
$toRemove.Add($t)
|
||||
continue
|
||||
}
|
||||
|
||||
$t.RetryCount++
|
||||
Write-Log "[$($t.Label)] Stale ${staleSecs}s — retry $($t.RetryCount)/$MaxRetryCount"
|
||||
# ── inline stop (no function — see PS7 crash note) ──
|
||||
Stop-Job -Id $t.JobId -ErrorAction SilentlyContinue
|
||||
Remove-Job -Id $t.JobId -Force -ErrorAction SilentlyContinue
|
||||
# ── inline Start-TrackedJob for retry (see note about PS7 crash) ──
|
||||
$ep = $t.ExecutionParameters
|
||||
New-Item -ItemType Directory -Path $ep.OutputDir -Force | Out-Null
|
||||
$job = Start-Job -Name $ep.JobName -ScriptBlock $_jobScriptBlock `
|
||||
-ArgumentList $ep.Command, $ep.Arguments, $ep.WorkingDir, $ep.LogPath
|
||||
$t.JobId = $job.Id
|
||||
$t.Status = 'Running'
|
||||
$t.LastFileSizes = @{}
|
||||
$t.LastChangeTime = [DateTime]::UtcNow
|
||||
foreach ($f in $t.MonitorFiles) { $t.LastFileSizes[$f] = 0L }
|
||||
Write-Log "[$($t.Label)] Retry started job $($job.Id)"
|
||||
}
|
||||
|
||||
foreach ($r in $toRemove) {
|
||||
$running.Remove($r) | Out-Null
|
||||
$finished.Add($r)
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log "Loop error (iter $loopIteration): $_ | $($_.Exception.GetType().FullName)"
|
||||
}
|
||||
|
||||
# log every iteration; console progress every iteration (REQUIRED:
|
||||
# PowerShell 7 silently kills the host process when a child-script
|
||||
# while loop produces no Write-Host output for ~8+ seconds).
|
||||
$qc = $queue.Count; $rc = $running.Count; $fc = $finished.Count
|
||||
Write-Log "queue=$qc running=$rc done=$fc (iter=$loopIteration)"
|
||||
$runLabels = ($running | ForEach-Object { $_.Label }) -join ', '
|
||||
Write-Host " [$((Get-Date).ToString('HH:mm:ss'))] queue=$qc running=$rc done=$fc (iter=$loopIteration) [$runLabels]"
|
||||
}
|
||||
|
||||
# ── results ──────────────────────────────────────────────────────────────
|
||||
|
||||
Write-ProgressMessage "All $($finished.Count) jobs finished. Log: $script:_orchestratorLog"
|
||||
|
||||
$results = foreach ($t in $finished) { Get-TrackerResult $t }
|
||||
return $results
|
||||
@@ -1,352 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Stress-tests Invoke-SimpleJobOrchestrator.ps1 with edge-case scenarios.
|
||||
|
||||
.DESCRIPTION
|
||||
Creates job definitions that simulate various failure modes:
|
||||
1. Happy-path jobs (should complete normally)
|
||||
2. Jobs that throw exceptions (should be detected as Failed)
|
||||
3. Jobs that hang with no log output (stale → retry → abandon)
|
||||
4. Jobs that write to the log once then hang (stale after initial burst)
|
||||
5. Jobs with a cleanup task (verify cleanup runs on completion)
|
||||
6. Jobs with a cleanup task that itself throws
|
||||
7. Concurrency pressure: many fast jobs queued beyond MaxConcurrent
|
||||
8. Mixed bag: all of the above in one run
|
||||
|
||||
Each scenario prints PASS / FAIL and the script exits with the total
|
||||
failure count so CI can gate on it.
|
||||
|
||||
.PARAMETER Scenario
|
||||
Which scenario to run. Default 'All' runs every scenario sequentially.
|
||||
|
||||
.PARAMETER OutputRoot
|
||||
Base directory for test artefacts. Cleaned before each scenario.
|
||||
#>
|
||||
# NOTE: Do NOT use [CmdletBinding()] or parameter attributes such as
|
||||
# [ValidateSet()] / [Parameter()] here. Any of those make this an "advanced
|
||||
# script", which propagates the caller's ErrorActionPreference via the implicit
|
||||
# -ErrorAction common parameter — silently terminating the entire script when
|
||||
# stray non-terminating errors bubble up from Stop-Job, Remove-Job, or file
|
||||
# locks between scenarios.
|
||||
param(
|
||||
[string]$Scenario = 'All',
|
||||
[string]$OutputRoot = 'Generated Files/orch-stress-test'
|
||||
)
|
||||
|
||||
# Manual validation instead of [ValidateSet()] to keep this a simple script.
|
||||
$validScenarios = @('All', 'HappyPath', 'ThrowException', 'StaleNoLog',
|
||||
'StaleThenHang', 'CleanupRuns', 'CleanupThrows', 'ConcurrencyPressure', 'MixedBag')
|
||||
if ($Scenario -notin $validScenarios) {
|
||||
Write-Error "Invalid -Scenario '$Scenario'. Valid values: $($validScenarios -join ', ')"
|
||||
return
|
||||
}
|
||||
|
||||
# Test scripts use 'Continue' globally. Individual assertions use try/catch.
|
||||
# Using 'Stop' causes stray non-terminating errors from completed-job cleanup,
|
||||
# file locks, Start-Job, etc. to silently terminate the whole script.
|
||||
$ErrorActionPreference = 'Continue'
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..\..\..')).Path
|
||||
$orchPath = Join-Path $PSScriptRoot 'Invoke-SimpleJobOrchestrator.ps1'
|
||||
|
||||
if (-not [System.IO.Path]::IsPathRooted($OutputRoot)) {
|
||||
$OutputRoot = Join-Path $repoRoot $OutputRoot
|
||||
}
|
||||
|
||||
# ── helper: build a single synthetic job definition ──────────────────────
|
||||
|
||||
function New-TestJob {
|
||||
param(
|
||||
[string]$Label,
|
||||
[string]$InlineScript, # PowerShell code to run inside the job
|
||||
[string]$OutDir,
|
||||
[scriptblock]$CleanupTask = $null
|
||||
)
|
||||
|
||||
$logPath = Join-Path $OutDir "$Label.log"
|
||||
|
||||
return @{
|
||||
Label = $Label
|
||||
ExecutionParameters = @{
|
||||
JobName = $Label
|
||||
Command = 'powershell'
|
||||
Arguments = @('-NoProfile', '-Command', $InlineScript)
|
||||
WorkingDir = $repoRoot
|
||||
OutputDir = $OutDir
|
||||
LogPath = $logPath
|
||||
}
|
||||
MonitorFiles = @($logPath)
|
||||
CleanupTask = $CleanupTask
|
||||
}
|
||||
}
|
||||
|
||||
# ── helper: run one scenario ─────────────────────────────────────────────
|
||||
|
||||
$script:passCount = 0
|
||||
$script:failCount = 0
|
||||
|
||||
function Invoke-Scenario {
|
||||
param(
|
||||
[string]$Name,
|
||||
[hashtable[]]$Defs,
|
||||
[int]$MaxConcurrent = 10,
|
||||
[int]$InactivityTimeout = 8,
|
||||
[int]$MaxRetry = 1,
|
||||
[int]$PollInterval = 2,
|
||||
[scriptblock]$Assertions # receives $results array
|
||||
)
|
||||
|
||||
Write-Host "`n╔══════════════════════════════════════════════════════╗" -ForegroundColor Yellow
|
||||
Write-Host "║ Scenario: $Name" -ForegroundColor Yellow
|
||||
Write-Host "╚══════════════════════════════════════════════════════╝" -ForegroundColor Yellow
|
||||
|
||||
$scenarioDir = Join-Path $OutputRoot $Name
|
||||
|
||||
# ── aggressive cleanup ───────────────────────────────────────────────
|
||||
# Previous stale-job scenarios may leave background processes with file
|
||||
# locks. Stop ALL jobs (not just the current scenario's) and wait a
|
||||
# moment for handles to release before wiping the directory.
|
||||
Get-Job | Stop-Job -ErrorAction SilentlyContinue
|
||||
Get-Job | Remove-Job -Force -ErrorAction SilentlyContinue
|
||||
|
||||
if (Test-Path $scenarioDir) {
|
||||
Start-Sleep -Milliseconds 500
|
||||
Remove-Item $scenarioDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
# Retry once if first attempt failed (file lock race)
|
||||
if (Test-Path $scenarioDir) {
|
||||
Start-Sleep -Seconds 1
|
||||
Remove-Item $scenarioDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
$results = & $orchPath `
|
||||
-JobDefinitions $Defs `
|
||||
-MaxConcurrent $MaxConcurrent `
|
||||
-InactivityTimeoutSeconds $InactivityTimeout `
|
||||
-MaxRetryCount $MaxRetry `
|
||||
-PollIntervalSeconds $PollInterval `
|
||||
-LogDir $scenarioDir
|
||||
|
||||
# run caller assertions
|
||||
try {
|
||||
& $Assertions $results
|
||||
}
|
||||
catch {
|
||||
Write-Host " FAIL (assertion error): $_" -ForegroundColor Red
|
||||
$script:failCount++
|
||||
}
|
||||
}
|
||||
|
||||
function Assert-True {
|
||||
param([bool]$Condition, [string]$Message)
|
||||
if ($Condition) {
|
||||
Write-Host " PASS: $Message" -ForegroundColor Green
|
||||
$script:passCount++
|
||||
}
|
||||
else {
|
||||
Write-Host " FAIL: $Message" -ForegroundColor Red
|
||||
$script:failCount++
|
||||
}
|
||||
}
|
||||
|
||||
# ── scenario definitions ─────────────────────────────────────────────────
|
||||
|
||||
$scenarios = @{}
|
||||
|
||||
# 1. Happy path — 3 jobs that complete quickly
|
||||
$scenarios['HappyPath'] = {
|
||||
$dir = Join-Path $OutputRoot 'HappyPath'
|
||||
$defs = @(1..3 | ForEach-Object {
|
||||
New-TestJob -Label "happy-$_" -OutDir $dir `
|
||||
-InlineScript "Write-Output 'hello from $_'; Start-Sleep -Milliseconds 500; Write-Output 'done $_'"
|
||||
})
|
||||
|
||||
Invoke-Scenario -Name 'HappyPath' -Defs $defs -Assertions {
|
||||
param($r)
|
||||
Assert-True ($r.Count -eq 3) 'Got 3 results'
|
||||
Assert-True (($r | Where-Object Status -eq 'Completed').Count -eq 3) 'All 3 completed'
|
||||
Assert-True (($r | Where-Object RetryCount -eq 0).Count -eq 3) 'Zero retries'
|
||||
}
|
||||
}
|
||||
|
||||
# 2. Throw exception — the command errors out immediately
|
||||
$scenarios['ThrowException'] = {
|
||||
$dir = Join-Path $OutputRoot 'ThrowException'
|
||||
$defs = @(
|
||||
(New-TestJob -Label 'throw-1' -OutDir $dir `
|
||||
-InlineScript "throw 'Simulated fatal error'"),
|
||||
(New-TestJob -Label 'good-1' -OutDir $dir `
|
||||
-InlineScript "Write-Output 'I am fine'; Start-Sleep -Milliseconds 300")
|
||||
)
|
||||
|
||||
Invoke-Scenario -Name 'ThrowException' -Defs $defs -Assertions {
|
||||
param($r)
|
||||
Assert-True ($r.Count -eq 2) 'Got 2 results'
|
||||
Assert-True (($r | Where-Object Label -eq 'throw-1').Status -in 'Completed','Failed') 'Throw job detected as finished'
|
||||
Assert-True (($r | Where-Object Label -eq 'good-1').Status -eq 'Completed') 'Good job completed'
|
||||
}
|
||||
}
|
||||
|
||||
# 3. Stale — no log output, job sleeps forever (beyond timeout)
|
||||
$scenarios['StaleNoLog'] = {
|
||||
$dir = Join-Path $OutputRoot 'StaleNoLog'
|
||||
$defs = @(
|
||||
(New-TestJob -Label 'stale-nolog' -OutDir $dir `
|
||||
-InlineScript "Start-Sleep -Seconds 120")
|
||||
)
|
||||
|
||||
# Timeout 8 s, poll 2 s, max retry 1 → should retry once then abandon
|
||||
Invoke-Scenario -Name 'StaleNoLog' -Defs $defs `
|
||||
-InactivityTimeout 8 -MaxRetry 1 -PollInterval 2 `
|
||||
-Assertions {
|
||||
param($r)
|
||||
Assert-True ($r.Count -eq 1) 'Got 1 result'
|
||||
Assert-True ($r[0].Status -eq 'Abandoned') 'Marked as Abandoned'
|
||||
Assert-True ($r[0].RetryCount -eq 1) 'Retried once before giving up'
|
||||
}
|
||||
}
|
||||
|
||||
# 4. Writes once then hangs — log grows initially then stops
|
||||
$scenarios['StaleThenHang'] = {
|
||||
$dir = Join-Path $OutputRoot 'StaleThenHang'
|
||||
$defs = @(
|
||||
(New-TestJob -Label 'burst-hang' -OutDir $dir `
|
||||
-InlineScript "Write-Output 'initial burst'; Start-Sleep -Seconds 120")
|
||||
)
|
||||
|
||||
Invoke-Scenario -Name 'StaleThenHang' -Defs $defs `
|
||||
-InactivityTimeout 8 -MaxRetry 1 -PollInterval 2 `
|
||||
-Assertions {
|
||||
param($r)
|
||||
Assert-True ($r.Count -eq 1) 'Got 1 result'
|
||||
Assert-True ($r[0].Status -eq 'Abandoned') 'Marked as Abandoned'
|
||||
Assert-True ($r[0].RetryCount -ge 1) 'Retried at least once'
|
||||
}
|
||||
}
|
||||
|
||||
# 5. Cleanup task runs on completion
|
||||
$scenarios['CleanupRuns'] = {
|
||||
$dir = Join-Path $OutputRoot 'CleanupRuns'
|
||||
$marker = Join-Path $dir 'cleanup-ran.marker'
|
||||
|
||||
$cleanupBlock = [scriptblock]::Create(
|
||||
"param(`$Tracker); New-Item -ItemType File -Path '$($marker -replace "'","''")' -Force | Out-Null"
|
||||
)
|
||||
|
||||
$defs = @(
|
||||
(New-TestJob -Label 'cleanup-ok' -OutDir $dir `
|
||||
-InlineScript "Write-Output 'will be cleaned'" `
|
||||
-CleanupTask $cleanupBlock)
|
||||
)
|
||||
|
||||
Invoke-Scenario -Name 'CleanupRuns' -Defs $defs -Assertions {
|
||||
param($r)
|
||||
Assert-True ($r.Count -eq 1) 'Got 1 result'
|
||||
Assert-True ($r[0].Status -eq 'Completed') 'Job completed'
|
||||
Assert-True (Test-Path $marker) 'Cleanup marker file exists'
|
||||
}
|
||||
}
|
||||
|
||||
# 6. Cleanup task that itself throws — should not crash the orchestrator
|
||||
$scenarios['CleanupThrows'] = {
|
||||
$dir = Join-Path $OutputRoot 'CleanupThrows'
|
||||
|
||||
$badCleanup = { param($Tracker); throw 'Cleanup explosion!' }
|
||||
|
||||
$defs = @(
|
||||
(New-TestJob -Label 'cleanup-boom' -OutDir $dir `
|
||||
-InlineScript "Write-Output 'boom prep'" `
|
||||
-CleanupTask $badCleanup),
|
||||
(New-TestJob -Label 'after-boom' -OutDir $dir `
|
||||
-InlineScript "Write-Output 'I should still finish'")
|
||||
)
|
||||
|
||||
Invoke-Scenario -Name 'CleanupThrows' -Defs $defs -Assertions {
|
||||
param($r)
|
||||
Assert-True ($r.Count -eq 2) 'Got 2 results'
|
||||
Assert-True (($r | Where-Object Label -eq 'cleanup-boom').Status -eq 'Completed') 'Boom job completed despite bad cleanup'
|
||||
Assert-True (($r | Where-Object Label -eq 'after-boom').Status -eq 'Completed') 'Next job also completed'
|
||||
}
|
||||
}
|
||||
|
||||
# 7. Concurrency pressure — 20 fast jobs, MaxConcurrent=5
|
||||
$scenarios['ConcurrencyPressure'] = {
|
||||
$dir = Join-Path $OutputRoot 'ConcurrencyPressure'
|
||||
$defs = @(1..20 | ForEach-Object {
|
||||
New-TestJob -Label "conc-$_" -OutDir $dir `
|
||||
-InlineScript "Write-Output 'job $_ at $(Get-Date -f s)'; Start-Sleep -Milliseconds $(Get-Random -Min 200 -Max 1500)"
|
||||
})
|
||||
|
||||
Invoke-Scenario -Name 'ConcurrencyPressure' -Defs $defs `
|
||||
-MaxConcurrent 5 -InactivityTimeout 15 -PollInterval 2 `
|
||||
-Assertions {
|
||||
param($r)
|
||||
Assert-True ($r.Count -eq 20) 'Got 20 results'
|
||||
Assert-True (($r | Where-Object Status -eq 'Completed').Count -eq 20) 'All 20 completed'
|
||||
# Verify logs have content
|
||||
$withContent = ($r | Where-Object {
|
||||
(Test-Path $_.LogPath) -and (Get-Item $_.LogPath).Length -gt 0
|
||||
}).Count
|
||||
Assert-True ($withContent -eq 20) 'All 20 logs have content'
|
||||
}
|
||||
}
|
||||
|
||||
# 8. Mixed bag — happy + throw + stale + cleanup in one run
|
||||
$scenarios['MixedBag'] = {
|
||||
$dir = Join-Path $OutputRoot 'MixedBag'
|
||||
$marker = Join-Path $dir 'mixed-cleanup.marker'
|
||||
|
||||
$cleanupOk = [scriptblock]::Create(
|
||||
"param(`$Tracker); New-Item -ItemType File -Path '$($marker -replace "'","''")' -Force | Out-Null"
|
||||
)
|
||||
|
||||
$defs = @(
|
||||
(New-TestJob -Label 'mix-happy' -OutDir $dir -InlineScript "Write-Output 'happy'; Start-Sleep -Milliseconds 500"),
|
||||
(New-TestJob -Label 'mix-throw' -OutDir $dir -InlineScript "throw 'kaboom'"),
|
||||
(New-TestJob -Label 'mix-stale' -OutDir $dir -InlineScript "Start-Sleep -Seconds 120"),
|
||||
(New-TestJob -Label 'mix-cleanup' -OutDir $dir -InlineScript "Write-Output 'with cleanup'" -CleanupTask $cleanupOk)
|
||||
)
|
||||
|
||||
Invoke-Scenario -Name 'MixedBag' -Defs $defs `
|
||||
-MaxConcurrent 10 -InactivityTimeout 8 -MaxRetry 1 -PollInterval 2 `
|
||||
-Assertions {
|
||||
param($r)
|
||||
Assert-True ($r.Count -eq 4) 'Got 4 results'
|
||||
Assert-True (($r | Where-Object Label -eq 'mix-happy').Status -eq 'Completed') 'Happy completed'
|
||||
Assert-True (($r | Where-Object Label -eq 'mix-throw').Status -in 'Completed','Failed') 'Throw detected'
|
||||
Assert-True (($r | Where-Object Label -eq 'mix-stale').Status -eq 'Abandoned') 'Stale abandoned'
|
||||
Assert-True (($r | Where-Object Label -eq 'mix-stale').RetryCount -ge 1) 'Stale retried'
|
||||
Assert-True (($r | Where-Object Label -eq 'mix-cleanup').Status -eq 'Completed') 'Cleanup job completed'
|
||||
Assert-True (Test-Path $marker) 'Mixed cleanup marker exists'
|
||||
}
|
||||
}
|
||||
|
||||
# ── run selected scenarios ───────────────────────────────────────────────
|
||||
|
||||
$toRun = if ($Scenario -eq 'All') { $scenarios.Keys | Sort-Object } else { @($Scenario) }
|
||||
|
||||
$sw = [System.Diagnostics.Stopwatch]::StartNew()
|
||||
|
||||
foreach ($name in $toRun) {
|
||||
& $scenarios[$name]
|
||||
|
||||
# ── inter-scenario cleanup ─────────────────────────────────
|
||||
# Kill any leftover jobs (especially long-running stale-sim sleeps),
|
||||
# force garbage collection, and pause briefly so handles release.
|
||||
Get-Job | Stop-Job -ErrorAction SilentlyContinue
|
||||
Get-Job | Remove-Job -Force -ErrorAction SilentlyContinue
|
||||
[System.GC]::Collect()
|
||||
Start-Sleep -Seconds 2
|
||||
}
|
||||
|
||||
$sw.Stop()
|
||||
|
||||
# ── summary ──────────────────────────────────────────────────────────────
|
||||
|
||||
Write-Host "`n════════════════════════════════════════════════════════" -ForegroundColor Cyan
|
||||
Write-Host " RESULTS: $($script:passCount) passed, $($script:failCount) failed ($([math]::Round($sw.Elapsed.TotalSeconds, 1))s)" -ForegroundColor Cyan
|
||||
Write-Host "════════════════════════════════════════════════════════" -ForegroundColor Cyan
|
||||
|
||||
# clean up jobs
|
||||
Get-Job | Remove-Job -Force -ErrorAction SilentlyContinue
|
||||
|
||||
exit $script:failCount
|
||||
21
.github/skills/pr-fix/LICENSE.txt
vendored
21
.github/skills/pr-fix/LICENSE.txt
vendored
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
238
.github/skills/pr-fix/SKILL.md
vendored
238
.github/skills/pr-fix/SKILL.md
vendored
@@ -1,238 +0,0 @@
|
||||
---
|
||||
name: pr-fix
|
||||
description: Fix active PR review comments and resolve threads. Use when asked to fix PR comments, address review feedback, resolve review threads, implement PR fixes, or handle review iterations. Works with VS Code MCP tools to resolve GitHub threads after fixes are applied.
|
||||
license: Complete terms in LICENSE.txt
|
||||
---
|
||||
|
||||
# PR Fix Skill
|
||||
|
||||
Fix active pull request review comments and resolve threads. This skill handles the **fix** part of the PR review cycle, separate from the review itself.
|
||||
|
||||
## ⚠️ Critical Architecture
|
||||
|
||||
This skill requires **both** CLI scripts AND VS Code MCP tools:
|
||||
|
||||
| Operation | Execution Method |
|
||||
|-----------|------------------|
|
||||
| Apply code fixes | Copilot/Claude CLI via script |
|
||||
| Resolve review threads | **VS Code Agent** via `gh api graphql` |
|
||||
| Check status | Script (read-only) |
|
||||
|
||||
**WHY**: Copilot CLI's MCP is **read-only**. Only VS Code can resolve threads.
|
||||
|
||||
## Skill Contents
|
||||
|
||||
```
|
||||
.github/skills/pr-fix/
|
||||
├── SKILL.md # This file
|
||||
├── LICENSE.txt # MIT License
|
||||
├── references/
|
||||
│ ├── fix-pr-comments.prompt.md # AI prompt for fixing comments
|
||||
│ └── mcp-config.json # MCP configuration
|
||||
└── scripts/
|
||||
├── Start-PRFix.ps1 # Main fix script
|
||||
├── Start-PRFixParallel.ps1 # Parallel runner (single terminal)
|
||||
├── Resolve-PRThreads.ps1 # Resolve threads helper
|
||||
├── Get-UnresolvedThreads.ps1 # Get threads needing resolution
|
||||
└── IssueReviewLib.ps1 # Shared helpers
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
- **Code changes**: Applied in the PR's worktree
|
||||
- **Signal file**: `Generated Files/prFix/<pr>/.signal`
|
||||
|
||||
## Signal File
|
||||
|
||||
On completion, a `.signal` file is created for orchestrator coordination:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"prNumber": 45365,
|
||||
"timestamp": "2026-02-04T10:05:23Z",
|
||||
"unresolvedBefore": 3,
|
||||
"unresolvedAfter": 0
|
||||
}
|
||||
```
|
||||
|
||||
Status values: `success`, `partial` (some threads remain), `failure`
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
- Fix active review comments on a PR
|
||||
- Address reviewer feedback
|
||||
- Resolve review threads after fixing
|
||||
- Run the fix portion of review/fix loop
|
||||
- Implement changes requested in PR reviews
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- GitHub CLI (`gh`) installed and authenticated
|
||||
- Copilot CLI or Claude CLI installed
|
||||
- PowerShell 7+
|
||||
- PR has active review comments to fix
|
||||
|
||||
## Required Variables
|
||||
|
||||
| Variable | Description | Example |
|
||||
|----------|-------------|---------|
|
||||
| `{{PRNumber}}` | Pull request number to fix | `45286` |
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Check Unresolved Threads
|
||||
|
||||
```powershell
|
||||
# See what needs to be fixed
|
||||
.github/skills/pr-fix/scripts/Get-UnresolvedThreads.ps1 -PRNumber {{PRNumber}}
|
||||
```
|
||||
|
||||
### Step 2: Run Fix (CLI Script)
|
||||
|
||||
```powershell
|
||||
# Apply AI-generated fixes to address comments
|
||||
.github/skills/pr-fix/scripts/Start-PRFix.ps1 -PRNumber {{PRNumber}} -CLIType copilot -Force
|
||||
```
|
||||
|
||||
### Step 3: Resolve Threads (VS Code Agent)
|
||||
|
||||
After fixes are pushed, **you (the VS Code agent) must resolve threads**:
|
||||
|
||||
```powershell
|
||||
# Get unresolved thread IDs
|
||||
gh api graphql -f query='
|
||||
query {
|
||||
repository(owner: "microsoft", name: "PowerToys") {
|
||||
pullRequest(number: {{PRNumber}}) {
|
||||
reviewThreads(first: 50) {
|
||||
nodes { id isResolved path line }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
' --jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false)'
|
||||
```
|
||||
|
||||
```powershell
|
||||
# Resolve each thread
|
||||
gh api graphql -f query='
|
||||
mutation {
|
||||
resolveReviewThread(input: {threadId: "{{threadId}}"}) {
|
||||
thread { isResolved }
|
||||
}
|
||||
}
|
||||
'
|
||||
```
|
||||
|
||||
### Step 4: Verify All Resolved
|
||||
|
||||
```powershell
|
||||
# Confirm no unresolved threads remain
|
||||
.github/skills/pr-fix/scripts/Get-UnresolvedThreads.ps1 -PRNumber {{PRNumber}}
|
||||
```
|
||||
|
||||
## CLI Options
|
||||
|
||||
| Parameter | Description | Default |
|
||||
|-----------|-------------|---------|
|
||||
| `-PRNumber` | PR number to fix | Required |
|
||||
| `-CLIType` | AI CLI: `copilot` or `claude` | `copilot` |
|
||||
| `-Model` | Copilot model (e.g., `gpt-5.2-codex`) | (optional) |
|
||||
| `-Force` | Skip confirmation prompts | `false` |
|
||||
| `-DryRun` | Show what would be done | `false` |
|
||||
|
||||
## Review/Fix Loop Integration
|
||||
|
||||
This skill is typically used with `pr-review` in a loop:
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ pr-review │ ← Generate review, post comments
|
||||
└────────┬────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ pr-fix │ ← Fix comments, resolve threads
|
||||
└────────┬────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ Check status │ ← Any threads unresolved?
|
||||
└────────┬────────┘
|
||||
│
|
||||
┌────┴────┐
|
||||
│ YES │ NO
|
||||
▼ ▼
|
||||
(loop) ✓ Done
|
||||
```
|
||||
|
||||
## VS Code Agent Operations
|
||||
|
||||
These operations **must** be done by the VS Code agent (not scripts):
|
||||
|
||||
| Operation | Method |
|
||||
|-----------|--------|
|
||||
| Resolve thread | `gh api graphql` with `resolveReviewThread` mutation |
|
||||
| Unresolve thread | `gh api graphql` with `unresolveReviewThread` mutation |
|
||||
|
||||
### Batch Resolve All Threads
|
||||
|
||||
```powershell
|
||||
# Get all unresolved thread IDs and resolve them
|
||||
$threads = gh api graphql -f query='
|
||||
query {
|
||||
repository(owner: "microsoft", name: "PowerToys") {
|
||||
pullRequest(number: {{PRNumber}}) {
|
||||
reviewThreads(first: 100) {
|
||||
nodes { id isResolved }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
' --jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false) | .id'
|
||||
|
||||
foreach ($threadId in $threads) {
|
||||
gh api graphql -f query="mutation { resolveReviewThread(input: {threadId: `"$threadId`"}) { thread { isResolved } } }"
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Problem | Solution |
|
||||
|---------|----------|
|
||||
| "Cannot resolve thread" | Use VS Code agent, not Copilot CLI |
|
||||
| Fix not applied | Check worktree is on correct branch |
|
||||
| Thread ID not found | Re-fetch threads, ID may have changed |
|
||||
| Fix pushed but thread unresolved | Must explicitly resolve via GraphQL |
|
||||
|
||||
## Batch Processing Multiple PRs (CRITICAL)
|
||||
|
||||
**DO NOT spawn separate terminals for each PR.** Use the dedicated scripts:
|
||||
|
||||
```powershell
|
||||
# Run fixes in parallel via orchestrator (single terminal)
|
||||
.github/skills/pr-fix/scripts/Start-PRFixParallel.ps1 -PRNumbers 45256,45257,45285,45286 -CLIType copilot -MaxConcurrent 3 -Force
|
||||
|
||||
# Resolve threads (VS Code agent)
|
||||
.github/skills/pr-fix/scripts/Resolve-PRThreads.ps1 -PRNumber 45256
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Skill | Used For |
|
||||
|-------|----------|
|
||||
| `parallel-job-orchestrator` | Parallel execution of fix jobs across multiple PRs |
|
||||
|
||||
`Start-PRFixParallel.ps1` delegates all parallel execution to the shared orchestrator.
|
||||
Do NOT introduce custom `ForEach-Object -Parallel`, `Start-Job`, or `Start-Process`
|
||||
patterns — use the orchestrator instead.
|
||||
|
||||
## Related Skills
|
||||
|
||||
| Skill | Purpose |
|
||||
|-------|--------|
|
||||
| `pr-review` | Review PR, generate findings, post comments |
|
||||
| `parallel-job-orchestrator` | Shared parallel execution engine |
|
||||
| `issue-fix` | Fix issues and create PRs |
|
||||
| `issue-to-pr-cycle` | Full orchestration |
|
||||
@@ -1,70 +0,0 @@
|
||||
---
|
||||
description: 'Fix active pull request comments with scoped changes'
|
||||
name: 'fix-pr-active-comments'
|
||||
agent: 'agent'
|
||||
argument-hint: 'PR number or active PR URL'
|
||||
---
|
||||
|
||||
# Fix Active PR Comments
|
||||
|
||||
## Mission
|
||||
Resolve active pull request comments by applying only simple fixes. For complex refactors, write a plan instead of changing code.
|
||||
|
||||
## Scope & Preconditions
|
||||
- You must have an active pull request context or a provided PR number.
|
||||
- Only implement simple changes. Do not implement large refactors.
|
||||
- If required context is missing, request it and stop.
|
||||
|
||||
## Inputs
|
||||
- Required: ${input:pr_number:PR number or URL}
|
||||
- Optional: ${input:comment_scope:files or areas to focus on}
|
||||
- Optional: ${input:fixing_guidelines:additional fixing guidelines from the user}
|
||||
|
||||
## Workflow
|
||||
1. Locate all active (unresolved) PR review comments for the given PR.
|
||||
2. For each comment, classify the change scope:
|
||||
- Simple change: limited edits, localized fix, low risk, no broad redesign.
|
||||
- Large refactor: multi-file redesign, architecture change, or risky behavior change.
|
||||
3. For each large refactor request:
|
||||
- Do not modify code.
|
||||
- Write a planning document to Generated Files/prReview/${input:pr_number}/fixPlan/.
|
||||
4. For each simple change request:
|
||||
- Implement the fix with minimal edits.
|
||||
- Run quick checks if needed.
|
||||
- Commit and push the change.
|
||||
5. For comments that seem invalid, unclear, or not applicable (even if simple):
|
||||
- Do not change code.
|
||||
- Add the item to a summary table in Generated Files/prReview/${input:pr_number}/fixPlan/overview.md.
|
||||
- Consult back to the end user in a friendly, polite tone.
|
||||
6. Respond to each comment that you fixed:
|
||||
- Reply in the active conversation.
|
||||
- Use a polite or friendly tone.
|
||||
- Keep the response under 200 words.
|
||||
- Resolve the comment after replying.
|
||||
|
||||
## Output Expectations
|
||||
- Simple fixes: code changes committed and pushed.
|
||||
- Large refactors: a plan file saved to Generated Files/prReview/${input:pr_number}/fixPlan/.
|
||||
- Invalid or unclear comments: captured in Generated Files/prReview/${input:pr_number}/fixPlan/overview.md.
|
||||
- Each fixed comment has a reply under 200 words and is resolved.
|
||||
|
||||
## Plan File Template
|
||||
Use this template for each large refactor item:
|
||||
|
||||
# Fix Plan: <short title>
|
||||
|
||||
## Context
|
||||
- Comment link:
|
||||
- Impacted areas:
|
||||
|
||||
## Overview Table Template
|
||||
Use this table in Generated Files/prReview/${input:pr_number}/fixPlan/overview.md:
|
||||
|
||||
| Comment link | Summary | Reason not applied | Suggested follow-up |
|
||||
| --- | --- | --- | --- |
|
||||
| | | | |
|
||||
|
||||
## Quality Assurance
|
||||
- Verify plan file path exists.
|
||||
- Ensure no code changes were made for large refactor items.
|
||||
- Confirm replies are under 200 words and comments are resolved.
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"github-artifacts": {
|
||||
"command": "cmd",
|
||||
"args": ["/c", "for /f %i in ('git rev-parse --show-toplevel') do node %i/tools/mcp/github-artifacts/launch.js"],
|
||||
"tools": ["*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Get unresolved review threads on a PR.
|
||||
|
||||
.DESCRIPTION
|
||||
Lists all unresolved review threads with their IDs, paths, and comment bodies.
|
||||
This information is needed to resolve threads via GraphQL.
|
||||
|
||||
.PARAMETER PRNumber
|
||||
PR number to check.
|
||||
|
||||
.PARAMETER JsonOutput
|
||||
Output as JSON for programmatic use.
|
||||
|
||||
.EXAMPLE
|
||||
./Get-UnresolvedThreads.ps1 -PRNumber 45286
|
||||
|
||||
.EXAMPLE
|
||||
./Get-UnresolvedThreads.ps1 -PRNumber 45286 -JsonOutput
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[int]$PRNumber,
|
||||
|
||||
[switch]$JsonOutput
|
||||
)
|
||||
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
. (Join-Path $scriptDir 'IssueReviewLib.ps1')
|
||||
|
||||
try {
|
||||
$query = @"
|
||||
query {
|
||||
repository(owner: "microsoft", name: "PowerToys") {
|
||||
pullRequest(number: $PRNumber) {
|
||||
reviewThreads(first: 100) {
|
||||
nodes {
|
||||
id
|
||||
isResolved
|
||||
path
|
||||
line
|
||||
comments(first: 1) {
|
||||
nodes {
|
||||
body
|
||||
author { login }
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"@
|
||||
|
||||
$result = gh api graphql -f query=$query 2>$null | ConvertFrom-Json
|
||||
|
||||
if (-not $result -or -not $result.data) {
|
||||
throw "Failed to fetch PR threads"
|
||||
}
|
||||
|
||||
$threads = $result.data.repository.pullRequest.reviewThreads.nodes
|
||||
$unresolvedThreads = $threads | Where-Object { -not $_.isResolved }
|
||||
|
||||
if ($JsonOutput) {
|
||||
$unresolvedThreads | ConvertTo-Json -Depth 5
|
||||
return
|
||||
}
|
||||
|
||||
if ($unresolvedThreads.Count -eq 0) {
|
||||
Write-Host "✓ No unresolved threads on PR #$PRNumber" -ForegroundColor Green
|
||||
return
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "=== UNRESOLVED THREADS ON PR #$PRNumber ===" -ForegroundColor Cyan
|
||||
Write-Host ("-" * 80)
|
||||
|
||||
foreach ($thread in $unresolvedThreads) {
|
||||
$comment = $thread.comments.nodes[0]
|
||||
$preview = if ($comment.body.Length -gt 100) {
|
||||
$comment.body.Substring(0, 100) + "..."
|
||||
} else {
|
||||
$comment.body
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Thread ID: " -NoNewline -ForegroundColor Yellow
|
||||
Write-Host $thread.id
|
||||
Write-Host "File: " -NoNewline -ForegroundColor Gray
|
||||
Write-Host "$($thread.path):$($thread.line)"
|
||||
Write-Host "Author: " -NoNewline -ForegroundColor Gray
|
||||
Write-Host $comment.author.login
|
||||
Write-Host "Comment: " -ForegroundColor Gray
|
||||
Write-Host " $preview"
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host ("-" * 80)
|
||||
Write-Host "Total unresolved: $($unresolvedThreads.Count)" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
Write-Host "To resolve a thread:" -ForegroundColor Cyan
|
||||
Write-Host ' gh api graphql -f query=''mutation { resolveReviewThread(input: {threadId: "THREAD_ID"}) { thread { isResolved } } }'''
|
||||
|
||||
return $unresolvedThreads
|
||||
}
|
||||
catch {
|
||||
Write-Host "Error: $($_.Exception.Message)" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
18
.github/skills/pr-fix/scripts/IssueReviewLib.ps1
vendored
18
.github/skills/pr-fix/scripts/IssueReviewLib.ps1
vendored
@@ -1,18 +0,0 @@
|
||||
# IssueReviewLib.ps1 - Minimal helpers for PR review workflow
|
||||
# Part of the PowerToys GitHub Copilot/Claude Code issue review system
|
||||
# This is a trimmed version - pr-review only needs console helpers and repo root
|
||||
|
||||
#region Console Output Helpers
|
||||
function Info { param([string]$Message) Write-Host $Message -ForegroundColor Cyan }
|
||||
function Warn { param([string]$Message) Write-Host $Message -ForegroundColor Yellow }
|
||||
function Err { param([string]$Message) Write-Host $Message -ForegroundColor Red }
|
||||
function Success { param([string]$Message) Write-Host $Message -ForegroundColor Green }
|
||||
#endregion
|
||||
|
||||
#region Repository Helpers
|
||||
function Get-RepoRoot {
|
||||
$root = git rev-parse --show-toplevel 2>$null
|
||||
if (-not $root) { throw 'Not inside a git repository.' }
|
||||
return (Resolve-Path $root).Path
|
||||
}
|
||||
#endregion
|
||||
@@ -1,31 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Resolve all unresolved review threads for a PR.
|
||||
|
||||
.PARAMETER PRNumber
|
||||
PR number to resolve.
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[int]$PRNumber
|
||||
)
|
||||
|
||||
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot '..\..\..\..')
|
||||
Set-Location $repoRoot
|
||||
|
||||
$query = 'query { repository(owner:"microsoft", name:"PowerToys") { pullRequest(number:' + $PRNumber + ') { reviewThreads(first:100) { nodes { id isResolved } } } } }'
|
||||
$threads = gh api graphql -f query=$query --jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved==false) | .id'
|
||||
|
||||
foreach ($threadId in $threads) {
|
||||
$mutation = 'mutation { resolveReviewThread(input:{threadId:"' + $threadId + '"}) { thread { isResolved } } }'
|
||||
gh api graphql -f query=$mutation | Out-Null
|
||||
}
|
||||
|
||||
$threadsAfter = gh api graphql -f query=$query --jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved==false) | .id'
|
||||
|
||||
if ($threadsAfter) {
|
||||
Write-Warning "Unresolved threads remain for PR #$PRNumber"
|
||||
} else {
|
||||
Write-Host "All threads resolved for PR #$PRNumber"
|
||||
}
|
||||
349
.github/skills/pr-fix/scripts/Start-PRFix.ps1
vendored
349
.github/skills/pr-fix/scripts/Start-PRFix.ps1
vendored
@@ -1,349 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Fix active PR review comments using AI CLI.
|
||||
|
||||
.DESCRIPTION
|
||||
Kicks off Copilot/Claude CLI to address active review comments on a PR.
|
||||
Does NOT resolve threads - that must be done by VS Code agent via GraphQL.
|
||||
|
||||
.PARAMETER PRNumber
|
||||
PR number to fix.
|
||||
|
||||
.PARAMETER CLIType
|
||||
AI CLI to use: copilot or claude. Default: copilot.
|
||||
|
||||
.PARAMETER Model
|
||||
Copilot CLI model to use (e.g., gpt-5.2-codex).
|
||||
|
||||
.PARAMETER WorktreePath
|
||||
Path to the worktree containing the PR branch. Auto-detected if not specified.
|
||||
|
||||
.PARAMETER DryRun
|
||||
Show what would be done without executing.
|
||||
|
||||
.PARAMETER Force
|
||||
Skip confirmation prompts.
|
||||
|
||||
.EXAMPLE
|
||||
./Start-PRFix.ps1 -PRNumber 45286 -CLIType copilot -Force
|
||||
|
||||
.NOTES
|
||||
After this script completes, use VS Code agent to resolve threads via GraphQL.
|
||||
#>
|
||||
|
||||
# NOTE: Do NOT use [CmdletBinding()], [Parameter(Mandatory)], or [ValidateSet()]
|
||||
# here. These make the script "advanced" which propagates ErrorActionPreference
|
||||
# through PS7's plumbing and can silently crash the orchestrator's monitoring loop.
|
||||
param(
|
||||
[int]$PRNumber,
|
||||
|
||||
[string]$CLIType = 'copilot',
|
||||
|
||||
[string]$Model,
|
||||
|
||||
[string]$WorktreePath,
|
||||
|
||||
[switch]$DryRun,
|
||||
|
||||
[switch]$Force,
|
||||
|
||||
[switch]$Help
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Manual validation
|
||||
if (-not $PRNumber -or $PRNumber -eq 0) {
|
||||
Write-Error 'Start-PRFix: -PRNumber is required.'
|
||||
return
|
||||
}
|
||||
if ($CLIType -notin 'copilot', 'claude') {
|
||||
Write-Error "Start-PRFix: Invalid -CLIType '$CLIType'. Must be 'copilot' or 'claude'."
|
||||
return
|
||||
}
|
||||
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
. (Join-Path $scriptDir 'IssueReviewLib.ps1')
|
||||
|
||||
$repoRoot = Get-RepoRoot
|
||||
|
||||
# Resolve config directory name (.github or .claude) from script location
|
||||
$_cfgDir = if ($PSScriptRoot -match '[\\/](\.github|\.claude)[\\/]') { $Matches[1] } else { '.github' }
|
||||
$worktreeLib = Join-Path $repoRoot 'tools/build/WorktreeLib.ps1'
|
||||
if (Test-Path $worktreeLib) {
|
||||
. $worktreeLib
|
||||
}
|
||||
|
||||
if ($Help) {
|
||||
Get-Help $MyInvocation.MyCommand.Path -Full
|
||||
return
|
||||
}
|
||||
|
||||
function Get-PRBranch {
|
||||
param([int]$PRNumber)
|
||||
|
||||
$prInfo = gh pr view $PRNumber --json headRefName 2>$null | ConvertFrom-Json
|
||||
if ($prInfo) {
|
||||
return $prInfo.headRefName
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function Find-WorktreeForPR {
|
||||
param([int]$PRNumber)
|
||||
|
||||
$branch = Get-PRBranch -PRNumber $PRNumber
|
||||
if (-not $branch) {
|
||||
return $null
|
||||
}
|
||||
|
||||
$worktrees = Get-WorktreeEntries
|
||||
$wt = $worktrees | Where-Object { $_.Branch -eq $branch } | Select-Object -First 1
|
||||
|
||||
if ($wt) {
|
||||
return $wt.Path
|
||||
}
|
||||
|
||||
# If no dedicated worktree, check if we're on that branch in main repo
|
||||
Push-Location $repoRoot
|
||||
try {
|
||||
$currentBranch = git branch --show-current 2>$null
|
||||
if ($currentBranch -eq $branch) {
|
||||
return $repoRoot
|
||||
}
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
function Get-ActiveComments {
|
||||
param([int]$PRNumber)
|
||||
|
||||
try {
|
||||
$comments = gh api "repos/microsoft/PowerToys/pulls/$PRNumber/comments" 2>$null | ConvertFrom-Json
|
||||
# Filter to root comments (not replies)
|
||||
$rootComments = $comments | Where-Object { $null -eq $_.in_reply_to_id }
|
||||
return $rootComments
|
||||
}
|
||||
catch {
|
||||
return @()
|
||||
}
|
||||
}
|
||||
|
||||
function Get-UnresolvedThreadCount {
|
||||
param([int]$PRNumber)
|
||||
|
||||
try {
|
||||
$result = gh api graphql -f query="query { repository(owner: `"microsoft`", name: `"PowerToys`") { pullRequest(number: $PRNumber) { reviewThreads(first: 100) { nodes { isResolved } } } } }" 2>$null | ConvertFrom-Json
|
||||
$threads = $result.data.repository.pullRequest.reviewThreads.nodes
|
||||
$unresolved = $threads | Where-Object { -not $_.isResolved }
|
||||
return @($unresolved).Count
|
||||
}
|
||||
catch {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
#region Main
|
||||
try {
|
||||
Info "=" * 60
|
||||
Info "PR FIX - PR #$PRNumber"
|
||||
Info "=" * 60
|
||||
|
||||
# Get PR info
|
||||
$prInfo = gh pr view $PRNumber --json state,headRefName,url 2>$null | ConvertFrom-Json
|
||||
if (-not $prInfo) {
|
||||
throw "PR #$PRNumber not found"
|
||||
}
|
||||
|
||||
if ($prInfo.state -ne 'OPEN') {
|
||||
Warn "PR #$PRNumber is $($prInfo.state), not OPEN"
|
||||
return
|
||||
}
|
||||
|
||||
Info "PR URL: $($prInfo.url)"
|
||||
Info "Branch: $($prInfo.headRefName)"
|
||||
Info "CLI: $CLIType"
|
||||
|
||||
# Find worktree
|
||||
if (-not $WorktreePath) {
|
||||
$WorktreePath = Find-WorktreeForPR -PRNumber $PRNumber
|
||||
}
|
||||
|
||||
if (-not $WorktreePath -or -not (Test-Path $WorktreePath)) {
|
||||
Warn "No worktree found for PR #$PRNumber"
|
||||
Warn "Using main repo root. Make sure the PR branch is checked out."
|
||||
$WorktreePath = $repoRoot
|
||||
}
|
||||
|
||||
Info "Working directory: $WorktreePath"
|
||||
|
||||
# Check for active comments
|
||||
$comments = Get-ActiveComments -PRNumber $PRNumber
|
||||
$unresolvedCount = Get-UnresolvedThreadCount -PRNumber $PRNumber
|
||||
|
||||
Info ""
|
||||
Info "Active review comments: $($comments.Count)"
|
||||
Info "Unresolved threads: $unresolvedCount"
|
||||
|
||||
if ($comments.Count -eq 0 -and $unresolvedCount -eq 0) {
|
||||
Success "No active comments or unresolved threads to fix!"
|
||||
return @{ PRNumber = $PRNumber; Status = 'NothingToFix' }
|
||||
}
|
||||
|
||||
if ($DryRun) {
|
||||
Info ""
|
||||
Warn "[DRY RUN] Would run AI CLI to fix comments"
|
||||
Info "Comments to address:"
|
||||
foreach ($c in $comments | Select-Object -First 5) {
|
||||
Info " - $($c.path):$($c.line) - $($c.body.Substring(0, [Math]::Min(80, $c.body.Length)))..."
|
||||
}
|
||||
return @{ PRNumber = $PRNumber; Status = 'DryRun' }
|
||||
}
|
||||
|
||||
# Confirm
|
||||
if (-not $Force) {
|
||||
$confirm = Read-Host "Fix $($comments.Count) comments on PR #$PRNumber? (y/N)"
|
||||
if ($confirm -notmatch '^[yY]') {
|
||||
Info "Cancelled."
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
# Build prompt
|
||||
$prompt = @"
|
||||
You are fixing review comments on PR #$PRNumber.
|
||||
|
||||
Read the active review comments using GitHub tools and address each one:
|
||||
1. Fetch the PR review comments
|
||||
2. For each comment, understand what change is requested
|
||||
3. Make the code changes to address the feedback
|
||||
4. Build and verify your changes work
|
||||
|
||||
Focus on the reviewer's feedback and make targeted fixes.
|
||||
"@
|
||||
|
||||
# Ensure config dirs exist in worktree (agents, skills, instructions, prompts, top-level md)
|
||||
# These aren't on the PR branch so the CLI can't find them without this.
|
||||
if ($WorktreePath -ne $repoRoot) {
|
||||
$sourceCfg = Join-Path $repoRoot $_cfgDir
|
||||
$destCfg = Join-Path $WorktreePath $_cfgDir
|
||||
if (Test-Path $sourceCfg) {
|
||||
if (-not (Test-Path $destCfg)) {
|
||||
New-Item -ItemType Directory -Path $destCfg -Force | Out-Null
|
||||
}
|
||||
foreach ($sub in @('agents', 'skills', 'instructions', 'prompts')) {
|
||||
$src = Join-Path $sourceCfg $sub
|
||||
$dst = Join-Path $destCfg $sub
|
||||
if ((Test-Path $src) -and -not (Test-Path $dst)) {
|
||||
Copy-Item -Path $src -Destination $dst -Recurse -Force
|
||||
Info "Copied $_cfgDir/$sub to worktree"
|
||||
}
|
||||
}
|
||||
foreach ($mdFile in @('copilot-instructions.md', 'CLAUDE.md')) {
|
||||
$src = Join-Path $sourceCfg $mdFile
|
||||
$dst = Join-Path $destCfg $mdFile
|
||||
if ((Test-Path $src) -and -not (Test-Path $dst)) {
|
||||
Copy-Item -Path $src -Destination $dst -Force
|
||||
Info "Copied $_cfgDir/$mdFile to worktree"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# MCP config
|
||||
$mcpConfig = "@$_cfgDir/skills/pr-fix/references/mcp-config.json"
|
||||
|
||||
Info ""
|
||||
Info "Starting AI fix..."
|
||||
|
||||
Push-Location $WorktreePath
|
||||
try {
|
||||
switch ($CLIType) {
|
||||
'copilot' {
|
||||
$copilotArgs = @('--additional-mcp-config', $mcpConfig, '-p', $prompt, '--yolo', '--agent', 'FixPR')
|
||||
if ($Model) {
|
||||
$copilotArgs += @('--model', $Model)
|
||||
}
|
||||
$output = & copilot @copilotArgs 2>&1
|
||||
# Log output
|
||||
$logPath = Join-Path $repoRoot "Generated Files/prReview/$PRNumber"
|
||||
if (-not (Test-Path $logPath)) {
|
||||
New-Item -ItemType Directory -Path $logPath -Force | Out-Null
|
||||
}
|
||||
$output | Out-File -FilePath (Join-Path $logPath "_fix.log") -Force
|
||||
}
|
||||
'claude' {
|
||||
$output = & claude --print --dangerously-skip-permissions --agent FixPR --prompt $prompt 2>&1
|
||||
$logPath = Join-Path $repoRoot "Generated Files/prReview/$PRNumber"
|
||||
if (-not (Test-Path $logPath)) {
|
||||
New-Item -ItemType Directory -Path $logPath -Force | Out-Null
|
||||
}
|
||||
$output | Out-File -FilePath (Join-Path $logPath "_fix.log") -Force
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
# Check results
|
||||
$newUnresolvedCount = Get-UnresolvedThreadCount -PRNumber $PRNumber
|
||||
|
||||
Info ""
|
||||
Info "Fix complete."
|
||||
Info "Unresolved threads before: $unresolvedCount"
|
||||
Info "Unresolved threads after: $newUnresolvedCount"
|
||||
|
||||
if ($newUnresolvedCount -gt 0) {
|
||||
Warn ""
|
||||
Warn "⚠️ $newUnresolvedCount threads still unresolved."
|
||||
Warn "Use VS Code agent to resolve them via GraphQL:"
|
||||
Warn " gh api graphql -f query='mutation { resolveReviewThread(input: {threadId: \"THREAD_ID\"}) { thread { isResolved } } }'"
|
||||
}
|
||||
else {
|
||||
Success "✓ All threads resolved!"
|
||||
}
|
||||
|
||||
# Write signal file
|
||||
$signalDir = Join-Path $repoRoot "Generated Files/prFix/$PRNumber"
|
||||
if (-not (Test-Path $signalDir)) { New-Item -ItemType Directory -Path $signalDir -Force | Out-Null }
|
||||
@{
|
||||
status = if ($newUnresolvedCount -eq 0) { "success" } else { "partial" }
|
||||
prNumber = $PRNumber
|
||||
timestamp = (Get-Date).ToString("o")
|
||||
unresolvedBefore = $unresolvedCount
|
||||
unresolvedAfter = $newUnresolvedCount
|
||||
} | ConvertTo-Json | Set-Content "$signalDir/.signal" -Force
|
||||
|
||||
return @{
|
||||
PRNumber = $PRNumber
|
||||
Status = 'FixApplied'
|
||||
UnresolvedBefore = $unresolvedCount
|
||||
UnresolvedAfter = $newUnresolvedCount
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Err "Error: $($_.Exception.Message)"
|
||||
|
||||
# Write failure signal
|
||||
$signalDir = Join-Path $repoRoot "Generated Files/prFix/$PRNumber"
|
||||
if (-not (Test-Path $signalDir)) { New-Item -ItemType Directory -Path $signalDir -Force | Out-Null }
|
||||
@{
|
||||
status = "failure"
|
||||
prNumber = $PRNumber
|
||||
timestamp = (Get-Date).ToString("o")
|
||||
error = $_.Exception.Message
|
||||
} | ConvertTo-Json | Set-Content "$signalDir/.signal" -Force
|
||||
|
||||
return @{
|
||||
PRNumber = $PRNumber
|
||||
Status = 'FixFailed'
|
||||
Error = $_.Exception.Message
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -1,165 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Run pr-fix in parallel via the parallel-job-orchestrator skill.
|
||||
|
||||
.DESCRIPTION
|
||||
Builds one job definition per PR and delegates to the shared
|
||||
parallel-job-orchestrator. Each job invokes Start-PRFix.ps1 for a
|
||||
single PR in its worktree.
|
||||
|
||||
DO NOT add [CmdletBinding()], [Parameter(Mandatory)], or [ValidateSet()]
|
||||
here — those attributes make the script "advanced" which propagates
|
||||
ErrorActionPreference and can crash the orchestrator's monitoring loop.
|
||||
|
||||
.PARAMETER PRNumbers
|
||||
PR numbers to fix (required).
|
||||
|
||||
.PARAMETER MaxConcurrent
|
||||
Maximum parallel fix jobs. Default: 3.
|
||||
|
||||
.PARAMETER CLIType
|
||||
AI CLI type: copilot or claude. Default: copilot.
|
||||
|
||||
.PARAMETER Model
|
||||
Copilot CLI model to use (e.g., gpt-5.2-codex).
|
||||
|
||||
.PARAMETER InactivityTimeoutSeconds
|
||||
Kill job if log doesn't grow for this many seconds. Default: 120.
|
||||
|
||||
.PARAMETER MaxRetryCount
|
||||
Retry attempts after inactivity kill. Default: 2.
|
||||
|
||||
.PARAMETER Force
|
||||
Skip confirmation prompts in Start-PRFix.ps1.
|
||||
|
||||
.EXAMPLE
|
||||
./Start-PRFixParallel.ps1 -PRNumbers 45286, 45287, 45288 -MaxConcurrent 4
|
||||
#>
|
||||
param(
|
||||
[int[]]$PRNumbers,
|
||||
|
||||
[int]$MaxConcurrent = 3,
|
||||
|
||||
[string]$CLIType = 'copilot',
|
||||
|
||||
[string]$Model,
|
||||
|
||||
[int]$InactivityTimeoutSeconds = 120,
|
||||
|
||||
[int]$MaxRetryCount = 2,
|
||||
|
||||
[switch]$Force
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Manual validation
|
||||
if (-not $PRNumbers -or $PRNumbers.Count -eq 0) {
|
||||
Write-Error 'Start-PRFixParallel: -PRNumbers is required.'
|
||||
return
|
||||
}
|
||||
if ($CLIType -notin 'copilot', 'claude') {
|
||||
Write-Error "Start-PRFixParallel: Invalid -CLIType '$CLIType'. Must be 'copilot' or 'claude'."
|
||||
return
|
||||
}
|
||||
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$repoRoot = Resolve-Path (Join-Path $scriptDir '..\..\..\..')
|
||||
$fixScript = Join-Path $scriptDir 'Start-PRFix.ps1'
|
||||
$orchPath = Join-Path $scriptDir '..\..\parallel-job-orchestrator\scripts\Invoke-SimpleJobOrchestrator.ps1'
|
||||
|
||||
if (-not (Test-Path $fixScript)) {
|
||||
Write-Error "Start-PRFix.ps1 not found: $fixScript"
|
||||
return
|
||||
}
|
||||
if (-not (Test-Path $orchPath)) {
|
||||
Write-Error "Orchestrator not found: $orchPath"
|
||||
return
|
||||
}
|
||||
|
||||
# Output root for logs
|
||||
$outputRoot = Join-Path $repoRoot 'Generated Files' 'prFix'
|
||||
if (-not (Test-Path $outputRoot)) {
|
||||
New-Item -ItemType Directory -Path $outputRoot -Force | Out-Null
|
||||
}
|
||||
|
||||
# Build job definitions
|
||||
$jobDefs = @(foreach ($pr in $PRNumbers) {
|
||||
# Resolve worktree for this PR
|
||||
$branch = $null
|
||||
try { $branch = (gh pr view $pr --json headRefName -q .headRefName 2>$null) } catch { }
|
||||
|
||||
$worktree = $null
|
||||
if ($branch) {
|
||||
$wtLine = git worktree list 2>$null | Select-String $branch | Select-Object -First 1
|
||||
if ($wtLine) { $worktree = ($wtLine -split '\s+')[0] }
|
||||
}
|
||||
|
||||
if (-not $worktree) {
|
||||
Write-Host "[pr-$pr] No worktree found for branch '$branch' — using repo root" -ForegroundColor Yellow
|
||||
$worktree = $repoRoot
|
||||
}
|
||||
|
||||
$prOutputDir = Join-Path $outputRoot "$pr"
|
||||
New-Item -ItemType Directory -Path $prOutputDir -Force | Out-Null
|
||||
$logFile = Join-Path $prOutputDir "_fix.log"
|
||||
|
||||
# Build the command arguments for Start-PRFix.ps1
|
||||
$fixArgs = @(
|
||||
'-File', $fixScript,
|
||||
'-PRNumber', $pr,
|
||||
'-CLIType', $CLIType,
|
||||
'-WorktreePath', $worktree,
|
||||
'-Force'
|
||||
)
|
||||
if ($Model) { $fixArgs += @('-Model', $Model) }
|
||||
|
||||
@{
|
||||
Label = "fix-pr-$pr"
|
||||
ExecutionParameters = @{
|
||||
JobName = "fix-pr-$pr"
|
||||
Command = 'pwsh'
|
||||
Arguments = $fixArgs
|
||||
WorkingDir = [string]$worktree
|
||||
OutputDir = $prOutputDir
|
||||
LogPath = $logFile
|
||||
}
|
||||
MonitorFiles = @($logFile)
|
||||
CleanupTask = $null
|
||||
}
|
||||
})
|
||||
|
||||
Write-Host "`nBuilt $($jobDefs.Count) fix job(s):" -ForegroundColor Cyan
|
||||
$jobDefs | ForEach-Object { Write-Host " $($_.Label)" -ForegroundColor Gray }
|
||||
|
||||
# Run via orchestrator
|
||||
$savedEAP = $ErrorActionPreference
|
||||
$ErrorActionPreference = 'Continue'
|
||||
|
||||
$results = & $orchPath `
|
||||
-JobDefinitions $jobDefs `
|
||||
-MaxConcurrent $MaxConcurrent `
|
||||
-InactivityTimeoutSeconds $InactivityTimeoutSeconds `
|
||||
-MaxRetryCount $MaxRetryCount `
|
||||
-PollIntervalSeconds 5 `
|
||||
-LogDir $outputRoot
|
||||
|
||||
$ErrorActionPreference = $savedEAP
|
||||
|
||||
# Summary
|
||||
$succeeded = @($results | Where-Object { $_.Status -eq 'Completed' })
|
||||
$failed = @($results | Where-Object { $_.Status -ne 'Completed' })
|
||||
|
||||
Write-Host "`n$("=" * 60)" -ForegroundColor Cyan
|
||||
Write-Host "PR FIX PARALLEL COMPLETE" -ForegroundColor Cyan
|
||||
Write-Host ("=" * 60) -ForegroundColor Cyan
|
||||
Write-Host "Total: $($results.Count)"
|
||||
Write-Host "Succeeded: $($succeeded.Count)" -ForegroundColor Green
|
||||
if ($failed.Count -gt 0) {
|
||||
Write-Host "Failed: $($failed.Count)" -ForegroundColor Red
|
||||
foreach ($r in $failed) { Write-Host " $($r.Label) — $($r.Status)" -ForegroundColor Red }
|
||||
}
|
||||
|
||||
$results | Format-Table Label, Status, JobState, ExitCode, RetryCount -AutoSize
|
||||
|
||||
return $results
|
||||
21
.github/skills/pr-review/LICENSE.txt
vendored
21
.github/skills/pr-review/LICENSE.txt
vendored
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
114
.github/skills/pr-review/SKILL.md
vendored
114
.github/skills/pr-review/SKILL.md
vendored
@@ -1,114 +0,0 @@
|
||||
---
|
||||
name: pr-review
|
||||
description: Comprehensive pull request review with multi-step analysis and comment posting. Use when asked to review a PR, analyze pull request changes, check PR for issues, post review comments, validate PR quality, run code review on a PR, or audit pull request. Generates 13 review step files covering functionality, security, performance, accessibility, and more. For FIXING PR comments, use the pr-fix skill instead.
|
||||
license: Complete terms in LICENSE.txt
|
||||
---
|
||||
|
||||
# PR Review Skill
|
||||
|
||||
**Review** PRs only. To **fix** review comments, use `pr-fix`.
|
||||
|
||||
## What to Do
|
||||
|
||||
Run the review script with the PR number(s):
|
||||
|
||||
```powershell
|
||||
.github/skills/pr-review/scripts/Start-PRReviewWorkflow.ps1 -PRNumbers <N>
|
||||
```
|
||||
|
||||
The script spawns Copilot CLI, which follows [review-pr.prompt.md](./references/review-pr.prompt.md) to execute 13 review steps and write results to `Generated Files/prReview/<N>/`.
|
||||
|
||||
### Options
|
||||
|
||||
| Parameter | Description | Default |
|
||||
|-----------|-------------|---------|
|
||||
| `-PRNumbers` | PR number(s) **(required)** | — |
|
||||
| `-CLIType` | `copilot` or `claude` | `copilot` |
|
||||
| `-Model` | Model override | (default) |
|
||||
| `-MinSeverity` | Min severity to post: `high` / `medium` / `low` / `info` | `medium` |
|
||||
| `-MaxConcurrent` | Max parallel review jobs (via orchestrator) | `4` |
|
||||
| `-InactivityTimeoutSeconds` | Kill CLI if log doesn't grow | `60` |
|
||||
| `-MaxRetryCount` | Retry attempts after inactivity kill | `3` |
|
||||
| `-OutputRoot` | Review output root folder | `Generated Files/prReview` |
|
||||
| `-LogPath` | Workflow log file path | `Start-PRReviewWorkflow.log` |
|
||||
| `-Force` | Re-review PRs that already have output | `false` |
|
||||
| `-DryRun` | Preview without executing | `false` |
|
||||
|
||||
Completed reviews are auto-skipped. Use `-Force` to redo.
|
||||
|
||||
### If You ARE the Reviewer
|
||||
|
||||
When running inside Copilot CLI (i.e. you were spawned by the script), follow [review-pr.prompt.md](./references/review-pr.prompt.md) directly. It tells you:
|
||||
|
||||
1. Fetch PR data with `gh`
|
||||
2. Execute each step by loading its prompt file on-demand
|
||||
3. Write each step's output to `Generated Files/prReview/<N>/XX-name.md`
|
||||
4. Update `.signal` after every step
|
||||
5. Generate `00-OVERVIEW.md` after all steps
|
||||
|
||||
Each step prompt also has `## External references (MUST research)` — fetch those URLs and include a `## References consulted` section citing specific violation IDs (WCAG 1.4.3, OWASP A03, etc.).
|
||||
|
||||
### Step Prompts (loaded on-demand)
|
||||
|
||||
| Step | Prompt | Focus |
|
||||
|------|--------|-------|
|
||||
| 01 | [Functionality](./references/01-functionality.prompt.md) | Correctness, edge cases |
|
||||
| 02 | [Compatibility](./references/02-compatibility.prompt.md) | Breaking changes, versioning |
|
||||
| 03 | [Performance](./references/03-performance.prompt.md) | Perf implications, async |
|
||||
| 04 | [Accessibility](./references/04-accessibility.prompt.md) | WCAG 2.1, a11y |
|
||||
| 05 | [Security](./references/05-security.prompt.md) | OWASP, CWE, SDL |
|
||||
| 06 | [Localization](./references/06-localization.prompt.md) | L10n readiness |
|
||||
| 07 | [Globalization](./references/07-globalization.prompt.md) | BiDi, ICU, date/time |
|
||||
| 08 | [Extensibility](./references/08-extensibility.prompt.md) | Plugin API, SemVer |
|
||||
| 09 | [SOLID Design](./references/09-solid-design.prompt.md) | Design principles |
|
||||
| 10 | [Repo Patterns](./references/10-repo-patterns.prompt.md) | PowerToys conventions |
|
||||
| 11 | [Docs & Automation](./references/11-docs-automation.prompt.md) | Documentation |
|
||||
| 12 | [Code Comments](./references/12-code-comments.prompt.md) | Comment quality |
|
||||
| 13 | [Copilot Guidance](./references/13-copilot-guidance.prompt.md) | Agent/prompt files |
|
||||
|
||||
## Scripts
|
||||
|
||||
| Script | Purpose |
|
||||
|--------|---------|
|
||||
| [Start-PRReviewWorkflow.ps1](./scripts/Start-PRReviewWorkflow.ps1) | Orchestrator — run this |
|
||||
| [Post-ReviewComments.ps1](./scripts/Post-ReviewComments.ps1) | Post comments to GitHub |
|
||||
| [Get-GitHubPrFilePatch.ps1](./scripts/Get-GitHubPrFilePatch.ps1) | Fetch PR file diffs |
|
||||
| [Get-GitHubRawFile.ps1](./scripts/Get-GitHubRawFile.ps1) | Download repo files at a ref |
|
||||
| [Get-PrIncrementalChanges.ps1](./scripts/Get-PrIncrementalChanges.ps1) | Detect changes since last review |
|
||||
| [Test-IncrementalReview.ps1](./scripts/Test-IncrementalReview.ps1) | Preview incremental detection |
|
||||
|
||||
## Execution & Monitoring Rules
|
||||
|
||||
Batch reviews take **5–30 minutes** depending on PR count and complexity. The agent MUST:
|
||||
|
||||
1. **Launch as a detached process** for batch runs (>2 PRs) — VS Code terminal idle detection kills background processes. Use `Start-Process -WindowStyle Hidden` with `Tee-Object` to a log file.
|
||||
2. **Poll the orchestrator log every 60–120 seconds** until all jobs report `Completed`, `Failed`, or `Abandoned`.
|
||||
3. **Do NOT exit or ask the user to check back** — keep monitoring until the orchestrator finishes.
|
||||
4. **On process death**, check the orchestrator log, clean up partial output, and relaunch automatically.
|
||||
5. **Report final results** with a table showing per-PR status, exit codes, and retry counts.
|
||||
|
||||
## Post-Execution Review
|
||||
|
||||
After each run, quickly validate quality and update guidance when needed:
|
||||
|
||||
1. Confirm outputs exist under the configured `-OutputRoot` for each PR.
|
||||
2. Spot-check `00-OVERVIEW.md` and 2-3 step files for correctness and completeness.
|
||||
3. If repeated gaps are found, refine the relevant prompt in [references](./references).
|
||||
4. If behavior changed, update this file’s Options/Workflow docs in the same change.
|
||||
5. Record concrete examples of failures to prevent repeating ambiguous guidance.
|
||||
|
||||
## Dependencies
|
||||
|
||||
This skill depends on the **parallel-job-orchestrator** skill for batch execution.
|
||||
The runner script (`Invoke-PRReviewSimpleRunner.ps1`) builds job definitions and
|
||||
delegates to `parallel-job-orchestrator/scripts/Invoke-SimpleJobOrchestrator.ps1`
|
||||
for queuing, monitoring, retry, and cleanup. Do NOT use `Start-Job`,
|
||||
`ForEach-Object -Parallel`, or `Start-Process` directly.
|
||||
|
||||
## Related Skills
|
||||
|
||||
| Skill | Purpose |
|
||||
|-------|---------|
|
||||
| `parallel-job-orchestrator` | Parallel execution engine (REQUIRED for batch runs) |
|
||||
| `pr-fix` | Fix review comments after this skill identifies issues |
|
||||
| `issue-to-pr-cycle` | Full orchestration (review → fix loop) |
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user