Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 133 additions & 0 deletions .github/workflows/label-closed-prs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
name: Label Closed PRs

# Trigger when a pull request is closed (either merged or without merging)
on:
pull_request:
types: [closed]

permissions:
pull-requests: write
issues: write
checks: read

jobs:
label-closure-reason:
name: Label PR Closure Reason
runs-on: ubuntu-latest
# Only label PRs that were closed WITHOUT merging
if: github.event.pull_request.merged == false
steps:
- name: Determine and apply closure reason label
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
with:
script: |
const { owner, repo } = context.repo;
const prNumber = context.payload.pull_request.number;

core.info('=================================================');
core.info('Label Closed PRs — Closure Reason Classifier');
core.info('=================================================');
core.info(`PR #${prNumber} closed without merging`);

// Determine closure reason by inspecting CI checks and review state
async function getClosureReason() {
// 1. Check the combined CI status for the PR head commit
const headSha = context.payload.pull_request.head.sha;

let ciFailure = false;
try {
// Fetch check runs for the head commit
const checkRunsResp = await github.rest.checks.listForRef({
owner,
repo,
ref: headSha,
per_page: 100,
});

const runs = checkRunsResp.data.check_runs;
const failed = runs.filter(
r => r.conclusion === 'failure' || r.conclusion === 'timed_out'
);
if (failed.length > 0) {
ciFailure = true;
core.info(`CI failure detected: ${failed.map(r => r.name).join(', ')}`);
}
} catch (err) {
core.warning(`Could not fetch check runs: ${err.message}`);
}

if (ciFailure) {
return 'closed:ci-failure';
}

// 2. Check if a reviewer explicitly requested changes or left a closing comment
let reviewerRejected = false;
try {
const reviewsResp = await github.rest.pulls.listReviews({
owner,
repo,
pull_number: prNumber,
});

const changesRequested = reviewsResp.data.some(
r => r.state === 'CHANGES_REQUESTED'
);
if (changesRequested) {
reviewerRejected = true;
core.info('Reviewer requested changes before PR was closed');
}
} catch (err) {
core.warning(`Could not fetch reviews: ${err.message}`);
}

if (reviewerRejected) {
return 'closed:reviewer-rejected';
}

// 3. Check for a duplicate/superseded label already applied
const existingLabels = context.payload.pull_request.labels.map(l => l.name);
if (existingLabels.some(l => l.includes('duplicate') || l.includes('superseded'))) {
return 'closed:duplicate';
}

// 4. Fall back to unknown
return 'closed:unknown';
}

const label = await getClosureReason();
core.info(`Applying label: ${label}`);

// Ensure the label exists (create it if missing)
const labelColors = {
'closed:ci-failure': 'e11d48', // red
'closed:reviewer-rejected': 'f97316', // orange
'closed:duplicate': '8b5cf6', // purple
'closed:unknown': '94a3b8', // gray
};

try {
await github.rest.issues.getLabel({ owner, repo, name: label });
} catch (err) {
if (err.status !== 404) {
throw err;
}
// Label doesn't exist yet — create it
await github.rest.issues.createLabel({
owner,
repo,
Comment on lines +108 to +117
name: label,
color: labelColors[label] || '94a3b8',
description: `PR was closed without merging: ${label.replace('closed:', '')}`,
});
core.info(`Created label: ${label}`);
}

// Apply the label to the PR
await github.rest.issues.addLabels({
owner,
repo,
issue_number: prNumber,
labels: [label],
});

core.info(`✅ Applied label '${label}' to PR #${prNumber}`);
53 changes: 40 additions & 13 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,24 +46,47 @@ Use the **report_progress** tool to commit and push your changes. This will auto

**Never leave file changes uncommitted.** Even for small or "obvious" changes, always use **report_progress** to push your work to a PR so it can be reviewed.

### ⚠️ MANDATORY PRE-COMMIT VALIDATION ⚠️
### ⚠️ MANDATORY PRE-COMMIT AND PRE-PR VALIDATION ⚠️

**🚨 BEFORE EVERY COMMIT - NO EXCEPTIONS:**
**🚨 TWO-CHECKPOINT VALIDATION STRATEGY — NO EXCEPTIONS:**

#### Checkpoint 1 — After First Significant Code Edit

Run this immediately after making your first substantial code change (do NOT wait until the end):

```bash
make agent-finish # Runs build, test, recompile, fmt, lint
make build && make fmt # Catch compile errors and formatting issues early
```

**Why this matters:**
**Why checkpoint 1 matters:**
- Surfaces compile errors before you spend more context on subsequent edits
- Prevents wasted work if the approach is fundamentally broken
- Cheap to run (~2s) and gives immediate feedback

