From 6617e8e4a67712a27ecd0cdfdda495575cc7689e Mon Sep 17 00:00:00 2001 From: Santiago Date: Tue, 4 Aug 2026 18:53:08 +0200 Subject: [PATCH] fix: emit plain Markdown in generated skill cards (#3385) Fixes #3384. Generated skill cards now use plain Markdown instead of HTML-only line-break tags while retaining compatibility normalization for existing cards. Co-authored-by: Vyctor H. Brzezowski --- .../skill-cards/run-skill-card-worker.test.ts | 27 ++++-- .../templates/clawhub-skill-card.md.j2 | 94 +++++++++++-------- 2 files changed, 74 insertions(+), 47 deletions(-) diff --git a/scripts/skill-cards/run-skill-card-worker.test.ts b/scripts/skill-cards/run-skill-card-worker.test.ts index 7a0c54d9..c2cc1642 100644 --- a/scripts/skill-cards/run-skill-card-worker.test.ts +++ b/scripts/skill-cards/run-skill-card-worker.test.ts @@ -161,21 +161,32 @@ describe("run-skill-card-worker Codex skill setup", () => { it("keeps the neutral template close to NVIDIA's public card shape", async () => { const template = await readFile(neutralTemplatePath(), "utf8"); - expect(template).toContain("## Description:
"); + expect(template).toContain("## Description:"); expect(template).toContain("## Publisher:"); - expect(template).toContain("### License/Terms of Use:
"); + expect(template).toContain("### License/Terms of Use:"); expect(template).toContain("license_identifier is defined"); - expect(template).toContain("## Use Case:
"); - expect(template).toContain("### Deployment Geography for Use:
"); - expect(template).toContain("## Known Risks and Mitigations:
"); - expect(template).toContain("## Reference(s):
"); - expect(template).toContain("## Skill Output:
"); - expect(template).toContain("## Skill Version(s):
"); + expect(template).toContain("## Use Case:"); + expect(template).toContain("### Deployment Geography for Use:"); + expect(template).toContain("## Known Risks and Mitigations:"); + expect(template).toContain("## Reference(s):"); + expect(template).toContain("## Skill Output:"); + expect(template).toContain("## Skill Version(s):"); expect(template).not.toContain("Third-Party Community Consideration"); expect(template).not.toContain("Provenance"); expect(template).not.toContain("For Release on NVIDIA Platforms Only"); }); + // skill-card.md ships inside the installed skill bundle and is read as plain + // Markdown by agents, the Files tab, the CLI, and the HTTP API. NVIDIA's + // upstream template ends every content line with
because its cards + // render into HTML surfaces; keeping those tags leaked literal markup into + // every raw consumer, so the neutral template must stay
-free. + it("emits plain Markdown with no
line-break tags", async () => { + const template = await readFile(neutralTemplatePath(), "utf8"); + + expect(template).not.toMatch(//i); + }); + it("rejects NVIDIA-only public-card boilerplate", () => { expect(() => assertPublicSkillCardMarkdown( diff --git a/scripts/skill-cards/templates/clawhub-skill-card.md.j2 b/scripts/skill-cards/templates/clawhub-skill-card.md.j2 index a0cc7eb0..f7235f76 100644 --- a/scripts/skill-cards/templates/clawhub-skill-card.md.j2 +++ b/scripts/skill-cards/templates/clawhub-skill-card.md.j2 @@ -1,64 +1,80 @@ {#- ClawHub Skill Card template. -This intentionally stays close to NVIDIA's public Skill Card template shape +This intentionally stays close to NVIDIA's public Skill Card section shape while replacing NVIDIA-only ownership, review, and platform boilerplate with neutral public-card language. + +Unlike NVIDIA's template this emits plain Markdown with no trailing HTML +line-break tags. NVIDIA's cards render into HTML surfaces where soft line +breaks collapse; ClawHub's skill-card.md ships inside the installed skill +bundle and is read as Markdown by agents, the Files tab, the CLI, and the HTTP +API, so blocks are separated with blank lines instead. Keep consecutive field +lines blank-line separated so they stay distinct paragraphs. -#} -## Description:
-{{ description_sentence }}
+## Description: + +{{ description_sentence }} {% if usage_posture == "commercial" -%} -This skill is ready for commercial/non-commercial use.
+This skill is ready for commercial/non-commercial use. {%- elif usage_posture == "research_dev" -%} -This skill is for research and development only.
+This skill is for research and development only. {%- elif usage_posture == "demonstration" -%} -This skill is for demonstration purposes and not for production usage.
+This skill is for demonstration purposes and not for production usage. {%- endif %} -## Publisher:
+## Publisher: + {% if owner.kind == "nvidia" -%} -NVIDIA
+NVIDIA {%- else -%} -[{{ owner.name }}]({{ owner.card_link }})
+[{{ owner.name }}]({{ owner.card_link }}) {%- endif %} -### License/Terms of Use:
-{% if license_identifier is defined and license_identifier -%} -{{ license_identifier }}
+### License/Terms of Use: +{% if license_identifier is defined and license_identifier %} +{{ license_identifier }} +{%- endif %} + +## Use Case: + +{{ use_case }} + +### Deployment Geography for Use: + +{{ deployment_geography }} + +## Known Risks and Mitigations: +{% if risk_mitigations is defined and risk_mitigations %}{% for item in risk_mitigations %} +Risk: {{ item.risk }} + +Mitigation: {{ item.mitigation }} +{% endfor %}{% else %} +Risk: Review before execution as proposals could introduce incorrect or misleading guidance into skills. + +Mitigation: Review and scan skill before deployment. {% endif %} +## Reference(s): +{% for ref in references %} +- [{{ ref.label }}]({{ ref.url }}) +{%- endfor %} -## Use Case:
-{{ use_case }}
+## Skill Output: -### Deployment Geography for Use:
-{{ deployment_geography }}
+**Output Type(s):** [{{ output.types | join(", ") }}] -## Known Risks and Mitigations:
-{% if risk_mitigations is defined and risk_mitigations -%} -{% for item in risk_mitigations -%} -Risk: {{ item.risk }}
-Mitigation: {{ item.mitigation }}
-{% endfor -%} -{%- else -%} -Risk: Review before execution as proposals could introduce incorrect or misleading guidance into skills.
-Mitigation: Review and scan skill before deployment.
-{%- endif %} +**Output Format:** [{{ output.format }}] -## Reference(s):
-{% for ref in references -%} -- [{{ ref.label }}]({{ ref.url }})
-{% endfor %} +**Output Parameters:** [{{ output.parameters }}] -## Skill Output:
-**Output Type(s):** [{{ output.types | join(", ") }}]
-**Output Format:** [{{ output.format }}]
-**Output Parameters:** [{{ output.parameters }}]
-**Other Properties Related to Output:** [{{ output.other_properties }}]
+**Other Properties Related to Output:** [{{ output.other_properties }}] -## Skill Version(s):
-{{ skill_version }}
+## Skill Version(s): -## Ethical Considerations:
-Users should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.
+{{ skill_version }} + +## Ethical Considerations: + +Users should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.