From 0cdd11fe0c56c3cda3380276ac271b255eea296a Mon Sep 17 00:00:00 2001 From: Dhruv Garg Date: Fri, 10 Jul 2026 06:34:06 +0530 Subject: [PATCH] fix: stop filterSkillBodyForMode from swallowing rule bullets that start with a mode word (#571) filterSkillBodyForMode is the shared filter every host adapter (Claude hooks, Pi, OpenCode, the MCP server, Hermes) uses to trim the ponytail skill body down to the active intensity. Its worked-example detection matched any bullet shaped "- label: text" and treated it as a mode-specific example whenever the label happened to normalize to a mode name (lite/full/ultra/off) -- silently dropping it in every other mode. The current SKILL.md doesn't trip this today (its only such bullets are the three real worked examples), but it's a landmine: an ordinary rule bullet added later that happens to start with "Full:" or "Lite:" would vanish from every other mode's instructions with no warning, in the one code path every adapter shares. Confirmed with a real bullet: filterSkillBodyForMode('- Full: real rule text.', 'ultra') silently drops the line before this fix. All three real worked examples are quoted (`- lite: "..."`); ordinary rule bullets aren't. Required the quote so only genuine examples are mode-filtered, and added a regression test plus fixed an existing test whose synthetic fixture used unquoted examples that don't match the real file's format. --- hooks/ponytail-instructions.js | 6 +++++- pi-extension/test/helpers.test.js | 18 +++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/hooks/ponytail-instructions.js b/hooks/ponytail-instructions.js index f3da002..3ec3980 100644 --- a/hooks/ponytail-instructions.js +++ b/hooks/ponytail-instructions.js @@ -25,7 +25,11 @@ function filterSkillBodyForMode(body, mode) { if (labelMode) return labelMode === effectiveMode; } - const exampleLabel = line.match(/^-\s*([^:]+):\s*/); + // Require a quoted value: every worked example is `- lite: "..."`. Without + // this, an ordinary rule bullet that happens to start with a mode word + // (e.g. "- Full: ...") is silently dropped in every other mode — it looks + // like a worked example but is really prose meant to survive verbatim. + const exampleLabel = line.match(/^-\s*([^:]+):\s*"/); if (exampleLabel) { const labelMode = normalizeMode(exampleLabel[1].trim()); if (labelMode) return labelMode === effectiveMode; diff --git a/pi-extension/test/helpers.test.js b/pi-extension/test/helpers.test.js index 12bf722..00258e2 100644 --- a/pi-extension/test/helpers.test.js +++ b/pi-extension/test/helpers.test.js @@ -104,7 +104,9 @@ test("readQuietStartup resolves env var, config file, and default in that order" }); test("filterSkillBodyForMode keeps only requested intensity examples and rows", () => { - const body = `---\nname: ponytail\n---\n| **lite** | keep lite |\n| **full** | keep full |\n| **ultra** | keep ultra |\n- lite: Lite example\n- full: Full example\n- ultra: Ultra example\nOther line`; + // Examples are quoted in the real SKILL.md (`- lite: "..."`) — match that + // shape here too; see the next test for why the quote is load-bearing. + const body = `---\nname: ponytail\n---\n| **lite** | keep lite |\n| **full** | keep full |\n| **ultra** | keep ultra |\n- lite: "Lite example"\n- full: "Full example"\n- ultra: "Ultra example"\nOther line`; const filtered = filterSkillBodyForMode(body, "ultra"); @@ -116,6 +118,20 @@ test("filterSkillBodyForMode keeps only requested intensity examples and rows", assert.ok(filtered.includes("Other line")); }); +test("filterSkillBodyForMode does not drop a rule bullet whose label matches a mode name", () => { + // A rule bullet like "- Full: ..." has the same "label: text" shape as a + // worked example, but isn't one — it must survive in every mode. Only the + // quoted, `- lite: "..."`-style bullets are real per-mode examples. + const body = `- Full: do not confuse this rule label with the mode name.\n- Lite: same risk, this is a real rule bullet.\n- lite: "real worked example"\n- ultra: "real worked example"`; + + const filtered = filterSkillBodyForMode(body, "ultra"); + + assert.ok(filtered.includes("Full: do not confuse"), "an unquoted rule bullet must not be treated as a mode example"); + assert.ok(filtered.includes("Lite: same risk"), "an unquoted rule bullet must not be treated as a mode example"); + assert.ok(!filtered.includes("- lite:"), "the real quoted lite example must still be filtered out in ultra mode"); + assert.ok(filtered.includes('ultra: "real worked example"')); +}); + test("filterSkillBodyForMode keeps rule bullets that contain a colon", () => { // Regression: rule bullets outside the Intensity section (e.g. the // "No unrequested abstractions:" rule or the `ponytail:` comment convention)