Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
33 changes: 31 additions & 2 deletions .github/scripts/issue_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,33 @@ def _section_text(body: str, start: int) -> str:
return "\n".join(lines[start + 1 : end])


def _without_fenced_code(text: str) -> str:
"""Remove Markdown fences so examples cannot satisfy issue requirements."""
kept: list[str] = []
fence: tuple[str, int] | None = None
for line in text.splitlines():
Comment thread
stranske marked this conversation as resolved.
match = re.match(r"\s{0,3}(`{3,}|~{3,})", line)
if match:
marker = match.group(1)
if fence is None:
fence = (marker[0], len(marker))
elif (
marker[0] == fence[0]
and len(marker) >= fence[1]
and re.fullmatch(
rf"\s{{0,3}}(?:`{{{fence[1]},}}|~{{{fence[1]},}})\s*",
line,
)
):
# Closing fences are marker-only (optional whitespace); trailing
# content such as a language tag must not end the fence.
fence = None
continue
if fence is None:
kept.append(line)
return "\n".join(kept)
Comment thread
coderabbitai[bot] marked this conversation as resolved.


@dataclass
class Report:
ok: bool = True
Expand Down Expand Up @@ -247,7 +274,9 @@ def validate(body: str) -> Report:
tasks_at = _find(body, REQUIRED["Tasks"])
if tasks_at is not None:
task_items = re.findall(
r"^\s*[-*]\s*\[[ xX]\]\s*(.+)$", _section_text(body, tasks_at), re.M
r"^\s*[-*]\s*\[[ xX]\]\s*(.+)$",
_without_fenced_code(_section_text(body, tasks_at)),
re.M,
)
if not task_items:
report.problems.append(
Expand All @@ -260,7 +289,7 @@ def validate(body: str) -> Report:

acceptance_at = _find(body, REQUIRED["Acceptance Criteria"])
if acceptance_at is not None:
acceptance = _section_text(body, acceptance_at)
acceptance = _without_fenced_code(_section_text(body, acceptance_at))
if not GATE.search(acceptance):
report.problems.append(
"`Acceptance Criteria` names no test, runnable command or observable "
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/agents-63-issue-intake.yml
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,8 @@ jobs:
)
)
) &&
!contains(github.event.issue.labels.*.name, 'agents:auto-pilot')
(github.event_name != 'issues' ||
!contains(github.event.issue.labels.*.name, 'agents:auto-pilot'))
name: Normalize workflow inputs
runs-on: ubuntu-latest
outputs:
Expand Down
6 changes: 3 additions & 3 deletions config/template-drift-allowlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@
[pair.1]
main = .github/workflows/agents-63-issue-intake.yml
template = templates/consumer-repo/.github/workflows/agents-issue-intake.yml
main_sha256 = e0ded7e7b98ff8abe3cb60b69c40bd8472d4c4d857d037540d52aefcc332dd7a
template_sha256 = 1381165cbf31a6fbe632bd9667337b7ac042d777af8965fc155f8351b66a233a
reason = Intentional divergence re-baselined 2026-08-08: both intake surfaces now reject closed issue label events before bridge/PR creation. Root remains the numeric-prefixed Workflows-internal superset while the consumer template retains its pinned action and minimal bridge contract.
main_sha256 = f63f28d047db415dac9036ead0dc111a55ae999179fdb44cb458c8a39d0e6c67
template_sha256 = 617cb594296c26ef8510b03dc43e835396a50ce84ed0d8a10d9e41b3e93f6772
reason = Intentional divergence re-baselined 2026-08-09: both intake surfaces keep the auto-pilot skip only for issue events so workflow_dispatch remains independent of issue payload labels. Root remains the numeric-prefixed Workflows-internal superset while the consumer template retains its pinned action and minimal bridge contract.

