Skip to content

Commit d18f300

Browse files
fix: last 3 PromptModel violations — ai.prompt.model_required should be 0
1 parent db7688f commit d18f300

7 files changed

Lines changed: 59 additions & 118 deletions

File tree

reports/audit/latest_audit.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"audit_id": "ca070e3b-8f65-4c90-9a39-8c7f1cdd6cbb",
3-
"timestamp": "2026-03-10T12:36:34Z",
2+
"audit_id": "e0985eff-9204-4ee9-b9a1-ae0070de4869",
3+
"timestamp": "2026-03-10T13:17:04Z",
44
"passed": false,
55
"findings_count": 140,
66
"executed_rules": [

reports/audit_auto_ignored.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
2-
"generated_at": "2026-03-10T12:36:34Z",
2+
"generated_at": "2026-03-10T13:17:04Z",
33
"items": []
44
}

reports/audit_auto_ignored.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# Audit Auto-Ignored Symbols
22

3-
- Generated: `2026-03-10T12:36:34Z`
3+
- Generated: `2026-03-10T13:17:04Z`
44
- Total auto-ignored: **0**

reports/pytest-report.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@
328328
</head>
329329
<body>
330330
<h1 id="title">pytest-report.html</h1>
331-
<p>Report generated on 10-Mar-2026 at 13:36:28 by <a href="https://pypi.python.org/pypi/pytest-html">pytest-html</a>
331+
<p>Report generated on 10-Mar-2026 at 14:16:58 by <a href="https://pypi.python.org/pypi/pytest-html">pytest-html</a>
332332
v4.1.1</p>
333333
<div id="environment-header">
334334
<h2>Environment</h2>

src/cli/logic/body_contracts_fixer.py

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44

55
from __future__ import annotations
66

7-
import textwrap
87
import time
98
from pathlib import Path
109
from typing import TYPE_CHECKING, Any
1110

1211
from cli.logic.body_contracts_checker import check_body_contracts
1312
from shared.action_types import ActionImpact, ActionResult
13+
from shared.ai.prompt_model import PromptModel
1414
from shared.atomic_action import atomic_action
1515
from shared.logger import getLogger
1616
from shared.utils.parallel_processor import ThrottledParallelProcessor
@@ -22,10 +22,6 @@
2222

2323
logger = getLogger(__name__)
2424

25-
_BODY_UI_FIX_PROMPT = textwrap.dedent(
26-
"\n You are refactoring Python code for a constitutional system called CORE.\n\n GOAL\n ----\n - Remove ALL terminal UI from the given module:\n - No Rich imports or usage\n - No console.print() or input()\n - No direct os.environ / os.environ[...] access\n - Preserve the module's behavior as a HEADLESS Body-layer service/logic.\n\n CONTEXT\n -------\n CORE governance rules for Body code:\n - Body modules MUST be headless:\n - No Rich UI (Console, Progress, status, etc.)\n - No console.print() / input() calls\n - Configuration must come from shared.config.settings, not os.environ.\n - Logging MUST use shared.logger.getLogger(__name__).\n\n REQUIREMENTS\n ------------\n 1. Remove or refactor any Rich / console imports and usage.\n - If the module needs observability, use logger.debug/info/warning/error.\n 2. Remove or refactor console.print() / input() calls.\n - Replace with logger.info/debug where appropriate, or return values.\n 3. Replace os.environ[...] or os.environ.get(...) with settings access\n (e.g., shared.config.settings or an injected config object) when possible.\n If you cannot infer an exact mapping, keep a FUTURE comment but do NOT\n keep direct os.environ in the Body module.\n 4. DO NOT change public function signatures unless absolutely necessary.\n 5. DO NOT introduce any new UI dependencies.\n\n OUTPUT FORMAT\n -------------\n Return ONLY the full corrected Python module.\n DO NOT wrap it in backticks, comments, or explanation.\n "
27-
)
28-
2925

3026
async def _process_single_file(
3127
item: tuple[Path, list[dict[str, Any]]],
@@ -53,18 +49,18 @@ async def _process_single_file(
5349
{f"- {v['rule_id']} @ line {v.get('line')}: {v['message']}" for v in vlist}
5450
)
5551
violation_summary = "\n".join(summary_lines)
56-
prompt = (
57-
_BODY_UI_FIX_PROMPT
58-
+ "\n\nFILE PATH:\n"
59-
+ str(path)
60-
+ "\n\nVIOLATIONS DETECTED:\n"
61-
+ violation_summary
62-
+ "\n\nCURRENT FILE CONTENT:\n\n"
63-
+ original_source
64-
)
6552

6653
try:
67-
raw_response = await agent.make_request_async(prompt, user_id="fix_body_ui")
54+
model = PromptModel.load("body_contracts_fixer")
55+
raw_response = await model.invoke(
56+
context={
57+
"file_path": str(path),
58+
"violation_summary": violation_summary,
59+
"original_source": original_source,
60+
},
61+
client=agent,
62+
user_id="fix_body_ui",
63+
)
6864
except Exception as e:
6965
logger.warning("Body UI fixer: LLM request failed for %s: %s", path, e)
7066
return {
@@ -154,7 +150,6 @@ async def fix_body_ui_violations(
154150

155151
# ID: dbf3bacb-1562-48e3-acfa-9127c985737b
156152
async def worker(item):
157-
# Pass the file_handler from context
158153
return await _process_single_file(item, agent, write, core_context.file_handler)
159154

160155
per_file_results = await processor.run_async(items, worker)

src/will/self_healing/test_generation/llm_correction.py

Lines changed: 30 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,21 @@
33
LLM-based code correction when automatic repairs are insufficient.
44
55
This is the "last resort" - only called when deterministic fixes don't work.
6+
HEALED: attempt_correction uses PromptModel.invoke() — ai.prompt.model_required compliant.
67
"""
78

89
from __future__ import annotations
910

1011
import json
11-
from pathlib import Path
1212
from typing import Any
1313

1414
from body.self_healing.test_context_analyzer import ModuleContext
1515
from mind.governance.audit_context import AuditorContext
16+
from shared.ai.prompt_model import PromptModel
1617
from shared.infrastructure.config_service import ConfigService
1718
from shared.logger import getLogger
1819
from shared.utils.parsing import parse_write_blocks
1920
from will.orchestration.cognitive_service import CognitiveService
20-
from will.orchestration.prompt_pipeline import PromptPipeline
2121
from will.orchestration.validation_pipeline import validate_code_async
2222

2323
from .code_extractor import CodeExtractor
@@ -65,40 +65,46 @@ async def attempt_correction(
6565
"message": "Missing required correction context.",
6666
}
6767

68-
# Analyze violations to choose prompting strategy
6968
syntax_only = all(
7069
v.get("rule", "").startswith(("tooling.", "syntax.")) for v in violations
7170
)
7271

73-
# Build appropriate prompt
74-
if self.config_service is not None:
75-
repo_root = Path(await self.config_service.get("REPO_PATH", required=True))
76-
else:
77-
repo_root = self.auditor.repo_path
78-
79-
prompt = self._build_correction_prompt(
80-
file_path=file_path,
81-
code=code,
82-
violations=violations,
83-
module_context=module_context,
84-
goal=goal,
85-
syntax_only=syntax_only,
86-
repo_root=repo_root,
87-
)
72+
violations_json = json.dumps(violations, indent=2)
8873

89-
# Get LLM response
9074
try:
9175
llm_client = await self.cognitive.aget_client_for_role("Coder")
92-
llm_output = await llm_client.make_request_async(
93-
prompt, user_id="self_correction"
94-
)
76+
77+
if syntax_only:
78+
model = PromptModel.load("llm_correction_syntax")
79+
llm_output = await model.invoke(
80+
context={
81+
"file_path": file_path,
82+
"violations_json": violations_json,
83+
"code": code,
84+
},
85+
client=llm_client,
86+
user_id="self_correction",
87+
)
88+
else:
89+
model = PromptModel.load("llm_correction_structural")
90+
llm_output = await model.invoke(
91+
context={
92+
"file_path": file_path,
93+
"violations_json": violations_json,
94+
"code": code.strip(),
95+
"module_name": module_context.module_name,
96+
"import_path": module_context.import_path,
97+
"goal": goal,
98+
},
99+
client=llm_client,
100+
user_id="self_correction",
101+
)
95102
except Exception as e:
96103
return {
97104
"status": "error",
98105
"message": f"LLM request failed: {e!s}",
99106
}
100107

101-
# Extract corrected code with lenient parsing
102108
corrected_code = self._extract_corrected_code(llm_output)
103109

104110
if not corrected_code:
@@ -107,7 +113,6 @@ async def attempt_correction(
107113
"message": "LLM did not produce valid code in any recognized format.",
108114
}
109115

110-
# Validate the corrected code
111116
validation_result = await validate_code_async(
112117
file_path, corrected_code, auditor_context=self.auditor
113118
)
@@ -117,7 +122,7 @@ async def attempt_correction(
117122
"status": "correction_failed_validation",
118123
"message": "The corrected code still fails validation.",
119124
"violations": validation_result["violations"],
120-
"code": corrected_code, # Return the code so automatic repairs can try
125+
"code": corrected_code,
121126
}
122127

123128
return {
@@ -126,65 +131,6 @@ async def attempt_correction(
126131
"message": "Corrected code generated and validated successfully.",
127132
}
128133

129-
def _build_correction_prompt(
130-
self,
131-
file_path: str,
132-
code: str,
133-
violations: list[dict[str, Any]],
134-
module_context: ModuleContext,
135-
goal: str,
136-
syntax_only: bool,
137-
repo_root: Path,
138-
) -> str:
139-
"""
140-
Build appropriate correction prompt based on violation type.
141-
"""
142-
if syntax_only:
143-
# For syntax errors: be strict about NOT rewriting
144-
base_prompt = (
145-
"You are CORE's syntax repair agent.\n\n"
146-
"The code below has ONLY syntax errors. Your job is to fix the syntax "
147-
"while preserving ALL logic and structure.\n\n"
148-
f"File: {file_path}\n\n"
149-
"SYNTAX ERRORS:\n"
150-
f"{json.dumps(violations, indent=2)}\n\n"
151-
"CODE TO FIX:\n"
152-
f"{code}\n\n"
153-
"CRITICAL: Fix ONLY the syntax errors listed above. "
154-
"Do NOT rewrite or restructure the code. "
155-
"Do NOT add or remove any logic or tests.\n\n"
156-
"Output the corrected code:"
157-
)
158-
else:
159-
# For structural/logic errors: allow more freedom
160-
base_prompt = (
161-
"You are CORE's self-correction agent.\n\n"
162-
"A recent code generation attempt failed validation.\n"
163-
"Please analyze the violations and fix the code below.\n\n"
164-
f"File: {file_path}\n\n"
165-
"[[violations]]\n"
166-
f"{json.dumps(violations, indent=2)}\n"
167-
"[[/violations]]\n\n"
168-
"[[code]]\n"
169-
f"{code.strip()}\n"
170-
"[[/code]]\n\n"
171-
"Module context:\n"
172-
f"- Module: {module_context.module_name}\n"
173-
f"- Import path: {module_context.import_path}\n"
174-
f"- Goal: {goal}\n\n"
175-
"CRITICAL INSTRUCTIONS:\n"
176-
"1. Fix ALL violations listed above\n"
177-
"2. Ensure the code is syntactically valid Python\n"
178-
"3. Pay special attention to docstring quotes - use MATCHING triple quotes\n"
179-
"4. NEVER mix quote types in a single docstring\n"
180-
"5. Output the COMPLETE corrected code\n\n"
181-
"Provide the corrected code now:"
182-
)
183-
184-
# Process through prompt pipeline
185-
pipeline = PromptPipeline(repo_path=repo_root)
186-
return pipeline.process(base_prompt)
187-
188134
def _extract_corrected_code(self, llm_output: str) -> str | None:
189135
"""
190136
Extract code from LLM response using multiple strategies.
@@ -194,19 +140,16 @@ def _extract_corrected_code(self, llm_output: str) -> str | None:
194140
2. Markdown code fences ```python...```
195141
3. Raw Python code
196142
"""
197-
# Strategy 1: Write blocks
198143
write_blocks = parse_write_blocks(llm_output)
199144
if write_blocks:
200145
logger.info("Extracted correction from write block")
201146
return next(iter(write_blocks.values()))
202147

203-
# Strategy 2: Markdown code fences
204148
code = self.code_extractor.extract(llm_output)
205149
if code:
206150
logger.info("Extracted correction from markdown code fence")
207151
return code
208152

209-
# Strategy 3: Raw Python
210153
stripped = llm_output.strip()
211154
if stripped.startswith(("import ", "from ", "def ", "class ", "@", "#")):
212155
logger.info("Extracted correction from raw response")

src/will/self_healing/test_generation/single_test_fixer.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22

33
"""
44
Single Test Fixer - Refined A3 Orchestrator.
5+
HEALED: fix_test uses PromptModel.invoke() — ai.prompt.model_required compliant.
56
"""
67

78
from __future__ import annotations
89

910
from pathlib import Path
1011

12+
from shared.ai.prompt_model import PromptModel
1113
from shared.logger import getLogger
12-
from will.orchestration.prompt_pipeline import PromptPipeline
1314

1415
from .failure_parser import TestFailureParser
1516
from .test_extractor import TestExtractor
@@ -27,10 +28,8 @@ def __init__(self, cognitive_service, file_handler, repo_root, max_attempts=3):
2728
self.max_attempts = max_attempts
2829
self.repo_root = repo_root
2930

30-
# Specialists
3131
self.parser = TestFailureParser()
3232
self.extractor = TestExtractor(file_handler, repo_root)
33-
self.pipeline = PromptPipeline(repo_path=repo_root)
3433

3534
# ID: 8adb4bee-9216-47a3-9d96-ec9714bb5daf
3635
async def fix_test(
@@ -45,15 +44,22 @@ async def fix_test(
4544
if not test_code:
4645
return {"status": "error", "error": "Source extraction failed"}
4746

47+
model = PromptModel.load("single_test_fixer")
48+
client = await self.cognitive.aget_client_for_role("Coder")
49+
4850
for attempt in range(self.max_attempts):
4951
logger.info(
5052
"🤖 Fix Attempt %d/%d for %s", attempt + 1, self.max_attempts, test_name
5153
)
5254

53-
prompt = self._build_prompt(test_name, test_code, failure_info)
54-
client = await self.cognitive.aget_client_for_role("Coder")
55-
response = await client.make_request_async(
56-
self.pipeline.process(prompt), user_id="test_fixer"
55+
response = await model.invoke(
56+
context={
57+
"test_name": test_name,
58+
"failure_type": failure_info.get("failure_type", ""),
59+
"test_code": test_code,
60+
},
61+
client=client,
62+
user_id="test_fixer",
5763
)
5864

5965
fixed_code = self._extract_code(response)
@@ -64,9 +70,6 @@ async def fix_test(
6470

6571
return {"status": "failed"}
6672

67-
def _build_prompt(self, name: str, code: str, info: dict) -> str:
68-
return f"Fix this failing test: {name}\nError: {info.get('failure_type')}\nCode:\n{code}"
69-
7073
def _extract_code(self, response: str) -> str | None:
7174
"""Simple extraction logic helper."""
7275
if "```python" in response:

0 commit comments

Comments
 (0)