fix: Handle empty AI diffs gracefully instead of raising errors
Changed approach from disabling outputs to properly handling AI's decision not to generate changes (e.g., gated outputs, conditional rules). Changes: 1. patcher.py - Allow empty diffs - sanitize_unified_patch() returns empty string instead of raising error - generate_output() returns early for empty patches (silent skip) - Common case: implementation_gate_writer when status != READY_FOR_IMPLEMENTATION - AI can now return explanatory text without a diff (no error) 2. features.ai-rules.yml - Override README rule - Add README.md → "readme_skip" association - Creates empty rule to disable README updates in Docs/features/ - Prevents unnecessary AI calls during feature discussions - README automation still works in root directory 3. root.ai-rules.yml - Restore default README rule - Removed "enabled: false" flag (back to default enabled) - Features directory overrides this with empty rule Benefits: - implementation_gate now calls AI but AI returns empty diff (as designed) - No more "[runner] error generating ...implementation.discussion.md" - No more "[runner] error generating README.md" - Clean separation: AI decides vs. config disables - Instructions to AI are still executed, AI just chooses no changes Testing: Setup completes cleanly with no [runner] errors. The automation runs and AI correctly returns no diff for implementation file when status is OPEN. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
19d6119542
commit
188f6b3b16
|
|
@ -6,19 +6,22 @@ file_associations:
|
||||||
"feature.discussion.sum.md": "discussion_summary"
|
"feature.discussion.sum.md": "discussion_summary"
|
||||||
"implementation.discussion.md": "implementation_discussion_update"
|
"implementation.discussion.md": "implementation_discussion_update"
|
||||||
"implementation.discussion.sum.md": "discussion_summary"
|
"implementation.discussion.sum.md": "discussion_summary"
|
||||||
|
"README.md": "readme_skip" # Override root rule - don't update README from features/
|
||||||
|
|
||||||
rules:
|
rules:
|
||||||
|
readme_skip:
|
||||||
|
# Disable README updates when editing files in Docs/features/
|
||||||
|
# This prevents unnecessary AI calls during feature discussions
|
||||||
|
outputs: {}
|
||||||
|
|
||||||
feature_request:
|
feature_request:
|
||||||
outputs:
|
outputs:
|
||||||
feature_discussion:
|
feature_discussion:
|
||||||
path: "Docs/features/{feature_id}/discussions/feature.discussion.md"
|
path: "Docs/features/{feature_id}/discussions/feature.discussion.md"
|
||||||
output_type: "feature_discussion_writer"
|
output_type: "feature_discussion_writer"
|
||||||
implementation_gate:
|
implementation_gate:
|
||||||
enabled: false # Disabled by default - enable when feature is ready for implementation
|
|
||||||
path: "Docs/features/{feature_id}/discussions/implementation.discussion.md"
|
path: "Docs/features/{feature_id}/discussions/implementation.discussion.md"
|
||||||
output_type: "implementation_gate_writer"
|
output_type: "implementation_gate_writer"
|
||||||
# To enable: set "enabled: true" in your project's .ai-rules.yml
|
|
||||||
# This prevents unnecessary AI calls during initial feature discussion phase
|
|
||||||
|
|
||||||
feature_discussion_update:
|
feature_discussion_update:
|
||||||
outputs:
|
outputs:
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ rules:
|
||||||
readme:
|
readme:
|
||||||
outputs:
|
outputs:
|
||||||
normalize:
|
normalize:
|
||||||
enabled: false # Disabled by default - enable in your project when you want AI to maintain README
|
|
||||||
path: "{repo}/README.md"
|
path: "{repo}/README.md"
|
||||||
output_type: "readme_normalizer"
|
output_type: "readme_normalizer"
|
||||||
instruction: |
|
instruction: |
|
||||||
|
|
|
||||||
|
|
@ -121,8 +121,13 @@ def generate_output(
|
||||||
save_debug_artifacts(repo_root, output_rel, raw_path, clean_path, sanitized_path, final_patch_path)
|
save_debug_artifacts(repo_root, output_rel, raw_path, clean_path, sanitized_path, final_patch_path)
|
||||||
|
|
||||||
# Check if the final patch is empty after sanitization.
|
# Check if the final patch is empty after sanitization.
|
||||||
|
# This is normal when AI determines no changes are needed (e.g., gated outputs)
|
||||||
if not final_patch_path.read_text(encoding="utf-8").strip():
|
if not final_patch_path.read_text(encoding="utf-8").strip():
|
||||||
raise PatchGenerationError("AI returned empty patch")
|
# Empty patch is not an error - AI decided not to make changes
|
||||||
|
# Common cases:
|
||||||
|
# - implementation_gate_writer when status != READY_FOR_IMPLEMENTATION
|
||||||
|
# - AI determines output is already correct
|
||||||
|
return # Skip silently
|
||||||
|
|
||||||
# Apply the generated patch to the Git repository.
|
# Apply the generated patch to the Git repository.
|
||||||
apply_patch(repo_root, final_patch_path, patch_level, output_rel)
|
apply_patch(repo_root, final_patch_path, patch_level, output_rel)
|
||||||
|
|
@ -414,10 +419,8 @@ def sanitize_unified_patch(patch: str) -> str:
|
||||||
patch: The raw unified diff string.
|
patch: The raw unified diff string.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The sanitized diff string.
|
The sanitized diff string, or empty string if no diff header found.
|
||||||
|
An empty result indicates the AI chose not to make changes (e.g., gated output).
|
||||||
Raises:
|
|
||||||
PatchGenerationError: If the sanitized patch is missing a 'diff --git' header.
|
|
||||||
"""
|
"""
|
||||||
lines = patch.replace("\r", "").splitlines()
|
lines = patch.replace("\r", "").splitlines()
|
||||||
cleaned = []
|
cleaned = []
|
||||||
|
|
@ -428,10 +431,12 @@ def sanitize_unified_patch(patch: str) -> str:
|
||||||
cleaned.append(line)
|
cleaned.append(line)
|
||||||
text = "\n".join(cleaned)
|
text = "\n".join(cleaned)
|
||||||
|
|
||||||
# Ensure the patch still contains a 'diff --git' header after sanitization.
|
# Look for the 'diff --git' header
|
||||||
diff_start = text.find("diff --git")
|
diff_start = text.find("diff --git")
|
||||||
if diff_start == -1:
|
if diff_start == -1:
|
||||||
raise PatchGenerationError("Sanitized patch missing diff header")
|
# No diff header means AI chose not to generate a patch
|
||||||
|
# This is normal for gated outputs or when AI determines no changes needed
|
||||||
|
return ""
|
||||||
return text[diff_start:] + "\n"
|
return text[diff_start:] + "\n"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue