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.
This commit is contained in:
parent
b6c04480c0
commit
0cdd11fe0c
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue