Skip to content

Commit 6fb8704

Browse files
authored
Merge branch 'main' into fix/ci-skip-docs-md-changes
2 parents 1c3753d + 74ea588 commit 6fb8704

14 files changed

Lines changed: 236 additions & 71 deletions

.github/workflows/auto-approve.yml

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,53 @@ jobs:
3434
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
3535
run: gh pr review --approve "${{ github.event.pull_request.number }}" --repo "${{ github.repository }}"
3636

37+
# Check for workflow file changes early (to decide whether we can safely use App token for auto-merge).
38+
# This step is made robust so a transient gh failure does not fail the job (approve step already ran).
39+
- name: Check for workflow file changes (use GITHUB_TOKEN fallback to avoid needing workflows:write on App)
40+
id: wf-changes
41+
env:
42+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
43+
run: |
44+
pr="${{ github.event.pull_request.number }}"
45+
files=$(gh pr view "$pr" --json files --jq '.files[].path' || echo "")
46+
if echo "$files" | grep -q '^\.github/workflows/'; then
47+
echo "changes=true" >> "$GITHUB_OUTPUT"
48+
echo "PR touches .github/workflows/; will use GITHUB_TOKEN for auto-merge (no workflows:write needed on App)"
49+
else
50+
echo "changes=false" >> "$GITHUB_OUTPUT"
51+
fi
52+
3753
- uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
38-
if: github.event.pull_request.user.login != 'patchloom-release[bot]'
54+
if: github.event.pull_request.user.login != 'patchloom-release[bot]' && steps.wf-changes.outputs.changes != 'true'
3955
id: app-token
4056
with:
4157
client-id: ${{ vars.APP_CLIENT_ID }}
4258
private-key: ${{ secrets.APP_PRIVATE_KEY }}
4359

44-
- name: Enable auto-merge
45-
if: github.event.pull_request.user.login != 'patchloom-release[bot]'
60+
# Strong guard against accidental/automated release PR merges is also
61+
# implemented here (label check below) and via scripts/guard-no-release-merge.sh.
62+
# See AGENTS.md "Release PRs - Strong Guard" section.
63+
64+
- name: Strong guard - detect release-please PRs (autorelease: pending label)
65+
id: release-guard
66+
env:
67+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
68+
run: |
69+
pr="${{ github.event.pull_request.number }}"
70+
labels=$(gh pr view "$pr" --json labels --jq '.labels[].name' || true)
71+
if echo "$labels" | grep -q 'autorelease: pending'; then
72+
echo "is_release_pr=true" >> "$GITHUB_OUTPUT"
73+
echo "Release PR detected by label 'autorelease: pending' - will skip auto-merge (user must explicitly approve merges of release PRs)"
74+
else
75+
echo "is_release_pr=false" >> "$GITHUB_OUTPUT"
76+
fi
77+
78+
- name: Enable auto-merge (App token when no wf changes; GITHUB_TOKEN fallback otherwise)
79+
if: >-
80+
github.event.pull_request.user.login != 'patchloom-release[bot]' &&
81+
steps.release-guard.outputs.is_release_pr != 'true'
4682
env:
47-
GH_TOKEN: ${{ steps.app-token.outputs.token }}
48-
run: gh pr merge --auto --squash "${{ github.event.pull_request.number }}" --repo "${{ github.repository }}"
83+
GH_TOKEN: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }}
84+
run: |
85+
gh pr merge --auto --squash "${{ github.event.pull_request.number }}" --repo "${{ github.repository }}" \
86+
|| echo "Could not enable auto-merge (common when PR modifies .github/workflows/* using GITHUB_TOKEN fallback, or release guard, or other). Approval from prior step still applies; use manual merge if needed."

.release-please-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
".": "0.0.4"
2+
".": "0.0.5"
33
}

