MCPcopy Index your code
hub / github.com/garrytan/gstack / applyCatalogTrim

Function applyCatalogTrim

scripts/gen-skill-docs.ts:426–479  ·  view source on GitHub ↗
(content: string, skillName: string)

Source from the content-addressed store, hash-verified

424 * JSON aggregation at the end of the run).
425 */
426export function applyCatalogTrim(content: string, skillName: string): { content: string; parts: CatalogParts } | null {
427 // Locate description block in frontmatter
428 if (!content.startsWith('---\n')) return null;
429 const fmEnd = content.indexOf('\n---', 4);
430 if (fmEnd === -1) return null;
431 const frontmatter = content.slice(4, fmEnd);
432
433 // Match `description: |` block + indented body lines
434 const descMatch = frontmatter.match(/^description:\s*\|?\s*\n((?:\s{2,}.*(?:\n|$))+)/m)
435 || frontmatter.match(/^description:\s+(.+)$/m);
436 if (!descMatch) return null;
437
438 // Extract full description text
439 let descText: string;
440 if (descMatch[0].startsWith('description: |') || /^description:\s*\|/.test(descMatch[0])) {
441 descText = descMatch[1].split('\n').map(l => l.replace(/^\s{2}/, '')).join('\n').trim();
442 } else {
443 descText = descMatch[1].trim();
444 }
445
446 // Skip skills with very short descriptions (already trimmed or no routing prose).
447 // Below ~120 chars, splitting adds no value.
448 if (descText.length < 120) return null;
449
450 const parts = splitCatalogDescription(descText);
451 // If lead + (gstack) is already most of the text, no trim needed.
452 const trimmedLen = buildTrimmedDescription(parts).length;
453 if (trimmedLen >= descText.length - 20) return null;
454
455 // Replace description in frontmatter — keep trailing newline so the next
456 // YAML field doesn't collide on the same line as the description value.
457 // Quote the value when it would be an invalid YAML plain scalar (the common
458 // case: an interior ": " like "Ship workflow: detect..." which a strict YAML
459 // parser reads as a nested mapping and rejects — #1778). toYamlInlineScalar
460 // only quotes when needed, so descriptions without special chars stay plain.
461 const newDesc = buildTrimmedDescription(parts);
462 // Function replacer (not a string) so a `$` in the description — e.g. a future
463 // skill referencing `$B`/`$D` — can't be interpreted as a `$&`/`$1` replacement
464 // pattern and silently corrupt the frontmatter.
465 const newDescLine = `description: ${toYamlInlineScalar(newDesc)}\n`;
466 const newFrontmatter = frontmatter.replace(descMatch[0], () => newDescLine);
467 let newContent = '---\n' + newFrontmatter + content.slice(fmEnd);
468
469 // Insert body section after frontmatter (after the closing ---\n and any
470 // existing GENERATED header). We insert before the first non-comment line.
471 const bodyStart = newContent.indexOf('\n---\n') + 5;
472 const whenToInvoke = '\n' + buildWhenToInvokeSection(parts).trim() + '\n';
473 // Skip past the generated header if present (it lives after frontmatter close)
474 const headerMatch = newContent.slice(bodyStart).match(/^(<!--[^>]*-->\s*\n)+/);
475 const insertAt = bodyStart + (headerMatch ? headerMatch[0].length : 0);
476 newContent = newContent.slice(0, insertAt) + whenToInvoke + '\n' + newContent.slice(insertAt);
477
478 return { content: newContent, parts };
479}
480
481const OPENAI_SHORT_DESCRIPTION_LIMIT = 120;
482

Callers 2

processTemplateFunction · 0.85

Calls 4

splitCatalogDescriptionFunction · 0.85
buildTrimmedDescriptionFunction · 0.85
toYamlInlineScalarFunction · 0.85
buildWhenToInvokeSectionFunction · 0.85

Tested by

no test coverage detected