Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,8 @@ actions:
- whatsapp_templates # Meta HSM send via WhatsAppAction (WhatsApp inbound only)
- whatsapp_flows # Meta Flow interactive send (WhatsApp text / voice-call)
- whatsapp_service_flows # signup/signin + appointment booking Flow intents
- smoke_frontmatter # regression fixture: its SKILL.md is deliberately
# BOM-prefixed with underscore keys — do not 'fix' it

# MCP gateway: a sandboxed filesystem server (npx). Its tools surface to the
# executive (tool_servers above) as mcp_filesystem__<tool>, sandboxed per
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
name: smoke_frontmatter
description: Regression fixture - declares its tools with underscore keys behind a UTF-8 BOM.
spec: jv
allowed_tools:
- file_interface__list_directory
- file_interface__read_file
requires_actions:
- FileInterfaceAction
tags:
- smoke
---

# Frontmatter Smoke - Standard Operating Procedure

> **This file is deliberately malformed. Do not "fix" it.**
>
> It is written the way a Windows or Office editor writes one: the first three
> bytes are a UTF-8 BOM (`EF BB BF`, invisible in every editor), and the
> frontmatter keys use underscores rather than the canonical hyphens. Both
> spellings once produced the same silent failure - the frontmatter parsed as
> body, so the skill owned no tools *and* `allowed-tools:` leaked into the
> rendered PROCEDURE. `tests/scaffold/test_skill_resolve.py` asserts on this
> exact file; normalising it would delete the regression it guards.

Use this procedure when the user asks to list or read their stored files.

1. Call `file_interface__list_directory` to see what the user has.
2. If they named a file, read it with `file_interface__read_file`.
3. Answer with what the files actually contain. Do not invent filenames.
60 changes: 60 additions & 0 deletions tests/scaffold/test_skill_resolve.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,3 +430,63 @@ def test_unrelated_unknown_key_is_left_alone(
)
assert meta["team_owner"] == "platform"
assert not caplog.records


class TestShippedFrontmatterFixture:
"""The example app carries a deliberately malformed SKILL.md.

The inline cases above build their own files, so they keep passing even if
the shipped fixture is quietly normalised by an editor that strips BOMs on
save — and then nothing exercises the real discovery path against a real
file on disk. These assert on the shipped bytes themselves.
"""

FIXTURE = (
Path(__file__).resolve().parents[2]
/ "examples"
/ "jvagent_app"
/ "agents"
/ "jvagent"
/ "orchestrator_agent"
/ "skills"
/ "smoke_frontmatter"
)

def test_fixture_is_still_malformed(self) -> None:
"""Guards the guard: a stripped BOM makes this suite silently weaker."""
raw = (self.FIXTURE / "SKILL.md").read_bytes()
assert raw.startswith(
b"\xef\xbb\xbf"
), "the UTF-8 BOM is the point of this file"
assert b"allowed_tools:" in raw, "underscore spelling is the point of this file"
assert b"requires_actions:" in raw

def test_shipped_fixture_parses_through_discovery(self) -> None:
from jvagent.scaffold.skill_resolve import parse_skill_bundle

data = parse_skill_bundle(self.FIXTURE, source="app")
assert data is not None
assert data["allowed_tools"] == [
"file_interface__list_directory",
"file_interface__read_file",
]
assert data["requires_actions"] == ["FileInterfaceAction"]
# The body explains the bug, so it legitimately mentions "allowed-tools:".
# Assert on tokens that only ever appear inside the frontmatter block.
content = data["content"]
assert content.startswith("# Frontmatter Smoke")
assert "name: smoke_frontmatter" not in content
assert "spec: jv" not in content
assert "allowed_tools:\n" not in content

def test_fixture_is_exposed_by_the_example_agent(self) -> None:
"""A fixture no agent loads is not exercising discovery at all."""
import yaml

agent_yaml = self.FIXTURE.parents[1] / "agent.yaml"
data = yaml.safe_load(agent_yaml.read_text(encoding="utf-8"))
skills: list = []
for entry in data.get("actions") or []:
if isinstance(entry, dict):
skills += (entry.get("context") or {}).get("skills") or []
assert "smoke_frontmatter" in skills