Skip to content

Commit d76b2e7

Browse files
committed
fix: strip skill expansions from collaboration word counts
/tdd, /ship, /goodnight etc expand into 500+ word skill prompts stored as "user" messages in the JSONL. The collaboration analyzer was counting these as human words, making 10-word commands look like Spec Dumps. Now strips everything from "Base directory for this skill:" to "ARGUMENTS:", keeping only the user's actual args. Skill invocations with no args produce empty turns (skipped).
1 parent 87d1464 commit d76b2e7

2 files changed

Lines changed: 53 additions & 0 deletions

File tree

sesh/analyzers/collaboration.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,16 @@
9292
re.DOTALL,
9393
)
9494

95+
# Skill expansion pattern — when user types /tdd or /ship, the JSONL stores
96+
# the full skill prompt (500+ words) as a "user" message. We need to strip
97+
# the skill body and keep only the user's actual arguments.
98+
_SKILL_EXPANSION_RE = re.compile(
99+
r"Base directory for this skill:.*?(?=ARGUMENTS:|$)",
100+
re.DOTALL,
101+
)
102+
# Also strip the ARGUMENTS: prefix itself
103+
_SKILL_ARGS_PREFIX_RE = re.compile(r"^ARGUMENTS:\s*", re.MULTILINE)
104+
95105

96106
# --- Data types ---
97107

@@ -245,6 +255,10 @@ def extract_human_turns(path: Path) -> list[HumanTurn]:
245255
# Strip system noise
246256
text = _SYSTEM_TAG_RE.sub("", text).strip()
247257

258+
# Strip skill expansions — keep only the user's actual arguments
259+
text = _SKILL_EXPANSION_RE.sub("", text).strip()
260+
text = _SKILL_ARGS_PREFIX_RE.sub("", text).strip()
261+
248262
# Skip empty or trivial turns
249263
if not text or len(text) <= 1:
250264
continue

tests/test_collaboration.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,45 @@ def test_skips_tool_results(self, tmp_path):
194194
assert turns[0].text == "Now fix this"
195195
assert "result data" not in turns[0].text
196196

197+
def test_strips_skill_expansions_with_args(self, tmp_path):
198+
"""Skill expansions are stripped, keeping only the user's ARGUMENTS."""
199+
path = tmp_path / "session.jsonl"
200+
skill_text = (
201+
"Base directory for this skill: /path/to/skill\n\n"
202+
"# My Skill\n\nThis is a long skill prompt with instructions "
203+
"that goes on for many lines and hundreds of words.\n\n"
204+
"ARGUMENTS: implement step 5 of the plan"
205+
)
206+
record = {
207+
"type": "user",
208+
"timestamp": "2026-03-14T10:00:00Z",
209+
"message": {"content": [{"type": "text", "text": skill_text}]}
210+
}
211+
path.write_text(json.dumps(record))
212+
turns = extract_human_turns(path)
213+
assert len(turns) == 1
214+
assert "implement step 5" in turns[0].text
215+
assert "Base directory" not in turns[0].text
216+
assert "long skill prompt" not in turns[0].text
217+
assert turns[0].word_count < 20 # Just the args, not the 500+ word skill
218+
219+
def test_strips_skill_expansions_without_args(self, tmp_path):
220+
"""Skill expansions with no ARGUMENTS produce empty turns (skipped)."""
221+
path = tmp_path / "session.jsonl"
222+
skill_text = (
223+
"Base directory for this skill: /path/to/skill\n\n"
224+
"# Goodnight\n\nYou're going to sleep. This is how you tuck yourself in."
225+
)
226+
record = {
227+
"type": "user",
228+
"timestamp": "2026-03-14T10:00:00Z",
229+
"message": {"content": [{"type": "text", "text": skill_text}]}
230+
}
231+
path.write_text(json.dumps(record))
232+
turns = extract_human_turns(path)
233+
# No ARGUMENTS = empty after stripping = skipped
234+
assert len(turns) == 0
235+
197236
def test_skips_empty_turns(self, tmp_path):
198237
"""Empty or trivial text is skipped."""
199238
path = tmp_path / "session.jsonl"

0 commit comments

Comments
 (0)