fix: enhance markdown table cell formatting for descriptions in README and skills

This commit is contained in:
Aaron Powell
2026-01-23 10:06:44 +11:00
parent 105c0f55e2
commit a2525e3112
4 changed files with 61 additions and 23 deletions

View File

@@ -22,13 +22,7 @@ Skills differ from other primitives by supporting bundled assets (scripts, code
| Name | Description | Bundled Assets |
| ---- | ----------- | -------------- |
| [agentic-eval](../skills/agentic-eval/SKILL.md) | Patterns and techniques for evaluating and improving AI agent outputs. Use this skill when:
- Implementing self-critique and reflection loops
- Building evaluator-optimizer pipelines for quality-critical generation
- Creating test-driven code refinement workflows
- Designing rubric-based or LLM-as-judge evaluation systems
- Adding iterative improvement to agent outputs (code, reports, analysis)
- Measuring and improving agent response quality | None |
| [agentic-eval](../skills/agentic-eval/SKILL.md) | Patterns and techniques for evaluating and improving AI agent outputs. Use this skill when:<br />- Implementing self-critique and reflection loops<br />- Building evaluator-optimizer pipelines for quality-critical generation<br />- Creating test-driven code refinement workflows<br />- Designing rubric-based or LLM-as-judge evaluation systems<br />- Adding iterative improvement to agent outputs (code, reports, analysis)<br />- Measuring and improving agent response quality | None |
| [appinsights-instrumentation](../skills/appinsights-instrumentation/SKILL.md) | Instrument a webapp to send useful telemetry data to Azure App Insights | `LICENSE.txt`<br />`examples/appinsights.bicep`<br />`references/ASPNETCORE.md`<br />`references/AUTO.md`<br />`references/NODEJS.md`<br />`references/PYTHON.md`<br />`scripts/appinsights.ps1` |
| [azure-resource-visualizer](../skills/azure-resource-visualizer/SKILL.md) | Analyze Azure resource groups and generate detailed Mermaid architecture diagrams showing the relationships between individual resources. Use this skill when the user asks for a diagram of their Azure resources or help in understanding how the resources relate to each other. | `LICENSE.txt`<br />`assets/template-architecture.md` |
| [azure-role-selector](../skills/azure-role-selector/SKILL.md) | When user is asking for guidance for which role to assign to an identity given desired permissions, this agent helps them understand the role that will meet the requirements with least privilege access and how to apply that role. | `LICENSE.txt` |

View File

@@ -238,6 +238,39 @@ function extractDescription(filePath) {
);
}
/**
* Format arbitrary multiline text for safe rendering inside a markdown table cell.
* - Preserves line breaks by converting to <br />
* - Escapes pipe characters (|) to avoid breaking table columns
* - Trims leading/trailing whitespace on each line
* - Collapses multiple consecutive blank lines
* This should be applied to descriptions across all file types when used in tables.
*
* @param {string|null|undefined} text
* @returns {string} table-safe content
*/
function formatTableCell(text) {
if (text === null || text === undefined) return "";
let s = String(text);
// Normalize line endings
s = s.replace(/\r\n/g, "\n");
// Split lines, trim, drop empty groups while preserving intentional breaks
const lines = s
.split("\n")
.map((l) => l.trim())
.filter((_, idx, arr) => {
// Keep single blank lines, drop consecutive blanks
if (arr[idx] !== "") return true;
return arr[idx - 1] !== ""; // allow one blank, remove duplicates
});
s = lines.join("\n");
// Escape table pipes
s = s.replace(/\|/g, "&#124;");
// Convert remaining newlines to <br /> for a single-cell rendering
s = s.replace(/\n/g, "<br />");
return s.trim();
}
function makeBadges(link, type) {
const aka = AKA_INSTALL_URLS[type] || AKA_INSTALL_URLS.instructions;
@@ -298,8 +331,10 @@ function generateInstructionsSection(instructionsDir) {
const badges = makeBadges(link, "instructions");
if (customDescription && customDescription !== "null") {
// Use the description from frontmatter
instructionsContent += `| [${title}](../${link})<br />${badges} | ${customDescription} |\n`;
// Use the description from frontmatter, table-safe
instructionsContent += `| [${title}](../${link})<br />${badges} | ${formatTableCell(
customDescription
)} |\n`;
} else {
// Fallback to the default approach - use last word of title for description, removing trailing 's' if present
const topic = title.split(" ").pop().replace(/s$/, "");
@@ -356,7 +391,9 @@ function generatePromptsSection(promptsDir) {
const badges = makeBadges(link, "prompt");
if (customDescription && customDescription !== "null") {
promptsContent += `| [${title}](../${link})<br />${badges} | ${customDescription} |\n`;
promptsContent += `| [${title}](../${link})<br />${badges} | ${formatTableCell(
customDescription
)} |\n`;
} else {
promptsContent += `| [${title}](../${link})<br />${badges} | | |\n`;
}
@@ -533,7 +570,9 @@ function generateSkillsSection(skillsDir) {
? skill.assets.map((a) => `\`${a}\``).join("<br />")
: "None";
content += `| [${skill.name}](${link}) | ${skill.description} | ${assetsList} |\n`;
content += `| [${skill.name}](${link}) | ${formatTableCell(
skill.description
)} | ${assetsList} |\n`;
}
return `${TEMPLATES.skillsSection}\n${TEMPLATES.skillsUsage}\n\n${content}`;
@@ -598,14 +637,12 @@ function generateUnifiedModeSection(cfg) {
mcpServerCell = generateMcpServerLinks(servers, registryNames);
}
const descCell =
description && description !== "null" ? formatTableCell(description) : "";
if (includeMcpServers) {
content += `| [${title}](../${link})<br />${badges} | ${
description && description !== "null" ? description : ""
} | ${mcpServerCell} |\n`;
content += `| [${title}](../${link})<br />${badges} | ${descCell} | ${mcpServerCell} |\n`;
} else {
content += `| [${title}](../${link})<br />${badges} | ${
description && description !== "null" ? description : ""
} |\n`;
content += `| [${title}](../${link})<br />${badges} | ${descCell} |\n`;
}
}
@@ -677,7 +714,9 @@ function generateCollectionsSection(collectionsDir) {
// Generate table rows for each collection file
for (const entry of sortedEntries) {
const { collection, collectionId, name, isFeatured } = entry;
const description = collection.description || "No description";
const description = formatTableCell(
collection.description || "No description"
);
const itemCount = collection.items ? collection.items.length : 0;
const tags = collection.tags ? collection.tags.join(", ") : "";
@@ -719,7 +758,9 @@ function generateFeaturedCollectionsSection(collectionsDir) {
const collectionId =
collection.id || path.basename(file, ".collection.yml");
const name = collection.name || collectionId;
const description = collection.description || "No description";
const description = formatTableCell(
collection.description || "No description"
);
const tags = collection.tags ? collection.tags.join(", ") : "";
const itemCount = collection.items ? collection.items.length : 0;
@@ -904,6 +945,9 @@ function buildCollectionRow({
? `[${title}](${link})<br />${badges}`
: `[${title}](${link})`;
// Ensure description is table-safe
const safeUsage = formatTableCell(usageDescription);
if (hasAgents) {
// Only agents currently have MCP servers; future migration may extend to chat modes.
const mcpServers =
@@ -912,9 +956,9 @@ function buildCollectionRow({
mcpServers.length > 0
? generateMcpServerLinks(mcpServers, registryNames)
: "";
return `| ${titleCell} | ${typeDisplay} | ${usageDescription} | ${mcpServerCell} |\n`;
return `| ${titleCell} | ${typeDisplay} | ${safeUsage} | ${mcpServerCell} |\n`;
}
return `| ${titleCell} | ${typeDisplay} | ${usageDescription} |\n`;
return `| ${titleCell} | ${typeDisplay} | ${safeUsage} |\n`;
}
// Utility: write file only if content changed