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
32 changes: 32 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -957,6 +957,38 @@ jobs:
if-no-files-found: ignore
retention-days: 14

check-validator-sizes:
name: Check validator file sizes
runs-on: ubuntu-latest
# Non-blocking: report violations but don't fail the build until existing files are cleaned up
continue-on-error: true
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Check validator file sizes
id: check
run: |
set +e
OUTPUT=$(NO_COLOR=1 bash scripts/check-validator-sizes.sh 2>&1)
EXIT_CODE=$?
echo "$OUTPUT"
echo "exit_code=$EXIT_CODE" >> "$GITHUB_OUTPUT"
echo "## Validator File Size Check" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "Checking \`*_validation.go\` files against the 768-line hard limit (AGENTS.md)." >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "\`\`\`" >> "$GITHUB_STEP_SUMMARY"
echo "$OUTPUT" >> "$GITHUB_STEP_SUMMARY"
echo "\`\`\`" >> "$GITHUB_STEP_SUMMARY"
if [ "$EXIT_CODE" -ne 0 ]; then
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "⚠️ This check is currently non-blocking. Fix violations to keep the codebase healthy." >> "$GITHUB_STEP_SUMMARY"
fi
exit "$EXIT_CODE"

lint-go:
runs-on: ubuntu-latest
permissions:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/cloclo.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions .github/workflows/cloclo.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ safe-outputs:
labels: [automation, cloclo]
excluded-files:
- ".github/workflows/*.lock.yml"
protected-files: fallback-to-issue
add-comment:
max: 1
messages:
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/smoke-call-workflow.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions .github/workflows/smoke-copilot.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions .github/workflows/smoke-copilot.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,14 @@ safe-outputs:
expires: 2h
group: true
close-older-issues: true
close-older-key: "smoke-copilot"
labels: [automation, testing]
create-discussion:
category: announcements
labels: [ai-generated]
expires: 2h
close-older-discussions: true
close-older-key: "smoke-copilot"
max: 1
create-pull-request-review-comment:
max: 5
Expand Down
8 changes: 8 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,12 @@ lint-errors:
check-file-sizes:
@bash scripts/check-file-sizes.sh

# Check that *_validation.go files stay within the 768-line hard limit
# Set WARN_ONLY=1 to report violations without failing (non-blocking mode)
.PHONY: check-validator-sizes
check-validator-sizes:
@bash scripts/check-validator-sizes.sh

# Validate all project files
.PHONY: lint
lint: fmt-check fmt-check-json lint-cjs golint
Expand Down Expand Up @@ -802,6 +808,8 @@ help:
@echo " lint-cjs - Lint JavaScript (.cjs) and JSON files in actions/setup/js"
@echo " lint-json - Lint JSON files in pkg directory (excluding actions/setup/js)"
@echo " lint-errors - Lint error messages for quality compliance"
@echo " check-file-sizes - Check Go file sizes and function counts (informational)"
@echo " check-validator-sizes - Check *_validation.go files against the 768-line hard limit"
@echo " security-scan - Run all security scans (gosec, govulncheck, trivy)"
@echo " security-gosec - Run gosec Go security scanner"
@echo " security-govulncheck - Run govulncheck for known vulnerabilities"
Expand Down
58 changes: 37 additions & 21 deletions actions/setup/js/close_older_discussions.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

const { getCloseOlderDiscussionMessage } = require("./messages_close_discussion.cjs");
const { getErrorMessage } = require("./error_helpers.cjs");
const { getWorkflowIdMarkerContent, generateWorkflowIdMarker, generateWorkflowCallIdMarker } = require("./generate_footer.cjs");
const { getWorkflowIdMarkerContent, generateWorkflowIdMarker, generateWorkflowCallIdMarker, generateCloseKeyMarker, getCloseKeyMarkerContent } = require("./generate_footer.cjs");
const { sanitizeContent } = require("./sanitize_content.cjs");
const { closeOlderEntities, MAX_CLOSE_COUNT: SHARED_MAX_CLOSE_COUNT } = require("./close_older_entities.cjs");