AGENTS.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,54 @@ All I/O-dependent functions accept an `inputs` object with injectable callbacks
126126
- All relative imports must use `.js` extensions (`from "./foo.js"`, not `from "./foo"`). Required by `moduleResolution: "node16"`.
127127
- All commits require a `Signed-off-by` line (DCO). Use `git commit -s`.
128128
- When adding commands to `package.json`, update the expected count in `test/suite/index.ts`.
129+
- **Branch & PR workflow (never push a branch and stop):** For any trackable work,
130+
after the first `git push` immediately create a draft PR (`gh pr create --draft`).
131+
Continue development with normal `git push` (updates the draft PR + CI).
132+
Only run `gh pr ready <number>` (and enable auto-merge if needed) when the
133+
changes are ready for review/merge. This ensures every pushed branch is
134+
backed by an open (draft) PR from the start. See `~/.grok/skills/owned-repo-gate/SKILL.md`.
135+
136+
- **Auto-approve self-modification:** PRs that change `.github/workflows/auto-approve.yml`
137+
cause GitHub to emit only "push" validation runs (0 jobs, failure) using the PR's workflow content
138+
(the pull_request runs use the definition from main). The approve step runs early using
139+
GITHUB_TOKEN (before wf-changes detection or merge logic) so reviews are added when the
140+
pull_request workflow runs from main. The Enable auto-merge step uses `|| echo` so the
141+
workflow reports success even when merge enable falls back or is restricted. In rare cases
142+
where no review appears, use the emergency bypass in ci-branch-protection skill + #159
143+
(add bypass actor, `gh pr merge --admin`, remove bypass immediately). See also patchloom's
144+
auto-approve.yml for the reference pattern.
145+
146+
## Release PRs - Strong Guard
147+
148+
Release PRs (created by release-please, titled "chore: release ..." or "chore(main): release ...", or labeled `autorelease: pending`) MUST NEVER be merged (with `gh pr merge`, `--auto`, or otherwise) without the user's explicit approval.
149+
150+
Merging a release PR:
151+
- Publishes a new version of the VSIX
152+
- Creates git tags
153+
- Triggers the full release pipeline (Marketplace, Open VSX, attestation bundles)
154+
- The user controls release cadence, not the agent.
155+
156+
### Required procedure (strong guard)
157+
158+
When you encounter a release PR (during triage, gate check, `gh pr list`, or status):
159+
160+
1. Report it clearly: "Release PR #N (vX.Y.Z) is ready to merge."
161+
2. Use the `ask_user_question` tool (or direct question) to ask: "Should I merge it?"
162+
3. **Only after receiving an explicit "yes" (or equivalent affirmative) from the user in this session**, proceed.
163+
4. Before executing any merge command, run the guard:
164+
```
165+
bash scripts/guard-no-release-merge.sh <number>
166+
```
167+
The script will abort with guidance unless `ALLOW_RELEASE_MERGE=yes` is set (only after user yes).
168+
5. If checks pass and user said yes: `gh pr merge <number> --squash` (or let auto if user directed).
169+
170+
This rule was strengthened after an incident where `gh pr merge 144 --auto` (under a broad "merge everything" instruction) resulted in v0.0.5 being published without explicit per-release "yes".
171+
172+
### Defense in depth
173+
174+
- Workflow: `.github/workflows/auto-approve.yml` uses author + label check + wf-changes to never enable `--auto` for release PRs.
175+
- Script: `scripts/guard-no-release-merge.sh` provides a hard runtime guard for shell commands.
176+
- Documentation: This section + global AGENTS.md rule.
177+
- Branch protection + ruleset: still enforces checks, but does not replace user approval for releases.
178+
179+
Never bypass the guard "just this once" or rationalize. Ask every time.

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
# Changelog
22

