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
13 changes: 11 additions & 2 deletions .github/scripts/issue_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,10 @@ def _concrete_span(span: str) -> bool:
return True
if "_" in span or "." in span:
return True
# Multi-segment CamelCase / PascalCase symbols (e.g. IssueFormatter).
return re.fullmatch(r"[A-Z][a-zA-Z0-9]*(?:[A-Z][a-zA-Z0-9]+)+", span) is not None
# Multi-segment PascalCase or lowerCamelCase symbols (e.g. IssueFormatter,
# calculateDiscount). A capitalized interior segment distinguishes them from
# generic lowercase prose.
return re.fullmatch(r"[A-Za-z][a-zA-Z0-9]*[A-Z][a-zA-Z0-9]+", span) is not None


def _task_has_concrete_target(item: str) -> bool:
Expand All @@ -130,6 +132,13 @@ def _task_has_concrete_target(item: str) -> bool:
for match in re.finditer(r"`([^`]+)`", item):
if _concrete_span(match.group(1)):
return True
for token in re.findall(r"\b[A-Za-z][A-Za-z0-9_]*\b", item):
# Unquoted: only unambiguous lowerCamelCase (calculateDiscount).
# Brand/prose capitals (GitHub, JavaScript, OpenAI) must not satisfy.
if not re.fullmatch(r"[a-z][a-zA-Z0-9]*[A-Z][a-zA-Z0-9]+", token):
continue
if _concrete_span(token):
Comment thread
stranske marked this conversation as resolved.
return True
if re.search(rf"(?:^|[\s])({_TASK_KNOWN_BASENAME})\b", item, re.I):
return True
if re.search(rf"(?:^|[\s])([\w.-]+\.(?:{_TASK_EXTENSION}))\b", item, re.I):
Expand Down
48 changes: 39 additions & 9 deletions .github/workflows/agents-issue-format-guard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,6 @@ jobs:
with:
persist-credentials: false

- name: Setup API client
uses: ./.github/actions/setup-api-client
with:
# This guard only reads issue comments with the workflow token. Do not
# expose the repository-wide secret bundle to the composite action.
github_token: ${{ github.token }}

- name: Resolve issue
id: issue
env:
Expand Down Expand Up @@ -77,8 +70,17 @@ jobs:
echo "number=$NUMBER"
echo "exempt=$exempt"
echo "held=$held"
echo "state=$(jq -r '.state' issue.json)"
} >> "$GITHUB_OUTPUT"

- name: Setup API client
if: steps.issue.outputs.exempt != 'true' && steps.issue.outputs.held != 'true'
uses: ./.github/actions/setup-api-client
with:
# This guard only reads issue comments with the workflow token. Do not
# expose the repository-wide secret bundle to the composite action.
github_token: ${{ github.token }}

Comment thread
stranske marked this conversation as resolved.
- name: Validate against AGENT_ISSUE_FORMAT
id: validate
if: steps.issue.outputs.exempt != 'true'
Expand Down Expand Up @@ -111,12 +113,36 @@ jobs:

- name: Invalidate stale format completion after an invalid issue change
if: >-
steps.issue.outputs.exempt != 'true' && steps.validate.outputs.rc == '1'
steps.issue.outputs.exempt != 'true' && steps.issue.outputs.state == 'OPEN' &&
steps.validate.outputs.rc == '1'
env:
GH_TOKEN: ${{ github.token }}
NUMBER: ${{ steps.issue.outputs.number }}
run: |
set -euo pipefail
live_issue="$(mktemp)"
live_body="$(mktemp)"
error_file="$(mktemp)"
trap 'rm -f "$live_issue" "$live_body" "$error_file"' EXIT
gh issue view "$NUMBER" --repo "$GITHUB_REPOSITORY" --json body,state > "$live_issue"
if [[ "$(jq -r '.state' "$live_issue")" != "OPEN" ]]; then
echo "Issue is now closed — preserving agents:formatted."
exit 0
fi
jq -r '.body // ""' "$live_issue" > "$live_body"
set +e
python3 .github/scripts/issue_format.py "$live_body" > /dev/null 2> "$error_file"
live_rc=$?
set -e
if [[ "$live_rc" -eq 0 ]]; then
echo "Live body now conforms — preserving agents:formatted."
exit 0
fi
if [[ "$live_rc" -ne 1 || -s "$error_file" ]]; then
echo "::error::issue-format validator failed unexpectedly during label revalidation"
cat "$error_file" >&2
exit "$live_rc"
fi
if gh issue view "$NUMBER" --repo "$GITHUB_REPOSITORY" --json labels \
--jq '.labels[].name' | grep -qx 'agents:formatted'; then
if gh issue edit "$NUMBER" --repo "$GITHUB_REPOSITORY" --remove-label "agents:formatted"; then
Expand All @@ -127,7 +153,7 @@ jobs:
fi