Expand All @@ -29,26 +29,42 @@ const GRAPHQL_DELAY_MS = 500;
* When set, filters by the `gh-aw-workflow-call-id` marker so callers sharing the same
* reusable workflow do not close each other's discussions. Falls back to `gh-aw-workflow-id`
* when not provided (backward compat for discussions created before this fix).
* @param {string} [closeOlderKey] - Optional explicit deduplication key. When set, the
* `gh-aw-close-key` marker is used as the primary search term and exact filter instead
* of the workflow-id / workflow-call-id markers.
* @returns {Promise<Array<{id: string, number: number, title: string, url: string}>>} Matching discussions
*/
async function searchOlderDiscussions(github, owner, repo, workflowId, categoryId, excludeNumber, callerWorkflowId) {
async function searchOlderDiscussions(github, owner, repo, workflowId, categoryId, excludeNumber, callerWorkflowId, closeOlderKey) {
core.info(`Starting search for older discussions in ${owner}/${repo}`);
core.info(` Workflow ID: ${workflowId || "(none)"}`);
core.info(` Exclude discussion number: ${excludeNumber}`);

if (!workflowId) {
core.info("No workflow ID provided - cannot search for older discussions");
if (!workflowId && !closeOlderKey) {
core.info("No workflow ID or close-older-key provided - cannot search for older discussions");
return [];
}

// Build GraphQL search query
// Search for open discussions with the workflow-id marker in the body
const workflowIdMarker = getWorkflowIdMarkerContent(workflowId);
// Escape quotes in workflow ID to prevent query injection
const escapedMarker = workflowIdMarker.replace(/"/g, '\\"');
let searchQuery = `repo:${owner}/${repo} is:open "${escapedMarker}" in:body`;

core.info(` Added workflow ID marker filter to query: "${escapedMarker}" in:body`);
// Build GraphQL search query.
// When a close-older-key is provided it becomes the primary search term; otherwise
// fall back to the workflow-id marker.
let searchQuery;
let exactMarker;
if (closeOlderKey) {
const closeKeyMarkerContent = getCloseKeyMarkerContent(closeOlderKey);
const escapedMarker = closeKeyMarkerContent.replace(/"/g, '\\"');
searchQuery = `repo:${owner}/${repo} is:open "${escapedMarker}" in:body`;
exactMarker = generateCloseKeyMarker(closeOlderKey);
core.info(` Using close-older-key for search: "${escapedMarker}" in:body`);
} else {
// Build GraphQL search query
// Search for open discussions with the workflow-id marker in the body
const workflowIdMarker = getWorkflowIdMarkerContent(workflowId);
// Escape quotes in workflow ID to prevent query injection
const escapedMarker = workflowIdMarker.replace(/"/g, '\\"');
searchQuery = `repo:${owner}/${repo} is:open "${escapedMarker}" in:body`;
exactMarker = callerWorkflowId ? generateWorkflowCallIdMarker(callerWorkflowId) : generateWorkflowIdMarker(workflowId);
core.info(` Added workflow ID marker filter to query: "${escapedMarker}" in:body`);
}
core.info(`Executing GitHub search with query: ${searchQuery}`);

const result = await github.graphql(
Expand Down Expand Up @@ -84,18 +100,16 @@ async function searchOlderDiscussions(github, owner, repo, workflowId, categoryI
// 1. Must not be the excluded discussion (newly created one)
// 2. Must not be already closed
// 3. If categoryId is specified, must match
// 4. Body must contain the exact marker for this workflow.
// When callerWorkflowId is set, match `gh-aw-workflow-call-id` so that callers
// sharing the same reusable workflow do not close each other's discussions.
// 4. Body must contain the exact marker. When closeOlderKey is set the close-key marker
// is used. Otherwise, when callerWorkflowId is set, match `gh-aw-workflow-call-id` so
// that callers sharing the same reusable workflow do not close each other's discussions.
// Fall back to `gh-aw-workflow-id` for backward compat with older discussions.
core.info("Filtering search results...");
let filteredCount = 0;
let excludedCount = 0;
let closedCount = 0;
let markerMismatchCount = 0;

const exactMarker = callerWorkflowId ? generateWorkflowCallIdMarker(callerWorkflowId) : generateWorkflowIdMarker(workflowId);

const filtered = result.search.nodes
.filter(
/** @param {any} d */ d => {
Expand Down Expand Up @@ -215,9 +229,10 @@ async function closeDiscussionAsOutdated(github, owner, repo, discussionId) {
* @param {string} workflowName - Name of the workflow
* @param {string} runUrl - URL of the workflow run
* @param {string} [callerWorkflowId] - Optional calling workflow identity for precise filtering
* @param {string} [closeOlderKey] - Optional explicit deduplication key for close-older matching
* @returns {Promise<Array<{number: number, url: string}>>} List of closed discussions
*/
async function closeOlderDiscussions(github, owner, repo, workflowId, categoryId, newDiscussion, workflowName, runUrl, callerWorkflowId) {
async function closeOlderDiscussions(github, owner, repo, workflowId, categoryId, newDiscussion, workflowName, runUrl, callerWorkflowId, closeOlderKey) {
const result = await closeOlderEntities(
github,
owner,
Expand All @@ -229,9 +244,10 @@ async function closeOlderDiscussions(github, owner, repo, workflowId, categoryId
{
entityType: "discussion",
entityTypePlural: "discussions",
// Use a closure so callerWorkflowId is forwarded to searchOlderDiscussions without going
// through the closeOlderEntities extraArgs mechanism (which appends excludeNumber last)
searchOlderEntities: (gh, o, r, wid, categoryId, excludeNumber) => searchOlderDiscussions(gh, o, r, wid, categoryId, excludeNumber, callerWorkflowId),
// Use a closure so callerWorkflowId and closeOlderKey are forwarded to
// searchOlderDiscussions without going through the closeOlderEntities extraArgs
// mechanism (which appends excludeNumber last)
searchOlderEntities: (gh, o, r, wid, categoryId, excludeNumber) => searchOlderDiscussions(gh, o, r, wid, categoryId, excludeNumber, callerWorkflowId, closeOlderKey),
getCloseMessage: params =>
getCloseOlderDiscussionMessage({
newDiscussionUrl: params.newEntityUrl,
Expand Down
Loading
Loading