Skip to content

Commit 0824c77

Browse files
committed
Add forward-only prompt hygiene enforcement
1 parent a3c01fc commit 0824c77

16 files changed

+520
-57
lines changed

configs/repo_health.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@
1313
"required": true,
1414
"description": "Task definitions valid (instruction length, test.sh, no placeholders)"
1515
},
16+
"prompt_hygiene": {
17+
"script": "scripts/prompt_hygiene.py",
18+
"args": ["--git-modified", "--include-mcp", "--fail-on-findings"],
19+
"required": true,
20+
"description": "Modified prompt files must not leak code locations, solution strategies, or verifier details"
21+
},
1622
"selection_file": {
1723
"script": null,
1824
"required": true,

docs/START_HERE_BY_TASK.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ python3 scripts/mcp_audit.py --run <run_dir>
5353
### Key Commands
5454
```bash
5555
python3 scripts/validate_task_run.py --run <run_dir>
56+
python3 scripts/prompt_hygiene.py --scan-root benchmarks/<suite_or_task_dir> --include-mcp
5657
python3 scripts/rerun_failed.py --help
5758
```
5859

@@ -101,5 +102,6 @@ python3 scripts/package_submission.py --help
101102
### Key Commands
102103
```bash
103104
python3 scripts/validate_tasks_preflight.py --task <task_dir> --smoke-runtime
105+
python3 scripts/prompt_hygiene.py --scan-root <task_dir> --include-mcp
104106
python3 scripts/sync_task_metadata.py --help
105107
```

docs/ops/QA_PROCESS.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ Runs before any benchmark execution to catch task definition errors that would w
3535
- **Language/difficulty mismatch** -- Cross-references `task.toml` fields against `selected_benchmark_tasks.json`
3636
- **Missing test scripts** -- Verifies `tests/test.sh` is present and executable
3737
- **Missing tasks** -- Detects tasks in the selection registry that have no corresponding benchmark directory
38+
- **Prompt hygiene on modified prompts** -- `instruction.md` and `instruction_mcp.md` files changed in the current worktree must not leak code locations, solution strategies, or verifier details
3839

3940
### Runtime Smoke (No Agent)
4041

@@ -92,6 +93,9 @@ python3 scripts/validate_tasks_preflight.py --suite csb_sdlc_feature
9293
# Validate a single task
9394
python3 scripts/validate_tasks_preflight.py --task benchmarks/csb_sdlc_feature/envoy-grpc-server-impl-001
9495

96+
# Audit modified prompt files for hygiene issues
97+
python3 scripts/prompt_hygiene.py --git-modified --include-mcp --fail-on-findings
98+
9599
# Runtime smoke for a single task (no agent)
96100
python3 scripts/validate_tasks_preflight.py --task benchmarks/csb_sdlc_understand/terraform-plan-pipeline-qa-001 --smoke-runtime
97101

@@ -236,6 +240,16 @@ Periodic full audits use a 6-dimension framework to ensure benchmark integrity:
236240

237241
Checks whether `instruction.md` files contain references to MCP tools or Sourcegraph that would leak context into baseline (no-tool) runs. Any MCP-specific instructions should be injected at runtime via the agent harness, not baked into the task definition.
238242

243+
Prompt-hygiene review is broader than MCP contamination. For both `instruction.md` and `instruction_mcp.md`, also investigate:
244+
- code-location hints such as exact source paths, directories to inspect, or named files/classes to open first
245+
- solution leakage such as prescribed helper names, replacement APIs, or step-by-step implementation plans
246+
- verifier leakage such as ground-truth diff counts, scoring formulas, or closed-world oracle wording
247+
248+
Recommended investigation command:
249+
```bash
250+
python3 scripts/prompt_hygiene.py --scan-root benchmarks/<suite_or_task_dir> --include-mcp
251+
```
252+
239253
### Dimension 2: Reproducibility
240254

241255
Verifies that task environments produce deterministic results:

docs/ops/SCRIPT_INDEX.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ Generated from `scripts/registry.json` by `scripts/generate_script_index.py`.
242242
- `scripts/promote_agent_oracles.py` - Utility script for promote agent oracles.
243243
- `scripts/promote_blocked.py` - Utility script for promote blocked.
244244
- `scripts/promoted_verifier.py` - Utility script for promoted verifier.
245+
- `scripts/prompt_hygiene.py` - Utility script for prompt hygiene.
245246
- `scripts/push_base_images_ghcr.sh` - Utility script for push base images ghcr.
246247
- `scripts/regenerate_artifact_dockerfiles.py` - Utility script for regenerate artifact dockerfiles.
247248
- `scripts/rehost_sweap_images.py` - Utility script for rehost sweap images.

docs/ops/WORKFLOWS.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@
3232
## Triage Workflow
3333
1. Confirm error class via `docs/ERROR_CATALOG.md` and status fingerprints.
3434
2. Check run outputs and trajectories.
35-
3. Isolate task-level fix or rerun scope.
36-
4. Avoid blind reruns; document root cause or limitation.
35+
3. Inspect prompt hygiene when the failure may be caused by prompt leakage or prompt wiring drift (`scripts/prompt_hygiene.py --scan-root benchmarks/<suite_or_task_dir> --include-mcp`).
36+
4. Isolate task-level fix or rerun scope.
37+
5. Avoid blind reruns; document root cause or limitation.
3738

3839
## ContextBench Calibration Workflow
3940
1. Ensure `claude` CLI is installed, authenticated, and `SOURCEGRAPH_ACCESS_TOKEN` is set.

docs/ops/task_routes.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
],
4949
"commands": [
5050
"python3 scripts/validate_task_run.py --run <run_dir>",
51+
"python3 scripts/prompt_hygiene.py --scan-root benchmarks/<suite_or_task_dir> --include-mcp",
5152
"python3 scripts/rerun_failed.py --help"
5253
]
5354
},
@@ -90,6 +91,7 @@
9091
],
9192
"commands": [
9293
"python3 scripts/validate_tasks_preflight.py --task <task_dir> --smoke-runtime",
94+
"python3 scripts/prompt_hygiene.py --scan-root <task_dir> --include-mcp",
9395
"python3 scripts/sync_task_metadata.py --help"
9496
]
9597
}

