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
@@ -344,6 +344,41 @@ If edits are needed, make the smallest correction necessary and report the corre
Do not silently change requested meaning.
## 14a. Enforced content preflight (#582)
`gitea_create_issue` runs a **content preflight gate** before duplicate
search or creation. It fails closed (returns `BLOCKED + DIAGNOSE`, no issue
created) when the title/body is a *vague reference* to out-of-band draft
content the session cannot prove it holds, and no durable source pointer is
present.
An LLM must never fabricate issue content from stale chat memory. If asked to
create "the drafted issue" / "the prepared issue" / "the issue we discussed"
/ "the previous draft" without the full content in the current context or a
durable source pointer, stop and return `BLOCKED + DIAGNOSE` naming the exact
vague/missing fields.
A **durable source pointer** is one of: an existing issue/PR reference
(`#582`), a comment id (`comment 8155`), a checked-in file path
(`task_capability_map.py`), a URL, or a scratchpad path. When a pointer is
present, retrieve the real content from it before creating.
The gate does **not** require a non-empty body; explicit title-only creation
stays allowed. It only blocks placeholder references. An operator may bypass
it with `allow_incomplete_content=True` (report the override).
**Invalid prompts (must block):**
* "Create the drafted issue."
* "File the prepared issue we discussed."
* "Open the previous draft."
**Valid prompts (proceed):**
* Full title + body + acceptance criteria supplied inline.
* "Create the issue drafted in `#582`." (durable pointer → fetch and use it)
* "Create the issue from `scratchpad/issue-draft.md`." (durable file pointer)
## 15. Labels, assignees, and metadata
Apply labels, assignees, milestones, or project fields only if: