feat: block vague-reference issue creation with content preflight (Closes #582)

Add a pre-create content gate to gitea_create_issue that fails closed with
BLOCKED + DIAGNOSE when the title/body is a vague reference to out-of-band
draft content ("the drafted issue", "the prepared issue", "the issue we
discussed", "the previous draft") and no durable source pointer (existing
issue/PR/comment, checked-in file, URL, or scratchpad path) is present.

- issue_content_gate.py: assess_issue_content / pre_create_issue_content_gate
  with vague-phrase detection, durable-source-pointer escape hatch, and a
  domination check so long specific bodies are not falsely blocked.
- gitea_create_issue: runs the gate after preflight purity, before duplicate
  search; new allow_incomplete_content override (off by default). Title-only
  creation stays allowed (no empty-body requirement) to preserve behavior.
- tests/test_issue_content_gate.py: unit + MCP integration coverage.
- create-issue.md: documents the enforced preflight with valid/invalid
  prompt examples.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
2026-07-09 13:07:53 -04:00
co-authored by Claude Opus 4.8
parent c738e5b10d
commit 5debfcf178
4 changed files with 433 additions and 0 deletions
+23
View File
@@ -799,6 +799,7 @@ import gitea_audit # noqa: E402
import gitea_config # noqa: E402
import capability_stop_terminal # noqa: E402
import issue_duplicate_gate # noqa: E402
import issue_content_gate # noqa: E402
import role_session_router # noqa: E402
import role_namespace_gate # noqa: E402
import task_capability_map # noqa: E402
@@ -1623,6 +1624,7 @@ def gitea_create_issue(
require_workflow_labels: bool = False,
allow_duplicate_override: bool = False,
split_from_issue: int | None = None,
allow_incomplete_content: bool = False,
worktree_path: str | None = None,
) -> dict:
"""Create a new issue on a Gitea repository.
@@ -1642,6 +1644,8 @@ def gitea_create_issue(
type:* and one status:* label.
allow_duplicate_override: Operator-approved split after duplicate found.
split_from_issue: Existing duplicate issue number when overriding.
allow_incomplete_content: Operator override to bypass the #582 content
gate (create despite vague/missing title/body). Off by default.
worktree_path: Optional path to verify branches-only guard.
Returns:
@@ -1670,6 +1674,25 @@ def gitea_create_issue(
if blocked:
return blocked
verify_preflight_purity(remote, worktree_path=worktree_path, task="create_issue")
content_gate = issue_content_gate.pre_create_issue_content_gate(
title,
body,
allow_incomplete=allow_incomplete_content,
)
if not content_gate.get("performed"):
return {
"success": False,
"performed": False,
"number": None,
"content_gate": content_gate,
"reasons": [
"BLOCKED + DIAGNOSE: issue content is a vague reference or "
"missing required fields; provide full title/body or a durable "
"source pointer (existing issue/PR/comment, checked-in file, "
"URL, or scratchpad path). "
+ content_gate.get("diagnose", ""),
],
}
base = repo_api_url(h, o, r)
requested_labels = issue_workflow_labels.labels_for_new_issue(
issue_type=issue_type,