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

Function resolvePlaceholders

scripts/gen-skill-docs.ts:667–707  ·  view source on GitHub ↗

* Resolve {{PLACEHOLDER}} / {{NAME:arg}} tokens against the RESOLVERS registry, * honoring host suppression and appliesTo gating, then assert nothing is left * unresolved. Extracted so SKILL.md and section templates resolve through the * exact same path — a security/sanitization fix to one can't

(
  tmplContent: string,
  ctx: TemplateContext,
  hostConfig: HostConfig,
  relTmplPath: string,
)

Source from the content-addressed store, hash-verified

665 * exact same path — a security/sanitization fix to one can't miss the other.
666 */
667function resolvePlaceholders(
668 tmplContent: string,
669 ctx: TemplateContext,
670 hostConfig: HostConfig,
671 relTmplPath: string,
672): string {
673 // effectiveSuppressedResolvers() honors --respect-detection: when gbrain is
674 // detected locally, GBRAIN_* resolvers un-suppress. Shared by SKILL.md and
675 // section generation so both paths get the same gbrain-aware behavior.
676 const suppressed = effectiveSuppressedResolvers(hostConfig);
677 const onePass = (input: string): string =>
678 input.replace(/\{\{(\w+(?::[^}]+)?)\}\}/g, (_match, fullKey) => {
679 const parts = fullKey.split(':');
680 const resolverName = parts[0];
681 const args = parts.slice(1);
682 if (suppressed.has(resolverName)) return '';
683 const entry = RESOLVERS[resolverName];
684 if (!entry) throw new Error(`Unknown placeholder {{${resolverName}}} in ${relTmplPath}`);
685 const { resolve, appliesTo } = unwrapResolver(entry);
686 if (appliesTo && !appliesTo(ctx)) return '';
687 return args.length > 0 ? resolve(ctx, args) : resolve(ctx);
688 });
689
690 // Multi-pass: a resolver may emit content that itself contains {{TOKENS}} — the
691 // {{SECTION:id}} resolver inlines a section template (with its own resolvers)
692 // for non-Claude hosts. .replace() doesn't re-scan inserted text, so loop until
693 // the output stabilizes. Bounded to avoid an infinite loop if a resolver ever
694 // emits its own placeholder; 6 passes is far more nesting than any skill needs.
695 let content = tmplContent;
696 for (let pass = 0; pass < 6; pass++) {
697 const next = onePass(content);
698 if (next === content) break;
699 content = next;
700 }
701
702 const remaining = content.match(/\{\{(\w+(?::[^}]+)?)\}\}/g);
703 if (remaining) {
704 throw new Error(`Unresolved placeholders in ${relTmplPath}: ${remaining.join(', ')}`);
705 }
706 return content;
707}
708
709/**
710 * Build the TemplateContext from a template's frontmatter. Shared by SKILL.md

Callers 2

processTemplateFunction · 0.85
processSectionTemplateFunction · 0.85

Calls 2

onePassFunction · 0.85

Tested by

no test coverage detected