scripts/abc_audit.py

Lines changed: 26 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
Status,
3636
get_criteria_for_suite,
3737
)
38+
from prompt_hygiene import audit_paths
3839

3940

4041
# ---------------------------------------------------------------------------
@@ -290,53 +291,36 @@ def check_t4_git_sha(tasks: list[Path]) -> CriterionResult:
290291

291292

292293
def check_t5_no_solution_leak(tasks: list[Path]) -> CriterionResult:
293-
"""T.5: instruction.md doesn't leak solution content."""
294-
issues = []
294+
"""T.5: instructions do not leak code locations, solution strategies, or verifier details."""
295+
prompt_paths: list[Path] = []
295296
for task_dir in tasks:
296-
instruction = task_dir / "instruction.md"
297-
if not instruction.is_file():
298-
continue
299-
300-
inst_text = instruction.read_text(errors="replace").lower()
301-
302-
# Check against solve.sh
303-
solve_sh = task_dir / "solve.sh"
304-
if solve_sh.is_file():
305-
solve_text = solve_sh.read_text(errors="replace")
306-
# Extract meaningful code lines (not comments/blank)
307-
solve_lines = [
308-
l.strip() for l in solve_text.splitlines()
309-
if l.strip() and not l.strip().startswith("#") and len(l.strip()) > 15
310-
]
311-
for line in solve_lines:
312-
if line.lower() in inst_text:
313-
issues.append(f"{task_dir.name}: instruction contains solve.sh line: {line[:60]}")
314-
break
315-
316-
# Check against expected.diff
317-
for diff_path in [task_dir / "expected.diff", task_dir / "tests" / "expected.diff"]:
318-
if diff_path.is_file():
319-
diff_text = diff_path.read_text(errors="replace")
320-
# Extract added lines from diff
321-
added_lines = [
322-
l[1:].strip() for l in diff_text.splitlines()
323-
if l.startswith("+") and not l.startswith("+++") and len(l.strip()) > 20
324-
]
325-
for line in added_lines[:20]: # Sample first 20
326-
if line.lower() in inst_text:
327-
issues.append(f"{task_dir.name}: instruction contains diff content: {line[:60]}")
328-
break
329-
330-
if not issues:
297+
for name in ("instruction.md", "instruction_mcp.md"):
298+
path = task_dir / name
299+
if path.is_file():
300+
prompt_paths.append(path)
301+
302+
report = audit_paths(prompt_paths)
303+
files = report["files"]
304+
if not files:
331305
return CriterionResult(
332306
criterion_id="T.5", status=Status.PASS,
333-
evidence="No solution content found in instructions",
307+
evidence="No prompt-hygiene findings across instruction.md or instruction_mcp.md",
334308
)
309+
issue_labels = {
310+
"code_location_hint": "code-location hints",
311+
"solution_leakage": "solution leakage",
312+
"scoring_leakage": "verifier leakage",
313+
}
314+
issues = []
315+
for file_entry in files[:10]:
316+
rel_path = Path(file_entry["file"]).relative_to(PROJECT_ROOT)
317+
kinds = sorted({issue["type"] for issue in file_entry["issues"]})
318+
issues.append(f"{rel_path}: {', '.join(issue_labels.get(kind, kind) for kind in kinds)}")
335319
return CriterionResult(
336320
criterion_id="T.5", status=Status.WARN,
337321
evidence="\n".join(issues[:10]),
338-
remediation="Review instructions to ensure they don't contain solution code",
339-
details={"issues": issues},
322+
remediation="Remove code-location guidance, prescribed fix steps, and verifier/scoring details from prompts.",
323+
details=report,
340324
)
341325

