Add a max_length option to slugify

Done

/home/devdi/devswarm/evals/tasks/slugify_max_length/repo · 2026-11-09 16:22:28 UTC · took 45.6s

  1. Plan (done)
  2. Code (done)
  3. Test (done)
  4. Review (done)
  5. Finish (done)
Cost
$0.0062
Tests
Passed
Iterations
1
Re-plans
0
LLM calls
11
Prompt read from cache
73%

Usage by agent

Cost per agent
planner: $0.0012 planner planner, Cost: $0.0012 $0.0012 coder: $0.0022 coder coder, Cost: $0.0022 $0.0022 tester: $0.0017 tester tester, Cost: $0.0017 $0.0017 reviewer: $0.0011 reviewer reviewer, Cost: $0.0011 $0.0011
Tokens per agent
  • Cache read
  • Cache write
  • Input
  • Output
planner: 8,659 planner planner, Cache read: 5,769 planner, Cache write: 766 planner, Output: 2,116 8,659 coder: 22.5K coder coder, Cache read: 15.1K coder, Cache write: 4,487 coder, Output: 2,918 22.5K tester: 20.1K tester tester, Cache read: 13.1K tester, Cache write: 4,974 tester, Output: 1,954 20.1K reviewer: 6,268 reviewer reviewer, Cache read: 1,727 reviewer, Cache write: 3,264 reviewer, Output: 1,273 6,268
View as table
AgentCostCache readCache writeInputOutput
planner$0.00125,76976682,116
coder$0.002215,1184,487102,918
tester$0.001713,1364,97481,954
reviewer$0.00111,7273,26441,273

Timeline

  1. planner iteration 1 Finished 3 calls · 8,659 tokens · $0.0012 · 11.6s

    Loading…

  2. coder iteration 1 Finished 4 calls · 22.5K tokens · $0.0022 · 15.3s

    Loading…

  3. tester iteration 1 Finished 3 calls · 20.1K tokens · $0.0017 · 11.1s

    Loading…

  4. verify iteration 1 Passed 0.6s
    .............                                                            [100%]
    13 passed in 0.07s
    
  5. reviewer iteration 1 Finished 1 calls · 6,268 tokens · $0.0011 · 6.4s

    Loading…

Final diff

diff --git a/slug.py b/slug.py
index c4d3700..108d6fd 100644
--- a/slug.py
+++ b/slug.py
@@ -1,5 +1,17 @@
 import re
+from typing import Optional
 
 
-def slugify(text: str) -> str:
-    return re.sub(r"[^a-z0-9]+", "-", text.lower()).strip("-")
+def slugify(text: str, max_length: Optional[int] = None) -> str:
+    slug = re.sub(r"[^a-z0-9]+", "-", text.lower()).strip("-")
+    if max_length is None or len(slug) <= max_length:
+        return slug
+    if max_length < 0:
+        raise ValueError("max_length must be non-negative")
+    # Look one character past max_length so a word ending exactly at
+    # max_length is kept whole.
+    idx = slug[:max_length + 1].rfind("-")
+    if idx == -1:
+        # The first word alone is longer than max_length: cut it.
+        return slug[:max_length]
+    return slug[:idx]
diff --git a/test_slug.py b/test_slug.py
index 73209ea..fa8fb14 100644
--- a/test_slug.py
+++ b/test_slug.py
@@ -1,5 +1,96 @@
+import random
+import re
+
+import pytest
+
 from slug import slugify
 
 
 def test_basic():
     assert slugify("Hello, World!") == "hello-world"
+
+
+def test_no_max_length_unchanged():
+    assert slugify("Hello, World!", None) == "hello-world"
+    assert slugify("Supercalifragilistic word") == "supercalifragilistic-word"
+
+
+def test_max_length_cuts_at_word_boundary():
+    assert slugify("Hello, World!", max_length=5) == "hello"
+    assert slugify("Hello, World!", max_length=6) == "hello"
+    assert slugify("Hello, World!", max_length=11) == "hello-world"
+
+
+def test_first_word_longer_than_max_length():
+    assert slugify("Supercalifragilistic word", max_length=5) == "super"
+
+
+def test_no_trailing_hyphen():
+    assert slugify("a-b-c", max_length=2) == "a"
+    assert slugify("a-b-c", max_length=3) == "a-b"
+
+
+def test_length_never_exceeds_max_length():
+    text = "The quick brown fox jumps over the lazy dog"
+    for n in range(1, len(text) + 2):
+        result = slugify(text, max_length=n)
+        assert len(result) <= n
+        assert not result.endswith("-")
+
+
+def test_empty_slug_with_max_length():
+    assert slugify("!!!", max_length=5) == ""
+
+
+def test_zero_max_length():
+    assert slugify("hello world", max_length=0) == ""
+
+
+def test_negative_max_length_raises():
+    with pytest.raises(ValueError):
+        slugify("hello world", max_length=-1)
+
+
+def test_word_ending_exactly_at_max_length_is_kept():
+    assert slugify("hello-world", max_length=5) == "hello"
+    assert slugify("abcde-f", max_length=5) == "abcde"
+    assert slugify("abcde", max_length=5) == "abcde"
+
+
+def test_first_word_one_char_over_limit_is_cut():
+    assert slugify("abcdef-g", max_length=5) == "abcde"
+
+
+def test_result_unchanged_when_within_limit():
+    assert slugify("hello world", max_length=11) == "hello-world"
+    assert slugify("hello world", max_length=100) == "hello-world"
+
+
+def _reference(text, max_length):
+    slug = re.sub(r"[^a-z0-9]+", "-", text.lower()).strip("-")
+    if len(slug) <= max_length:
+        return slug
+    words = slug.split("-")
+    if len(words[0]) > max_length:
+        return words[0][:max_length]
+    kept = []
+    total = 0
+    for w in words:
+        extra = len(w) if not kept else len(w) + 1
+        if total + extra > max_length:
+            break
+        kept.append(w)
+        total += extra
+    return "-".join(kept)
+
+
+def test_matches_reference_on_random_inputs():
+    rng = random.Random(1234)
+    alphabet = "abc XYZ-!019é_"
+    for _ in range(2000):
+        text = "".join(rng.choice(alphabet) for _ in range(rng.randint(0, 30)))
+        n = rng.randint(0, 35)
+        result = slugify(text, max_length=n)
+        assert result == _reference(text, n), (text, n)
+        assert len(result) <= n
+        assert not result.endswith("-")