Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
18 changes: 14 additions & 4 deletions .github/scripts/issue_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,16 @@ def _task_has_concrete_target(item: str) -> bool:
for match in re.finditer(rf"\b{_TASK_CATEGORY}\s+(`[^`]+`|[^\s]+)", item, re.I):
token = match.group(1)
span = token[1:-1] if token.startswith("`") and token.endswith("`") else token.strip("'\"")
# A quoted identifier immediately following an explicit target category
# is concrete even when its spelling is a normal lowercase word (for
# example, ``function `validate` `` or ``key `timeout` ``).
if (
token.startswith("`")
and token.endswith("`")
and re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*", span)
and not re.fullmatch(_TASK_CATEGORY, span, re.I)
):
return True
if _concrete_span(span):
return True
for match in re.finditer(r"`([^`]+)`", item):
Expand Down Expand Up @@ -158,7 +168,7 @@ def _headings(body: str) -> list[tuple[str, int, int]]:
out: list[tuple[str, int, int]] = []
fence: tuple[str, int] | None = None
for i, line in enumerate(body.splitlines()):
fence_match = re.match(r"\s{0,3}(`{3,}|~{3,})", line)
fence_match = re.match(r"\s*(`{3,}|~{3,})", line)
if fence_match:
marker = fence_match.group(1)
if fence is None:
Expand All @@ -167,7 +177,7 @@ def _headings(body: str) -> list[tuple[str, int, int]]:
marker[0] == fence[0]
and len(marker) >= fence[1]
and re.fullmatch(
rf"\s{{0,3}}(?:`{{{fence[1]},}}|~{{{fence[1]},}})\s*",
rf"\s*(?:`{{{fence[1]},}}|~{{{fence[1]},}})\s*",
line,
)
):
Expand Down Expand Up @@ -204,7 +214,7 @@ def _without_fenced_code(text: str) -> str:
kept: list[str] = []
fence: tuple[str, int] | None = None
for line in text.splitlines():
match = re.match(r"\s{0,3}(`{3,}|~{3,})", line)
match = re.match(r"\s*(`{3,}|~{3,})", line)
if match:
marker = match.group(1)
if fence is None:
Expand All @@ -213,7 +223,7 @@ def _without_fenced_code(text: str) -> str:
marker[0] == fence[0]
and len(marker) >= fence[1]
and re.fullmatch(
rf"\s{{0,3}}(?:`{{{fence[1]},}}|~{{{fence[1]},}})\s*",
rf"\s*(?:`{{{fence[1]},}}|~{{{fence[1]},}})\s*",
line,
)
):
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/agents-issue-format-guard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ jobs:
github.event.action == 'opened' || github.event.action == 'edited' ||
github.event.action == 'reopened' ||
((github.event.action == 'labeled' || github.event.action == 'unlabeled') &&
(github.event.label.name == 'agents:auto-pilot-pause' ||
(github.event.label.name == 'agents:format' ||
Comment thread
stranske marked this conversation as resolved.
Resolved
Comment thread
stranske marked this conversation as resolved.
Resolved
github.event.label.name == 'agents:auto-pilot-pause' ||
github.event.label.name == 'needs-human' ||
github.event.label.name == 'tracker:durable' || github.event.label.name == 'wontfix'))
runs-on: ubuntu-latest
Expand Down
18 changes: 14 additions & 4 deletions templates/consumer-repo/.github/scripts/issue_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,16 @@ def _task_has_concrete_target(item: str) -> bool:
for match in re.finditer(rf"\b{_TASK_CATEGORY}\s+(`[^`]+`|[^\s]+)", item, re.I):
token = match.group(1)
span = token[1:-1] if token.startswith("`") and token.endswith("`") else token.strip("'\"")
# A quoted identifier immediately following an explicit target category
# is concrete even when its spelling is a normal lowercase word (for
# example, ``function `validate` `` or ``key `timeout` ``).
if (
token.startswith("`")
and token.endswith("`")
and re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*", span)
and not re.fullmatch(_TASK_CATEGORY, span, re.I)
):
return True
if _concrete_span(span):
return True
for match in re.finditer(r"`([^`]+)`", item):
Expand Down Expand Up @@ -158,7 +168,7 @@ def _headings(body: str) -> list[tuple[str, int, int]]:
out: list[tuple[str, int, int]] = []
fence: tuple[str, int] | None = None
for i, line in enumerate(body.splitlines()):
fence_match = re.match(r"\s{0,3}(`{3,}|~{3,})", line)
fence_match = re.match(r"\s*(`{3,}|~{3,})", line)
if fence_match:
marker = fence_match.group(1)
if fence is None:
Expand All @@ -167,7 +177,7 @@ def _headings(body: str) -> list[tuple[str, int, int]]:
marker[0] == fence[0]
and len(marker) >= fence[1]
and re.fullmatch(
rf"\s{{0,3}}(?:`{{{fence[1]},}}|~{{{fence[1]},}})\s*",
rf"\s*(?:`{{{fence[1]},}}|~{{{fence[1]},}})\s*",
line,
)
):
Expand Down Expand Up @@ -204,7 +214,7 @@ def _without_fenced_code(text: str) -> str:
kept: list[str] = []
fence: tuple[str, int] | None = None
for line in text.splitlines():
match = re.match(r"\s{0,3}(`{3,}|~{3,})", line)
match = re.match(r"\s*(`{3,}|~{3,})", line)
if match:
marker = match.group(1)
if fence is None:
Expand All @@ -213,7 +223,7 @@ def _without_fenced_code(text: str) -> str:
marker[0] == fence[0]
and len(marker) >= fence[1]
and re.fullmatch(
rf"\s{{0,3}}(?:`{{{fence[1]},}}|~{{{fence[1]},}})\s*",
rf"\s*(?:`{{{fence[1]},}}|~{{{fence[1]},}})\s*",
line,
)
):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ jobs:
github.event.action == 'opened' || github.event.action == 'edited' ||
github.event.action == 'reopened' ||
((github.event.action == 'labeled' || github.event.action == 'unlabeled') &&
(github.event.label.name == 'agents:auto-pilot-pause' ||
(github.event.label.name == 'agents:format' ||
github.event.label.name == 'agents:auto-pilot-pause' ||
github.event.label.name == 'needs-human' ||
github.event.label.name == 'tracker:durable' || github.event.label.name == 'wontfix'))
runs-on: ubuntu-latest
Expand Down
40 changes: 40 additions & 0 deletions tests/scripts/test_issue_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,46 @@ def test_fence_with_language_marker_does_not_close_a_code_block() -> None:
assert "Acceptance Criteria" in report.missing_required


@pytest.mark.parametrize(
"validator_path",
[
Path(".github/scripts/issue_format.py"),
Path("templates/consumer-repo/.github/scripts/issue_format.py"),
],
)
def test_list_indented_fenced_acceptance_command_is_ignored(validator_path: Path) -> None:
"""A four-space list fence is an example, not an observable gate."""
validator = _validator(validator_path)
report = validator.validate(
VALID_CONTEXT
+ "## Tasks\n- [ ] Update `src/client.py`\n\n"
+ "## Acceptance Criteria\n- Example command:\n\n ```sh\n pytest tests/test_x.py\n ```\n"
)
assert not report.ok
assert any("names no test" in problem for problem in report.problems)

Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

@pytest.mark.parametrize(
"validator_path",
[
Path(".github/scripts/issue_format.py"),
Path("templates/consumer-repo/.github/scripts/issue_format.py"),
],
)
@pytest.mark.parametrize("target", ["validate", "timeout"])
def test_explicit_category_accepts_quoted_lowercase_identifier(
validator_path: Path, target: str
) -> None:
validator = _validator(validator_path)
category = "function" if target == "validate" else "key"
report = validator.validate(
VALID_CONTEXT
+ f"## Tasks\n- [ ] Update {category} `{target}`\n\n"
+ "## Acceptance Criteria\n- pytest tests/test_x.py passes\n"
)
assert report.ok


@pytest.mark.parametrize("marker", ["```", "~~~"])
def test_fence_close_requires_marker_only_line(marker: str) -> None:
"""Trailing text after fence markers must not end the fenced region."""
Expand Down
8 changes: 8 additions & 0 deletions tests/workflows/test_agents_issue_optimizer_format_trigger.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,14 @@ def test_format_guard_deduplicates_inflight_optimizer_dispatch() -> None:
)


def test_format_guard_rechecks_after_format_lease_release() -> None:
for path in (GUARD_PATH, CONSUMER_GUARD_PATH):
text = path.read_text(encoding="utf-8")
condition = text[text.index("if: >-") : text.index("runs-on: ubuntu-latest")]
assert "github.event.action == 'unlabeled'" in condition
assert "github.event.label.name == 'agents:format'" in condition


def test_format_guard_attempt_marker_sequence_consumes_retry_budget() -> None:
"""Exercise the marker contract used by the workflow's routing step."""
fingerprint = "abc123def456"
Expand Down
Loading