idnameraj Cursor commited on
Commit
f47a29e
·
1 Parent(s): ffb5352

Refuse voice flips that would strand openers or preverbal adverbs.

Browse files

Leftovers from active_to_passive land after the agent, which silently moved fronted phrases and words like "also" / "carefully" into the wrong place. Hyphenated compounds (high-quality) are kept intact.

Co-authored-by: Cursor <cursoragent@cursor.com>

app/engine/voice/__init__.py CHANGED
@@ -28,6 +28,17 @@ def _ranges_text(doc, indices: set[int]) -> str:
28
  return " ".join(part for part in parts if part)
29
 
30
 
 
 
 
 
 
 
 
 
 
 
 
31
  def _continue_case(text: str, head) -> str:
32
  value = text.strip()
33
  if not value or head.pos_ == "PROPN":
@@ -221,7 +232,7 @@ def active_to_passive(text: str) -> str | None:
221
  | {root.i}
222
  | {token.i for token in auxiliaries}
223
  | {token.i for token in negations}
224
- | {token.i for token in doc if token.dep_ == "punct"}
225
  )
226
  extras = {
227
  token.i
@@ -229,6 +240,19 @@ def active_to_passive(text: str) -> str | None:
229
  if token.i not in consumed
230
  }
231
 
 
 
 
 
 
 
 
 
 
 
 
 
 
232
  new_subject = _start_case(_ranges_text(doc, object_indices))
233
  agent = _continue_case(_ranges_text(doc, subject_indices), subject)
234
  complement = _ranges_text(doc, complement_indices)
 
28
  return " ".join(part for part in parts if part)
29
 
30
 
31
+ def _is_structural_punct(token, doc) -> bool:
32
+ """Punctuation that separates phrases, as opposed to joining a compound.
33
+
34
+ The hyphens in "high-quality" and "word-of-mouth" are ``punct`` too;
35
+ dropping them would rebuild the sentence as "high quality".
36
+ """
37
+ if token.dep_ != "punct":
38
+ return False
39
+ return bool(token.whitespace_) or token.i == len(doc) - 1
40
+
41
+
42
  def _continue_case(text: str, head) -> str:
43
  value = text.strip()
44
  if not value or head.pos_ == "PROPN":
 
232
  | {root.i}
233
  | {token.i for token in auxiliaries}
234
  | {token.i for token in negations}
235
+ | {token.i for token in doc if _is_structural_punct(token, doc)}
236
  )
237
  extras = {
238
  token.i
 
240
  if token.i not in consumed
241
  }
242
 
243
+ # Leftovers are appended after the agent, which only reads correctly for
244
+ # trailing modifiers. Anything that opened the sentence ("By delivering
245
+ # good service, organizations can …") or sat before the verb ("also")
246
+ # would land in the wrong place and change what it modifies.
247
+ if extras:
248
+ subject_start = min(subject_indices)
249
+ if any(index < subject_start for index in extras):
250
+ return None
251
+ if any(
252
+ doc[index].pos_ in {"ADV", "PART"} and index < root.i for index in extras
253
+ ):
254
+ return None
255
+
256
  new_subject = _start_case(_ranges_text(doc, object_indices))
257
  agent = _continue_case(_ranges_text(doc, subject_indices), subject)
258
  complement = _ranges_text(doc, complement_indices)
tests/test_structural_variation.py CHANGED
@@ -37,6 +37,27 @@ def test_passive_to_active_skips_negation_needing_do_support():
37
  assert passive_to_active("Mistakes were not corrected by the editor.") is None
38
 
39
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  def test_active_and_passive_are_not_mutually_reachable():
41
  text = "Readers reviewed the report yesterday."
42
  passive = active_to_passive(text)
 
37
  assert passive_to_active("Mistakes were not corrected by the editor.") is None
38
 
39
 
40
+ def test_active_to_passive_refuses_to_relocate_a_fronted_phrase():
41
+ """Leftovers land after the agent, which would strip the opener's scope."""
42
+ text = (
43
+ "By consistently delivering high-quality service, organizations can "
44
+ "establish a strong reputation."
45
+ )
46
+ assert active_to_passive(text) is None
47
+
48
+
49
+ def test_active_to_passive_refuses_to_strand_a_preverbal_adverb():
50
+ assert active_to_passive(
51
+ "Organizations can also encourage positive recommendations."
52
+ ) is None
53
+ assert active_to_passive("Maria carefully submitted the final report.") is None
54
+
55
+
56
+ def test_active_to_passive_keeps_hyphenated_compounds_intact():
57
+ result = active_to_passive("The team published the high-quality version last week.")
58
+ assert result == "The high-quality version was published by the team last week."
59
+
60
+
61
  def test_active_and_passive_are_not_mutually_reachable():
62
  text = "Readers reviewed the report yesterday."
63
  passive = active_to_passive(text)