[pair.2]
main = .github/workflows/agents-71-codex-belt-dispatcher.yml
Expand Down
4 changes: 2 additions & 2 deletions langsmith-fleet-worker-attempt.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"agent": "codex",
"cli_version": "0.144.1",
"emitted_at": "2026-08-09T07:45:12.662386Z",
"emitted_at": "2026-08-09T11:43:24.687070Z",
"execution_profile": "codex-default",
"fallback_models": [
"gpt-5.5"
],
"operation_role": "worker",
"pr_number": "3004",
"pr_number": "3005",
"requested_model": "gpt-5.6-terra",
"runner": "reusable-codex-run",
"schema": "langsmith-fleet/v1",
Expand Down
30 changes: 27 additions & 3 deletions scripts/langchain/issue_formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,11 +170,11 @@ def _strip_reuse_marker(text: str) -> str:
VERIFY_HINT_REGEX = re.compile(r"\(verify:\s*([^\n)]+)\)", re.IGNORECASE)
SAFE_VERIFY_COMMAND_RE = re.compile(
r"^(?:"
r"(?:python(?:3)?\s+-m\s+)?pytest\b"
r"(?:python(?:3)?\s+-m\s+)?(?:pytest|unittest)\b"
r"|node\s+--test\b"
r"|(?:npm|pnpm|yarn)\s+(?:run\s+)?(?:test|vitest|jest|playwright)\b"
r"|(?:make|just|cargo|go|dotnet)\s+(?:test|check)\b"
r"|gh\s+(?:workflow\s+run|run)\b"
r"|gh\s+(?:workflow\s+run|run)\s+\S+"
Comment thread
stranske marked this conversation as resolved.
r"|curl\s+\S+"
r")",
re.IGNORECASE,
Expand Down Expand Up @@ -477,10 +477,34 @@ def _strip_original_issue_blocks(text: str) -> str:
kept.append(text[cursor : match.start()])
depth = 1
end = match.end()
fence: tuple[str, int] | None = None
for tag in _DETAILS_TAG_RE.finditer(text, match.end()):
# Literal HTML in the verbatim Original-Issue fence is content, not
# structural markup. Only count tags outside Markdown fences.
before = text[end : tag.start()]
for line in before.splitlines():
fence_match = re.match(r"\s{0,3}(`{3,}|~{3,})", line)
if not fence_match:
continue
marker = fence_match.group(1)
if fence is None:
fence = (marker[0], len(marker))
elif (
marker[0] == fence[0]
and len(marker) >= fence[1]
and re.fullmatch(
rf"\s{{0,3}}(?:`{{{fence[1]},}}|~{{{fence[1]},}})\s*",
line,
)
):
# Closing fences are marker-only; language tags / trailing
# text must not toggle the fence state.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
fence = None
end = tag.end()
if fence is not None:
continue
depth += -1 if tag.group(0).startswith("</") else 1
if depth == 0:
Comment thread
stranske marked this conversation as resolved.
end = tag.end()
break
else:
# Leave malformed markup intact rather than silently discarding it.
Expand Down
34 changes: 31 additions & 3 deletions templates/consumer-repo/.github/scripts/issue_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,6 @@ def _task_has_concrete_target(item: str) -> bool:
return True
# Command names in prose ("make the UI better", "go improve it") are not
# concrete targets. Require a command-shaped invocation instead.
# Allow a leading backtick so `make test` / `npm test` count.
return re.search(rf"(?:^|[\s`]){_TASK_COMMAND}", item, re.I) is not None


Expand Down Expand Up @@ -200,6 +199,33 @@ def _section_text(body: str, start: int) -> str:
return "\n".join(lines[start + 1 : end])


def _without_fenced_code(text: str) -> str:
"""Remove Markdown fences so examples cannot satisfy issue requirements."""
kept: list[str] = []
fence: tuple[str, int] | None = None
for line in text.splitlines():
match = re.match(r"\s{0,3}(`{3,}|~{3,})", line)
if match:
marker = match.group(1)
if fence is None:
fence = (marker[0], len(marker))
elif (
marker[0] == fence[0]
and len(marker) >= fence[1]
and re.fullmatch(
rf"\s{{0,3}}(?:`{{{fence[1]},}}|~{{{fence[1]},}})\s*",
line,
)
):
# Closing fences are marker-only (optional whitespace); trailing
# content such as a language tag must not end the fence.
fence = None
continue
if fence is None:
kept.append(line)
return "\n".join(kept)


@dataclass
class Report:
ok: bool = True
Expand Down Expand Up @@ -248,7 +274,9 @@ def validate(body: str) -> Report:
tasks_at = _find(body, REQUIRED["Tasks"])
if tasks_at is not None:
task_items = re.findall(
r"^\s*[-*]\s*\[[ xX]\]\s*(.+)$", _section_text(body, tasks_at), re.M
r"^\s*[-*]\s*\[[ xX]\]\s*(.+)$",
_without_fenced_code(_section_text(body, tasks_at)),
re.M,
)
if not task_items:
report.problems.append(
Expand All @@ -261,7 +289,7 @@ def validate(body: str) -> Report:

acceptance_at = _find(body, REQUIRED["Acceptance Criteria"])
if acceptance_at is not None:
acceptance = _section_text(body, acceptance_at)
acceptance = _without_fenced_code(_section_text(body, acceptance_at))
if not GATE.search(acceptance):
report.problems.append(
"`Acceptance Criteria` names no test, runnable command or observable "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ jobs:
(github.event.issue.state != 'closed' &&
(contains(toJson(github.event.issue.labels.*.name), 'agent:') ||
contains(toJson(github.event.issue.labels.*.name), 'agents:')))) &&
!contains(github.event.issue.labels.*.name, 'agents:auto-pilot')
(github.event_name != 'issues' ||
!contains(github.event.issue.labels.*.name, 'agents:auto-pilot'))
runs-on: ubuntu-latest
outputs:
should_run: ${{ steps.check.outputs.should_run }}
Expand Down
21 changes: 21 additions & 0 deletions tests/scripts/test_issue_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,27 @@ def test_fence_with_language_marker_does_not_close_a_code_block() -> None:
assert "Acceptance Criteria" in report.missing_required


@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."""
validator = _validator()
close_with_junk = f"{marker} not-a-close"
body = (
f"{marker}markdown\n"
"## Tasks\n- [ ] pretend\n"
f"{close_with_junk}\n"
"## Acceptance Criteria\n- pytest tests/test_x.py\n"
f"{marker}\n"
)
report = validator.validate(body)
assert not report.ok
assert set(report.missing_required) >= {"Tasks", "Acceptance Criteria"}
# Section body stripping must keep pretending the junk line is still inside.
stripped = validator._without_fenced_code(body)
assert "Acceptance Criteria" not in stripped
assert "pretend" not in stripped


def test_heading_with_trailing_qualifier_matches_required_section() -> None:
validator = _validator()
report = validator.validate(
Expand Down
55 changes: 53 additions & 2 deletions tests/scripts/test_issue_formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -678,7 +678,7 @@ def test_strip_original_issue_blocks_removes_balanced_nested_details() -> None:
<details>
<summary>Original Issue</summary>

```text
````text
outer
<details>
<summary>Original Issue</summary>
Expand All @@ -687,7 +687,7 @@ def test_strip_original_issue_blocks_removes_balanced_nested_details() -> None:
inner
```
</details>
```
````
</details>

## Scope
Expand All @@ -703,6 +703,43 @@ def test_strip_original_issue_blocks_removes_balanced_nested_details() -> None:
assert "</details>" not in stripped


@pytest.mark.parametrize("marker", ["```", "~~~"])
def test_strip_original_issue_ignores_details_when_fence_close_has_trailing_text(
marker: str,
) -> None:
"""A fence line with trailing junk must not resume HTML details counting."""
payload = "\n".join(
[
"## Why",
"",
"Keep this text.",
"",
"<details>",
"<summary>Original Issue</summary>",
"",
f"{marker}text",
"literal <details>",
f"{marker} still-inside",
"literal </details>",
marker,
"</details>",
"",
"## Scope",
"",
"Keep this too.",
"",
]
)

stripped = issue_formatter._strip_original_issue_blocks(payload)

assert "Keep this text." in stripped
assert "Keep this too." in stripped
assert "Original Issue" not in stripped
assert "literal <details>" not in stripped
assert "</details>" not in stripped


def test_reuse_sets_needs_refinement_when_validator_fails() -> None:
"""Structurally conformant but contract-invalid bodies must not claim ready."""
conformant = "\n".join(
Expand Down Expand Up @@ -750,3 +787,17 @@ def test_bare_curl_is_not_a_safe_verify_command() -> None:
assert (
issue_formatter.SAFE_VERIFY_COMMAND_RE.match("curl https://example.test/health") is not None
)


def test_safe_verify_command_accepts_unittest_and_requires_gh_args() -> None:
assert (
issue_formatter.SAFE_VERIFY_COMMAND_RE.match("python -m unittest tests.test_x") is not None
)
assert issue_formatter.SAFE_VERIFY_COMMAND_RE.match("unittest") is not None
assert issue_formatter.SAFE_VERIFY_COMMAND_RE.match("gh run") is None
assert issue_formatter.SAFE_VERIFY_COMMAND_RE.match("gh workflow run") is None
assert issue_formatter.SAFE_VERIFY_COMMAND_RE.match("gh run watch") is not None
assert (
issue_formatter.SAFE_VERIFY_COMMAND_RE.match("gh workflow run agents-issue-optimizer.yml")
is not None
)
Loading