#### Checkpoint 2 — Before Every `report_progress` / PR Creation

**🚨 DO NOT call `report_progress` (which creates/updates the PR) until this passes:**

```bash
make agent-report-progress # build + fmt + test-unit (<30s) — fast pre-PR gate
```

When more time is available, prefer the full suite:

```bash
make agent-finish # Runs build, test, recompile, fmt, lint (full validation)
```

**Why checkpoint 2 matters:**
- **CI WILL FAIL** if you skip this step - this is automatic and non-negotiable
- Unformatted code causes immediate CI failures that block all other work
- This has caused **5 CI failures in a single day** - don't be the 6th!
- The formatting check (`go fmt`) is strict and cannot be disabled
- PRs that fail CI immediately after opening are closed without merging — a wasted session

**If you're in a hurry** and `make agent-finish` takes too long, **at minimum run**:
**If you're in a hurry** and `make agent-finish` takes too long, use the dedicated fast gate:
```bash
make fmt # Format Go, JavaScript, and JSON files
make test-unit # Fast unit tests (~25s)
make agent-report-progress # build + fmt + test-unit (~30s)
```

**After making Go code changes (*.go files):**
Expand Down Expand Up @@ -1138,7 +1161,7 @@ Use the mcpscripts-make tool with args: "build" ← may fail with context ca
Use the mcpscripts-go tool with args: "test ./..." ← may fail with context canceled
```

**Additional rule**: Add an **intermediate validation checkpoint** using bash after the first major code edit (e.g., `make build`), not just at the very end of the session. This surfaces compile errors early, before the agent spends more context on subsequent edits.
**Additional rule**: Follow the **two-checkpoint validation strategy** (see Critical Requirements): run `make build && make fmt` after the first major code edit (Checkpoint 1), and run `make agent-report-progress` before every `report_progress` call (Checkpoint 2). Both checkpoints must use direct `bash` commands, not MCP tools.

**When `mcpscripts-*` tools are safe to use:**
- Early in a session, before any long exploration phase
Expand Down Expand Up @@ -1208,13 +1231,17 @@ make minor-release # Automated via GitHub Actions

Use **report_progress** to commit, push, and update the PR. Never leave changes uncommitted.

### 🚨 CRITICAL - Pre-Commit Checklist
Before EVERY commit:
1. ✅ Run `make agent-finish` (or at minimum `make fmt`)
### 🚨 CRITICAL - Two-Checkpoint Validation (Pre-Commit + Pre-PR)
**Checkpoint 1** — After first significant code edit:
1. ✅ Run `make build && make fmt` (fast early feedback, ~2s)
2. ✅ Fix any compile errors or formatting issues before proceeding

**Checkpoint 2** — Before every `report_progress` call (creates/updates PR):
1. ✅ Run `make agent-report-progress` (build + fmt + test-unit, <30s)
2. ✅ Verify no errors from the above command
3. ✅ Only then commit and push
3. ✅ Only then call `report_progress`

**This is NOT optional** - skipping this causes immediate CI failures.
**This is NOT optional** — PRs that fail CI immediately after opening are closed without merging, wasting the entire agent session.

### Development Guidelines
- Go project with Makefile-managed build/test/lint
Expand Down
11 changes: 10 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,14 @@ sbom:
agent-finish: deps-dev fmt lint build build-wasm test-all fix recompile dependabot generate-schema-docs generate-agent-factory security-scan
@echo "Agent finished tasks successfully."

# Lightweight pre-PR gate — run before every report_progress / create_pull_request call.
# Targets < 30 seconds: build (~2s) + fmt (~1s) + test-unit (~25s).
# This catches compile errors, formatting violations, and common unit-test regressions
# before the PR is opened, without waiting for the full agent-finish suite.
.PHONY: agent-report-progress
agent-report-progress: build fmt test-unit
@echo "Pre-PR validation passed. Safe to call report_progress."

# Help target
.PHONY: help
help:
Expand Down Expand Up @@ -822,6 +830,7 @@ help:
@echo " preview-docs - Preview built documentation with Astro"
@echo " clean-docs - Clean documentation artifacts (dist, node_modules, .astro)"

@echo " agent-finish - Complete validation sequence (build, test, fix, recompile, fmt, lint, security-scan)"
@echo " agent-finish - Complete validation sequence (build, test, fix, recompile, fmt, lint, security-scan)"
@echo " agent-report-progress - Lightweight pre-PR gate: build + fmt + test-unit (<30s)"
@echo " sbom - Generate SBOM in SPDX and CycloneDX formats (requires syft)"
@echo " help - Show this help message"