File size: 3,397 Bytes
78d2164
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
"""Build the PreToolUse prompt from session state + proposed action.

The <signals> block surfaces the actual decision features as explicit
booleans so the model latches onto them instead of re-deriving from raw
state. Both the synthetic and real builders call format_prompt, so train
and inference prompts stay identical.
"""
import re

TEST_RE = re.compile(r"\b(pytest|npm test|pnpm test|yarn test|cargo test|go test|bun test|unittest|jest|vitest|tox)\b")

TEMPLATE = """<task>
{goal}
</task>

<recent_trajectory>
{trajectory}
</recent_trajectory>

<state>
observed_files: {observed_files}
edited_files: {edited_files}
traceback_path: {traceback_path}
traceback_symbols: {traceback_symbols}
last_test_status: {last_test_status}
last_search_result_class: {last_search_result_class}
code_changed_since_last_test: {code_changed_since_last_test}
</state>

<proposed_action>
Tool: {tool}
Args: {args}
</proposed_action>

<signals>
proposed_edit_path: {proposed_edit_path}
proposed_action_is_test: {proposed_action_is_test}
traceback_file_read: {traceback_file_read}
edit_target_read: {edit_target_read}
last_failure_type: {last_failure_type}
repeated_command: {repeated_command}
</signals>

<instruction>
Output exactly one short sentence of guidance, or NO_HINT.
</instruction>
"""


def _join(v, last=None):
    if not v:
        return "none"
    if isinstance(v, (list, tuple)):
        v = list(v)[-last:] if last else list(v)
        return ", ".join(str(x) for x in v) if v else "none"
    return str(v)


def _b(x):
    return str(bool(x)).lower()


def format_prompt(goal, trajectory, state, proposed):
    goal = goal.strip()
    if len(goal) > 500:
        goal = goal[:500] + " …"
    tool = proposed["name"]
    args = proposed.get("args", {}) or {}
    cmd = args.get("command", "") or ""
    edit_path = args.get("file_path") if tool in {"Edit", "Write", "MultiEdit", "NotebookEdit"} else None
    observed = set(state.get("observed_files") or [])
    tb = (state.get("traceback_paths") or [None])[-1]
    hist = state.get("command_history") or []

    return TEMPLATE.format(
        goal=goal,
        trajectory=trajectory.strip() if isinstance(trajectory, str) else _fmt_traj(trajectory),
        observed_files=_join(state.get("observed_files"), last=20),
        edited_files=_join(state.get("edited_files"), last=20),
        traceback_path=tb or "none",
        traceback_symbols=_join(state.get("traceback_symbols"), last=5),
        last_test_status=state.get("last_test_status", "unknown"),
        last_search_result_class=state.get("last_search_result_class") or "none",
        code_changed_since_last_test=_b(state.get("code_changed_since_last_test")),
        tool=tool,
        args=_join(edit_path or cmd or args)[:200],
        proposed_edit_path=edit_path or "none",
        proposed_action_is_test=_b(tool == "Bash" and TEST_RE.search(cmd)),
        traceback_file_read=_b(tb and tb in observed),
        edit_target_read=_b(edit_path and edit_path in observed),
        last_failure_type=state.get("last_failure_type") or "none",
        repeated_command=_b(tool == "Bash" and cmd and sum(1 for x in hist[-3:] if x == cmd) >= 2),
    )


def _fmt_traj(calls):
    lines = []
    for i, c in enumerate(calls[-12:], 1):
        lines.append(f"{i}. {c['tool']}: {c.get('arg','')}\n   Result: {c.get('result','')}")
    return "\n".join(lines) if lines else "none"