Compare commits

..

306 Commits

Author SHA1 Message Date
donlaci
d34d4e2d54 spell checker 2024-08-23 14:47:43 +02:00
donlaci
8588dff066 creating temporal workspace for launch. 2024-08-23 14:31:23 +02:00
Seraphima
cbf6624ddd Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-08-22 20:11:33 +02:00
Seraphima
010fccd12a fix launch and edit: save new project 2024-08-22 20:11:18 +02:00
Seraphima
b68b7724c9 fix launch and edit for the new project 2024-08-22 19:02:38 +02:00
Niels Laute
ab47d91b5f New icon 2024-08-22 18:44:34 +02:00
Seraphima
f6282773c1 logs 2024-08-22 16:43:19 +02:00
Seraphima
868ba040d8 remove unused argument 2024-08-22 16:40:02 +02:00
Seraphima
179b4ed197 comment out unused 2024-08-22 16:39:05 +02:00
Seraphima
dc5bc12896 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-08-22 16:37:41 +02:00
Seraphima
558ea132d1 get process path without waiting 2024-08-22 16:37:33 +02:00
Niels Laute
919fe830a2 Sentence case labels 2024-08-22 16:26:41 +02:00
Seraphima
f665966db6 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-08-22 15:07:24 +02:00
donlaci
40a2510c52 Removing old shortcut on workspace renaming 2024-08-22 15:05:05 +02:00
Seraphima
cec9a73a6b fix terminating UI 2024-08-22 14:41:42 +02:00
Seraphima
02f792b13e Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-08-22 13:32:26 +02:00
Seraphima
b7743d28b5 reordered launching 2024-08-22 13:32:17 +02:00
donlaci
f180d51367 Adding cancellation to launcher UI. 2024-08-22 10:36:50 +02:00
donlaci
8911a6eb63 Delete icon on workspace removal 2024-08-22 09:58:45 +02:00
Stefan Markovic
c38412c7cb Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-08-21 17:23:37 +02:00
Stefan Markovic
3bc0a518fc BugReportTool - omit cmd arg data 2024-08-21 17:20:53 +02:00
Seraphima
e49c1e1d60 UI closing 2024-08-21 16:57:44 +02:00
Seraphima
fcf5c5c05c clean up 2024-08-21 11:34:29 +02:00
Seraphima
56d87133af fix topmost LauncherUI 2024-08-21 11:34:26 +02:00
donlaci
2093dd4cde [Workspaces] Close launcher if there are failed launches. Plus adding new spinner gif 2024-08-21 11:13:22 +02:00
Seraphima
7beae3e470 fix xaml formatting 2024-08-20 22:06:49 +02:00
Seraphima
34f4514ebb fixed module order in settings 2024-08-20 21:39:27 +02:00
Seraphima
62d7b40ab0 launching with AppUserModel.ID 2024-08-20 21:16:14 +02:00
Seraphima
cd0b8aa6d9 moved AppUtils 2024-08-20 19:52:09 +02:00
Seraphima
7b23e5ba99 update OOBE image with current module name 2024-08-19 18:52:29 +02:00
Seraphima
d3faa86600 address PR comment: rename oobe view 2024-08-19 18:09:20 +02:00
Seraphima
2bc3d1228d address PR comment: uncomment gpo in settings 2024-08-19 18:05:10 +02:00
Seraphima
169c231509 address PR comments: updated projects 2024-08-19 18:02:03 +02:00
Seraphima
690dce5482 addressPR comment: use BringToForeground 2024-08-19 15:10:25 +02:00
Seraphima
a20fd15800 added supported version definition 2024-08-19 14:47:04 +02:00
Seraphima
8c30da743c address PR comment: changed version for workspaces and revision 2024-08-19 14:31:54 +02:00
Seraphima
601eab5c88 address PR comment: rename function 2024-08-19 14:26:23 +02:00
Seraphima
e23cf3c914 indentation 2024-08-19 14:19:55 +02:00
Seraphima
e17747cc1e address PR comment: fix isdigit 2024-08-19 14:16:56 +02:00
Stefan Markovic
817bec9c4c Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-08-18 14:36:24 +02:00
Stefan Markovic
ca49cab49b build fix - align CppWinRT version 2024-08-18 14:35:50 +02:00
Davide Giacometti
ff225044f9 [Run][PowerToys] Fix Workspaces utility (#34336)
polished workspaces utility
2024-08-18 14:24:36 +02:00
Stefan Markovic
aee1eb82a2 more .sln cleanup 2024-08-18 12:26:41 +02:00
Stefan Markovic
1f58a9e8c9 Add missing method defition to interop::Constants idl
Remove Any CPU config
2024-08-18 11:44:53 +02:00
Stefan Markovic
114ddbeeba xaml formatting 2024-08-17 10:15:32 +02:00
Seraphima
e12bf70081 added launch editor event constant 2024-08-16 18:49:48 +02:00
Seraphima
24533e4344 fix custom actions build 2024-08-16 18:49:26 +02:00
Seraphima
fa50208973 Merge remote-tracking branch 'microsoft/main' into dev/feature/projects 2024-08-16 18:09:44 +02:00
Seraphima
2d4d49f783 rename: module interface 2024-08-16 18:03:46 +02:00
Seraphima
af85be3b90 rename: json 2024-08-16 17:56:53 +02:00
Seraphima
33442e8853 fix empty file crash 2024-08-16 17:54:06 +02:00
Seraphima
ec049aa27b rename: editor resources 2024-08-16 17:53:58 +02:00
Seraphima
71d40b96cd rename: launcher 2024-08-16 17:36:27 +02:00
Seraphima
f6afee9012 rename: common files 2024-08-16 17:27:32 +02:00
Seraphima
d8b4798476 rename: editor 2024-08-16 17:27:12 +02:00
Seraphima
344506b1db rename: snapshot tool 2024-08-16 17:10:40 +02:00
Seraphima
23b6a4bedb rename: icon 2024-08-16 16:59:28 +02:00
Seraphima
c28afa1fc8 rename: module interface 2024-08-16 16:58:22 +02:00
Seraphima
2d812fa6d8 rename: fz 2024-08-16 16:42:31 +02:00
Seraphima
dc39ec771e rename: pt run 2024-08-16 16:40:14 +02:00
Seraphima
a4e07a7b5d rename: other 2024-08-16 16:39:39 +02:00
Seraphima
4825c62c50 rename: launcher UI 2024-08-16 15:55:15 +02:00
Seraphima
74c891b0f3 rename: settings 2024-08-16 15:32:16 +02:00
Seraphima
d1266cf080 rename: gpo 2024-08-16 15:07:07 +02:00
Seraphima
26119ea0e0 rename ProjectsLib -> WorkspacesLib 2024-08-16 14:18:51 +02:00
Seraphima
5420beb700 rename: workspaces data 2024-08-16 14:06:04 +02:00
Seraphima
b72ec989fa rename: telemetry namespace 2024-08-16 14:00:30 +02:00
Seraphima
8675a93f69 rename: common data structures 2024-08-16 13:56:35 +02:00
Seraphima
877d5c0506 rename: files and folders 2024-08-16 13:51:25 +02:00
Seraphima
6820e0cd80 rename: window property 2024-08-16 12:06:00 +02:00
Seraphima
98eaffc475 remove outdated 2024-08-16 11:57:17 +02:00
Seraphima
bc86e4fb04 rename: folders 2024-08-16 11:57:00 +02:00
Seraphima
95d9521cce resources 2024-08-16 11:23:05 +02:00
Seraphima
19c660a8ff MonitorRect comparison 2024-08-16 11:22:56 +02:00
Seraphima
8bd74accc0 telemetry: launch event 2024-08-16 11:22:43 +02:00
Seraphima
443f709899 replaced tuple with struct 2024-08-16 11:20:26 +02:00
Seraphima
86b1bff927 parse invoke point 2024-08-16 11:17:43 +02:00
Seraphima
00aa8b4b88 added utils 2024-08-16 11:15:58 +02:00
Seraphima
6db620c79f added invoke point to launcher args 2024-08-16 10:57:23 +02:00
Seraphima
b72e6bb7d6 updated Edit telemetry event 2024-08-15 20:48:42 +02:00
Seraphima
b67b7a4ee7 telemetry: delete event 2024-08-15 15:43:52 +02:00
Seraphima
65be209c2d fix muted Launch as admin checkbox 2024-08-15 15:37:54 +02:00
Seraphima
3b7f7f6851 telemetry: edit event 2024-08-15 15:31:35 +02:00
Seraphima
f10610e1e1 WindowPosition comparison 2024-08-15 13:50:03 +02:00
Seraphima
903c64b29a telemetry: create event 2024-08-15 13:32:18 +02:00
Seraphima
13e8743802 removed unused sln 2024-08-12 19:40:05 +02:00
Seraphima
05ddd019af consistent file name 2024-08-12 19:39:38 +02:00
donlaci
55d8631650 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-08-08 16:00:17 +02:00
donlaci
666d0c2840 opening apps in minimized state which are placed on a monitor, which is not found at the moment of launching 2024-08-08 15:47:44 +02:00
Jaime Bernardo
ed18f10fa9 Add ProjectsLauncherUI to signing 2024-08-06 22:34:59 +01:00
donlaci
ef511d6318 spell checker 2024-08-06 17:13:20 +02:00
donlaci
f80eaf7b23 commenting out feature "move apps if exist" 2024-08-06 14:44:33 +02:00
donlaci
ad88f71398 setting window size according to the screen (making it bigger) 2024-08-06 14:32:21 +02:00
donlaci
26b7a48278 Re-ordering modules after Renaming Projects + spell checker 2024-08-06 12:32:01 +02:00
donlaci
b407bf0f2b Renaming Projects to App Layouts. Replacing only string values, not the variable names 2024-08-06 12:16:21 +02:00
donlaci
82cfa4cbfe spell checker 2024-08-05 14:38:49 +02:00
donlaci
ac5d9dbef2 Adding new app which is launched when launching a project. It shows the status of the launch process 2024-08-05 14:02:32 +02:00
donlaci
2af86355ac Adding launch button to projects settings, dashboard and flyout 2024-07-30 13:03:52 +02:00
seraphima
e16f3ab4e5 fix default shortcut 2024-07-25 15:26:07 +02:00
seraphima
6686c5b5f5 launcher error messages 2024-07-25 15:17:02 +02:00
seraphima
f922f56890 show screen numbers instead of monitor names 2024-07-25 12:10:03 +02:00
seraphima
193581a9ac clean up 2024-07-25 12:01:56 +02:00
seraphima
78c3f95179 project launch on "launch and edit" 2024-07-25 11:57:40 +02:00
seraphima
0212892c94 fix corrupted data: project id and monitor id 2024-07-25 11:57:12 +02:00
seraphima
50792b9520 use common theme 2024-07-25 11:51:45 +02:00
seraphima
885c4ffd77 handle powertoys settings 2024-07-24 14:39:47 +02:00
seraphima
d3c9fe8ea5 keep opened windows at the same positions 2024-07-23 23:39:37 +02:00
seraphima
475a059ef6 move existing windows 2024-07-23 22:40:35 +02:00
seraphima
39086f702e restart unelevated 2024-07-22 20:37:49 +02:00
seraphima
ed98a94d2a notification about elevated apps 2024-07-20 12:57:12 +02:00
donlaci
04ff5373d9 add move apps checkbox 2024-07-19 14:00:01 +02:00
seraphima
3fb51eea9e Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-07-18 17:51:14 +02:00
seraphima
a208742012 telemetry 2024-07-18 17:50:00 +02:00
seraphima
3320dc2d0d moved projects data parsing 2024-07-18 17:11:51 +02:00
donlaci
4c12db9fa6 minor string correction 2024-07-18 14:54:56 +02:00
donlaci
abc5a80a1e Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-07-18 14:47:37 +02:00
donlaci
548bfdfcee Implementing store of setting "order by". 2024-07-18 14:46:21 +02:00
seraphima
ea6f5c8d88 projects module interface telemetry 2024-07-18 14:28:50 +02:00
seraphima
93c81bb93e Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-07-18 13:05:49 +02:00
seraphima
81fb58ed20 can-launch-elevated check 2024-07-18 13:00:31 +02:00
donlaci
23aa46a4a5 enhancing shortcut handling 2024-07-18 12:50:38 +02:00
donlaci
ae7eb60b0c minor fix 2024-07-17 21:12:09 +02:00
donlaci
6c09b251b5 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-07-17 18:48:44 +02:00
donlaci
e5c8305c5c minor fixes 2024-07-17 18:48:13 +02:00
seraphima
3c422438e8 launch elevated apps 2024-07-17 17:32:14 +02:00
seraphima
82e0749779 change scroll speed to 15 2024-07-17 17:08:55 +02:00
seraphima
4fc83786e3 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-07-17 16:41:49 +02:00
donlaci
513dd0cbf4 fixing scroll speed issue 2024-07-17 16:32:48 +02:00
seraphima
672206d17b Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-07-17 15:11:43 +02:00
donlaci
9f93e238c3 Fix for packaged app's icons 2024-07-17 14:58:19 +02:00
seraphima
158f4ee222 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-07-17 14:31:09 +02:00
donlaci
5aa4580bdb fixing: update of listed projects on the main page after hitting save in editor 2024-07-17 13:32:40 +02:00
donlaci
dcc0634fb1 Implementing Launch and Edit feature 2024-07-16 09:37:40 +02:00
seraphima
d218e36a77 get app elevated property 2024-07-16 07:17:50 +02:00
seraphima
89d6aed118 Revert "Hide Admin checkbox"
This reverts commit 3036df9d7f.
2024-07-15 20:20:26 +02:00
seraphima
a7e5c0330b spell-check 2024-07-15 16:47:39 +02:00
seraphima
b5251c27e9 Merge branches 'dev/feature/projects' and 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-07-15 16:44:56 +02:00
seraphima
16a71e2acd hide WIP 2024-07-15 16:44:52 +02:00
donlaci
3036df9d7f Hide Admin checkbox 2024-07-15 16:37:00 +02:00
seraphima
12308babc7 Merge remote-tracking branch 'microsoft/main' into dev/feature/projects 2024-07-15 15:43:21 +02:00
seraphima
5576c7b23f clean up 2024-07-13 21:07:37 +02:00
seraphima
8620d80047 launcher project filters 2024-07-11 14:01:15 +02:00
seraphima
be202df44b Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-07-11 13:35:41 +02:00
donlaci
3fa5aae70c modifying colors 2024-07-11 13:25:47 +02:00
donlaci
dae91d2874 Implementing major new features: remove button, position manipulation, arguments, admin, minimized, maximized 2024-07-11 12:45:19 +02:00
seraphima
2e2e47a039 FZ: ignore projects launched windows 2024-07-10 19:03:36 +02:00
seraphima
06ecbd58b8 set window property after launching 2024-07-10 19:02:52 +02:00
seraphima
fc8918e538 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-07-09 20:19:48 +02:00
seraphima
f93aa42b19 minimized settings snap fix 2024-07-09 20:19:06 +02:00
seraphima
cbedf45796 get command line args in the snapshot 2024-07-09 20:18:42 +02:00
seraphima
7b8e229544 remove unused from the project 2024-07-09 20:15:57 +02:00
donlaci
d7467e24ae Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-07-09 15:22:57 +02:00
donlaci
c493d27dd3 Remove checkboxes, delete feature 2024-07-09 15:22:10 +02:00
seraphima
b48c8aa80b spell-check 2024-07-09 11:35:32 +02:00
seraphima
deb1680f91 changed project creation save-cancel handles
https://github.com/JaneaSystems/PowerToys-DevProjects/issues/14
2024-07-04 16:01:07 +02:00
seraphima
647ea2db1e changed project data
https://github.com/JaneaSystems/PowerToys-DevProjects/issues/14
2024-07-04 16:00:24 +02:00
seraphima
6516af8a7a change snapshot project saving
https://github.com/JaneaSystems/PowerToys-DevProjects/issues/14
2024-07-04 15:58:14 +02:00
seraphima
d0775dac28 clean up 2024-07-04 12:25:22 +02:00
seraphima
9906141322 launcher resources 2024-07-04 12:24:47 +02:00
seraphima
353aeb268b localizable default project prefix 2024-07-04 12:01:00 +02:00
seraphima
83ee449b1b projects common lib 2024-07-04 11:42:37 +02:00
seraphima
9a3519cd0a refactoring 2024-07-03 14:50:39 +02:00
seraphima
54ed4834a8 clean up 2024-07-02 15:48:06 +02:00
seraphima
c00ed8f996 changed window filter
https://github.com/JaneaSystems/PowerToys-DevProjects/issues/2
2024-07-02 15:47:24 +02:00
seraphima
db8935dca3 refactoring: added utils 2024-07-02 14:26:47 +02:00
seraphima
10a3f1162f get dpi unaware screen bounds 2024-07-01 19:53:26 +02:00
donlaci
fd5e4590d7 Minor fixes in the capture dialog 2024-06-29 11:37:42 +02:00
donlaci
907a6f5ec9 spell checker 2024-06-28 18:07:37 +02:00
donlaci
3ea27571f3 spell checker 2024-06-28 18:03:02 +02:00
donlaci
ed423d8264 Adding helper class for getting the right bounds for screens 2024-06-28 17:58:28 +02:00
donlaci
9733c1035b Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-28 16:15:48 +02:00
donlaci
477094057d Fixing bug: re-draw the preview on app deletion in the editor 2024-06-28 16:15:06 +02:00
seraphima
1912f1c533 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-28 16:12:08 +02:00
donlaci
550f7ec08d modifying highlight for minimized apps 2024-06-28 16:11:10 +02:00
seraphima
aae69ab125 mutex fix 2024-06-28 15:41:26 +02:00
seraphima
8344060e5e Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-28 15:40:57 +02:00
donlaci
54a8f47df7 minor bugfix 2024-06-28 15:25:04 +02:00
donlaci
d9e92eca01 Add intermediate step to project creation 2024-06-28 15:00:48 +02:00
seraphima
2643f00fa1 exclude help windows
https://github.com/JaneaSystems/PowerToys-DevProjects/issues/49
2024-06-28 14:50:10 +02:00
seraphima
8f076cb5da Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-28 14:49:30 +02:00
seraphima
9b8d86858f logs 2024-06-28 14:49:20 +02:00
donlaci
c94c0431f4 spell checker 2024-06-28 13:07:30 +02:00
donlaci
64c5439b60 modifying highlight in preview 2024-06-28 13:02:32 +02:00
donlaci
e0bdba9772 Add Checkbox for per monitor selection 2024-06-27 20:10:44 +02:00
donlaci
c4a93d2482 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-27 18:59:12 +02:00
donlaci
4960af8a13 [Projects] Add Select all checkbox, Delete selected button 2024-06-27 18:58:16 +02:00
seraphima
a6b223083c projects editor single instance 2024-06-27 14:02:49 +02:00
seraphima
dc9af4bc30 update packaged apps path 2024-06-26 17:40:57 +02:00
seraphima
18e1d851fe spell-check 2024-06-26 16:20:56 +02:00
seraphima
663b0281c2 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-26 15:56:37 +02:00
seraphima
3c694e428c launch packaged apps using names when possible 2024-06-26 15:56:30 +02:00
Jaime Bernardo
98ef8c4e70 Fix ARM64 CI build 2024-06-26 11:21:13 +01:00
seraphima
c89b4bb9fd fix launching with command line args 2024-06-24 16:34:17 +02:00
donlaci
17a607fda0 [Projects] fix boundary calculation, use DPI aware values 2024-06-24 10:38:08 +02:00
donlaci
371a05ac5e [Projects] Re-implementing preview drawing - one common image 2024-06-24 09:34:47 +02:00
seraphima
65e75d1c42 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-23 23:15:32 +02:00
seraphima
15ae5a03dc app placement 2024-06-23 23:15:26 +02:00
seraphima
f39e4bb058 set awareness 2024-06-23 23:15:06 +02:00
seraphima
0679664d4c unsigned monitor number 2024-06-23 23:14:57 +02:00
seraphima
5e012b8891 added convert rect 2024-06-23 23:11:24 +02:00
seraphima
77c59faa8a moved display utils 2024-06-23 23:10:54 +02:00
seraphima
ed7d3ec973 moved on thread executor to common 2024-06-23 13:17:29 +02:00
donlaci
26bbefa004 [Projects] Removing not selected apps on save 2024-06-20 13:31:56 +02:00
donlaci
e6b56c4152 [Projects] optimizing click handlers 2024-06-20 13:25:32 +02:00
donlaci
4c4630ef75 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-20 13:23:18 +02:00
donlaci
6fc164df98 [Projects] bringing back the breadcrumb on the editor page. Make it clickable. 2024-06-20 13:22:02 +02:00
seraphima
5f42c6ef66 fix placing windows of the same app in the project 2024-06-20 12:54:47 +02:00
seraphima
1e28cb8c40 remove opening the first proj 2024-06-20 12:54:39 +02:00
seraphima
0c83e632ed changed "no projects" text color and position 2024-06-20 11:29:13 +02:00
donlaci
852d67b569 [Projects] Making popup having rounded corners 2024-06-20 11:02:04 +02:00
donlaci
01d6c916d6 [Projects] Adding Edit button to the popup. + minor changes 2024-06-19 15:17:25 +02:00
donlaci
90979cbecb [projects] Adding info message for cases: there are no projects or no results for the search 2024-06-19 14:58:29 +02:00
donlaci
20a7dd4a7f Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-19 12:29:17 +02:00
donlaci
ba19333ad7 [Projects] re-implementing icon size calculation to have similar sized icons for every app. 2024-06-19 12:28:09 +02:00
seraphima
6eafa57c68 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-19 11:17:20 +02:00
seraphima
8c450f3457 spellcheck 2024-06-19 11:16:58 +02:00
donlaci
0137bc9697 spell checker 2024-06-19 11:11:27 +02:00
donlaci
e4ccde49f7 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-19 11:03:55 +02:00
donlaci
0efe6c91e8 [Projects] Editor: brining the highlighted app's icon into the foreground. + minor UI fixes 2024-06-19 11:02:41 +02:00
seraphima
a62d95c8fa shortcut saving fix 2024-06-19 00:06:57 +02:00
seraphima
ba13f5c0c4 update projects names to filter them out 2024-06-18 22:10:48 +02:00
seraphima
bd50d6961d editor dll signing 2024-06-18 16:44:23 +02:00
seraphima
fbb9f4188f spellcheck 2024-06-17 20:40:16 +02:00
seraphima
ae91aa3869 editor version 2024-06-17 20:40:07 +02:00
seraphima
4cccbecf54 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-17 19:57:14 +02:00
seraphima
0e74b2ee6b version 2024-06-17 19:57:09 +02:00
donlaci
f7cab16fb6 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-17 15:22:34 +02:00
donlaci
bfb569e894 Do not allow saving project if name or applist is empty. Also minor UI changes 2024-06-17 15:21:13 +02:00
seraphima
ce39ef2360 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-17 14:09:35 +02:00
seraphima
0c3108bca1 arm build fix 2024-06-17 14:09:31 +02:00
donlaci
28bf6de36c [Projects] fixing general settings gpo handling in runner + minor changes 2024-06-17 10:31:34 +02:00
seraphima
3de23f1c82 guid prefix 2024-06-14 16:26:20 +02:00
seraphima
198b7a6890 Merge remote-tracking branch 'microsoft/main' into dev/feature/projects 2024-06-14 12:52:38 +02:00
seraphima
f9fe9cc204 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-14 12:15:10 +02:00
seraphima
5d09e2e821 pipeline 2024-06-14 12:14:44 +02:00
seraphima
9914d31d6b github 2024-06-14 12:14:13 +02:00
seraphima
add5078f43 dsc 2024-06-14 12:13:41 +02:00
donlaci
68a66ff03d [Projects] Editor: Main page: fix layout if there are many apps, launch button not disappearing on the right side 2024-06-14 11:50:56 +02:00
donlaci
9cac577b7f [Projects] fix grammatical issue #43 (1 app - many apps) 2024-06-14 10:50:26 +02:00
donlaci
cc9b7d62df extend search for projects by search over the containing apps' names 2024-06-14 10:36:09 +02:00
seraphima
27d0620cac exit event constant 2024-06-13 23:11:26 +02:00
seraphima
6a353e0941 gpo 2024-06-13 23:07:43 +02:00
seraphima
73146e844e installer 2024-06-13 22:55:36 +02:00
seraphima
07ab6191d0 bug report tool 2024-06-13 21:52:04 +02:00
seraphima
4a672e2ed5 rename projects editor 2024-06-13 21:30:26 +02:00
seraphima
f869f99144 module interface 2024-06-13 21:30:03 +02:00
seraphima
2493fd6a1a changed the default hotkey 2024-06-13 21:29:08 +02:00
donlaci
64c0ca77bf Adding OOBE Projects page 2024-06-13 17:32:34 +02:00
donlaci
0bdfa32cd0 spell checker 2024-06-13 13:31:06 +02:00
donlaci
8f69109689 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-13 13:24:25 +02:00
donlaci
5abba947d8 adding GPO role + wrappers + GPO-check methods 2024-06-13 13:20:21 +02:00
seraphima
12bc9d7206 spelling 2024-06-13 12:34:08 +02:00
seraphima
a5c21d3432 snapshot logger 2024-06-12 22:00:11 +02:00
seraphima
6bfe924234 remove quotation marks 2024-06-12 21:47:31 +02:00
seraphima
3cc3701465 launcher logger 2024-06-12 21:45:39 +02:00
seraphima
71c7241fe1 common dependencies 2024-06-12 19:31:43 +02:00
seraphima
ae6cb122c8 init projects 2024-06-12 17:52:28 +02:00
seraphima
6fba21d9ad update data folders 2024-06-12 17:52:04 +02:00
seraphima
7e93d1f767 updated app names in editor 2024-06-12 17:45:27 +02:00
seraphima
e08aa76085 changed editor output path 2024-06-12 17:42:38 +02:00
seraphima
e5bbeb738e updated SnapshotTool project 2024-06-12 17:24:50 +02:00
seraphima
404a189c87 update Launcher project 2024-06-12 17:24:19 +02:00
seraphima
26dd8ea18e Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-12 14:13:10 +02:00
seraphima
80f4611b0a shortcut icons 2024-06-12 13:16:34 +02:00
seraphima
a5b69d9be5 optimized launching 2024-06-11 15:43:42 +02:00
donlaci
927ac511c8 First steps toward integration of the projects utility to PT 2024-06-11 13:32:40 +02:00
seraphima
07e0ef8a2c Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-10 15:58:59 +02:00
donlaci
1cf5d88507 minor adjustments on the not found warning icon 2024-06-10 15:57:28 +02:00
seraphima
1a33257314 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-10 14:19:34 +02:00
donlaci
6c7b99fbbc Adding not found icon + logic for not present detection. Moving icon drawing into drawhelper class. adjusting "selected cursor" size for preview drawing 2024-06-10 10:19:57 +02:00
seraphima
29e0bad3ea changed waiting time 2024-06-06 16:47:48 +02:00
seraphima
67a336bc46 checkbox default style 2024-06-05 19:10:50 +02:00
seraphima
40f2572b22 save-cancel buttons style 2024-06-05 19:10:38 +02:00
seraphima
cdb40a91fa remove unused 2024-06-05 19:10:23 +02:00
seraphima
3dea02a981 updated light theme colors 2024-06-05 18:58:30 +02:00
seraphima
4b371726c2 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-05 16:30:37 +02:00
seraphima
93bda77554 removed unused package 2024-06-05 16:30:25 +02:00
seraphima
47ccfde156 removed log 2024-06-05 16:30:14 +02:00
seraphima
782a2e1b1e check single-instance apps running 2024-06-05 16:28:00 +02:00
seraphima
5c600ebc92 moved app utils 2024-06-05 16:27:40 +02:00
donlaci
b07b15cf42 Modifying preview drawing colors for light theme. 2024-06-05 13:58:39 +02:00
donlaci
910b259ce1 fixing colors of the remove button (dark and light mode) 2024-06-05 12:53:59 +02:00
seraphima
3d75b8471c Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-06-05 11:28:45 +02:00
seraphima
f5cc0cba40 detecting and placing new windows 2024-06-05 11:28:35 +02:00
donlaci
449078be8e minor modification in light mode colors 2024-06-05 09:53:01 +02:00
donlaci
690b537e9f spell check 2024-06-04 12:55:05 +02:00
donlaci
af49b36d20 adding shortcut icon drawing. Minor changes 2024-06-04 12:45:50 +02:00
seraphima
9867395b5b moved utils to common 2024-06-03 16:49:08 +02:00
donlaci
cb2a4ec6e9 corrections in the snippet tool.
extending icon handling in the editor: for packaged apps look for the exe in the current path
adding shortcut icon creation.
2024-05-31 19:09:45 +02:00
donlaci
7e80c1bf73 spell checker 2024-05-29 13:51:28 +02:00
donlaci
24add7d4f8 adding dependent projects to the projects solution 2024-05-28 19:43:32 +02:00
donlaci
e11deec96f Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-05-28 18:26:36 +02:00
donlaci
feba2e6f17 adding logs 2024-05-28 18:21:30 +02:00
seraphima
c18c70afdb spellcheck 2024-05-27 21:44:42 +02:00
seraphima
aa788dad04 Merge branch 'dev/feature/projects' of https://github.com/microsoft/PowerToys into dev/feature/projects 2024-05-27 17:01:06 +02:00
seraphima
42905045b4 launch 2024-05-27 16:33:56 +02:00
seraphima
ccaf327baa run launcher 2024-05-27 16:33:30 +02:00
seraphima
6f813d9e66 app name fix 2024-05-27 16:33:13 +02:00
seraphima
2b71d97449 removed hwnd from json 2024-05-27 16:23:03 +02:00
seraphima
9f61f0793d updated app data 2024-05-27 15:15:34 +02:00
seraphima
cc553a60d5 get packaged app info 2024-05-27 15:07:53 +02:00
donlaci
3cd57eee63 creating drawing helper class to separate drawing functionallity 2024-05-24 15:36:01 +02:00
seraphima
6f6c32f989 update packages 2024-05-24 13:50:27 +02:00
seraphima
15c3956eda spelling 2024-05-21 17:48:58 +02:00
seraphima
ce19c4e5ff spelling 2024-05-21 17:45:07 +02:00
seraphima
c157e64f28 Projects 2024-05-21 16:55:15 +02:00
5487 changed files with 43978 additions and 428304 deletions

View File

@@ -1 +0,0 @@
../.github/copilot-instructions.md

View File

@@ -1 +0,0 @@
../.github/agents

View File

@@ -1 +0,0 @@
../.github/prompts

View File

@@ -1 +0,0 @@
../.github/instructions

View File

@@ -1 +0,0 @@
../.github/skills

View File

@@ -3,13 +3,13 @@
"isRoot": true,
"tools": {
"dotnet-consolidate": {
"version": "4.2.0",
"version": "2.0.0",
"commands": [
"dotnet-consolidate"
]
},
"xamlstyler.console": {
"version": "3.2501.8",
"version": "3.2404.2",
"commands": [
"xstyler"
]

View File

@@ -2,20 +2,17 @@
# Reference: https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/readme.md#compiling-powertoys
properties:
resources:
- resource: Microsoft.Windows.Settings/WindowsSettings
- resource: Microsoft.Windows.Developer/DeveloperMode
directives:
description: Enable Developer Mode
allowPrerelease: true
# Requires elevation for the set operation
securityContext: elevated
settings:
DeveloperMode: true
Ensure: Present
- resource: Microsoft.WinGet.DSC/WinGetPackage
id: vsPackage
directives:
description: Install Visual Studio 2022 Community (Any edition will work)
# Requires elevation for the set operation
securityContext: elevated
allowPrerelease: true
settings:
id: Microsoft.VisualStudio.2022.Community
source: winget
@@ -25,8 +22,6 @@ properties:
directives:
description: Install required VS workloads
allowPrerelease: true
# Requires elevation for the get and set operations
securityContext: elevated
settings:
productId: Microsoft.VisualStudio.Product.Community
channelId: VisualStudio.17.Release

View File

@@ -2,20 +2,17 @@
# Reference: https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/readme.md#compiling-powertoys
properties:
resources:
- resource: Microsoft.Windows.Settings/WindowsSettings
- resource: Microsoft.Windows.Developer/DeveloperMode
directives:
description: Enable Developer Mode
allowPrerelease: true
# Requires elevation for the set operation
securityContext: elevated
settings:
DeveloperMode: true
Ensure: Present
- resource: Microsoft.WinGet.DSC/WinGetPackage
id: vsPackage
directives:
description: Install Visual Studio 2022 Enterprise (Any edition will work)
# Requires elevation for the set operation
securityContext: elevated
allowPrerelease: true
settings:
id: Microsoft.VisualStudio.2022.Enterprise
source: winget
@@ -25,8 +22,6 @@ properties:
directives:
description: Install required VS workloads
allowPrerelease: true
# Requires elevation for the get and set operations
securityContext: elevated
settings:
productId: Microsoft.VisualStudio.Product.Enterprise
channelId: VisualStudio.17.Release

View File

@@ -2,20 +2,17 @@
# Reference: https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/readme.md#compiling-powertoys
properties:
resources:
- resource: Microsoft.Windows.Settings/WindowsSettings
- resource: Microsoft.Windows.Developer/DeveloperMode
directives:
description: Enable Developer Mode
allowPrerelease: true
# Requires elevation for the set operation
securityContext: elevated
settings:
DeveloperMode: true
Ensure: Present
- resource: Microsoft.WinGet.DSC/WinGetPackage
id: vsPackage
directives:
description: Install Visual Studio 2022 Professional (Any edition will work)
# Requires elevation for the set operation
securityContext: elevated
allowPrerelease: true
settings:
id: Microsoft.VisualStudio.2022.Professional
source: winget
@@ -25,8 +22,6 @@ properties:
directives:
description: Install required VS workloads
allowPrerelease: true
# Requires elevation for the get and set operations
securityContext: elevated
settings:
productId: Microsoft.VisualStudio.Product.Professional
channelId: VisualStudio.17.Release

14
.github/CODEOWNERS vendored
View File

@@ -1,16 +1,16 @@
# Protect `.github` folder except the spell-check rules inside it. (The exception happens by not defining any owner user or group for the path.)
# Protection of the spell-check rules makes no sense as it needs to be changed in nearly every PR.
/.github/ @microsoft/powertoys-code-owners
/.github/ @crutkas @DHowett @ethanfangg
/.github/actions/spell-check/
# locking down pipeline folder
/.pipelines/ @microsoft/powertoys-code-owners
/.pipelines/ @crutkas @DHowett @ethanfangg
# locking down nuget config
nuget.config @microsoft/powertoys-code-owners
packages.config @microsoft/powertoys-code-owners
nuget.config @crutkas @DHowett @ethanfangg
packages.config @crutkas @DHowett @ethanfangg
# locking down files that should not change
LICENSE @microsoft/powertoys-code-owners
SECURITY.md @microsoft/powertoys-code-owners
CODE_OF_CONDUCT.md @microsoft/powertoys-code-owners
LICENSE @crutkas @DHowett @ethanfangg
SECURITY.md @crutkas @DHowett @ethanfangg
CODE_OF_CONDUCT.md @crutkas @DHowett @ethanfangg

View File

@@ -7,27 +7,18 @@ body:
- type: markdown
attributes:
value: Please make sure to [search for existing issues](https://github.com/microsoft/PowerToys/issues) before filing a new one!
- type: markdown
attributes:
value: |
We are aware of the following high-volume issues and are actively working on them. Please check if your issue is one of these before filing a new bug report:
* **PowerToys Run crash related to "Desktop composition is disabled"**: This may appear as `COMException: 0x80263001`. For more details, see issue [#31226](https://github.com/microsoft/PowerToys/issues/31226).
* **PowerToys Run crash with `COMException (0xD0000701)`**: For more details, see issue [#30769](https://github.com/microsoft/PowerToys/issues/30769).
* **PowerToys Run crash with a "Cyclic reference" error**: This `System.InvalidOperationException` is detailed in issue [#36451](https://github.com/microsoft/PowerToys/issues/36451).
- id: version
type: input
- type: input
attributes:
label: Microsoft PowerToys version
placeholder: X.XX.X
description: Hover over the system tray icon or look at Settings
placeholder: 0.70.0
description: Hover over system tray icon or look at Settings
validations:
required: true
- id: installed
type: dropdown
- type: dropdown
attributes:
label: Installation method
description: How / where was PowerToys installed from?
description: How / Where was PowerToys installed from?
multiple: true
options:
- GitHub
@@ -41,6 +32,14 @@ body:
validations:
required: true
- type: dropdown
attributes:
label: Running as admin
description: Are you running PowerToys as Admin?
options:
- "Yes"
- "No"
- type: dropdown
attributes:
label: Area(s) with issue?
@@ -53,7 +52,6 @@ body:
- Awake
- ColorPicker
- Command not found
- Command Palette
- Crop and Lock
- Environment Variables
- FancyZones
@@ -65,10 +63,8 @@ body:
- Image Resizer
- Installer
- Keyboard Manager
- Light Switch
- Mouse Utilities
- Mouse Without Borders
- New+
- Peek
- PowerRename
- PowerToys Run
@@ -79,9 +75,9 @@ body:
- Shortcut Guide
- System tray interaction
- TextExtractor
- Video Conference Mute
- Workspaces
- Welcome / PowerToys Tour window
- ZoomIt
validations:
required: true
@@ -107,19 +103,6 @@ body:
validations:
required: false
- id: additionalInfo
type: textarea
attributes:
label: Additional Information
placeholder: |
OS version
.Net version
System Language
User or System Installation
Running as admin
validations:
required: false
- type: textarea
attributes:
label: Other Software
@@ -130,4 +113,3 @@ body:
My Cool Application v0.3 (include a code snippet if it would help!)
validations:
required: false

View File

@@ -6,7 +6,7 @@ labels:
body:
- type: textarea
attributes:
label: Describe the requested doc changes
label: Provide a description of requested docs changes
placeholder: Briefly describe which document needs to be corrected and why.
validations:
required: true

View File

@@ -1,6 +1,5 @@
name: "⭐ Feature or enhancement request"
description: Propose something new.
type: Feature
labels:
- Needs-Triage
body:
@@ -13,7 +12,7 @@ body:
- type: textarea
attributes:
label: Scenario when this would be used?
placeholder: What is the scenario this would be used in? Why is this important to your workflow as a power user?
placeholder: What is the scenario this would be used? Why is this important to your workflow as a power user?
validations:
required: true
- type: textarea

View File

@@ -1,6 +1,5 @@
name: "🌐 Localization/Translation issue"
description: Report incorrect translations.
type: Bug
labels:
- Issue-Bug
- Area-Localization
@@ -14,7 +13,7 @@ body:
attributes:
label: Microsoft PowerToys version
placeholder: 0.70.0
description: Hover over the system tray icon or look at Settings
description: Hover over system tray icon or look at Settings
validations:
required: true
- type: dropdown
@@ -38,10 +37,8 @@ body:
- Image Resizer
- Installer
- Keyboard Manager
- Light Switch
- Mouse Utilities
- Mouse Without Borders
- New+
- Peek
- PowerRename
- PowerToys Run
@@ -52,9 +49,9 @@ body:
- Shortcut Guide
- System tray interaction
- TextExtractor
- Video Conference Mute
- Workspaces
- Welcome / PowerToys Tour window
- ZoomIt
validations:
required: true
- type: input
@@ -66,7 +63,7 @@ body:
- type: textarea
attributes:
label: ❌ Actual phrase(s)
placeholder: What is there? Please include a screenshot, as that is extremely helpful.
placeholder: What is there? Please include a screenshot as that is extremely helpful.
validations:
required: true
- type: textarea

View File

@@ -21,22 +21,16 @@ Pbgra
WHITEONBLACK
# COUNTRIES
RUS
# FILES
AYUV
bak
Bcl
bgcode
Deflatealgorithm
exa
exabyte
Gbits
Gbps
gcode
Heatshrink
Mbits
MBs
mkv
@@ -56,8 +50,6 @@ YVU
YVYU
zipfolder
CODEOWNERS
VNext
vnext
# FONTS
@@ -83,44 +75,18 @@ sinclairinat
stylecop
uipi
yinwang
myaccess
onmicrosoft
aep
epsf
howto
onefuzzconfig
oip
onefuzzingestionpreparationtool
OTP
Yubi
Yubico
Perplexity
Groq
svgl
# KEYS
altdown
BUTTONUP
bafunctions
Baf
Bitness
BUILDARCHSHORT
CTRLALTDEL
Ctrls
CSilent
CBal
CREATEBAFUNCTIONS
CPrereq
dirutil
DUtil
Editbox
EXSEL
HOLDENTER
HOLDESC
HOLDSPACE
HOLDBACKSPACE
IDIGNORE
KBDLLHOOKSTRUCT
keyevent
LAlt
@@ -132,16 +98,12 @@ LCONTROL
LCtrl
LEFTDOWN
LEFTUP
locutil
logutil
msimg
MBUTTON
MBUTTONDBLCLK
MBUTTONDOWN
MBUTTONUP
MIDDLEDOWN
MIDDLEUP
memutil
NCRBUTTONDBLCLK
NCRBUTTONDOWN
NCRBUTTONUP
@@ -154,18 +116,8 @@ RCONTROL
RCtrl
RIGHTDOWN
RIGHTUP
Richedit
rgwz
resrutil
srd
scz
shelutil
thmutil
uriutil
VKTAB
wcautil
winkey
wininet
WMKEYDOWN
WMKEYUP
WMSYSKEYDOWN
@@ -175,10 +127,6 @@ XBUTTONDBLCLK
XBUTTONDOWN
XBUTTONUP
XDOWN
xmlutil
# Prefix
pcs
# User32.SYSTEM_METRICS_INDEX.cs
@@ -267,78 +215,14 @@ SWAPBUTTON
SYSTEMDOCKED
TABLETPC
# Units
nmi
# MATH
artanh
arsinh
arcosh
roundf
# Linux
dbus
anypass
github
gpg
https
ssh
ubuntu
workarounds
# For upgrade to check-spelling v0.0.24
pwa
# .NET
AOT
Aot
# YML
onefuzz
# NameInCode
leilzh
mengyuanchen
# DllName
testhost
Testably
#Tools
OIP
xef
xes
PACKAGEVERSIONNUMBER
APPXMANIFESTVERSION
# MRU lists
CACHEWRITE
MRUCMPPROC
MRUINFO
REGSTR
# Misc Win32 APIs and PInvokes
INVOKEIDLIST
# PowerRename metadata pattern abbreviations (used in tests and regex patterns)
DDDD
FFF
HHH
riday
YYY
# Unicode
precomposed
# GitHub issue/PR commands
azp
feedbackhub
needinfo
reportbug
#ffmpeg
crf
nostdin

View File

@@ -23,17 +23,16 @@ registrypreview
rooler
scoobe
shortcutguide
videoconference
# USERS
# 8LWXpg is user name but user folder causes a flag
LWXpg
8LWXpg
Adoumie
Advaith
alekhyareddy
Aleks
amihaiuc
angularsen
Anirudha
arjunbalgovind
@@ -44,10 +43,7 @@ Bartosz
betadele
betsegaw
bricelam
bsky
CCcat
chemwolf
chenmy
Chinh
chrdavis
Chrzan
@@ -55,76 +51,54 @@ clayton
Coplen
craigloewen
crutkas
Chubercik
damienleroy
daverayment
davidegiacometti
debian
Deibisu
Deibisu
Delimarsky
Deondre
DHowett
ductdo
Essey
ethanfangg
Feng
ferraridavide
foxmsft
frankychen
Gaarden
gaardmark
gabime
Galaxi
Garside
Gershaft
Giordani
Gleb
Gokce
gordon
Griese
grzhan
Guo
hanselman
haoliuu
Harmath
Heiko
Hemmerlein
hlaueriksson
Horvalds
Howett
hotkidfamily
htcfreek
Huynh
Ionut
jamrobot
Jaswal
Jaylyn
jefflord
Jeremic
Jordi
jyuwono
kai
Kairu
Kairu
Kamra
Kantarci
Karthick
kaylacinnamon
kevinguo
Khmyznikov
Krigun
Lambson
Laute
laviusmotileng
Leilei
Loewen
Luecking
Mahalingam
Markovic
martinchrzan
martinmoene
Melman
Mengyuan
Mihaiuc
Mikhayelyan
msft
Mykhailo
@@ -133,39 +107,30 @@ Naro
nathancartlidge
Nemeth
nielslaute
Noraa
noraajunker
oldnewthing
onegreatworld
palenshus
pedrolamas
Peiyao
peteblois
phoboslab
Ponten
Pooja
Pylyp
Qingpeng
quachpas
Quriz
randyrants
rayment
ricardosantos
riri
riri
ritchielawrence
robmikh
ruslanlap
Russinovich
Rutkas
ryanbodrug
saahmedm
sachaple
Sameerjs
Santossio
Schoen
Sekan
Seraphima
Shmuelie
skttl
somil
Soref
@@ -176,43 +141,24 @@ Tadele
talynone
Taras
TBM
Teutsch
tilovell
Triet
urnotdfs
vednig
waaverecords
wang
Whuihuan
Xiaofeng
Xpg
Yaqing
yaqingmi
ycv
yeelam
Yuniardi
yuyoyuppe
Zeol
Zhao
Zhaopeng
zhaopy
zhaoqpcn
Zoltan
Zykova
# OTHERS
Bilibili
BVID
capturevideosample
cmdow
Controlz
cortana
devhints
dlnilsson
fancymouse
firefox
fudan
gpt
Inkscape
Markdig
@@ -226,11 +172,8 @@ openai
Quickime
regedit
roslyn
Skia
Spotify
tldr
Vanara
wangyi
WEX
windowwalker
winui
@@ -238,9 +181,7 @@ winuiex
wix
wordpad
WWL
wyhash
xamlstyler
Xavalon
Xbox
Youdao
zadjii

View File

@@ -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

View File

@@ -1,9 +1,3 @@
# D2D
#D?2D
# Repeated letters
\b([a-z])\g{-1}{2,}\b
# marker to ignore all code on line
^.*/\* #no-spell-check-line \*/.*$
# marker to ignore all code on line
@@ -13,20 +7,14 @@
# cspell inline
^.*\b[Cc][Ss][Pp][Ee][Ll]{2}:\s*[Dd][Ii][Ss][Aa][Bb][Ll][Ee]-[Ll][Ii][Nn][Ee]\b
# copyright
Copyright (?:\([Cc]\)|)(?:[-\d, ]|and)+(?: [A-Z][a-z]+ [A-Z][a-z]+,?)+
# patch hunk comments
^@@ -\d+(?:,\d+|) \+\d+(?:,\d+|) @@ .*
^\@\@ -\d+(?:,\d+|) \+\d+(?:,\d+|) \@\@ .*
# git index header
index (?:[0-9a-z]{7,40},|)[0-9a-z]{7,40}\.\.[0-9a-z]{7,40}
# file permissions
['"`\s][-bcdLlpsw](?:[-r][-w][-Ssx]){2}[-r][-w][-SsTtx]\+?['"`\s]
# css fonts
\bfont(?:-family|):[^;}]+
# css url wrappings
\burl\([^)]+\)
@@ -38,13 +26,13 @@ index (?:[0-9a-z]{7,40},|)[0-9a-z]{7,40}\.\.[0-9a-z]{7,40}
# data url in quotes
([`'"])data:(?:[^ `'"].*?|)(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,}).*\g{-1}
# data url
\bdata:[-a-zA-Z=;:/0-9+_]*,\S*
data:[-a-zA-Z=;:/0-9+]*,\S*
# https/http/file urls
#(?:\b(?:https?|ftp|file)://)[-A-Za-z0-9+&@#/*%?=~_|!:,.;]+[-A-Za-z0-9+&@#/*%=~_|]
#(?:\b(?:https?|ftp|file)://)[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]
# mailto urls
#mailto:[-a-zA-Z=;:/?%&0-9+@._]{3,}
mailto:[-a-zA-Z=;:/?%&0-9+@.]{3,}
# magnet urls
magnet:[?=:\w]+
@@ -77,8 +65,6 @@ magnet:[?=:\w]+
# Amazon
\bamazon\.com/[-\w]+/(?:dp/[0-9A-Z]+|)
# AWS ARN
arn:aws:[-/:\w]+
# AWS S3
\b\w*\.s3[^.]*\.amazonaws\.com/[-\w/&#%_?:=]*
# AWS execute-api
@@ -105,8 +91,6 @@ vpc-\w+
\bgoogle-analytics\.com/collect.[-0-9a-zA-Z?%=&_.~]*
# Google APIs
\bgoogleapis\.(?:com|dev)/[a-z]+/(?:v\d+/|)[a-z]+/[-@:./?=\w+|&]+
# Google Artifact Registry
\.pkg\.dev(?:/[-\w]+)+(?::[-\w]+|)
# Google Storage
\b[-a-zA-Z0-9.]*\bstorage\d*\.googleapis\.com(?:/\S*|)
# Google Calendar
@@ -142,8 +126,6 @@ themes\.googleusercontent\.com/static/fonts/[^/\s"]+/v\d+/[^.]+.
\bscholar\.google\.com/citations\?user=[A-Za-z0-9_]+
# Google Colab Research Drive
\bcolab\.research\.google\.com/drive/[-0-9a-zA-Z_?=]*
# Google Cloud regions
(?:us|(?:north|south)america|europe|asia|australia|me|africa)-(?:north|south|east|west|central){1,2}\d+
# GitHub SHAs (api)
\bapi.github\.com/repos(?:/[^/\s"]+){3}/[0-9a-f]+\b
@@ -170,9 +152,6 @@ themes\.googleusercontent\.com/static/fonts/[^/\s"]+/v\d+/[^.]+.
# GHSA
GHSA(?:-[0-9a-z]{4}){3}
# GitHub actions
\buses:\s+[-\w.]+/[-\w./]+@[-\w.]+
# GitLab commit
\bgitlab\.[^/\s"]*/\S+/\S+/commit/[0-9a-f]{7,16}#[0-9a-f]{40}\b
# GitLab merge requests
@@ -182,12 +161,6 @@ GHSA(?:-[0-9a-z]{4}){3}
# GitLab commits
\bgitlab\.[^/\s"]*/(?:[^/\s"]+/){2}commits?/[0-9a-f]+\b
# #includes
^\s*#include\s*(?:<.*?>|".*?")
# #pragma lib
^\s*#pragma comment\(lib, ".*?"\)
# binance
accounts\.binance\.com/[a-z/]*oauth/authorize\?[-0-9a-zA-Z&%]*
@@ -237,10 +210,10 @@ accounts\.binance\.com/[a-z/]*oauth/authorize\?[-0-9a-zA-Z&%]*
# medium link
\blink\.medium\.com/[a-zA-Z0-9]+
# medium
\bmedium\.com/@?[^/\s"]+/[-\w]+
\bmedium\.com/\@?[^/\s"]+/[-\w]+
# microsoft
\b(?:https?://|)(?:(?:(?:blogs|download\.visualstudio|docs|msdn2?|research)\.|)microsoft|blogs\.msdn)\.co(?:m|\.\w\w)/[-_a-zA-Z0-9()=./%]*
\b(?:https?://|)(?:(?:download\.visualstudio|docs|msdn2?|research)\.microsoft|blogs\.msdn)\.com/[-_a-zA-Z0-9()=./%]*
# powerbi
\bapp\.powerbi\.com/reportEmbed/[^"' ]*
# vs devops
@@ -302,7 +275,7 @@ slack://[a-zA-Z0-9?&=]+
[0-9a-f]{32}\@o\d+\.ingest\.sentry\.io\b
# Twitter markdown
\[@[^[/\]:]*?\]\(https://twitter.com/[^/\s"')]*(?:/status/\d+(?:\?[-_0-9a-zA-Z&=]*|)|)\)
\[\@[^[/\]:]*?\]\(https://twitter.com/[^/\s"')]*(?:/status/\d+(?:\?[-_0-9a-zA-Z&=]*|)|)\)
# Twitter hashtag
\btwitter\.com/hashtag/[\w?_=&]*
# Twitter status
@@ -357,7 +330,7 @@ ipfs://[0-9a-zA-Z]{3,}
[^"\s]+/gitweb/\S+;h=[0-9a-f]+
# HyperKitty lists
/archives/list/[^@/]+@[^/\s"]*/message/[^/\s"]*/
/archives/list/[^@/]+\@[^/\s"]*/message/[^/\s"]*/
# lists
/thread\.html/[^"\s]+
@@ -375,7 +348,7 @@ ipfs://[0-9a-zA-Z]{3,}
\bopen\.spotify\.com/embed/playlist/\w+
# Mastodon
\bmastodon\.[-a-z.]*/(?:media/|@)[?&=0-9a-zA-Z_]*
\bmastodon\.[-a-z.]*/(?:media/|\@)[?&=0-9a-zA-Z_]*
# scastie
\bscastie\.scala-lang\.org/[^/]+/\w+
@@ -414,12 +387,12 @@ ipfs://[0-9a-zA-Z]{3,}
\bgetopts\s+(?:"[^"]+"|'[^']+')
# ANSI color codes
(?:\\(?:u00|x)1[Bb]|\\03[1-7]|\x1b|\\u\{1[Bb]\})\[\d+(?:;\d+)*m
(?:\\(?:u00|x)1[Bb]|\x1b|\\u\{1[Bb]\})\[\d+(?:;\d+|)m
# URL escaped characters
%[0-9A-F][A-F](?=[A-Za-z])
\%[0-9A-F][A-F]
# lower URL escaped characters
%[0-9a-f][a-f](?=[a-z]{2,})
\%[0-9a-f][a-f](?=[a-z]{2,})
# IPv6
\b(?:[0-9a-fA-F]{0,4}:){3,7}[0-9a-fA-F]{0,4}\b
# c99 hex digits (not the full format, just one I've seen)
@@ -427,7 +400,7 @@ ipfs://[0-9a-zA-Z]{3,}
# Punycode
\bxn--[-0-9a-z]+
# sha
sha\d+:[0-9a-f]*?[a-f]{3,}[0-9a-f]*
sha\d+:[0-9]*[a-f]{3,}[0-9a-f]*
# sha-... -- uses a fancy capture
(\\?['"]|&quot;)[0-9a-f]{40,}\g{-1}
# hex runs
@@ -447,17 +420,10 @@ sha\d+:[0-9a-f]*?[a-f]{3,}[0-9a-f]*
# pki
-----BEGIN.*-----END
# pki (base64)
LS0tLS1CRUdJT.*
# C# includes
^\s*using [^;]+;
# uuid:
\b[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\b
# hex digits including css/html color classes:
(?:[\\0][xX]|\\u\{?|[uU]\+|#x?|%23|&H)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|[iu]\d+)\b
(?:[\\0][xX]|\\u|[uU]\+|#x?|\%23)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|[iu]\d+)\b
# integrity
integrity=(['"])(?:\s*sha\d+-[-a-zA-Z=;:/0-9+]{40,})+\g{-1}
@@ -475,47 +441,20 @@ integrity=(['"])(?:\s*sha\d+-[-a-zA-Z=;:/0-9+]{40,})+\g{-1}
Name\[[^\]]+\]=.*
# IServiceProvider / isAThing
(?:(?:\b|_|(?<=[a-z]))I|(?:\b|_)(?:nsI|isA))(?=(?:[A-Z][a-z]{2,})+(?:[A-Z\d]|\b))
# python
#\b(?i)py(?!gments|gmy|lon|ramid|ro|th)(?=[a-z]{2,})
\b(?:I|isA)(?=(?:[A-Z][a-z]{2,})+\b)
# crypt
(['"])\$2[ayb]\$.{56}\g{-1}
# apache/old crypt
(['"]|)\$+(?:apr|)1\$+.{8}\$+.{22}\g{-1}
# sha1 hash
\{SHA\}[-a-zA-Z=;:/0-9+]{3,}
# machine learning (?)
#\b(?i)ml(?=[a-z]{2,})
# scrypt / argon
\$(?:scrypt|argon\d+[di]*)\$\S+
# go.sum
\bh1:\S+
# imports
^import\s+(?:(?:static|type)\s+|)(?:[\w.]|\{\s*\w*?(?:,\s*(?:\w*|\*))+\s*\})+
# scala modules
#("[^"]+"\s*%%?\s*){2,3}"[^"]+"
# container images
image: [-\w./:@]+
# Docker images
^\s*(?i)FROM\s+\S+:\S+(?:\s+AS\s+\S+|)
# `docker images` REPOSITORY TAG IMAGE ID CREATED SIZE
\s*\S+/\S+\s+\S+\s+[0-9a-f]{8,}\s+\d+\s+(?:hour|day|week)s ago\s+[\d.]+[KMGT]B
# Intel intrinsics
_mm\d*_(?!dd)\w+
# Input to GitHub JSON
content: (['"])[-a-zA-Z=;:/0-9+]*=\g{-1}
@@ -523,49 +462,39 @@ content: (['"])[-a-zA-Z=;:/0-9+]*=\g{-1}
# you'll want to remove the `(?=.*?")` suffix.
# The `(?=.*?")` suffix should limit the false positives rate
# printf
#%(?:(?:(?:hh?|ll?|[jzt])?[diuoxn]|l?[cs]|L?[fega]|p)(?=[a-z]{2,})|(?:X|L?[FEGA])(?=[a-zA-Z]{2,}))(?!%)(?=[_a-zA-Z]+(?!%)\b)(?=.*?['"])
# Alternative printf
# %s
%(?:s(?=[a-z]{2,}))(?!%)(?=[_a-zA-Z]+(?!%[^s])\b)(?=.*?['"])
#%(?:(?:(?:hh?|ll?|[jzt])?[diuoxn]|l?[cs]|L?[fega]|p)(?=[a-z]{2,})|(?:X|L?[FEGA]|p)(?=[a-zA-Z]{2,}))(?=[_a-zA-Z]+\b)(?!%)(?=.*?['"])
# Python string prefix / binary prefix
# Note that there's a high false positive rate, remove the `?=` and search for the regex to see if the matches seem like reasonable strings
(?<!['"])\b(?:B|BR|Br|F|FR|Fr|R|RB|RF|Rb|Rf|U|UR|Ur|b|bR|br|f|fR|fr|r|rB|rF|rb|rf|u|uR|ur)['"](?=[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})
(?<!')\b(?:B|BR|Br|F|FR|Fr|R|RB|RF|Rb|Rf|U|UR|Ur|b|bR|br|f|fR|fr|r|rB|rF|rb|rf|u|uR|ur)'(?=[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})
# Regular expressions for (P|p)assword
\([A-Z]\|[a-z]\)[a-z]+
# JavaScript regular expressions
# javascript test regex
/.{3,}/[gim]*\.test\(
/.*/[gim]*\.test\(
# javascript match regex
\.match\(/[^/\s"]{3,}/[gim]*\s*
\.match\(/[^/\s"]*/[gim]*\s*
# javascript match regex
\.match\(/\\[b].{3,}?/[gim]*\s*\)(?:;|$)
\.match\(/\\[b].*?/[gim]*\s*\)(?:;|$)
# javascript regex
^\s*/\\[b].{3,}?/[gim]*\s*(?:\)(?:;|$)|,$)
^\s*/\\[b].*/[gim]*\s*(?:\)(?:;|$)|,$)
# javascript replace regex
\.replace\(/[^/\s"]{3,}/[gim]*\s*,
\.replace\(/[^/\s"]*/[gim]*\s*,
# assign regex
= /[^*].*?(?:[a-z]{3,}|[A-Z]{3,}|[A-Z][a-z]{2,}).*/[gim]*(?=\W|$)
= /[^*]*?(?:[a-z]{3,}|[A-Z]{3,}|[A-Z][a-z]{2,}).*/
# perl regex test
[!=]~ (?:/.*/|m\{.*?\}|m<.*?>|m([|!/@#,;']).*?\g{-1})
# perl qr regex
(?<!\$)\bqr(?:\{.*?\}|<.*?>|\(.*?\)|([|!/@#,;']).*?\g{-1})
# perl run
perl(?:\s+-[a-zA-Z]\w*)+
# C network byte conversions
#(?:\d|\bh)to(?!ken)(?=[a-z])|to(?=[adhiklpun]\()
# Go regular expressions
regexp?\.MustCompile\((?:`[^`]*`|".*"|'.*')\)
regexp?\.MustCompile\(`[^`]*`\)
# regex choice
# \(\?:[^)]+\|[^)]+\)
\(\?:[^)]+\|[^)]+\)
# proto
^\s*(\w+)\s\g{-1} =
@@ -574,20 +503,14 @@ regexp?\.MustCompile\((?:`[^`]*`|".*"|'.*')\)
sed 's/(?:[^/]*?[a-zA-Z]{3,}[^/]*?/){2}
# node packages
(["'])@[^/'" ]+/[^/'" ]+\g{-1}
(["'])\@[^/'" ]+/[^/'" ]+\g{-1}
# go install
go install(?:\s+[a-z]+\.[-@\w/.]+)+
# pom.xml
<(?:group|artifact)Id>.*?<
# jetbrains schema https://youtrack.jetbrains.com/issue/RSRP-489571
urn:shemas-jetbrains-com
# Debian changelog severity
[-\w]+ \(.*\) (?:\w+|baseline|unstable|experimental); urgency=(?:low|medium|high|emergency|critical)\b
# kubernetes pod status lists
# https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-phase
\w+(?:-\w+)+\s+\d+/\d+\s+(?:Running|Pending|Succeeded|Failed|Unknown)\s+
@@ -595,29 +518,20 @@ urn:shemas-jetbrains-com
# kubectl - pods in CrashLoopBackOff
\w+-[0-9a-f]+-\w+\s+\d+/\d+\s+CrashLoopBackOff\s+
# kubernetes applications
\.apps/[-\w]+
# kubernetes object suffix
-[0-9a-f]{10}-\w{5}\s
# kubernetes crd patterns
^\s*pattern: .*$
# posthog secrets
([`'"])phc_[^"',]+\g{-1}
# xcode
# xcodeproject scenes
(?:Controller|destination|(?:first|second)Item|ID|id)="\w{3}-\w{2}-\w{3}"
(?:Controller|destination|ID|id)="\w{3}-\w{2}-\w{3}"
# xcode api botches
customObjectInstantitationMethod
# msvc api botches
PrependWithABINamepsace
# configure flags
.* \| --\w{2,}.*?(?=\w+\s\w+)
@@ -625,42 +539,23 @@ PrependWithABINamepsace
\.fa-[-a-z0-9]+
# bearer auth
(['"])[Bb]ear[e][r] .{3,}?\g{-1}
# bearer auth
\b[Bb]ear[e][r]:? [-a-zA-Z=;:/0-9+.]{3,}
(['"])Bear[e][r] .*?\g{-1}
# basic auth
(['"])[Bb]asic [-a-zA-Z=;:/0-9+]{3,}\g{-1}
# basic auth
: [Bb]asic [-a-zA-Z=;:/0-9+.]{3,}
(['"])Basic [-a-zA-Z=;:/0-9+]{3,}\g{-1}
# base64 encoded content
#([`'"])[-a-zA-Z=;:/0-9+]{3,}=\g{-1}
([`'"])[-a-zA-Z=;:/0-9+]+=\g{-1}
# base64 encoded content in xml/sgml
>[-a-zA-Z=;:/0-9+]{3,}=</
>[-a-zA-Z=;:/0-9+]+=</
# base64 encoded content, possibly wrapped in mime
#(?:^|[\s=;:?])[-a-zA-Z=;:/0-9+]{50,}(?:[\s=;:?]|$)
# base64 encoded json
\beyJ[-a-zA-Z=;:/0-9+]+
# base64 encoded pkcs
#\bMII[-a-zA-Z=;:/0-9+]+
# uuencoded
#[!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_]{40,}
# DNS rr data
#(?:\d+\s+){3}(?:[-+/=.\w]{2,}\s*){1,2}
(?:^|[\s=;:?])[-a-zA-Z=;:/0-9+]{50,}(?:[\s=;:?]|$)
# encoded-word
=\?[-a-zA-Z0-9"*%]+\?[BQ]\?[^?]{0,75}\?=
# numerator
\bnumer\b(?=.*denom)
# Time Zones
\b(?:Africa|Atlantic|America|Antarctica|Arctic|Asia|Australia|Europe|Indian|Pacific)(?:/[-\w]+)+
\b(?:Africa|Atlantic|America|Antarctica|Asia|Australia|Europe|Indian|Pacific)(?:/\w+)+
# linux kernel info
^(?:bugs|flags|Features)\s+:.*
@@ -668,22 +563,16 @@ PrependWithABINamepsace
# systemd mode
systemd.*?running in system mode \([-+].*\)$
# Lorem
# Update Lorem based on your content (requires `ge` and `w` from https://github.com/jsoref/spelling; and `review` from https://github.com/check-spelling/check-spelling/wiki/Looking-for-items-locally )
# grep '^[^#].*lorem' .github/actions/spelling/patterns.txt|perl -pne 's/.*i..\?://;s/\).*//' |tr '|' "\n"|sort -f |xargs -n1 ge|perl -pne 's/^[^:]*://'|sort -u|w|sed -e 's/ .*//'|w|review -
# Warning, while `(?i)` is very neat and fancy, if you have some binary files that aren't proper unicode, you might run into:
# ... Operation "substitution (s///)" returns its argument for non-Unicode code point 0x1C19AE (the code point will vary).
# ... You could manually change `(?i)X...` to use `[Xx]...`
# ... or you could add the files to your `excludes` file (a version after 0.0.19 should identify the file path)
(?:(?:\w|\s|[,.])*\b(?i)(?:amet|consectetur|cursus|dolor|eros|ipsum|lacus|libero|ligula|lorem|magna|neque|nulla|suscipit|tempus)\b(?:\w|\s|[,.])*)
## Operation "substitution (s///)" returns its argument for non-Unicode code point 0x1C19AE (the code point will vary).
## You could manually change `(?i)X...` to use `[Xx]...`
## or you could add the files to your `excludes` file (a version after 0.0.19 should identify the file path)
# Lorem
(?:\w|\s|[,.])*\b(?i)(?:amet|consectetur|cursus|dolor|eros|ipsum|lacus|libero|ligula|lorem|magna|neque|nulla|suscipit|tempus)\b(?:\w|\s|[,.])*
# Non-English
# Even repositories expecting pure English content can unintentionally have Non-English content... People will occasionally mistakenly enter [homoglyphs](https://en.wikipedia.org/wiki/Homoglyph) which are essentially typos, and using this pattern will mean check-spelling will not complain about them.
#
# If the content to be checked should be written in English and the only Non-English items will be people's names, then you can consider adding this.
#
# Alternatively, if you're using check-spelling v0.0.25+, and you would like to _check_ the Non-English content for spelling errors, you can. For information on how to do so, see:
# https://docs.check-spelling.dev/Feature:-Configurable-word-characters.html#unicode
[a-zA-Z]*[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3}[a-zA-ZÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]*|[a-zA-Z]{3,}[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]|[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3,}
# highlighted letters
@@ -696,51 +585,27 @@ systemd.*?running in system mode \([-+].*\)$
# latex (check-spelling >= 0.0.22)
\\\w{2,}\{
# American Mathematical Society (AMS) / Doxygen
TeX/AMS
# File extensions
#\*\.[+\w]+,
# eslint
"varsIgnorePattern": ".+"
# nolint
nolint:\s*[\w,]+
# Windows short paths
[/\\][^/\\]{5,6}~\d{1,2}(?=[/\\])
# Windows Resources with accelerators
\b[A-Z]&[a-z]+\b(?!;)
# signed off by
(?i)Signed-off-by: .*
# cygwin paths
/cygdrive/[a-zA-Z]/(?:Program Files(?: \(.*?\)| ?)(?:/[-+.~\\/()\w ]+)*|[-+.~\\/()\w])+
[/\\][^/\\]{5,6}~\d{1,2}[/\\]
# in check-spelling@v0.0.22+, printf markers aren't automatically consumed
# printf markers
#(?<!\\)\\[nrt](?=[a-z]{2,})
# alternate printf markers if you run into latex and friends
# alternate markers if you run into latex and friends
#(?<!\\)\\[nrt](?=[a-z]{2,})(?=.*['"`])
# Markdown anchor links
\(#\S*?[a-zA-Z]\S*?\)
# apache
a2(?:en|dis)
# weak e-tag
W/"[^"]+"
# authors/credits
^\*(?: [A-Z](?:\w+|\.)){2,} (?=\[|$)
# the negative lookahead here is to allow catching 'templatesz' as a misspelling
# but to otherwise recognize a Windows path with \templates\foo.template or similar:
#\\(?:necessary|r(?:elease|eport|esolve[dr]?|esult)|t(?:arget|emplates?))(?![a-z])
#\\(?:necessary|r(?:eport|esolve[dr]?|esult)|t(?:arget|emplates?))(?![a-z])
# ignore long runs of a single character:
\b([A-Za-z])\g{-1}{3,}\b
@@ -749,36 +614,23 @@ W/"[^"]+"
# Compiler flags (Unix, Java/Scala)
# Use if you have things like `-Pdocker` and want to treat them as `docker`
#(?:^|[\t ,>"'`=(#])-(?:(?:J-|)[DPWXY]|[Llf])(?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})
#(?:^|[\t ,>"'`=(])-(?:(?:J-|)[DPWXY]|[Llf])(?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})
# Compiler flags (Windows / PowerShell)
# This is a subset of the more general compiler flags pattern.
# It avoids matching `-Path` to prevent it from being treated as `ath`
#(?:^|[\t ,"'`=(#])-(?:[DPL](?=[A-Z]{2,})|[WXYlf](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,}))
#(?:^|[\t ,"'`=(])-(?:[DPL](?=[A-Z]{2,})|[WXYlf](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,}))
# Compiler flags (linker)
,-B
# Library prefix
# e.g., `lib`+`archive`, `lib`+`raw`, `lib`+`unwind`
# (ignores some words that happen to start with `lib`)
(?:\b|_)[Ll]ib(?:re(?=office)|)(?!era[lt]|ero|erty|rar(?:i(?:an|es)|y))(?=[a-z])
# iSCSI iqn (approximate regex)
\biqn\.[0-9]{4}-[0-9]{2}(?:[\.-][a-z][a-z0-9]*)*\b
# WWNN/WWPN (NAA identifiers)
\b(?:0x)?10[0-9a-f]{14}\b|\b(?:0x|3)?[25][0-9a-f]{15}\b|\b(?:0x|3)?6[0-9a-f]{31}\b
# curl arguments
\b(?:\\n|)curl(?:\.exe|)(?:\s+-[a-zA-Z]{1,2}\b)*(?:\s+-[a-zA-Z]{3,})(?:\s+-[a-zA-Z]+)*
\b(?:\\n|)curl(?:\s+-[a-zA-Z]{1,2}\b)*(?:\s+-[a-zA-Z]{3,})(?:\s+-[a-zA-Z]+)*
# set arguments
\b(?:bash|sh|set)(?:\s+[-+][abefimouxE]{1,2})*\s+[-+][abefimouxE]{3,}(?:\s+[-+][abefimouxE]+)*
\bset(?:\s+-[abefimouxE]{1,2})*\s+-[abefimouxE]{3,}(?:\s+-[abefimouxE]+)*
# tar arguments
\b(?:\\n|)g?tar(?:\.exe|)(?:(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+
# tput arguments -- https://man7.org/linux/man-pages/man5/terminfo.5.html -- technically they can be more than 5 chars long...
\btput\s+(?:(?:-[SV]|-T\s*\w+)\s+)*\w{3,5}\b
# macOS temp folders
/var/folders/\w\w/[+\w]+/(?:T|-Caches-)/
# github runner temp folders
/home/runner/work/_temp/[-_/a-z0-9]+

View File

@@ -1,24 +1,26 @@
# See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-excludes
(?:^|/)(?i)COPYRIGHT
(?:^|/)(?i)LICEN[CS]E
(?:^|/)(?i)third[-_]?party/
(?:^|/)3rdparty/
(?:^|/)generated/
(?:^|/)FilePreviewCommon/Assets/Monaco/customLanguages/
(?:^|/)FilePreviewCommon/Assets/Monaco/generateLanguagesJson.html
(?:^|/)FilePreviewCommon/Assets/Monaco/index.html
(?:^|/)FilePreviewCommon/Assets/Monaco/monaco_languages.json
(?:^|/)FilePreviewCommon/Assets/Monaco/monacoSpecialLanguages.js
(?:^|/)go\.sum$
(?:^|/)monacoSRC/
(?:^|/)package(?:-lock|)\.json$
(?:^|/)Pipfile$
(?:^|/)power-rename-ui-flags$
(?:^|/)pyproject.toml
(?:^|/)requirements(?:-dev|-doc|-test|)\.txt$
(?:^|/)sample\.qoi$
(?:^|/)timezones\.json$
(?:^|/)vendor/
(?:^|/)WindowsSettings\.json$
(?:^|/|\b)requirements(?:-dev|-doc|-test|)\.txt$
(?:|$^ 92.31% - excluded 12/13)/editor/[^/]+$
/images/launcher/[^/]+$
/TestFiles/
[^/]\.cur$
[^/]\.gcode$
[^/]\.bgcode$
[^/]\.rgs$
\.a$
\.ai$
@@ -26,10 +28,11 @@
\.avi$
\.bmp$
\.bz2$
\.cert?$|\.crt$
\.cer$
\.class$
\.coveragerc$
\.crl$
\.crt$
\.csr$
\.dll$
\.docx?$
@@ -74,9 +77,7 @@
\.qm$
\.s$
\.sig$
\.snk$
\.so$
\.stl$
\.svgz?$
\.sys$
\.tar$
@@ -93,52 +94,33 @@
\.xz$
\.zip$
^\.github/actions/spell-check/
^\.github/workflows/spelling\d*\.yml$
^\.gitmodules$
^\Q.github/workflows/spelling2.yml\E$
^\Q.pipelines/ESRPSigning_core.json\E$
^\Qdoc/devdocs/localization.md\E$
^\Qsrc/modules/MouseUtils/MouseJump.Common/NativeMethods/User32/UI/WindowsAndMessaging/User32.SYSTEM_METRICS_INDEX.cs\E$
^\Q.pipelines/sdl.gdnbaselines\E$
^\Qinstaller/PowerToysSetup/Settings.wxs\E$
^\Qsrc/common/FilePreviewCommon/Assets/Monaco/monaco_languages.json\E$
^\Qsrc/common/ManagedCommon/ColorFormatHelper.cs\E$
^\Qsrc/common/notifications/BackgroundActivatorDLL/cpp.hint\E$
^\Qsrc/modules/colorPicker/ColorPickerUI/Assets/ColorPicker/colorPicker.cur\E$
^\Qsrc/modules/colorPicker/ColorPickerUI/Shaders/GridShader.cso\E$
^\Qsrc/modules/MouseUtils/MouseJumpUI/MainForm.resx\E$
^\Qsrc/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/WindowsAndMessaging/User32.SYSTEM_METRICS_INDEX.cs\E$
^\Qsrc/modules/MouseWithoutBorders/App/Form/frmAbout.cs\E$
^\Qsrc/modules/MouseWithoutBorders/App/Form/frmInputCallback.resx\E$
^\Qsrc/modules/MouseWithoutBorders/App/Form/frmLogon.resx\E$
^\Qsrc/modules/MouseWithoutBorders/App/Form/frmMatrix.resx\E$
^\Qsrc/modules/MouseWithoutBorders/App/Form/frmScreen.resx\E$
^\Qsrc/modules/MouseWithoutBorders/ModuleInterface/generateSecurityDescriptor.h\E$
^\Qsrc/modules/peek/Peek.Common/NativeMethods.txt\E$
^\Qsrc/modules/previewpane/SvgPreviewHandler/SvgHTMLPreviewGenerator.cs\E$
^\Qsrc/modules/previewpane/UnitTests-StlThumbnailProvider/HelperFiles/sample.stl\E$
^\Qtools/project_template/ModuleTemplate/resource.h\E$
^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$
^src/modules/MouseWithoutBorders/App/.*/NativeMethods\.cs$
^src/modules/MouseWithoutBorders/App/Form/.*\.Designer\.cs$
^src/modules/MouseWithoutBorders/App/Form/.*\.resx$
^src/modules/MouseWithoutBorders/App/Form/frmAbout\.cs$
^src/modules/MouseWithoutBorders/App/Form/frmInputCallback\.resx$
^src/modules/MouseWithoutBorders/App/Form/frmLogon\.resx$
^src/modules/MouseWithoutBorders/App/Form/frmMatrix\.resx$
^src/modules/MouseWithoutBorders/App/Form/frmMessage\.resx$
^src/modules/MouseWithoutBorders/App/Form/frmMouseCursor\.resx$
^src/modules/MouseWithoutBorders/App/Form/frmScreen\.resx$
^src/modules/MouseWithoutBorders/App/Helper/.*\.resx$
^src/modules/MouseWithoutBorders/ModuleInterface/generateSecurityDescriptor\.h$
^src/modules/peek/Peek.Common/NativeMethods\.txt$
^src/modules/peek/Peek.UITests/TestAssets/4\.qoi$
^src/modules/powerrename/PowerRenameUITest/testItems/folder1/testCase2\.txt$
^src/modules/powerrename/PowerRenameUITest/testItems/folder2/SpecialCase\.txt$
^src/modules/powerrename/PowerRenameUITest/testItems/testCase1\.txt$
^src/modules/previewpane/SvgPreviewHandler/SvgHTMLPreviewGenerator\.cs$
^src/modules/previewpane/UnitTests-MarkdownPreviewHandler/HelperFiles/MarkdownWithHTMLImageTag\.txt$
^src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/.*$
^src/modules/ZoomIt/ZoomIt/ZoomIt\.idc$
^src/Monaco/
^tools/project_template/ModuleTemplate/resource\.h$
^src/modules/previewpane/UnitTests-MarkdownPreviewHandler/HelperFiles/MarkdownWithHTMLImageTag.txt$
^tools/Verification scripts/Check preview handler registration\.ps1$
ignore$
^src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/.*$
^src/common/CalculatorEngineCommon/exprtk\.hpp$
src/modules/cmdpal/ext/SamplePagesExtension/Pages/SampleMarkdownImagesPage.cs

File diff suppressed because it is too large Load Diff

View File

@@ -1,570 +1,119 @@
# reject `m_data` as VxWorks defined it and that breaks things if it's used elsewhere
# see [fprime](https://github.com/nasa/fprime/commit/d589f0a25c59ea9a800d851ea84c2f5df02fb529)
# and [Qt](https://github.com/qtproject/qt-solutions/blame/fb7bc42bfcc578ff3fa3b9ca21a41e96eb37c1c7/qtscriptclassic/src/qscriptbuffer_p.h#L46)
#\bm_data\b
# \bm_data\b
# Were you debugging using a framework with `fit()`?
# If you have a framework that uses `it()` for testing and `fit()` for debugging a specific test,
# you might not want to check in code where you skip all the other tests.
# you might not want to check in code where you were debugging w/ `fit()`, in which case, you might want
# to use this:
#\bfit\(
# English does not use a hyphen between adverbs and nouns
# https://twitter.com/nyttypos/status/1894815686192685239
(?:^|\s)[A-Z]?[a-z]+ly-(?=[a-z]{3,})(?:[.,?!]?\s|$)
# Smart quotes should match
\s[^.?!]+[^.?!]+[^.?!]+|\s[^.?!]+[^.?!]+[^.?!]+|\s”[^.?!“”]+”[^.?!“”]+“[^.?!“”]+”|\s“[^.?!“”]+”[^.?!“”]+”[^.?!“”]+”
# Don't use `requires that` + `to be`
# https://twitter.com/nyttypos/status/1894816551435641027
\brequires that \w+\b[^.]+to be\b
# A fully parenthetical sentences period goes inside the parentheses, not outside.
# https://twitter.com/nyttypos/status/1898844061873639490
\([A-Z][a-z]{2,}(?: [a-z]+){3,}\)\.\s
# Complete sentences shouldn't be in the middle of another sentence as a parenthetical.
(?<!\.)(?<!\betc)\.\),
# Complete sentences in parentheticals should not have a space before the period.
\s\.\)(?!.*\}\})
# This probably indicates Mojibake https://en.wikipedia.org/wiki/Mojibake
# You probably should try to unbake this content
Ã(?:Â[¤¶¥]|[£¢])|Ã
# Should be `HH:MM:SS`
\bHH:SS:MM\b
# Should be `86400` (seconds in a standard day)
\b84600\b(?:.*\bday\b)
# Should probably be `2006-01-02` (yyyy-mm-dd)
# Assuming that the time is being passed to https://go.dev/src/time/format.go
\b2006-02-01\b
# Should probably be `YYYYMMDD`
\b[Yy]{4}[Dd]{2}[Mm]{2}(?!.*[Yy]{4}[Dd]{2}[Mm]{2}).*$
# Should be `a priori` or `and prior`
(?i)(?<!posteriori)\sand priori\s
# Should be `a`
\san (?=(?:[b-df-gj-npqtv-xz]|h(?!our|tml|ttp)|r(?!c\b)|s(?!sh|vg))[a-z])
# Articles generally shouldn't be used without a noun and a verb
# - Perhaps you're missing a verb between the noun and the second article.
# - Or, perhaps you should remove the first verb and treat the intervening word as a verb?
# - In some cases you should add a `,` between the noun and the second article.
\s(?:an?|the(?! action))\s(?!way|wh|how\b)[A-Za-z][a-z]+[a-qs-z]\s(?:a(?! bit)n?|the)\s
# Should only be one of `a`, `an`, or `the`
\b(?:(?:an?|the)\s+){2,}\b
# Should be a list `something, a second thing, or a third thing` or `something, a thing to do a thing`
# -- This rule is experimental, if you find it has a high false-positive rate, please let the maintainer know
#(?:^|[?!.] )[^()?!;,.]+, a(?:\s+(?!to\b)\w+)+?\s+an?\b
# Should only be `are` or `can`, not both
\b(?:(?:are|can)\s+){2,}\b
# Should probably be `ABCDEFGHIJKLMNOPQRSTUVWXYZ`
(?i)(?!ABCDEFGHIJKLMNOPQRSTUVWXYZ)ABC[A-Z]{21}YZ
# Should be `an`
#(?<!\b[Ii] |\.)\bam\b
# Should be `anymore`
# s.b. anymore
\bany more[,.]
# Should be `Ask`
(?:^|[.?]\s+)As\s+[A-Z][a-z]{2,}\s[^.?]*?(?:how|if|wh\w+)\b
# s.b. GitHub
(?<![&*.]|// |\btype )\bGithub\b(?![{)])
# Should be `at one fell swoop`
# and only when talking about killing, not some other completion
# Act 4 Scene 3, Macbeth
# https://www.opensourceshakespeare.org/views/plays/play_view.php?WorkID=macbeth&Act=4&Scene=3&Scope=scene
\bin one fell s[lw]?oop\b
# s.b. GitLab
(?<![&*.]|// |\btype )\bGitlab\b(?![{)])
# Should be `'`
(?i)\b(?:(?:i|s?he|they|what|who|you)[`"]ll|(?:are|ca|did|do|does|ha[ds]|have|is|should|were|wo|would)n[`"]t|(?:s?he|let|that|there|what|where|who)[`"]s|(?:i|they|we|what|who|you)[`"]ve)\b
# Should be `background` / `intro text` / `introduction` / `prologue` unless it's a brand or relates to _subterfuge_
(?i)\bpretext\b
# Should be `bearer`
\b(?<=the )burden(?= of bad news\b)
# Should be `bona`
# unless talking about bones
\bbone(?= fide\b)
# Should be `branches`
# ... unless it's really about the meal that replaces breakfast and lunch.
\b[Bb]runches\b
# Should be `briefcase`
\bbrief-case\b
# Should be `by far` or `far and away`
\bby far and away\b
# Should be `by and large`
\bby in large\b
# Should be `bytes`
# unless talking about sports where a team gets to skip a game, or
# saying `goodbyes` (even this is questionable)
(?<!\\)\bbyes\b
# Should be `can, not only ..., ... also...`
\bcan not only.*can also\b
# Should be `cannot` (or `can't`)
# See https://www.grammarly.com/blog/cannot-or-can-not/
# > Don't use `can not` when you mean `cannot`. The only time you're likely to see `can not` written as separate words is when the word `can` happens to precede some other phrase that happens to start with `not`.
# > `Can't` is a contraction of `cannot`, and it's best suited for informal writing.
# > In formal writing and where contractions are frowned upon, use `cannot`.
# > It is possible to write `can not`, but you generally find it only as part of some other construction, such as `not only . . . but also.`
# - if you encounter such a case, add a pattern for that case to patterns.txt.
\b[Cc]an not\b(?! only\b)
# Should be `chart`
(?i)\bhelm\b.*\bchard\b
# Do not use `(click) here` links
# For more information, see:
# * https://www.w3.org/QA/Tips/noClickHere
# * https://webaim.org/techniques/hypertext/link_text
# * https://granicus.com/blog/why-click-here-links-are-bad/
# * https://heyoka.medium.com/dont-use-click-here-f32f445d1021
(?i)(?:>|\[)(?:(?:click |)here|this(?=\]\([^\)]+:/)|link|(?:read |)more(?!</value))(?:</|\]\()
# Including "image of" or "picture of" in alt text is unnecessary.
\balt=['"](?:an? |)(?:image|picture) of
# Alt text should be short
\balt=(?:'[^']{126,}'|"[^"]{126,}")
# Should be `effect`
(?<=\btake )affect\b
# Should be `-endian`
\b(?i)(?<=big|little) endian\b
# Should be `equals` to `is equal to`
\bequals to\b
# Should be `ECMA` 262 (JavaScript)
(?i)\bTS\/EMCA\b|\bEMCA(?: \d|\s*Script)|\bEMCA\b(?=.*\bTS\b)
# Should be `ECMA` 340 (Near Field Communications)
(?i)EMCA[- ]340
# Should be `fall back`
\bfallback(?= to)\b
# Should be `for`, `for, to` or `to`
\b(?:for to|to for)\b
# Should be `GitHub`
(?<![&*.]|// |\b(?:from|import|type) )\bGithub\b(?![{()])
# Should be `GitLab`
(?<![&*.]|// |\b(?:from|import|type) )\bGitlab\b(?![{()])
# Should be `heartrending` unless talking about drawing hearts
\b(?i)heart[- ]rendering\b(?![^.?!]*(?:hearts|quirk))
# Should probably be `https://`...
# Markdown generally doesn't assume that links are to urls
\]\(www\.\w
# Should be `intents and purposes`
(?<=[Ff]or all )intensive purposes\b
# Should be `JavaScript`
# s.b. JavaScript
\bJavascript\b
# Should be `macOS` or `Mac OS X` or ...
# s.b. macOS or Mac OS X or ...
\bMacOS\b
# Should be `Microsoft`
# s.b. Microsoft
\bMicroSoft\b
# Should be `OAuth`
(?:^|[^-/*$])[ '"]oAuth(?: [a-z]|\d+ |[^ a-zA-Z0-9:;_.()])
# Should be `RabbitMQ`
\bRabbitmq\b
# Should be `TensorFlow`
\bTensorflow\b
# Should be `TypeScript`
# s.b. TypeScript
\bTypescript\b
# Should be `another`
\ban[- ]other(?!-)\b
# s.b. another
\ban[- ]other\b
# Should be `case-(in)sensitive`
\bcase (?:in|)sensitive\b
# Should be `coinciding`
\bco-inciding\b
# Should be `deprecation warning(s)`
# s.b. deprecation warning
\b[Dd]epreciation [Ww]arnings?\b
# Should be `greater than`
# s.b. greater than
\bgreater then\b
# Should be `has`
\b[Ii]t only have\b
# Should be `here-in`, `the`, `them`, `this`, `these` or reworded in some other way
\bthe here(?:\.|,| (?!and|defined))
# Should be `going to bed` or `going to a bad`
\bgoing to bad(?!-)\b
# Should be `greater than`
#\bhigher than\b
# Should be `ID` (unless it's a flag/property)
#(?<![-\.])\bId\b(?![(])
# Should be `in front of`
# s.b. in front of
\bin from of\b
# Should be `into`
# s.b. into
# when not phrasal and when `in order to` would be wrong:
# https://thewritepractice.com/into-vs-in-to/
\sin to\s(?!if\b)
# Should be `use`
\sin used by\b
# Should be `in-depth` if used as an adjective (but `in depth` when used as an adverb)
\bin depth\s(?!rather\b)\w{6,}
# Should be `in-flight` or `on the fly` (unless actually talking about airline flights)
\bon[- ]flight\b(?!=\s+(?:(?:\w{2}|)\d+|availability|booking|computer|data|delay|departure|management|performance|radar|reservation|scheduling|software|status|ticket|time|type|.*(?:hotel|taxi)))
# Should be `is obsolete`
# s.b. is obsolete
\bis obsolescent\b
# Should be `it's` or `its`
(?<![.'])\bits[']
# s.b. it's or its
\bits[']
# Should be `its`
\bit's(?= (?:child|only purpose|own(?:er|)|parent|sibling)\b)
# s.b. opt-in
(?<!\sfor)\sopt in\s
# Should be `for its` (possessive) or `because it is`
\bfor it(?:'s| is)\b
# Should be `lends`
\bleads(?= credence)
# Should be `log in`
\blogin to the
# Should be `long-standing`
\blong standing\b
# Should be `lose`
(?<=\bwill )loose\b
# `apt-key` is deprecated
# ... instead you should be writing a pair of files:
# ... * the gpg key added to a distinct key ring file based on your project/distro/key...
# ... * the sources.list in a district file -- not simply appended to `/etc/apt/sources.list` -- (there is a newer format [DEB822](https://manpages.debian.org/bookworm/dpkg-dev/deb822.5.en.html)) that references the gpg key.
# Consider:
# ````sh
# curl http://download.something.example.com/$DISTRO/Release.key | \
# gpg --dearmor --yes --output /usr/share/keyrings/something-distro.gpg
# echo "deb [signed-by=/usr/share/keyrings/something-distro.gpg] http://download.something.example.com/repositories/home:/$DISTRO ./" \
# >> /etc/apt/sources.list.d/something-distro.list
# ````
\bapt-key add\b
# Should be `nearby`
\bnear by\b
# Should probably be a person named `Nick` or the abbreviation `NIC`
\bNic\b
# Should be `not supposed`
\bsupposed not\b
# Should be `Once this` or `On this` or even `One that`. Rarely `One, this`
[?!.] One this\b
# Should probably be `much more`
\bmore much\b
# Should be `perform its`
\bperform it's\b
# Should be `opt-in`
(?<!\scan|for)(?<!\smust)(?<!\sif)\sopt in\s
# Should be `out-of-date` if acting as an adjective before a noun
\bout of date(?= \w{3,}\b)
# Should be `less than`
# s.b. less than
\bless then\b
# Should be `load balancer`
\b[Ll]oud balancer
# s.b. one of
\bon of\b
# Should be `moot`
\bmute point\b
# Should be `one of`
(?<!-)\bon of\b
# Should be `on the other hand`
\b(?i)on another hand\b
# Reword to `on at runtime` or `enabled at launch`
# The former if you mean it can be changed dynamically.
# The latter if you mean that it can be changed without recompiling but not after the program starts.
\bswitched on runtime\b
# Should be `Of course,`
[?.!]\s+Of course\s(?=[-\w\s]+[.?;!,])
# Most people only have two hands. Reword.
\b(?i)on the third hand\b
# Should be `Open Graph`
# unless talking about a specific Open Graph implementation:
# - Java
# - Node
# - Py
# - Ruby
\bOpenGraph\b
# Should be `OpenShift`
\bOpenshift\b
# Should be `otherwise`
# s.b. otherwise
\bother[- ]wise\b
# Should be `; otherwise` or `. Otherwise`
# https://study.com/learn/lesson/otherwise-in-a-sentence.html
, [Oo]therwise\b
# Should probably be `Otherwise,`
(?<=\. )Otherwise\s
# Should be `or (more|less)`
# s.b. or (more|less)
\bore (?:more|less)\b
# Should be `or`
\b(?i)true of .*false\b
# Should be `pale`
\b(?<=beyond the )pail\b
# Should be reworded.
# `passthrough` is an adjective
# `pass-through` could be a noun
# `pass through` would be a verb phrase
\b(?i)passthrough(?= an?\b)
# Should be `rather than`
\brather then\b
# Should be `Red Hat`
\bRed[Hh]at\b
# Should be `regardless, ...` or `regardless of (whether)`
\b[Rr]egardless if you\b
# Should be `self-signed`
\bself signed\b
# Should be `SendGrid`
\bSendgrid\b
# Should be `set up` (`setup` is a noun / `set up` is a verb)
\b[Ss]etup(?= (?:an?|the)\b)
# Should be `state`
\bsate(?=\b|[A-Z])|(?<=[a-z])Sate(?=\b|[A-Z])|(?<=[A-Z]{2})Sate(?=\b|[A-Z])
# Should be `this`
\b[Tt]oday(?= morning\b)
# Should be `let's` or `let us`
\b[Ll]ets (?=throw\.)
# Should be `no longer needed`
\bno more needed\b(?! than\b)
# Should be `<see|look> below for the`
(?i)\bfind below the\b
# Should be `then any` unless there's a comparison before the `,`
, than any\b
# Should be `did not exist`
\bwere not existent\b
# Should be `nonexistent`
# s.b. nonexistent
\bnon existing\b
# Should be `nonexistent`
\b[Nn]o[nt][- ]existent\b
# Should be `our`
\bspending out time\b
# Should be `@brief` / `@details` / `@param` / `@return` / `@retval`
# s.b. brief / details/ param / return / retval
(?:^\s*|(?:\*|//|/*)\s+`)[\\@](?:breif|(?:detail|detials)|(?:params(?!\.)|prama?)|ret(?:uns?)|retvl)\b
# Should be `more than` or `more, then`
\bmore then\b
# Should be `Pipeline`/`pipeline`
(?:(?<=\b|[A-Z])p|P)ipeLine(?:\b|(?=[A-Z]))
# Should be `preexisting`
# s.b. preexisting
[Pp]re[- ]existing
# Should be `preempt`
# s.b. preempt
[Pp]re[- ]empt\b
# Should be `preemptively`
# s.b. preemptively
[Pp]re[- ]emptively
# Should be `prepopulate`
[Pp]re[- ]populate
# Should be `prerequisite`
[Pp]re[- ]requisite
# Should be `QuickTime`
\bQuicktime\b
# Should be `recently changed` or `recent changes`
# s.b. recently changed or recent changes
[Rr]ecent changed
# Should be `reentrancy`
# s.b. reentrancy
[Rr]e[- ]entrancy
# Should be `reentrant`
# s.b. reentrant
[Rr]e[- ]entrant
# Should be `room for`
\brooms for (?!lease|rent|sale)
# Should be `socioeconomic`
# https://dictionary.cambridge.org/us/dictionary/english/socioeconomic
socio-economic
# Should be `strong suit`
\b(?:my|his|her|their) strong suite\b
# Should probably be `temperatures` unless actually talking about thermal drafts (things birds may fly on)
\bthermals\b
# Should be `there are` or `they are` (or `they're`)
(?i)\btheir are\b
# Should be `understand`
# s.b. understand
\bunder stand\b
# Should be `URI` or `uri` unless it refers to a person named `Uri` (or a flag)
#(?<![-\.])\bUri\b(?![(])
# Should be `true`
(?i)(?<![\[\]()])\brue(?:= or false)
# Should be `it uses is`
/\bis uses is\b/
# Should be `uses it as`
(?:^|\. |and )uses is as (?!an?\b|follows|livestock|[^.]+\s+as\b)
# Should be `was`
\bhas been(?= removed in v?\d)
# Should be `where`
\bwere they are\b
# Should be `why`
, way(?= is [^.]*\?)
# should be `vCenter`
\bV[Cc]enter\b
# Should be `VM`
\bVm\b
# Should be `walkthrough(s)`
\bwalk-throughs?\b
# Should be `want`
\bdon't ant\b
# Should be `we'll`
\bwe 'll\b
# Should be `week`
# unless you're really talking about people or pointers
\bevery weak[.,?!]
# Should be `well`
\b[Yy]ou(?:'re| are) doing good\b
# Should be `whereas`
\bwhere as\b
# Should be `WinGet`
\bWinget\b
# Should be `without` (unless `out` is a modifier of the next word)
\bwith out\b(?!-)
# Should be `work around`
\b[Ww]orkaround(?= an?\b)
# Should be `workarounds`
# s.b. workarounds
#\bwork[- ]arounds\b
# Should be `workaround`
# s.b. workaround
(?:(?:[Aa]|[Tt]he|ugly)\swork[- ]around\b|\swork[- ]around\s+for)
# Should be `worst`
(?i)worse-case
# Should be `you are not` or reworded
\byour not\b
# Should be `(coarse|fine)-grained`
# s.b. (coarse|fine)-grained
\b(?:coarse|fine) grained\b
# Homoglyph (Cyrillic) should be `A`/`B`/`C`/`E`/`H`/`I`/`I`/`J`/`K`/`M`/`O`/`P`/`S`/`T`/`Y`
# It's possible that your content is intentionally mixing Cyrillic and Latin scripts, but if it isn't, you definitely want to correct this.
(?<=[A-Z]{2})[АВСЕНІӀЈКМОРЅТУ]|[АВСЕНІӀЈКМОРЅТУ](?=[A-Z]+(?:\b|[a-z]+)|[a-z]+(?:[^a-z]|$))
# s.b. neither/nor -- or reword
#\bnot\b[^.?!"/(]+\bnor\b
# Homoglyph (Cyrillic) should be `a`/`b`/`c`/`e`/`o`/`p`/`x`/`y`
# It's possible that your content is intentionally mixing Cyrillic and Latin scripts, but if it isn't, you definitely want to correct this.
[авсеорху](?=[A-Za-z]{2,})|(?<=[A-Za-z]{2})[авсеорху]|(?<=[A-Za-z])[авсеорху](?=[A-Za-z])
# probably a double negative
# s.b. neither/nor (plus rewording the beginning)
\bnot\b[^.?!"/]*\bneither\b[^.?!"/(]*\bnor\b
# Should be `neither/nor` -- or reword
#(?<!do )\bnot\b([^.?!"/(](?!neither|,.*?,))+\bnor\b
# Should be `neither/nor` (plus rewording the beginning)
# This is probably a double negative...
\bnot\b[^.?!"/(]*\bneither\b[^.?!"/(]*\bnor\b
# In English, duplicated words are generally mistakes
# There are a few exceptions (e.g. "that that").
# If the highlighted doubled word pair is in:
# * code, write a pattern to mask it.
# * prose, have someone read the English before you dismiss this error.
# In English, it is generally wrong to have the same word twice in a row without punctuation.
# Duplicated words are generally mistakes.
# There are a few exceptions where it is acceptable (e.g. "that that").
# If the highlighted doubled word pair is in a code snippet, you can write a pattern to mask it.
# If the highlighted doubled word pair is in prose, have someone read the English before you dismiss this error.
\s([A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})\s\g{-1}\s

View File

@@ -1,41 +1,125 @@
# See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns
# marker to ignore all code on line
^.*/\* #no-spell-check-line \*/.*$
# marker for ignoring a comment to the end of the line
// #no-spell-check.*$
# Automatically suggested patterns
# hit-count: 3011 file-count: 842
# IServiceProvider / isAThing
\b(?:I|isA)(?=(?:[A-Z][a-z]{2,})+\b)
# Gaelic
Gàidhlig
# hit-count: 2239 file-count: 134
# hex runs
\b[0-9a-fA-F]{16,}\b
Ov_erwrite
# hit-count: 1868 file-count: 1
# sha-... -- uses a fancy capture
(\\?['"]|&quot;)[0-9a-f]{40,}\g{-1}
# languageHashTable
"\w+(?:-\w+|)"\s+=\s+@\(".*"\)
# hit-count: 1100 file-count: 97
# base64 encoded content, possibly wrapped in mime
(?:^|[\s=;:?])[-a-zA-Z=;:/0-9+]{50,}(?:[\s=;:?]|$)
# Regular expression with `\b`
\\b(?=[a-z]\S*\{)
# hit-count: 426 file-count: 165
# GitHub SHAs (markdown)
(?:\[`?[0-9a-f]+`?\]\(https:/|)/(?:www\.|)github\.com(?:/[^/\s"]+){2,}(?:/[^/\s")]+)(?:[0-9a-f]+(?:[-0-9a-zA-Z/#.]*|)\b|)
# long lorem
L"Lorem.*"
# hit-count: 331 file-count: 117
# hex digits including css/html color classes:
(?:[\\0][xX]|\\u|[uU]\+|#x?|\%23)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|[iu]\d+)\b
# hit-count: 275 file-count: 45
# version suffix <word>v#
(?:(?<=[A-Z]{2})V|(?<=[a-z]{2}|[A-Z]{2})v)\d+(?:\b|(?=[a-zA-Z_]))
# hit-count: 209 file-count: 97
# w3
\bw3\.org/[-0-9a-zA-Z/#.]+
# hit-count: 137 file-count: 38
# alternate markers if you run into latex and friends
(?<!\\)\\[nrt](?=[a-z]{2,})(?=.*['"`](?:, "[^{]|\]))
# tabs in c#
\$"\\t
# Hexadecimal character pattern in code
\\x[0-9a-fA-F]{4}
fontFamily": ".*"
D[23]D(?=[A-Z][a-z])
(?<=[a-z])3D(?=[A-Z])
\.monitorId = \{ .*\}
json::value\(L"\S+"
\\x[0-9a-fA-F][0-9a-fA-F]
# windows line breaks in strings
\\r\\n(?=[A-Za-z])
\\r\\n
# hit-count: 104 file-count: 43
# regex choice
\(\?:[^)]+\|[^)]+\)
# hit-count: 76 file-count: 28
# base64 encoded content
([`'"])[-a-zA-Z=;:/0-9+]+=\g{-1}
# hit-count: 70 file-count: 5
# Contributor
\[[^\]]+\]\(https://github\.com/[^/\s"]+/?\)
# hit-count: 28 file-count: 22
# stackexchange -- https://stackexchange.com/feeds/sites
\b(?:askubuntu|serverfault|stack(?:exchange|overflow)|superuser).com/(?:questions/\w+/[-\w]+|a/)
# hit-count: 21 file-count: 2
# Update Lorem based on your content (requires `ge` and `w` from https://github.com/jsoref/spelling; and `review` from https://github.com/check-spelling/check-spelling/wiki/Looking-for-items-locally )
# grep '^[^#].*lorem' .github/actions/spelling/patterns.txt|perl -pne 's/.*i..\?://;s/\).*//' |tr '|' "\n"|sort -f |xargs -n1 ge|perl -pne 's/^[^:]*://'|sort -u|w|sed -e 's/ .*//'|w|review -
# Warning, while `(?i)` is very neat and fancy, if you have some binary files that aren't proper unicode, you might run into:
## Operation "substitution (s///)" returns its argument for non-Unicode code point 0x1C19AE (the code point will vary).
## You could manually change `(?i)X...` to use `[Xx]...`
## or you could add the files to your `excludes` file (a version after 0.0.19 should identify the file path)
# Lorem
(?:\w|\s|[,.])*\b(?i)(?:amet|consectetur|cursus|dolor|eros|ipsum|lacus|libero|ligula|lorem|magna|neque|nulla|suscipit|tempus)\b(?:\w|\s|[,.])*
# hit-count: 18 file-count: 15
# microsoft
\b(?:https?://|)(?:(?:download\.visualstudio|docs|msdn2?|research)\.microsoft|blogs\.msdn)\.com/[-_a-zA-Z0-9()=./%]*
# hit-count: 14 file-count: 5
# githubusercontent
/[-a-z0-9]+\.githubusercontent\.com/[-a-zA-Z0-9?&=_\/.]*
# hit-count: 14 file-count: 3
# node packages
(["'])\@[^/'" ]+/[^/'" ]+\g{-1}
# hit-count: 10 file-count: 4
# URL escaped characters
\%[0-9A-F][A-F]
# hit-count: 9 file-count: 5
# Wikipedia
\ben\.wikipedia\.org/wiki/[-\w%.#]+
# hit-count: 6 file-count: 3
# css url wrappings
\burl\([^)]+\)
# hit-count: 5 file-count: 3
# vs devops
\bvisualstudio.com(?::443|)/[-\w/?=%&.]*
# hit-count: 4 file-count: 3
# Non-English
[a-zA-Z]*[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3}[a-zA-ZÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]*|[a-zA-Z]{3,}[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]|[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3,}
# hit-count: 4 file-count: 2
# data url in quotes
([`'"])data:(?:[^ `'"].*?|)(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,}).*\g{-1}
# hit-count: 2 file-count: 2
# mailto urls
mailto:[-a-zA-Z=;:/?%&0-9+@.]{3,}
# msdn
\b(?:download\.visualstudio|docs|msdn|learn)\.microsoft\.com/[-_a-zA-Z0-9()=./]*
aka\.ms/[a-zA-Z0-9]+
# hit-count: 2 file-count: 1
# While you could try to match `http://` and `https://` by using `s?` in `https?://`, sometimes there
# YouTube url
\b(?:(?:www\.|)youtube\.com|youtu.be)/(?:channel/|embed/|user/|playlist\?list=|watch\?v=|v/|)[-a-zA-Z0-9?&=_%]*
# power shell gallery website
\bpowershellgallery.com/[-_a-zA-Z0-9()=./%]*
@@ -45,199 +129,55 @@ L?(["']|[-<({>]|\b)[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{10,12}(?:\g{
(?:L"[abAB]+", ){3}L"[abAB]+"
\. (?: @[-A-Za-z\d]+\b(?!\.[A-Z]),?)+
auto deviceId = L".*"
deviceId(?:\.id|) = L".*"
StringComparer.OrdinalIgnoreCase\) \{.*\}
# namespaces
\b[a-z]+::
"Author": ".+"
(?:Include|Link)=".*?"
# You could ignore `xmlns`, but it's probably better to enforce rules about them...
#\s(?:xmlns:[a-z]+(?:[A-Z][a-z]+|)=|[a-z]+(?:[A-Z][a-z]+):(?=[a-z]+=))
# hit-count: 1 file-count: 1
# marker to ignore all code on line
^.*/\* #no-spell-check-line \*/.*$
# UnitTests
\[DataRow\(.*\)\]
# AdditionalDependencies
<AdditionalDependencies>.*<
# the last line of mimetype="application/x-microsoft.net.object.bytearray.base64" things in .resx files
^\s*[-a-zA-Z=;:/0-9+]*[-a-zA-Z;:/0-9+][-a-zA-Z=;:/0-9+]*=$
# Automatically suggested patterns
# hit-count: 5402 file-count: 1339
# IServiceProvider / isAThing
(?:(?:\b|_|(?<=[a-z]))[IT]|(?:\b|_)(?:nsI|isA))(?=(?:[A-Z][a-z]{2,})+(?:[A-Z\d]|\b))
# hit-count: 2073 file-count: 842
# #includes
^\s*#include\s*(?:<.*?>|".*?")
# hit-count: 1639 file-count: 855
# C# includes
^\s*using [^;]+;
# hit-count: 1491 file-count: 693
# microsoft
\b(?:https?://|)(?:(?:(?:blogs|download\.visualstudio|docs|msdn2?|research)\.|)microsoft|blogs\.msdn)\.co(?:m|\.\w\w)/[-_a-zA-Z0-9()=./%]*
# hit-count: 398 file-count: 133
# hex digits including css/html color classes:
(?:[\\0][xX]|\\u\{?|[uU]\+|#x?|%23|&H)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|[iu]\d+)\b
# hit-count: 339 file-count: 146
# hex runs
\b[0-9a-fA-F]{16,}\b
# hit-count: 253 file-count: 100
# GitHub SHAs (markdown)
(?:\[`?[0-9a-f]+`?\]\(https:/|)/(?:www\.|)github\.com(?:/[^/\s"]+){2,}(?:/[^/\s")]+)(?:[0-9a-f]+(?:[-0-9a-zA-Z/#.]*|)\b|)
# hit-count: 241 file-count: 37
# version suffix <word>v#
(?:(?<=[A-Z]{2})V|(?<=[a-z]{2}|[A-Z]{2})v)\d+(?:\b|(?=[a-zA-Z_]))
# hit-count: 141 file-count: 6
# Contributor / Project
\[[^\]\s]+\]\(https://github\.com/[^)]+\)(?: -(?: [A-Z]\S+)+|)|\[[^\]]+\]\(https://github\.com/(?:[^/\s"]+/?){1,2}\)
https://github.com/(?:[-\w]+/?){1,2}
# hit-count: 131 file-count: 125
# Repeated letters
\b([a-z])\g{-1}{2,}\b
# hit-count: 99 file-count: 97
# w3
\bw3\.org/[-0-9a-zA-Z/#.]+
# hit-count: 59 file-count: 11
# Markdown anchor links
\(#\S*?[a-zA-Z]\S*?\)
# hit-count: 29 file-count: 23
# stackexchange -- https://stackexchange.com/feeds/sites
\b(?:askubuntu|serverfault|stack(?:exchange|overflow)|superuser).com/(?:questions/\w+/[-\w]+|a/)
# hit-count: 24 file-count: 11
# Library prefix
# e.g., `lib`+`archive`, `lib`+`raw`, `lib`+`unwind`
# (ignores some words that happen to start with `lib`)
(?:\b|_)[Ll]ib(?:re(?=office)|)(?!era[lt]|ero|erty|rar(?:i(?:an|es)|y))(?=[a-z])
# hit-count: 20 file-count: 2
# Intel intrinsics
_mm\d*_(?!dd)\w+
# hit-count: 15 file-count: 8
# Wikipedia
\ben\.wikipedia\.org/wiki/[-\w%.#]+
# hit-count: 14 file-count: 10
# vs devops
\bvisualstudio.com(?::443|)/[-\w/?=%&.]*
# hit-count: 8 file-count: 2
# copyright
Copyright (?:\([Cc]\)|)(?:[-\d, ]|and)+(?: [A-Z][a-z]+ [A-Z][a-z]+,?)+
# hit-count: 8 file-count: 1
# css fonts
\bfont(?:-family|):[^;}]+
aka\.ms/[a-zA-Z0-9]+
# hit-count: 8 file-count: 1
# kubernetes crd patterns
^\s*pattern: .*$
# hit-count: 5 file-count: 3
# URL escaped characters
%[0-9A-F][A-F](?=[A-Za-z])
# hit-count: 3 file-count: 3
# githubusercontent
/[-a-z0-9]+\.githubusercontent\.com/[-a-zA-Z0-9?&=_\/.]*
# hit-count: 2 file-count: 2
# medium
\bmedium\.com/@?[^/\s"]+/[-\w:/*.]+
# hit-count: 2 file-count: 1
# While you could try to match `http://` and `https://` by using `s?` in `https?://`, sometimes there
# YouTube url
\b(?:(?:www\.|)youtube\.com|youtu.be)/(?:channel/|embed/|user/|playlist\?list=|watch\?v=|v/|)[-a-zA-Z0-9?&=_%]*
# hit-count: 2 file-count: 1
# GitHub actions
\buses:\s+[-\w.]+/[-\w./]+@[-\w.]+
# D2D
D?2D
# hit-count: 1 file-count: 1
# curl arguments
\b(?:\\n|)curl(?:\.exe|)(?:\s+-[a-zA-Z]{1,2}\b)*(?:\s+-[a-zA-Z]{3,})(?:\s+-[a-zA-Z]+)*
# GHSA
GHSA(?:-[0-9a-z]{4}){3}
# hit-count: 1 file-count: 1
# medium
\bmedium\.com/\@?[^/\s"]+/[-\w]+
# hit-count: 1 file-count: 1
# kubectl.kubernetes.io/last-applied-configuration
"kubectl.kubernetes.io/last-applied-configuration": ".*"
# hit-count: 1 file-count: 1
# tar arguments
\b(?:\\n|)g?tar(?:\.exe|)(?:(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+
# #pragma lib
^\s*#pragma comment\(lib, ".*?"\)
# UnitTests
\[DataRow\(.*\)\]
# AdditionalDependencies
<AdditionalDependencies>.*<
# the last line of mimetype="application/x-microsoft.net.object.bytearray.base64" things in .resx files
^\s*[-a-zA-Z=;:/0-9+]*[-a-zA-Z;:/0-9+][-a-zA-Z=;:/0-9+]*=$
RegExp\(@?([`'"]).*?\g{-1}\)|(?:escapes|regEx):\s*(?:/.*/|([`'"]).*?\g{-1})|return/.*?/
\bSecur32
# Questionably acceptable forms of `in to`
# Personally, I prefer `log into`, but people object
# https://www.tprteaching.com/log-into-log-in-to-login/
\b(?:(?:[Ll]og(?:g(?=[a-z])|)|[Ss]ign)(?:ed|ing)?) in to\b
\b(?:[Ll]og|[Ss]ign) in to\b
# to opt in
\bto opt in\b
# pass(ed|ing) in
\bpass(?:ed|ing) in\b
# acceptable duplicates
# ls directory listings
[-bcdlpsw](?:[-r][-w][-SsTtx]){3}[\.+*]?\s+\d+\s+\S+\s+\S+\s+[.\d]+(?:[KMGT]|)\s+
[-bcdlpsw](?:[-r][-w][-Ssx]){3}\s+\d+\s+\S+\s+\S+\s+\d+\s+
# mount
\bmount\s+-t\s+(\w+)\s+\g{-1}\b
# C types and repeated CSS values
\s(auto|buffalo|center|div|inherit|long|LONG|none|normal|solid|thin|transparent|very)(?:\s\g{-1})+\s
# C enum and struct
\b(?:enum|struct)\s+(\w+)\s+\g{-1}\b
\s(auto|center|div|inherit|long|LONG|none|normal|solid|thin|transparent|very)(?: \g{-1})+\s
# C struct
\bstruct\s+(\w+)\s+\g{-1}\b
# go templates
\s(\w+)\s+\g{-1}\s+\`(?:graphql|inject|json|yaml):
# doxygen / javadoc / .net
(?:[\\@](?:brief|defgroup|groupname|link|t?param|return|retval)|(?:public|private|\[Parameter(?:\(.+\)|)\])(?:\s+(?:static|override|readonly|required|virtual))*)(?:\s+\{\w+\}|)\s+(\w+)\s+\g{-1}\s
# macOS file path
(?:Contents\W+|(?!iOS)/)MacOS\b
# Python package registry has incorrect spelling for macOS / Mac OS X
"Operating System :: MacOS :: MacOS X"
# "company" in Germany
\bGmbH\b
# IntelliJ
\bIntelliJ\b
(?:[\\@](?:brief|groupname|t?param|return|retval)|(?:public|private|\[Parameter(?:\(.+\)|)\])(?:\s+static|\s+override|\s+readonly)*)(?:\s+\{\w+\}|)\s+(\w+)\s+\g{-1}\s
# Commit message -- Signed-off-by and friends
^\s*(?:(?:Based-on-patch|Co-authored|Helped|Mentored|Reported|Reviewed|Signed-off)-by|Thanks-to): (?:[^<]*<[^>]*>|[^<]*)\s*$
@@ -250,42 +190,3 @@ _SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING
# ignore long runs of a single character:
\b([A-Za-z])\g{-1}{3,}\b
# hit-count: 1 file-count: 1
# Amazon
\bamazon\.com/[-\w]+/(?:dp/[0-9A-Z]+|)
# hit-count: 3 file-count: 3
# imgur
\bimgur\.com/[^.]+
# Process Process (typename varname)
Process Process
# ZoomIt menu items with accelerator keys
E&xit
St&yle
# This matches a relative clause where the relative pronoun "that" is omitted.
# Example: "Gets or sets the window the TitleBar should configure."
\bthe\s+\w+\s+the\b
# 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+

View File

@@ -1,23 +1,11 @@
^attache$
^bellows?$
^bellow$
benefitting
occurences?
^dependan.*
^develope$
^developement$
^developpe
^Devers?$
^devex
^devide
^Devinn?[ae]
^devisal
^devisor
^diables?$
^oer$
Sorce
^[Ss]pae.*
^Teh$
^untill$
^untilling$
^venders?$
^wether.*

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -1,36 +0,0 @@
---
description: 'PowerToys AI contributor guidance'
---
# PowerToys Copilot Instructions
Concise guidance for AI contributions. For complete details, see [AGENTS.md](../AGENTS.md).
## Key Rules
- Atomic PRs: one logical change, no drive-by refactors
- Add tests when changing behavior
- Keep hot paths quiet (no logging in hooks/tight loops)
## Style Enforcement
- C#: `src/.editorconfig`, StyleCop.Analyzers
- C++: `src/.clang-format`
- XAML: XamlStyler
## When to Ask for Clarification
- Ambiguous spec after scanning docs
- Cross-module impact unclear
- Security, elevation, or installer changes
## Component-Specific Instructions
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)

View File

@@ -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)

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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
};
}
}
);
```

View File

@@ -236,22 +236,11 @@ configuration:
- if:
- payloadType: Issue_Comment
- commentContains:
pattern: "I(( would|'d) (like|love|be happy)| want) (to help|helping|to contribute|contributing|to implement|implementing|to fix|fixing)"
pattern: 'I would [like|love] [to help|helping|to contribute|contributing|to implement|implementing|to fix|fixing]'
isRegex: True
then:
- addReply:
reply: Hi! Your last comment indicates to our system, that you might want to contribute to this feature/fix this bug. Thank you! Please make us aware on our ["Would you like to contribute to PowerToys?" thread](https://github.com/microsoft/PowerToys/issues/28769), as we don't see all the comments. <br /><br />_I'm a bot (beep!) so please excuse any mistakes I may make_
description:
- if:
- payloadType: Issues
- isAction:
action: Opened
- bodyContains:
pattern: 'Area\(s\) with issue\?\s*\nWorkspaces'
isRegex: True
then:
- addLabel:
label: Product-Workspaces
description:
onFailure:
onSuccess:

View File

@@ -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`

View File

@@ -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.

View File

@@ -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

View File

@@ -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.

View File

@@ -1,24 +0,0 @@
---
agent: 'agent'
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.
**Prerequisites:**
- Install GitHub CLI if it is not present: `winget install GitHub.cli`.
- Run `gh auth login` once before the first CLI use.
**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.

View File

@@ -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 (0100) — 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.51d, S 12, M 24, L 47, XL 714, XXL 1430, 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.

View File

@@ -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 machinereadable 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 24 bullet highlights.
### Iteration ITERATION
- <Latest key point 1>
- <Latest key point 2>
## Checks executed
- List the concrete checks for *this step only* (510 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 & multifile issues
- Map headside lines from `patch` hunks (`@@ -a,b +c,d @@` → new lines `+c..+c+d-1`).
- For crossfile 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 isnt available, still write all files.
## Constraint
Read/analyze only; don't modify code. Keep comments small, specific, and fixoriented.
**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 ~300600 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.

View File

@@ -4,11 +4,10 @@
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
- [ ] Closes: #xxx
<!-- - [ ] Closes: #yyy (add separate lines for additional resolved issues) -->
- [ ] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected
- [ ] **Closes:** #xxx
- [ ] **Communication:** I've discussed this with core contributors already. If work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Localization:** All end user facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries
@@ -17,7 +16,7 @@
- [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx
<!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here -->
<!-- Provide a more detailed description of the PR, other things fixed or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->

View File

@@ -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

View File

@@ -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 $_ }
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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.

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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).

View File

@@ -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.

View File

@@ -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).

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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

View File

@@ -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.

View File

@@ -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 |

View File

@@ -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`

View File

@@ -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.

View File

@@ -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

View File

@@ -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": ["*"]
}
}
}

View File

@@ -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
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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).

View File

@@ -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": ["*"]
}
}
}

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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) |

View File

@@ -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": ["*"]
}
}
}

View File

@@ -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 (0100) — 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.51d, S 12, M 24, L 47, XL 714, XXL 1430, 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.

View File

@@ -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"

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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 30120 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 |

Some files were not shown because too many files have changed in this diff Show More