342326

@@ -747,12 +731,12 @@ def check_r2_no_contamination(tasks: list[Path]) -> CriterionResult:
747731
if not issues:
748732
return CriterionResult(
749733
criterion_id="R.2", status=Status.PASS,
750-
evidence="No MCP/Sourcegraph tool guidance in baseline instructions",
734+
evidence="No MCP/Sourcegraph tool guidance in baseline instruction.md files",
751735
)
752736
return CriterionResult(
753737
criterion_id="R.2", status=Status.FAIL,
754738
evidence="\n".join(issues[:10]),
755-
remediation="Remove MCP/Sourcegraph tool guidance from baseline instructions",
739+
remediation="Remove MCP/Sourcegraph tool guidance from baseline instruction.md files and keep MCP-specific instructions in runtime injection or instruction_mcp.md only.",
756740
details={"issue_count": len(issues), "issues": issues[:20]},
757741
)
758742

scripts/abc_criteria.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -252,8 +252,8 @@ def to_table(self) -> str:
252252
ABCCriterion(
253253
id="T.5",
254254
dimension=Dimension.TASK_VALIDITY,
255-
title="instruction.md doesn't leak solution content",
256-
description="Instructions must not contain solution code or test answers.",
255+
title="Instructions do not leak code locations, solution strategy, or verifier details",
256+
description="instruction.md and instruction_mcp.md must stay discovery-oriented and must not reveal where to look, how to fix it, or how the verifier scores.",
257257
severity=Severity.CRITICAL,
258258
automation=Automation.AUTOMATED,
259259
),
@@ -388,8 +388,8 @@ def to_table(self) -> str:
388388
ABCCriterion(
389389
id="R.2",
390390
dimension=Dimension.REPORTING,
391-
title="No MCP/Sourcegraph contamination in instruction.md",
392-
description="Baseline instructions must not reference MCP, Sourcegraph, or Deep Search.",
391+
title="Baseline instruction.md has no MCP/Sourcegraph contamination",
392+
description="Baseline instructions must not reference MCP, Sourcegraph, or Deep Search. Prompt-hygiene review should also confirm the prompt stays tool-neutral and avoids code-location guidance.",
393393
severity=Severity.CRITICAL,
394394
automation=Automation.AUTOMATED,
395395
),

scripts/generate_csb_org_tasks.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
from string import Template
2424
from typing import Dict, List, Optional
2525

26+
from prompt_hygiene import sanitize_instruction_text
27+
2628

2729
# ---------------------------------------------------------------------------
2830
# Constants and mappings
@@ -381,6 +383,8 @@ def generate_task(
381383

382384
def write_rendered(fname: str, out_path: Path) -> None:
383385
content = render_template(templates_dir, fname, variables)
386+
if out_path.name in {"instruction.md", "instruction_mcp.md"}:
387+
content = sanitize_instruction_text(content)
384388
out_path.write_text(content)
385389
if verbose:
386390
logging.debug(" Wrote %s", out_path)

0 commit comments

Comments
 (0)