claude-dev-env 1.31.0 → 1.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/hooks/blocking/code_rules_enforcer.py +109 -0
- package/hooks/blocking/test_windows_rmtree_blocker.py +155 -0
- package/hooks/blocking/windows_rmtree_blocker.py +102 -0
- package/hooks/config/hook_log_extractor_constants.py +13 -0
- package/hooks/config/session_env_cleanup_constants.py +20 -0
- package/hooks/config/test_hook_log_extractor_constants.py +27 -0
- package/hooks/config/test_session_env_cleanup_constants.py +60 -0
- package/hooks/diagnostic/hook_log_stop_wrapper.py +107 -19
- package/hooks/diagnostic/test_hook_log_stop_wrapper.py +258 -11
- package/hooks/hooks.json +15 -0
- package/hooks/session/session_env_cleanup.py +130 -0
- package/hooks/session/test_session_env_cleanup.py +280 -0
- package/package.json +1 -1
- package/rules/windows-filesystem-safe.md +91 -0
- package/skills/bugteam/PROMPTS.md +39 -0
- package/skills/bugteam/SKILL.md +49 -1
- package/skills/bugteam/SKILL_EVALS.md +1 -1
- package/skills/bugteam/reference/copilot-gap-analysis.md +496 -0
- package/skills/bugteam/reference/teardown-publish-permissions.md +1 -1
- package/skills/bugteam/scripts/README.md +17 -0
- package/skills/bugteam/scripts/bugteam_code_rules_gate.py +94 -0
- package/skills/bugteam/scripts/bugteam_fix_hookspath.py +260 -0
- package/skills/bugteam/scripts/config/__init__.py +0 -0
- package/skills/bugteam/scripts/config/bugteam_fix_hookspath_constants.py +17 -0
- package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +267 -0
- package/skills/logifix/SKILL.md +69 -0
- package/skills/logifix/scripts/logifix.ps1 +205 -0
- package/skills/rebase/SKILL.md +164 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: logifix
|
|
3
|
+
description: Restore the Logitech Gaming Software (LCore) tray icon when it disappears on Windows. Calls a PowerShell script that reproduces the verified Session 2 recovery procedure from 2026-04-25. Triggers on "logifix", "/logifix", "logitech tray icon missing", "LCore tray gone", "logitech is not loaded".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# logifix
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Restore the Logitech Gaming Software (LCore) tray icon by reproducing the verified recovery procedure documented in `sessions/System Support/2. Logitech Tray Icon Fix Recurrence.md`.
|
|
11
|
+
|
|
12
|
+
**Announce at start:** "Running /logifix to restore the LCore tray icon."
|
|
13
|
+
|
|
14
|
+
## What the script does
|
|
15
|
+
|
|
16
|
+
`scripts/logifix.ps1` runs this sequence (verified during Session 2 on 2026-04-25):
|
|
17
|
+
|
|
18
|
+
1. **State snapshot.** Explorer instances per session, Logitech services state, LCore process state.
|
|
19
|
+
2. **Stop LCore in user context.** `Stop-Process -Name LCore -Force`.
|
|
20
|
+
3. **Single elevated UAC step:**
|
|
21
|
+
- `Start-Service -Name LogiRegistryService` (handles the recurrence case where the service is Stopped).
|
|
22
|
+
- `Stop-Process -Name explorer -Force`.
|
|
23
|
+
- **Does NOT** call `Start-Process explorer` from inside the elevated block. Windows shell auto-respawn handles the user-session explorer cleanly. Skipping this line is the operative fix discovered in Session 2 — including it created a duplicate admin-context `explorer.exe` (no resolvable owner, parent = the admin pwsh) that blocked LCore tray registration.
|
|
24
|
+
4. **Wait for Windows shell auto-respawn** (default 5 seconds).
|
|
25
|
+
5. **Verify exactly one explorer in the user's session.**
|
|
26
|
+
6. **Perform `LCoreRelaunchAttemptCount` full stop-then-launch cycles of `LCore.exe /minimized`** (default 2). The full count always runs — a responsive LCore on the first attempt does NOT imply the tray icon registered. Per Session 2 gotcha #2, the first relaunch can leave LCore responsive with no tray icon; only a second stop+launch reliably triggers `Shell_NotifyIcon` registration.
|
|
27
|
+
7. **Final state snapshot.**
|
|
28
|
+
|
|
29
|
+
## Invocation
|
|
30
|
+
|
|
31
|
+
From Claude Code:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
/logifix
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Direct PowerShell:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
pwsh -File "$HOME\.claude\skills\logifix\scripts\logifix.ps1"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Optional parameters (all have safe defaults):
|
|
44
|
+
|
|
45
|
+
- `-ExplorerAutoRespawnWaitSeconds <int>` — wait after the elevated kill (default 5).
|
|
46
|
+
- `-LCoreInitializationWaitSeconds <int>` — wait after each LCore relaunch before checking responsiveness (default 5).
|
|
47
|
+
- `-LCoreRelaunchAttemptCount <int>` — number of LCore stop+launch cycles to perform (default 2). Always runs the full count.
|
|
48
|
+
|
|
49
|
+
## When to use
|
|
50
|
+
|
|
51
|
+
- LCore tray icon missing (also confirmed absent from the overflow `^` chevron).
|
|
52
|
+
- LCore process is running but the tray icon never appears.
|
|
53
|
+
- After resume from sleep, system restart, or a Logitech service crash that leaves LCore in a half-loaded state.
|
|
54
|
+
|
|
55
|
+
## Fallback (if the script does not restore the icon)
|
|
56
|
+
|
|
57
|
+
If `/logifix` reports that UAC was canceled, or LCore is still not responding after both relaunch cycles:
|
|
58
|
+
|
|
59
|
+
1. **Ctrl+Shift+Esc** → Task Manager.
|
|
60
|
+
2. Find **Windows Explorer** → right-click → **Restart**.
|
|
61
|
+
3. Re-run `/logifix`.
|
|
62
|
+
|
|
63
|
+
The Task Manager restart path is guaranteed to hit the correct interactive session and elevation context regardless of how the calling shell was launched. In Session 2, elevated calls from Claude Code's PowerShell tool could not be confirmed to reach the user's interactive Session 1 — Task Manager bypasses that ambiguity.
|
|
64
|
+
|
|
65
|
+
## Source
|
|
66
|
+
|
|
67
|
+
Procedure verified during Session 2 (Logitech Tray Icon Fix Recurrence), 2026-04-25. The session log lists the verified command set, the gotcha catalog, and the final process/service state.
|
|
68
|
+
|
|
69
|
+
The "always run the full relaunch count" behavior was added on 2026-04-26 after `/logifix` reported success on the first responsive attempt but the tray icon never appeared — the original early-break optimization conflicted with documented Session 2 gotcha #2 (responsive LCore, no tray icon).
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Restores the Logitech Gaming Software (LCore) tray icon when it is missing on Windows.
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Reproduces the verified recovery procedure from
|
|
7
|
+
sessions/System Support/2. Logitech Tray Icon Fix Recurrence.md (2026-04-25):
|
|
8
|
+
1. Stops LCore in user context.
|
|
9
|
+
2. Single elevated UAC step:
|
|
10
|
+
- Starts LogiRegistryService (handles the case where it is Stopped).
|
|
11
|
+
- Stops explorer.exe and lets Windows shell auto-respawn rebuild it.
|
|
12
|
+
Deliberately omits Start-Process explorer from the elevated block to
|
|
13
|
+
avoid the duplicate admin-context explorer that blocked tray registration
|
|
14
|
+
during Session 2.
|
|
15
|
+
3. Waits for shell auto-respawn.
|
|
16
|
+
4. Verifies a single user-session explorer.
|
|
17
|
+
5. Performs LCoreRelaunchAttemptCount full stop-then-launch cycles of
|
|
18
|
+
LCore /minimized. Always runs the full count: a responsive LCore on the
|
|
19
|
+
first attempt does NOT imply Shell_NotifyIcon registered, per Session 2
|
|
20
|
+
gotcha #2 (responsive process, no tray icon).
|
|
21
|
+
|
|
22
|
+
.PARAMETER ExplorerAutoRespawnWaitSeconds
|
|
23
|
+
Seconds to wait after the elevated explorer kill before verifying respawn.
|
|
24
|
+
|
|
25
|
+
.PARAMETER LCoreInitializationWaitSeconds
|
|
26
|
+
Seconds to wait after each LCore relaunch before checking responsiveness.
|
|
27
|
+
|
|
28
|
+
.PARAMETER LCoreRelaunchAttemptCount
|
|
29
|
+
Total number of LCore relaunch cycles to perform (default 2). The full count
|
|
30
|
+
always runs, because the documented failure mode is a responsive LCore that
|
|
31
|
+
never registered a tray icon -- only a second stop+launch reliably triggers
|
|
32
|
+
Shell_NotifyIcon registration.
|
|
33
|
+
#>
|
|
34
|
+
|
|
35
|
+
[CmdletBinding()]
|
|
36
|
+
param(
|
|
37
|
+
[int] $ExplorerAutoRespawnWaitSeconds = 5,
|
|
38
|
+
[int] $LCoreInitializationWaitSeconds = 5,
|
|
39
|
+
[int] $LCoreRelaunchAttemptCount = 2
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
Set-StrictMode -Version Latest
|
|
43
|
+
$ErrorActionPreference = 'Stop'
|
|
44
|
+
|
|
45
|
+
$script:LCoreExecutablePath = 'C:\Program Files\Logitech Gaming Software\LCore.exe'
|
|
46
|
+
$script:LCoreProcessName = 'LCore'
|
|
47
|
+
|
|
48
|
+
function Get-CurrentInteractiveSessionId {
|
|
49
|
+
[OutputType([int])]
|
|
50
|
+
param()
|
|
51
|
+
return (Get-CimInstance Win32_Process -Filter "ProcessId=$PID").SessionId
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function Get-ExplorerProcessesInSession {
|
|
55
|
+
[OutputType([object[]])]
|
|
56
|
+
param([Parameter(Mandatory)] [int] $TargetSessionId)
|
|
57
|
+
$allExplorers = Get-CimInstance Win32_Process -Filter "Name='explorer.exe'"
|
|
58
|
+
$matchingExplorers = @()
|
|
59
|
+
foreach ($eachExplorer in $allExplorers) {
|
|
60
|
+
if ($eachExplorer.SessionId -ne $TargetSessionId) { continue }
|
|
61
|
+
$ownerInfo = Invoke-CimMethod -InputObject $eachExplorer -MethodName GetOwner -ErrorAction SilentlyContinue
|
|
62
|
+
$matchingExplorers += [PSCustomObject]@{
|
|
63
|
+
ProcessId = $eachExplorer.ProcessId
|
|
64
|
+
SessionId = $eachExplorer.SessionId
|
|
65
|
+
OwnerUser = if ($ownerInfo) { $ownerInfo.User } else { $null }
|
|
66
|
+
CreationDate = $eachExplorer.CreationDate
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return ,$matchingExplorers
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function Stop-LCoreIfRunning {
|
|
73
|
+
[OutputType([void])]
|
|
74
|
+
param()
|
|
75
|
+
$stopSettleMilliseconds = 500
|
|
76
|
+
Stop-Process -Name $script:LCoreProcessName -Force -ErrorAction SilentlyContinue
|
|
77
|
+
Start-Sleep -Milliseconds $stopSettleMilliseconds
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function Invoke-ElevatedExplorerRebuild {
|
|
81
|
+
[OutputType([bool])]
|
|
82
|
+
param()
|
|
83
|
+
$elevatedScriptBlockText = @'
|
|
84
|
+
Start-Service -Name LogiRegistryService -ErrorAction SilentlyContinue
|
|
85
|
+
Stop-Process -Name explorer -Force -ErrorAction SilentlyContinue
|
|
86
|
+
'@
|
|
87
|
+
$encodedElevatedCommand = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($elevatedScriptBlockText))
|
|
88
|
+
try {
|
|
89
|
+
Start-Process -FilePath 'pwsh' `
|
|
90
|
+
-ArgumentList '-NoProfile', '-EncodedCommand', $encodedElevatedCommand `
|
|
91
|
+
-Verb RunAs `
|
|
92
|
+
-Wait `
|
|
93
|
+
-ErrorAction Stop
|
|
94
|
+
return $true
|
|
95
|
+
} catch {
|
|
96
|
+
Write-Warning "Elevated step did not run: $($_.Exception.Message)"
|
|
97
|
+
return $false
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function Start-LCoreMinimized {
|
|
102
|
+
[OutputType([void])]
|
|
103
|
+
param()
|
|
104
|
+
$launchArguments = '/minimized'
|
|
105
|
+
Start-Process -FilePath $script:LCoreExecutablePath -ArgumentList $launchArguments
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function Test-LCoreIsResponding {
|
|
109
|
+
[OutputType([bool])]
|
|
110
|
+
param([Parameter(Mandatory)] [int] $WaitSeconds)
|
|
111
|
+
Start-Sleep -Seconds $WaitSeconds
|
|
112
|
+
$lcoreProcesses = Get-Process -Name $script:LCoreProcessName -ErrorAction SilentlyContinue
|
|
113
|
+
if (-not $lcoreProcesses) { return $false }
|
|
114
|
+
foreach ($eachLCore in @($lcoreProcesses)) {
|
|
115
|
+
if (-not $eachLCore.Responding) { return $false }
|
|
116
|
+
}
|
|
117
|
+
return $true
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function Write-StateSnapshot {
|
|
121
|
+
[OutputType([void])]
|
|
122
|
+
param([Parameter(Mandatory)] [string] $Label)
|
|
123
|
+
$logitechServiceNamesToVerify = @('LogiRegistryService', 'logi_lamparray_service')
|
|
124
|
+
Write-Host ""
|
|
125
|
+
Write-Host "=== $Label ==="
|
|
126
|
+
$sessionId = Get-CurrentInteractiveSessionId
|
|
127
|
+
$explorersInSession = Get-ExplorerProcessesInSession -TargetSessionId $sessionId
|
|
128
|
+
Write-Host "Explorer instances in session ${sessionId}: $($explorersInSession.Count)"
|
|
129
|
+
if ($explorersInSession.Count -gt 0) {
|
|
130
|
+
$explorersInSession | Format-Table ProcessId, OwnerUser, CreationDate -AutoSize | Out-String | Write-Host
|
|
131
|
+
}
|
|
132
|
+
$servicesObserved = Get-Service -Name $logitechServiceNamesToVerify -ErrorAction SilentlyContinue
|
|
133
|
+
if ($servicesObserved) {
|
|
134
|
+
$servicesObserved | Format-Table Name, Status -AutoSize | Out-String | Write-Host
|
|
135
|
+
}
|
|
136
|
+
$lcoreProcessesObserved = Get-Process -Name $script:LCoreProcessName -ErrorAction SilentlyContinue
|
|
137
|
+
if ($lcoreProcessesObserved) {
|
|
138
|
+
$lcoreProcessesObserved | Select-Object Id, Responding | Format-Table -AutoSize | Out-String | Write-Host
|
|
139
|
+
} else {
|
|
140
|
+
Write-Host "LCore not running."
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function Invoke-LogifixRecovery {
|
|
145
|
+
[OutputType([void])]
|
|
146
|
+
param()
|
|
147
|
+
|
|
148
|
+
Write-StateSnapshot -Label 'Before'
|
|
149
|
+
|
|
150
|
+
if (-not (Test-Path -Path $script:LCoreExecutablePath)) {
|
|
151
|
+
Write-Error "LCore.exe not found at expected path: $script:LCoreExecutablePath"
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
Stop-LCoreIfRunning
|
|
156
|
+
|
|
157
|
+
$elevatedSucceeded = Invoke-ElevatedExplorerRebuild
|
|
158
|
+
if (-not $elevatedSucceeded) {
|
|
159
|
+
Write-Warning "UAC was canceled or the elevated step failed."
|
|
160
|
+
Write-Warning "Fallback: open Task Manager (Ctrl+Shift+Esc), find 'Windows Explorer', right-click, Restart. Then re-run /logifix."
|
|
161
|
+
return
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
Start-Sleep -Seconds $ExplorerAutoRespawnWaitSeconds
|
|
165
|
+
|
|
166
|
+
$sessionId = Get-CurrentInteractiveSessionId
|
|
167
|
+
$explorersAfterRespawn = Get-ExplorerProcessesInSession -TargetSessionId $sessionId
|
|
168
|
+
$explorerCountAfterRespawn = @($explorersAfterRespawn).Count
|
|
169
|
+
if ($explorerCountAfterRespawn -ne 1) {
|
|
170
|
+
if ($explorerCountAfterRespawn -eq 0) {
|
|
171
|
+
Write-Warning "No explorer.exe found in session $sessionId after $ExplorerAutoRespawnWaitSeconds-second wait."
|
|
172
|
+
} else {
|
|
173
|
+
Write-Warning "Expected exactly 1 explorer.exe in session $sessionId after shell auto-respawn, but found $explorerCountAfterRespawn."
|
|
174
|
+
Write-Warning "Multiple explorer.exe processes in the same session reproduce the Session 2 duplicate-explorer failure mode that blocks tray icon registration."
|
|
175
|
+
$explorersAfterRespawn | Format-Table ProcessId, OwnerUser, CreationDate -AutoSize | Out-String | Write-Host
|
|
176
|
+
}
|
|
177
|
+
Write-Warning "Restart Explorer manually via Task Manager (Ctrl+Shift+Esc, Windows Explorer, Restart), then re-run /logifix."
|
|
178
|
+
return
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
$lastAttemptResponded = $false
|
|
182
|
+
for ($attemptIndex = 1; $attemptIndex -le $LCoreRelaunchAttemptCount; $attemptIndex++) {
|
|
183
|
+
Stop-LCoreIfRunning
|
|
184
|
+
Start-LCoreMinimized
|
|
185
|
+
$lastAttemptResponded = Test-LCoreIsResponding -WaitSeconds $LCoreInitializationWaitSeconds
|
|
186
|
+
if ($lastAttemptResponded) {
|
|
187
|
+
Write-Host "LCore relaunch attempt ${attemptIndex}: process is responding."
|
|
188
|
+
} else {
|
|
189
|
+
Write-Warning "LCore relaunch attempt ${attemptIndex}: process not responding."
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
Write-StateSnapshot -Label 'After'
|
|
194
|
+
|
|
195
|
+
if (-not $lastAttemptResponded) {
|
|
196
|
+
Write-Warning "LCore did not respond after $LCoreRelaunchAttemptCount attempts."
|
|
197
|
+
Write-Warning "Use Task Manager to Restart Windows Explorer, then re-run /logifix."
|
|
198
|
+
return
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
Write-Host ""
|
|
202
|
+
Write-Host "Recovery complete ($LCoreRelaunchAttemptCount stop+launch cycles performed). If the tray icon is still not visible, use Task Manager to Restart Windows Explorer (guaranteed correct session/elevation), then re-run /logifix."
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
Invoke-LogifixRecovery
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rebase
|
|
3
|
+
description: Rebase a branch onto its base ref with the verification gates needed to catch logically broken results before pushing. Use when the user invokes `/rebase`, says "rebase this branch", "PR has merge conflicts", "rebase onto main", or asks for a force-push to update a remote branch's history. Critical for stacked PRs where the base merged via squash, and for any rebase that includes deletions or renames.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /rebase
|
|
7
|
+
|
|
8
|
+
Rebase the current branch (or a named branch's worktree) onto its correct base ref. Resolve conflicts. Verify the result is **logically correct**, not just textually clean. Push only after explicit authorization.
|
|
9
|
+
|
|
10
|
+
The default failure mode is shipping a rebase that compiled but doesn't run. This skill exists to prevent that.
|
|
11
|
+
|
|
12
|
+
## When to rebase vs. merge
|
|
13
|
+
|
|
14
|
+
> **Decision thresholds**
|
|
15
|
+
>
|
|
16
|
+
> | Threshold | Value |
|
|
17
|
+
> |---|---|
|
|
18
|
+
> | Maximum commits for solo rebase | 5 |
|
|
19
|
+
> | Divergence age that triggers merge-instead | 2 weeks |
|
|
20
|
+
|
|
21
|
+
**Default to rebase** when:
|
|
22
|
+
|
|
23
|
+
- Solo branch (you are the only author of the commits being rebased).
|
|
24
|
+
- 1–5 commits ahead of base (see Decision thresholds above).
|
|
25
|
+
- Stacked PR whose base just merged via squash.
|
|
26
|
+
|
|
27
|
+
**Default to merge** when:
|
|
28
|
+
|
|
29
|
+
- Branch has multiple authors (force-push would clobber their state).
|
|
30
|
+
- More than 5 commits or more than 2 weeks of divergence (see Decision thresholds above; rebase complexity grows non-linearly).
|
|
31
|
+
- The user said "merge", not "rebase".
|
|
32
|
+
- Open PR with approving reviews already on the current SHA (force-push invalidates them).
|
|
33
|
+
|
|
34
|
+
When in doubt, ask. Both work; the choice affects history shape, not correctness.
|
|
35
|
+
|
|
36
|
+
## Phase 1 — Pre-rebase analysis
|
|
37
|
+
|
|
38
|
+
1. **Resolve the canonical base.** Stacked-PR base refs change when the base PR merges. Always re-resolve:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
gh pr view <N> --json baseRefName,mergeable,mergeStateStatus
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Trust GitHub's `baseRefName`, not whatever the local branch was originally based on.
|
|
45
|
+
|
|
46
|
+
2. **Identify the rebase scenario.** Three playbooks:
|
|
47
|
+
|
|
48
|
+
| Scenario | Signal | Approach |
|
|
49
|
+
|---|---|---|
|
|
50
|
+
| Stacked PR, base merged via squash | base PR `state: MERGED`, base ref redirected to main | Rebase onto main; expect to `--skip` the squash-absorbed commit |
|
|
51
|
+
| Stacked PR, base still open | base PR `state: OPEN` | Rebase onto base PR's tip, not main |
|
|
52
|
+
| Long-lived feature branch | many commits, no stacking | Straight `rebase origin/main` |
|
|
53
|
+
|
|
54
|
+
3. **Fetch fresh.** `git fetch origin <base-ref> <head-ref>` before starting. Stale local refs cause false conflicts.
|
|
55
|
+
|
|
56
|
+
4. **Read every rebased commit's message.** Author intent lives in commit messages ("removes orphan constants X, Y, Z"). For every named symbol mentioned as deleted or renamed, scan `origin/main` for new consumers — if main has new uses of those symbols, the rebase will produce broken state without conflicting at the textual level.
|
|
57
|
+
|
|
58
|
+
**Tool preference for symbol scans** (in order):
|
|
59
|
+
|
|
60
|
+
| Tool | Use when | Example |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| `mcp__serena__find_symbol` / `find_referencing_symbols` | Symbol-aware language server is available — definition vs. reference distinction matters, and you want call-site context | `find_referencing_symbols(symbol_name)` returns every caller with file/line and surrounding code |
|
|
63
|
+
| `mcp__zoekt__search_symbols` / `search` | Cross-repo or large codebase indexed in zoekt; faster than grep on big trees | `search(query)` returns ranked matches with snippets |
|
|
64
|
+
| `Grep` tool (ripgrep) | Local single-repo plain-text scan; no symbol awareness needed | `Grep(pattern, type="py")` — much faster than shell `grep` and respects `.gitignore` |
|
|
65
|
+
| `grep -rn` | Last resort; only when the above are unavailable | — |
|
|
66
|
+
|
|
67
|
+
The Grep tool is the default for plain-text scans (faster than shell grep, respects gitignore). Reach for serena when you need to distinguish "this name is defined here" from "this name is referenced here," which catches false positives from comments, docstrings, and string literals. Reach for zoekt for cross-repo scans.
|
|
68
|
+
|
|
69
|
+
This is the bug that hides best. Don't skip it.
|
|
70
|
+
|
|
71
|
+
## Phase 2 — During rebase
|
|
72
|
+
|
|
73
|
+
5. **`--skip` only after verifying content overlap.** When a commit fails to apply because main already has its content (squash-merge case), confirm the content is actually equivalent:
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
git diff <skipped-commit> origin/<base> -- <files-touched-by-skipped-commit>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Skip only after that diff shows the equivalent content lives in main. Don't skip on the heuristic "main is downstream."
|
|
80
|
+
|
|
81
|
+
6. **Audit auto-merged files.** Files that git merged without conflict markers are not automatically correct. After each commit applies, run:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
git diff --name-only --diff-filter=M ORIG_HEAD
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Use `ORIG_HEAD`, which git sets at rebase start; the reflog index `HEAD@{1}` shifts as each rebase step runs and is unreliable mid-rebase. For each modified file with no conflict markers, eyeball the changes — auto-merge can produce duplicate blocks (when both sides added similar content) or silently drop content (when both sides removed adjacent lines). Pay extra attention to `config/`, `constants.*`, and `__init__.py` files where additions often sit near each other.
|
|
88
|
+
|
|
89
|
+
7. **At every conflict, take both sides' intent seriously.** Read both, then decide based on the post-rebase logical state. Do not reflex-pick HEAD or `origin/main`. Document the resolution reasoning in the commit message if it is non-obvious.
|
|
90
|
+
|
|
91
|
+
## Phase 3 — Verification gates (mandatory before push)
|
|
92
|
+
|
|
93
|
+
`py_compile`, `tsc --noEmit`, `cargo check`, etc. validate **syntax and types**, not **import resolution and runtime correctness**. Run real checks:
|
|
94
|
+
|
|
95
|
+
8. **Real import check.** For Python:
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
python -m compileall -q <package>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Follow immediately with the test-collection step below. `compileall` catches import-time failures across every module file; combined with `--collect-only` it surfaces `NameError`, `AttributeError`, and `ImportError` cases that a syntax-only check misses.
|
|
102
|
+
|
|
103
|
+
9. **Test collection.** `pytest --collect-only -q` on the changed packages catches NameError, AttributeError, and ImportError surfaces beyond plain imports.
|
|
104
|
+
|
|
105
|
+
10. **Targeted test run.** Run the test suite for every package the rebase touched. Do not push a rebase that dropped or broke test coverage that was passing pre-rebase.
|
|
106
|
+
|
|
107
|
+
11. **Reference scan for removals/renames.** For every symbol the rebase deleted or renamed (per the commit messages from step 4), scan the post-rebase tree using the same tool-preference order as step 4:
|
|
108
|
+
|
|
109
|
+
- **Preferred:** `mcp__serena__find_referencing_symbols` (symbol-aware; ignores false matches in comments and string literals).
|
|
110
|
+
- **Fallback:** `mcp__zoekt__search` for cross-repo or large trees.
|
|
111
|
+
- **Then:** the `Grep` tool (e.g., `Grep(pattern="<symbol>", type="py")`) for fast in-repo scans.
|
|
112
|
+
- **Last resort:** `grep -rn "<symbol>" .` (let ripgrep defaults and `.gitignore` handle scoping)
|
|
113
|
+
|
|
114
|
+
Any reference outside the rebased commits' own changes is a stale reference. Either update it (with user authorization) or surface it and refuse to push.
|
|
115
|
+
|
|
116
|
+
12. **"Unchanged" files are not safe.** A rebase can break files it never textually modified if their imports depend on something the rebase removed or renamed. List `git diff --name-only origin/<base>..HEAD`, then for every file NOT in that list but that imports from a file IN that list, manually verify the imports still resolve.
|
|
117
|
+
|
|
118
|
+
## Phase 4 — Push
|
|
119
|
+
|
|
120
|
+
13. **Force-push requires explicit authorization.** Auto mode does not bypass this. Before any `git push --force` or `--force-with-lease`:
|
|
121
|
+
|
|
122
|
+
- State the rewrite scope: "this rewrites N commits of remote history on branch `<name>`".
|
|
123
|
+
- State the branch's PR state: "PR #M is OPEN with K approving reviews on the current SHA" (force-push invalidates approvals).
|
|
124
|
+
- Ask for explicit authorization.
|
|
125
|
+
- If denied: leave the rebase result locally, report merge-instead as the alternative, stop.
|
|
126
|
+
|
|
127
|
+
14. **Refuse to force-push** `main`, `master`, `release/*`, `production`, or any branch with more than one unique author. Count unique authors in a cross-platform way: `git log --format='%ae' origin/<branch> | python -c "import sys; print(len(set(sys.stdin)))"`. This form works on both Windows (PowerShell) and Unix without shell pipeline extensions. Surface the refusal; do not ask for authorization on these.
|
|
128
|
+
|
|
129
|
+
15. **Always `--force-with-lease=<branch>:<sha>`**, never bare `--force`. Pin the lease to the SHA you started from so concurrent pushes are detected as a lease mismatch instead of clobbered.
|
|
130
|
+
|
|
131
|
+
16. **Confirm mergeability** post-push:
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
gh pr view <N> --json mergeable,mergeStateStatus,headRefOid
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Expect `mergeable: MERGEABLE`, `mergeStateStatus: CLEAN`. Anything else means the rebase didn't actually unblock the PR — investigate before declaring done.
|
|
138
|
+
|
|
139
|
+
## Anti-patterns (the failure modes this skill prevents)
|
|
140
|
+
|
|
141
|
+
- **`py_compile` exit 0 → "verified"**. Wrong. Syntax-clean is not import-clean. Always run a real `import` check.
|
|
142
|
+
- **Skipping squash-absorbed commits without diff verification.** Heuristically usually right, occasionally wrong, never confirmed without the diff.
|
|
143
|
+
- **Auditing only `git diff --name-only`.** Files unchanged by the rebase can still break if their imports depended on what the rebase removed.
|
|
144
|
+
- **Force-pushing under "the user asked me to fix the conflicts" interpretation.** Force-push is a separate authorization from "fix this." Ask.
|
|
145
|
+
- **Bare `--force` instead of `--force-with-lease=<branch>:<sha>`.** Loses the safety net against concurrent pushes.
|
|
146
|
+
|
|
147
|
+
## Quick decision flowchart
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
Branch shared with others? ──► merge instead
|
|
151
|
+
Stacked PR, base merged? ──► rebase onto main, expect --skip
|
|
152
|
+
Stacked PR, base open? ──► rebase onto base PR tip
|
|
153
|
+
Solo, ≤5 commits ahead? ──► straight rebase
|
|
154
|
+
|
|
155
|
+
After rebase, BEFORE push:
|
|
156
|
+
python -c "import …" ──► must succeed
|
|
157
|
+
pytest --collect-only ──► must succeed
|
|
158
|
+
targeted pytest run ──► must pass
|
|
159
|
+
symbol scan (serena → zoekt → Grep) ──► no stale references
|
|
160
|
+
|
|
161
|
+
Push:
|
|
162
|
+
not main/master/release ──► force-with-lease, ask first
|
|
163
|
+
main/master/release ──► refuse
|
|
164
|
+
```
|