- name: Route non-conforming issue to the optimizer
if: steps.issue.outputs.exempt != 'true' && steps.issue.outputs.held != 'true' && steps.validate.outputs.rc == '1'
if: steps.issue.outputs.exempt != 'true' && steps.issue.outputs.held != 'true' && steps.issue.outputs.state == 'OPEN' && steps.validate.outputs.rc == '1'
env:
GH_TOKEN: ${{ github.token }}
NUMBER: ${{ steps.issue.outputs.number }}
Expand All @@ -138,6 +164,10 @@ jobs:
gh issue view "$NUMBER" --repo "$GITHUB_REPOSITORY" \
--json number,body,labels,state,author > live.json
jq -r '.body // ""' live.json > body.md
if [[ "$(jq -r '.state' live.json)" != "OPEN" ]]; then
echo "Issue is now closed — skipping optimizer dispatch."
exit 0
fi
if jq -e '[.labels[].name] | any(. == "agents:auto-pilot-pause" or . == "needs-human")' live.json >/dev/null; then
echo "Issue is now held — skipping optimizer dispatch."
exit 0
Expand Down
13 changes: 11 additions & 2 deletions templates/consumer-repo/.github/scripts/issue_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,10 @@ def _concrete_span(span: str) -> bool:
return True
if "_" in span or "." in span:
return True
# Multi-segment CamelCase / PascalCase symbols (e.g. IssueFormatter).
return re.fullmatch(r"[A-Z][a-zA-Z0-9]*(?:[A-Z][a-zA-Z0-9]+)+", span) is not None
# Multi-segment PascalCase or lowerCamelCase symbols (e.g. IssueFormatter,
# calculateDiscount). A capitalized interior segment distinguishes them from
# generic lowercase prose.
return re.fullmatch(r"[A-Za-z][a-zA-Z0-9]*[A-Z][a-zA-Z0-9]+", span) is not None


def _task_has_concrete_target(item: str) -> bool:
Expand All @@ -130,6 +132,13 @@ def _task_has_concrete_target(item: str) -> bool:
for match in re.finditer(r"`([^`]+)`", item):
if _concrete_span(match.group(1)):
return True
for token in re.findall(r"\b[A-Za-z][A-Za-z0-9_]*\b", item):
# Unquoted: only unambiguous lowerCamelCase (calculateDiscount).
# Brand/prose capitals (GitHub, JavaScript, OpenAI) must not satisfy.
if not re.fullmatch(r"[a-z][a-zA-Z0-9]*[A-Z][a-zA-Z0-9]+", token):
continue
if _concrete_span(token):
return True
if re.search(rf"(?:^|[\s])({_TASK_KNOWN_BASENAME})\b", item, re.I):
return True
if re.search(rf"(?:^|[\s])([\w.-]+\.(?:{_TASK_EXTENSION}))\b", item, re.I):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,6 @@ jobs:
with:
persist-credentials: false

- name: Setup API client
uses: ./.github/actions/setup-api-client
with:
# This guard only reads issue comments with the workflow token. Do not
# expose the repository-wide secret bundle to the composite action.
github_token: ${{ github.token }}

- name: Resolve issue
id: issue
env:
Expand Down Expand Up @@ -77,8 +70,17 @@ jobs:
echo "number=$NUMBER"
echo "exempt=$exempt"
echo "held=$held"
echo "state=$(jq -r '.state' issue.json)"
} >> "$GITHUB_OUTPUT"

- name: Setup API client
if: steps.issue.outputs.exempt != 'true' && steps.issue.outputs.held != 'true'
uses: ./.github/actions/setup-api-client
with:
# This guard only reads issue comments with the workflow token. Do not
# expose the repository-wide secret bundle to the composite action.
github_token: ${{ github.token }}

- name: Validate against AGENT_ISSUE_FORMAT
id: validate
if: steps.issue.outputs.exempt != 'true'
Expand Down Expand Up @@ -111,12 +113,36 @@ jobs:

- name: Invalidate stale format completion after an invalid issue change
if: >-
steps.issue.outputs.exempt != 'true' && steps.validate.outputs.rc == '1'
steps.issue.outputs.exempt != 'true' && steps.issue.outputs.state == 'OPEN' &&
steps.validate.outputs.rc == '1'
env:
GH_TOKEN: ${{ github.token }}
NUMBER: ${{ steps.issue.outputs.number }}
run: |
set -euo pipefail
live_issue="$(mktemp)"
live_body="$(mktemp)"
error_file="$(mktemp)"
trap 'rm -f "$live_issue" "$live_body" "$error_file"' EXIT
gh issue view "$NUMBER" --repo "$GITHUB_REPOSITORY" --json body,state > "$live_issue"
if [[ "$(jq -r '.state' "$live_issue")" != "OPEN" ]]; then
echo "Issue is now closed — preserving agents:formatted."
exit 0
fi
jq -r '.body // ""' "$live_issue" > "$live_body"
set +e
python3 .github/scripts/issue_format.py "$live_body" > /dev/null 2> "$error_file"
live_rc=$?
set -e
if [[ "$live_rc" -eq 0 ]]; then
echo "Live body now conforms — preserving agents:formatted."
exit 0
fi
if [[ "$live_rc" -ne 1 || -s "$error_file" ]]; then
echo "::error::issue-format validator failed unexpectedly during label revalidation"
cat "$error_file" >&2
exit "$live_rc"
fi
if gh issue view "$NUMBER" --repo "$GITHUB_REPOSITORY" --json labels \
--jq '.labels[].name' | grep -qx 'agents:formatted'; then
if gh issue edit "$NUMBER" --repo "$GITHUB_REPOSITORY" --remove-label "agents:formatted"; then
Expand All @@ -127,7 +153,7 @@ jobs:
fi

- name: Route non-conforming issue to the optimizer
if: steps.issue.outputs.exempt != 'true' && steps.issue.outputs.held != 'true' && steps.validate.outputs.rc == '1'
if: steps.issue.outputs.exempt != 'true' && steps.issue.outputs.held != 'true' && steps.issue.outputs.state == 'OPEN' && steps.validate.outputs.rc == '1'
env:
GH_TOKEN: ${{ github.token }}
NUMBER: ${{ steps.issue.outputs.number }}
Expand All @@ -138,6 +164,10 @@ jobs:
gh issue view "$NUMBER" --repo "$GITHUB_REPOSITORY" \
--json number,body,labels,state,author > live.json
jq -r '.body // ""' live.json > body.md
if [[ "$(jq -r '.state' live.json)" != "OPEN" ]]; then
echo "Issue is now closed — skipping optimizer dispatch."
exit 0
fi
if jq -e '[.labels[].name] | any(. == "agents:auto-pilot-pause" or . == "needs-human")' live.json >/dev/null; then
echo "Issue is now held — skipping optimizer dispatch."
exit 0
Expand Down
3 changes: 3 additions & 0 deletions tests/scripts/test_issue_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@ def test_task_without_concrete_target_is_non_conforming() -> None:
"Fix bugs `later`",
"Fix file handling",
"Update the configuration",
"Update the GitHub configuration",
"Update the JavaScript documentation",
],
)
def test_vague_task_targets_are_non_conforming(task: str) -> None:
Expand All @@ -192,6 +194,7 @@ def test_vague_task_targets_are_non_conforming(task: str) -> None:
"Update pom.xml",
"Update Dockerfile",
"Wire `IssueFormatter` into the guard",
"Write unit tests for calculateDiscount function",
"Touch file `src/client.py`",
"Run pytest tests/scripts/test_issue_format.py",
],
Expand Down
23 changes: 23 additions & 0 deletions tests/workflows/test_agents_issue_optimizer_format_trigger.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,26 @@ def test_format_guard_uses_event_inputs_and_fails_closed_on_revalidation_errors(
assert (
"github.event.issue.number || github.event.inputs.issue_number || github.run_id" in text
)


def test_format_guard_revalidates_before_clearing_state_and_skips_closed_issues() -> None:
for text in (
GUARD_PATH.read_text(encoding="utf-8"),
CONSUMER_GUARD_PATH.read_text(encoding="utf-8"),
):
resolve_idx = text.index("- name: Resolve issue")
setup_idx = text.index("- name: Setup API client")
assert resolve_idx < setup_idx
assert (
"steps.issue.outputs.exempt != 'true' && steps.issue.outputs.held != 'true'"
in text[setup_idx : setup_idx + 250]
)
invalidate_idx = text.index("- name: Invalidate stale format completion")
route_idx = text.index("- name: Route non-conforming issue to the optimizer")
invalidate = text[invalidate_idx:route_idx]
assert "steps.issue.outputs.state == 'OPEN'" in invalidate
assert "Live body now conforms — preserving agents:formatted." in invalidate
assert "validator failed unexpectedly during label revalidation" in invalidate
route = text[route_idx:]
assert "steps.issue.outputs.state == 'OPEN'" in route
assert "Issue is now closed — skipping optimizer dispatch." in route
Loading