claude-dev-env 1.17.5 → 1.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/hooks/HOOK_SPECS_PROMPT_WORKFLOW.md +0 -64
- package/hooks/blocking/prompt_workflow_clipboard.py +0 -63
- package/hooks/blocking/prompt_workflow_gate_config.py +0 -113
- package/hooks/blocking/prompt_workflow_gate_core.py +0 -289
- package/hooks/blocking/prompt_workflow_validate.py +0 -218
- package/hooks/blocking/test_prompt_workflow_clipboard.py +0 -54
- package/hooks/blocking/test_prompt_workflow_gate_core.py +0 -195
- package/hooks/blocking/test_prompt_workflow_validate.py +0 -339
- package/rules/prompt-workflow-context-controls.md +0 -48
- package/skills/agent-prompt/SKILL.md +0 -199
- package/skills/prompt-generator/ARCHITECTURE.md +0 -18
- package/skills/prompt-generator/REFERENCE.md +0 -254
- package/skills/prompt-generator/REFINEMENT_PIPELINE_RUNBOOK.md +0 -177
- package/skills/prompt-generator/SKILL.md +0 -354
- package/skills/prompt-generator/TARGET_OUTPUT.md +0 -133
- package/skills/prompt-generator/evals/prompt-generator.json +0 -207
- package/skills/prompt-generator/templates/skill-from-ground-up.md +0 -104
- package/skills/prompt-generator/templates/skill-refinement-package.md +0 -109
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Shared prompt-workflow validator callable from tests, CLI, and the Stop hook.
|
|
3
|
-
|
|
4
|
-
Public API
|
|
5
|
-
----------
|
|
6
|
-
validate_prompt_workflow(assistant_message, user_context="")
|
|
7
|
-
Returns a ``ValidationResult`` with allowed/blocked status and reasons.
|
|
8
|
-
|
|
9
|
-
CLI
|
|
10
|
-
---
|
|
11
|
-
python prompt_workflow_validate.py path/to/draft.md
|
|
12
|
-
cat draft.md | python prompt_workflow_validate.py
|
|
13
|
-
|
|
14
|
-
Exit codes match hook conventions: 0 allowed, 2 blocked.
|
|
15
|
-
"""
|
|
16
|
-
|
|
17
|
-
from __future__ import annotations
|
|
18
|
-
|
|
19
|
-
import sys
|
|
20
|
-
from dataclasses import dataclass, field
|
|
21
|
-
from pathlib import Path
|
|
22
|
-
|
|
23
|
-
from prompt_workflow_gate_core import (
|
|
24
|
-
find_ambiguous_scope_terms,
|
|
25
|
-
find_negative_keywords_in_fenced_xml,
|
|
26
|
-
has_checklist_container,
|
|
27
|
-
has_debug_intent,
|
|
28
|
-
has_internal_object_leak,
|
|
29
|
-
is_prompt_workflow_response,
|
|
30
|
-
missing_checklist_rows,
|
|
31
|
-
missing_context_control_signals,
|
|
32
|
-
missing_required_xml_sections,
|
|
33
|
-
missing_scope_anchors,
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
@dataclass(frozen=True)
|
|
37
|
-
class ValidationReason:
|
|
38
|
-
code: str
|
|
39
|
-
message: str
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
@dataclass(frozen=True)
|
|
43
|
-
class ValidationResult:
|
|
44
|
-
allowed: bool
|
|
45
|
-
reasons: tuple[ValidationReason, ...] = field(default_factory=tuple)
|
|
46
|
-
|
|
47
|
-
@property
|
|
48
|
-
def reason_messages(self) -> list[str]:
|
|
49
|
-
return [each_reason.message for each_reason in self.reasons]
|
|
50
|
-
|
|
51
|
-
@property
|
|
52
|
-
def reason_codes(self) -> list[str]:
|
|
53
|
-
return [each_reason.code for each_reason in self.reasons]
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def _blocked(code: str, message: str) -> ValidationResult:
|
|
57
|
-
return ValidationResult(
|
|
58
|
-
allowed=False,
|
|
59
|
-
reasons=(ValidationReason(code=code, message=message),),
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def _check_internal_leak(
|
|
64
|
-
assistant_message: str,
|
|
65
|
-
debug_requested: bool,
|
|
66
|
-
) -> ValidationResult | None:
|
|
67
|
-
if not has_internal_object_leak(assistant_message) or debug_requested:
|
|
68
|
-
return None
|
|
69
|
-
return _blocked(
|
|
70
|
-
code="internal_object_leak",
|
|
71
|
-
message=(
|
|
72
|
-
"Raw internal refinement object leakage detected. "
|
|
73
|
-
"Return sanitized user-facing output unless explicit debug intent is present."
|
|
74
|
-
),
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def _check_required_sections(assistant_message: str) -> ValidationResult | None:
|
|
79
|
-
missing_sections = missing_required_xml_sections(assistant_message)
|
|
80
|
-
if not missing_sections:
|
|
81
|
-
return None
|
|
82
|
-
return _blocked(
|
|
83
|
-
code="missing_xml_sections",
|
|
84
|
-
message=(
|
|
85
|
-
"Fenced XML artifact missing required sections: "
|
|
86
|
-
+ ", ".join(missing_sections)
|
|
87
|
-
),
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def _check_checklist_rows(assistant_message: str) -> ValidationResult | None:
|
|
92
|
-
if not has_checklist_container(assistant_message):
|
|
93
|
-
return None
|
|
94
|
-
missing_rows = missing_checklist_rows(assistant_message)
|
|
95
|
-
if not missing_rows:
|
|
96
|
-
return None
|
|
97
|
-
return _blocked(
|
|
98
|
-
code="missing_checklist_rows",
|
|
99
|
-
message=("Deterministic checklist rows missing: " + ", ".join(missing_rows)),
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def _check_scope_anchors(assistant_message: str) -> ValidationResult | None:
|
|
104
|
-
missing_anchors = missing_scope_anchors(assistant_message)
|
|
105
|
-
if not missing_anchors:
|
|
106
|
-
return None
|
|
107
|
-
return _blocked(
|
|
108
|
-
code="missing_scope_anchors",
|
|
109
|
-
message=("Required scope anchors missing: " + ", ".join(missing_anchors)),
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
def _check_context_signals(assistant_message: str) -> ValidationResult | None:
|
|
114
|
-
missing_signals = missing_context_control_signals(assistant_message)
|
|
115
|
-
if not missing_signals:
|
|
116
|
-
return None
|
|
117
|
-
return _blocked(
|
|
118
|
-
code="missing_context_signals",
|
|
119
|
-
message=(
|
|
120
|
-
"Runtime context-control preamble missing. "
|
|
121
|
-
"Include the two required lines from prompt-workflow-context-controls "
|
|
122
|
-
"(minimal instruction layer and on-demand skill loading)."
|
|
123
|
-
),
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
def _check_ambiguous_scope(assistant_message: str) -> ValidationResult | None:
|
|
128
|
-
ambiguous_terms = find_ambiguous_scope_terms(assistant_message)
|
|
129
|
-
if not ambiguous_terms:
|
|
130
|
-
return None
|
|
131
|
-
return _blocked(
|
|
132
|
-
code="ambiguous_scope",
|
|
133
|
-
message=("Ambiguous scope phrasing detected: " + ", ".join(ambiguous_terms)),
|
|
134
|
-
)
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
def _check_negative_keywords(assistant_message: str) -> ValidationResult | None:
|
|
138
|
-
violations = find_negative_keywords_in_fenced_xml(assistant_message)
|
|
139
|
-
if not violations:
|
|
140
|
-
return None
|
|
141
|
-
violation_descriptions = [
|
|
142
|
-
f" line {each_violation['line_number']}: "
|
|
143
|
-
f'"{each_violation["keyword"]}" in: {each_violation["line_text"]}'
|
|
144
|
-
for each_violation in violations
|
|
145
|
-
]
|
|
146
|
-
return _blocked(
|
|
147
|
-
code="negative_keywords_in_artifact",
|
|
148
|
-
message=(
|
|
149
|
-
"Banned negative keywords found inside fenced XML artifact. "
|
|
150
|
-
"Rephrase as positive directives (what TO do, not what to avoid):\n"
|
|
151
|
-
+ "\n".join(violation_descriptions)
|
|
152
|
-
),
|
|
153
|
-
)
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
def validate_prompt_workflow(
|
|
157
|
-
assistant_message: str,
|
|
158
|
-
user_context: str = "",
|
|
159
|
-
) -> ValidationResult:
|
|
160
|
-
"""Run all prompt-workflow gates on *assistant_message*.
|
|
161
|
-
|
|
162
|
-
Returns ``ValidationResult.allowed == True`` when every gate passes.
|
|
163
|
-
The first failing gate short-circuits and its reason is returned.
|
|
164
|
-
"""
|
|
165
|
-
allowed_result = ValidationResult(allowed=True)
|
|
166
|
-
|
|
167
|
-
if not assistant_message.strip():
|
|
168
|
-
return allowed_result
|
|
169
|
-
|
|
170
|
-
debug_requested = has_debug_intent(user_context)
|
|
171
|
-
|
|
172
|
-
leak_result = _check_internal_leak(assistant_message, debug_requested)
|
|
173
|
-
if leak_result is not None:
|
|
174
|
-
return leak_result
|
|
175
|
-
|
|
176
|
-
if not is_prompt_workflow_response(assistant_message):
|
|
177
|
-
return allowed_result
|
|
178
|
-
|
|
179
|
-
workflow_checks = (
|
|
180
|
-
_check_required_sections,
|
|
181
|
-
_check_checklist_rows,
|
|
182
|
-
_check_scope_anchors,
|
|
183
|
-
_check_context_signals,
|
|
184
|
-
_check_ambiguous_scope,
|
|
185
|
-
_check_negative_keywords,
|
|
186
|
-
)
|
|
187
|
-
for each_check in workflow_checks:
|
|
188
|
-
gate_result = each_check(assistant_message)
|
|
189
|
-
if gate_result is not None:
|
|
190
|
-
return gate_result
|
|
191
|
-
|
|
192
|
-
return allowed_result
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
def main() -> None:
|
|
196
|
-
blocked_exit_code: int = 2
|
|
197
|
-
allowed_exit_code: int = 0
|
|
198
|
-
|
|
199
|
-
if len(sys.argv) > 1:
|
|
200
|
-
file_path = Path(sys.argv[1])
|
|
201
|
-
assistant_text = file_path.read_text(encoding="utf-8")
|
|
202
|
-
elif not sys.stdin.isatty():
|
|
203
|
-
assistant_text = sys.stdin.read()
|
|
204
|
-
else:
|
|
205
|
-
sys.stderr.write("Usage: prompt_workflow_validate.py [path/to/draft.md]\n")
|
|
206
|
-
sys.stderr.write(" cat draft.md | prompt_workflow_validate.py\n")
|
|
207
|
-
sys.exit(blocked_exit_code)
|
|
208
|
-
|
|
209
|
-
validation_result = validate_prompt_workflow(assistant_text)
|
|
210
|
-
if validation_result.allowed:
|
|
211
|
-
sys.exit(allowed_exit_code)
|
|
212
|
-
for each_reason in validation_result.reasons:
|
|
213
|
-
sys.stderr.write(f"[{each_reason.code}] {each_reason.message}\n")
|
|
214
|
-
sys.exit(blocked_exit_code)
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
if __name__ == "__main__":
|
|
218
|
-
main()
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
"""Tests for prompt_workflow_clipboard."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import pytest
|
|
6
|
-
|
|
7
|
-
from prompt_workflow_clipboard import copy_text_to_system_clipboard
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def test_empty_text_returns_false() -> None:
|
|
11
|
-
assert copy_text_to_system_clipboard("") is False
|
|
12
|
-
assert copy_text_to_system_clipboard(" \n") is False
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def test_respects_skip_env(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
16
|
-
monkeypatch.setenv("PROMPT_WORKFLOW_SKIP_CLIPBOARD", "1")
|
|
17
|
-
assert copy_text_to_system_clipboard("hello") is False
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
@pytest.mark.parametrize(
|
|
21
|
-
"flag_value",
|
|
22
|
-
("1", "true", "YES", "on"),
|
|
23
|
-
)
|
|
24
|
-
def test_skip_env_variants(monkeypatch: pytest.MonkeyPatch, flag_value: str) -> None:
|
|
25
|
-
monkeypatch.setenv("PROMPT_WORKFLOW_SKIP_CLIPBOARD", flag_value)
|
|
26
|
-
assert copy_text_to_system_clipboard("payload") is False
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def test_prefers_tkinter_when_it_succeeds(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
30
|
-
monkeypatch.delenv("PROMPT_WORKFLOW_SKIP_CLIPBOARD", raising=False)
|
|
31
|
-
monkeypatch.setattr(
|
|
32
|
-
"prompt_workflow_clipboard._copy_via_tkinter",
|
|
33
|
-
lambda t: True,
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
def _fail_pyperclip(_t: str) -> bool:
|
|
37
|
-
raise AssertionError("pyperclip must run only when tkinter fails")
|
|
38
|
-
|
|
39
|
-
monkeypatch.setattr("prompt_workflow_clipboard._copy_via_pyperclip", _fail_pyperclip)
|
|
40
|
-
assert copy_text_to_system_clipboard("hello") is True
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def test_falls_back_to_pyperclip(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
44
|
-
monkeypatch.delenv("PROMPT_WORKFLOW_SKIP_CLIPBOARD", raising=False)
|
|
45
|
-
monkeypatch.setattr("prompt_workflow_clipboard._copy_via_tkinter", lambda t: False)
|
|
46
|
-
monkeypatch.setattr("prompt_workflow_clipboard._copy_via_pyperclip", lambda t: True)
|
|
47
|
-
assert copy_text_to_system_clipboard("hello") is True
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def test_returns_false_when_both_backends_fail(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
51
|
-
monkeypatch.delenv("PROMPT_WORKFLOW_SKIP_CLIPBOARD", raising=False)
|
|
52
|
-
monkeypatch.setattr("prompt_workflow_clipboard._copy_via_tkinter", lambda t: False)
|
|
53
|
-
monkeypatch.setattr("prompt_workflow_clipboard._copy_via_pyperclip", lambda t: False)
|
|
54
|
-
assert copy_text_to_system_clipboard("hello") is False
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
"""Unit tests for shared prompt workflow gate logic."""
|
|
2
|
-
|
|
3
|
-
from prompt_workflow_gate_core import (
|
|
4
|
-
extract_fenced_xml_content,
|
|
5
|
-
extract_fenced_xml_content_from_export,
|
|
6
|
-
find_ambiguous_scope_terms,
|
|
7
|
-
has_checklist_container,
|
|
8
|
-
has_internal_object_leak,
|
|
9
|
-
is_prompt_workflow_response,
|
|
10
|
-
missing_context_control_signals,
|
|
11
|
-
missing_checklist_rows,
|
|
12
|
-
missing_required_xml_sections,
|
|
13
|
-
missing_scope_anchors,
|
|
14
|
-
normalize_prompt_workflow_export,
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
def test_internal_object_leak_detected() -> None:
|
|
18
|
-
text = '{"pipeline_mode": "internal_section_refinement_with_final_audit"}'
|
|
19
|
-
assert has_internal_object_leak(text)
|
|
20
|
-
|
|
21
|
-
def test_missing_scope_anchors_returns_expected_rows() -> None:
|
|
22
|
-
text = "target_local_roots only."
|
|
23
|
-
missing = missing_scope_anchors(text)
|
|
24
|
-
assert "target_canonical_roots" in missing
|
|
25
|
-
assert "completion_boundary" in missing
|
|
26
|
-
|
|
27
|
-
def test_missing_checklist_rows_detected() -> None:
|
|
28
|
-
text = "checklist_results: structured_scoped_instructions only"
|
|
29
|
-
missing = missing_checklist_rows(text)
|
|
30
|
-
assert "completion_boundary_measurable" in missing
|
|
31
|
-
|
|
32
|
-
def test_checklist_container_detection() -> None:
|
|
33
|
-
assert has_checklist_container("checklist_results:\n- structured_scoped_instructions")
|
|
34
|
-
|
|
35
|
-
def test_prompt_workflow_response_detection() -> None:
|
|
36
|
-
message = (
|
|
37
|
-
"overall_status: pass\n"
|
|
38
|
-
"target_local_roots: /repo\n"
|
|
39
|
-
"comparison_basis: current behavior vs deterministic guarantees\n"
|
|
40
|
-
)
|
|
41
|
-
assert is_prompt_workflow_response(message)
|
|
42
|
-
|
|
43
|
-
def test_missing_context_control_signals_detected() -> None:
|
|
44
|
-
missing = missing_context_control_signals("base_minimal_instruction_layer: true")
|
|
45
|
-
assert "on_demand_skill_loading: true" in missing
|
|
46
|
-
|
|
47
|
-
def test_ambiguous_scope_terms_detected() -> None:
|
|
48
|
-
text = "Scope applies to this session and current files."
|
|
49
|
-
terms = find_ambiguous_scope_terms(text)
|
|
50
|
-
assert "this session" in terms
|
|
51
|
-
assert "current files" in terms
|
|
52
|
-
|
|
53
|
-
def _fenced_xml(body: str) -> str:
|
|
54
|
-
return f"```xml\n{body}\n```"
|
|
55
|
-
|
|
56
|
-
def _runtime_context_lines() -> tuple[str, ...]:
|
|
57
|
-
return (
|
|
58
|
-
"<runtime_context>",
|
|
59
|
-
"base_minimal_instruction_layer: true",
|
|
60
|
-
"on_demand_skill_loading: true",
|
|
61
|
-
"</runtime_context>",
|
|
62
|
-
"",
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
def _flattened_transcript(*lines: str) -> str:
|
|
66
|
-
return "\n".join(lines) + "\n"
|
|
67
|
-
|
|
68
|
-
def _flattened_attempt(*body_lines: str, audit_line: str = "Audit: pass 15/15") -> str:
|
|
69
|
-
flattened_lines = [audit_line, ""]
|
|
70
|
-
for line in body_lines:
|
|
71
|
-
flattened_lines.append(f" {line}" if line else "")
|
|
72
|
-
return "\n".join(flattened_lines)
|
|
73
|
-
|
|
74
|
-
def test_missing_required_xml_sections_all_present_returns_empty() -> None:
|
|
75
|
-
body = (
|
|
76
|
-
"<role>R.</role>\n"
|
|
77
|
-
"<background>C.</background>\n"
|
|
78
|
-
"<instructions>I.</instructions>\n"
|
|
79
|
-
"<constraints>Co.</constraints>\n"
|
|
80
|
-
"<output_format>O.</output_format>\n"
|
|
81
|
-
)
|
|
82
|
-
assert missing_required_xml_sections(_fenced_xml(body)) == []
|
|
83
|
-
|
|
84
|
-
def test_missing_required_xml_sections_missing_background() -> None:
|
|
85
|
-
body = (
|
|
86
|
-
"<role>R.</role>\n"
|
|
87
|
-
"<instructions>I.</instructions>\n"
|
|
88
|
-
"<constraints>Co.</constraints>\n"
|
|
89
|
-
"<output_format>O.</output_format>\n"
|
|
90
|
-
)
|
|
91
|
-
assert missing_required_xml_sections(_fenced_xml(body)) == ["background"]
|
|
92
|
-
|
|
93
|
-
def test_missing_required_xml_sections_missing_role_and_output_format() -> None:
|
|
94
|
-
body = (
|
|
95
|
-
"<background>C.</background>\n"
|
|
96
|
-
"<instructions>I.</instructions>\n"
|
|
97
|
-
"<constraints>Co.</constraints>\n"
|
|
98
|
-
)
|
|
99
|
-
missing = missing_required_xml_sections(_fenced_xml(body))
|
|
100
|
-
assert missing == ["role", "output_format"]
|
|
101
|
-
|
|
102
|
-
def test_missing_required_xml_sections_no_fence_returns_empty() -> None:
|
|
103
|
-
assert missing_required_xml_sections("no fenced xml here") == []
|
|
104
|
-
|
|
105
|
-
def test_missing_required_xml_sections_prose_without_tags_counts_as_missing() -> None:
|
|
106
|
-
body = (
|
|
107
|
-
"<role>R.</role>\n"
|
|
108
|
-
"background appears in prose but has no tags.\n"
|
|
109
|
-
"<instructions>I.</instructions>\n"
|
|
110
|
-
"<constraints>Co.</constraints>\n"
|
|
111
|
-
"<output_format>O.</output_format>\n"
|
|
112
|
-
)
|
|
113
|
-
assert missing_required_xml_sections(_fenced_xml(body)) == ["background"]
|
|
114
|
-
|
|
115
|
-
def test_extract_fenced_xml_preserves_content_after_nested_inner_fence() -> None:
|
|
116
|
-
message = (
|
|
117
|
-
"```xml\n"
|
|
118
|
-
"<role>R</role>\n"
|
|
119
|
-
"<illustrations>\n"
|
|
120
|
-
"```bash\necho hi\n```\n"
|
|
121
|
-
"</illustrations>\n"
|
|
122
|
-
"<background>B</background>\n"
|
|
123
|
-
"<instructions>I</instructions>\n"
|
|
124
|
-
"<constraints>C</constraints>\n"
|
|
125
|
-
"<output_format>O</output_format>\n"
|
|
126
|
-
"```\n"
|
|
127
|
-
)
|
|
128
|
-
extracted = extract_fenced_xml_content(message)
|
|
129
|
-
assert "</illustrations>" in extracted
|
|
130
|
-
assert "<background>B</background>" in extracted
|
|
131
|
-
|
|
132
|
-
def test_normalize_prompt_workflow_export_rebuilds_fence_from_flattened_transcript() -> None:
|
|
133
|
-
transcript = _flattened_transcript(
|
|
134
|
-
_flattened_attempt(
|
|
135
|
-
*_runtime_context_lines(),
|
|
136
|
-
"<role>R</role>",
|
|
137
|
-
"<background>B</background>",
|
|
138
|
-
"<instructions>I</instructions>",
|
|
139
|
-
"<constraints>C</constraints>",
|
|
140
|
-
"<output_format>O</output_format>",
|
|
141
|
-
"✻ Worked for 1m 7s",
|
|
142
|
-
audit_line="● Audit: pass 15/15",
|
|
143
|
-
),
|
|
144
|
-
)
|
|
145
|
-
normalized = normalize_prompt_workflow_export(transcript)
|
|
146
|
-
assert normalized.startswith("Audit: pass 15/15\n```xml\n")
|
|
147
|
-
assert normalized.endswith("\n```")
|
|
148
|
-
assert "<runtime_context>" in normalized
|
|
149
|
-
assert "✻ Worked for 1m 7s" not in normalized
|
|
150
|
-
|
|
151
|
-
def test_normalize_prompt_workflow_export_uses_last_audit_attempt() -> None:
|
|
152
|
-
first_attempt = _flattened_attempt(
|
|
153
|
-
"<role>FIRST</role>",
|
|
154
|
-
"<background>Old</background>",
|
|
155
|
-
"<instructions>Old</instructions>",
|
|
156
|
-
"<constraints>Old</constraints>",
|
|
157
|
-
"<output_format>Old</output_format>",
|
|
158
|
-
audit_line="● Audit: pass 15/15",
|
|
159
|
-
)
|
|
160
|
-
second_attempt = _flattened_attempt(
|
|
161
|
-
*_runtime_context_lines(),
|
|
162
|
-
"<role>FINAL</role>",
|
|
163
|
-
"<background>Fresh</background>",
|
|
164
|
-
"<instructions>I</instructions>",
|
|
165
|
-
"<constraints>C</constraints>",
|
|
166
|
-
"<output_format>O</output_format>",
|
|
167
|
-
"✻ Worked for 2m 8s",
|
|
168
|
-
)
|
|
169
|
-
transcript = _flattened_transcript(
|
|
170
|
-
first_attempt,
|
|
171
|
-
"",
|
|
172
|
-
"● Re-emitting the full artifact with the runtime signals added.",
|
|
173
|
-
"",
|
|
174
|
-
second_attempt,
|
|
175
|
-
)
|
|
176
|
-
normalized = normalize_prompt_workflow_export(transcript)
|
|
177
|
-
assert "<role>FINAL</role>" in normalized
|
|
178
|
-
assert "<role>FIRST</role>" not in normalized
|
|
179
|
-
|
|
180
|
-
def test_extract_fenced_xml_content_from_export_supports_flattened_transcript() -> None:
|
|
181
|
-
transcript = _flattened_transcript(
|
|
182
|
-
_flattened_attempt(
|
|
183
|
-
"<role>R</role>",
|
|
184
|
-
"<background>B</background>",
|
|
185
|
-
"<instructions>I</instructions>",
|
|
186
|
-
"<constraints>C</constraints>",
|
|
187
|
-
"<output_format>O</output_format>",
|
|
188
|
-
"✻ Worked for 31s",
|
|
189
|
-
audit_line="● Audit: pass 15/15",
|
|
190
|
-
),
|
|
191
|
-
)
|
|
192
|
-
extracted = extract_fenced_xml_content_from_export(transcript)
|
|
193
|
-
assert extracted.startswith("<role>R</role>")
|
|
194
|
-
assert "<output_format>O</output_format>" in extracted
|
|
195
|
-
assert "Worked for" not in extracted
|