3+
## [0.0.5](https://github.com/patchloom/patchloom-vscode/compare/patchloom-v0.0.4...patchloom-v0.0.5) (2026-06-22)
4+
5+
6+
### Features
7+
8+
* align extension with patchloom CLI v0.4.0 ([0629af9](https://github.com/patchloom/patchloom-vscode/commit/0629af926a7a9eee8d6c634eee10bc6148d4d6ad))
9+
* align with patchloom CLI v0.4.0 ([#145](https://github.com/patchloom/patchloom-vscode/issues/145)) ([0629af9](https://github.com/patchloom/patchloom-vscode/commit/0629af926a7a9eee8d6c634eee10bc6148d4d6ad))
10+
11+
12+
### Bug Fixes
13+
14+
* **ci:** prevent spurious Dependabot PR failures from auto-approve and @types/vscode bumps ([#143](https://github.com/patchloom/patchloom-vscode/issues/143)) ([3b50515](https://github.com/patchloom/patchloom-vscode/commit/3b505152f40ed37ac1120b5fccf4f8345d0b4cbb))
15+
316
## [0.0.4](https://github.com/patchloom/patchloom-vscode/compare/patchloom-v0.0.3...patchloom-v0.0.4) (2026-06-18)
417

518

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "patchloom",
33
"displayName": "Patchloom",
44
"description": "VS Code extension for Patchloom. Installs, configures, and integrates the Patchloom CLI for editor-first workflows.",
5-
"version": "0.0.4",
5+
"version": "0.0.5",
66
"publisher": "patchloom",
77
"license": "MIT",
88
"repository": {

scripts/guard-no-release-merge.sh

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#!/usr/bin/env bash
2+
# Strong guard against merging release-please PRs without explicit user approval.
3+
#
4+
# Usage:
5+
# bash scripts/guard-no-release-merge.sh [PR_NUMBER]
6+
# # or with current branch context (will try to detect open PR)
7+
#
8+
# Exits 0 if safe to merge (non-release PR).
9+
# Exits 1 and prints guidance if release PR detected, unless ALLOW_RELEASE_MERGE=yes.
10+
#
11+
# This is a defense-in-depth guard for agents and humans.
12+
# See AGENTS.md for the full "Release PRs - Strong Guard" policy.
13+
# Per global rules: report the PR, ask user "Should I merge it?", only proceed after explicit "yes".
14+
15+
set -euo pipefail
16+
17+
pr="${1:-}"
18+
19+
if [[ -z "$pr" ]]; then
20+
# Try to detect PR from current context (works if gh pr view succeeds for head)
21+
if pr=$(gh pr view --json number --jq '.number' 2>/dev/null); then
22+
:
23+
else
24+
echo "ERROR: No PR number provided and could not auto-detect current PR."
25+
echo "Usage: $0 <pr-number>"
26+
exit 2
27+
fi
28+
fi
29+
30+
echo "Guard: inspecting PR #$pr for release-please markers..."
31+
32+
title=$(gh pr view "$pr" --json title --jq '.title' 2>/dev/null || echo "")
33+
labels=$(gh pr view "$pr" --json labels --jq '.labels[].name' 2>/dev/null || true)
34+
body=$(gh pr view "$pr" --json body --jq '.body' 2>/dev/null | head -c 500 || true)
35+
36+
is_release=false
37+
reason=""
38+
39+
if echo "$labels" | grep -q 'autorelease: pending'; then
40+
is_release=true
41+
reason="has label 'autorelease: pending'"
42+
elif echo "$title" | grep -qiE '^(chore|release).*release |release v?[0-9]+\.[0-9]+'; then
43+
is_release=true
44+
reason="title looks like release: '$title'"
45+
elif echo "$body" | grep -qi 'release-please'; then
46+
is_release=true
47+
reason="body mentions release-please"
48+
fi
49+
50+
if [[ "$is_release" == "true" ]]; then
51+
echo ""
52+
echo "================================================================"
53+
echo "STRONG GUARD TRIGGERED"
54+
echo "================================================================"
55+
echo "PR #$pr is a release PR ($reason)."
56+
echo "Title: $title"
57+
echo ""
58+
echo "Release PRs (release-please etc) MUST NEVER be merged without the"
59+
echo "user's explicit approval in this chat session."
60+
echo ""
61+
echo " 1. Report: \"Release PR #$pr ($title) is ready to merge.\""
62+
echo " 2. Ask the user using ask_user_question or directly: \"Should I merge it?\""
63+
echo " 3. ONLY proceed after an explicit \"yes\" (or equivalent)."
64+
echo ""
65+
echo "Merging publishes a new version, creates tags, and triggers releases."
66+
echo "The user (not the agent) controls release cadence."
67+
echo ""
68+
echo "To bypass (ONLY after receiving explicit user yes):"
69+
echo " ALLOW_RELEASE_MERGE=yes $0 $pr"
70+
echo "================================================================"
71+
echo ""
72+
73+
if [[ "${ALLOW_RELEASE_MERGE:-}" != "yes" ]]; then
74+
exit 1
75+
else
76+
echo "BYPASS: ALLOW_RELEASE_MERGE=yes detected. Proceeding (user approved)."
77+
fi
78+
fi
79+
80+
echo "Guard OK: PR #$pr does not appear to be a release PR. Safe to consider merge."
81+
exit 0

src/binary/patchloom.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,11 +160,16 @@ export function patchloomNeedsUpgrade(status: PatchloomStatus): boolean {
160160
*
161161
* This removes duplicated ready-check + notify logic across commands.
162162
*/
163-
export async function ensurePatchloomReadyOrNotify(contextSuffix = ""): Promise<string | null> {
164-
const status = await resolvePatchloomStatus();
165-
const vscode = await import("vscode");
163+
export async function ensurePatchloomReadyOrNotify(
164+
contextSuffix = "",
165+
testInputs?: PatchloomStatusInputs
166+
): Promise<string | null> {
167+
const status = testInputs
168+
? await resolvePatchloomStatusWithInputs(testInputs)
169+
: await resolvePatchloomStatus();
166170

167171
if (!status.ready || !status.binaryPath) {
172+
const vscode = await import("vscode");
168173
const choice = await vscode.window.showWarningMessage(
169174
`${status.message}${contextSuffix ? `\n\n${contextSuffix}` : ""}`,
170175
"Open Settings"
@@ -176,6 +181,7 @@ export async function ensurePatchloomReadyOrNotify(contextSuffix = ""): Promise<
176181
}
177182

178183
if (patchloomNeedsUpgrade(status)) {
184+
const vscode = await import("vscode");
179185
const choice = await vscode.window.showWarningMessage(
180186
`${status.compatibilityMessage}${contextSuffix ? `\n\n${contextSuffix}` : ""}`,
181187
"Open Releases"

src/commands/configureMcp.ts

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,14 @@
11
import * as fs from "node:fs/promises";
22
import * as path from "node:path";
33
import * as vscode from "vscode";
4-
import { patchloomNeedsUpgrade, resolvePatchloomStatus } from "../binary/patchloom.js";
4+
import { ensurePatchloomReadyOrNotify } from "../binary/patchloom.js";
55
import { configureMcpTargets, inspectMcpTargets } from "../mcp/config.js";
66
import { activeWorkspaceFolder, describeWorkspaceEnvironment } from "../workspace/readiness.js";
77
import { refreshStatusBar } from "../status/statusBar.js";
88

99
export async function configureMcp(): Promise<void> {
10-
const status = await resolvePatchloomStatus();
11-
if (!status.ready) {
12-
const choice = await vscode.window.showWarningMessage(
13-
`${status.message}\n\nPatchloom needs a working binary before MCP setup can continue.`,
14-
"Open Settings"
15-
);
16-
if (choice === "Open Settings") {
17-
await vscode.commands.executeCommand("patchloom.openPatchloomSettings");
18-
}
19-
return;
20-
}
21-
22-
if (patchloomNeedsUpgrade(status)) {
23-
const choice = await vscode.window.showWarningMessage(
24-
`${status.compatibilityMessage}\n\nUpgrade Patchloom before MCP setup can continue.`,
25-
"Open Releases"
26-
);
27-
if (choice === "Open Releases") {
28-
await vscode.commands.executeCommand("patchloom.openPatchloomReleases");
29-
}
10+
const binaryPath = await ensurePatchloomReadyOrNotify("Patchloom needs a working binary before MCP setup can continue.");
11+
if (!binaryPath) {
3012
return;
3113
}
3214

@@ -66,7 +48,7 @@ export async function configureMcp(): Promise<void> {
6648
workspaceFolderPath,
6749
includeKinds: selectedKinds,
6850
includeUserTarget: environment.supportsUserMcpConfig,
69-
patchloomPathSetting: status.binaryPath,
51+
patchloomPathSetting: binaryPath,
7052
readFile: async (filePath) => {
7153
try {
7254
return await fs.readFile(filePath, "utf8");

src/commands/setupWorkspace.ts

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,17 @@
11
import * as vscode from "vscode";
2-
import { PATCHLOOM_DOCS_URL, PATCHLOOM_RELEASES_URL, patchloomNeedsUpgrade, resolvePatchloomStatus } from "../binary/patchloom.js";
2+
import { ensurePatchloomReadyOrNotify, PATCHLOOM_DOCS_URL, PATCHLOOM_RELEASES_URL } from "../binary/patchloom.js";
33
import { describeWorkspaceEnvironment, inspectWorkspaceReadiness } from "../workspace/readiness.js";
44

55
export async function setupWorkspace(): Promise<void> {
6-
const status = await resolvePatchloomStatus();
7-
if (!status.ready) {
8-
const choice = await vscode.window.showWarningMessage(
9-
`${status.message}\n\nPatchloom needs a working binary before workspace setup can continue.`,
10-
"Open Settings"
11-
);
12-
if (choice === "Open Settings") {
13-
await vscode.commands.executeCommand("patchloom.openPatchloomSettings");
14-
}
6+
const binaryPath = await ensurePatchloomReadyOrNotify("Patchloom needs a working binary before workspace setup can continue.");
7+
if (!binaryPath) {
158
return;
169
}
1710

1811
const readiness = await inspectWorkspaceReadiness({
1912
promptIfMany: true,
2013
placeHolder: "Select the workspace folder to inspect for Patchloom setup"
2114
});
22-
if (patchloomNeedsUpgrade(status)) {
23-
const choice = await vscode.window.showWarningMessage(
24-
`${status.compatibilityMessage}\n\nUpgrade Patchloom before workspace setup can continue.`,
25-
"Open Releases"
26-
);
27-
if (choice === "Open Releases") {
28-
await vscode.commands.executeCommand("patchloom.openPatchloomReleases");
29-
}
30-
return;
31-
}
3215

3316
if (!readiness.hasWorkspace) {
3417
await vscode.window.showWarningMessage("Open a workspace folder before running Patchloom: Setup Workspace.");

0 commit comments

Comments
 (0)