mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-20 11:19:56 +08:00
feat: add parameterized resolver support to gen-skill-docs
Extend the placeholder regex from {{WORD}} to {{WORD:arg1:arg2}},
enabling parameterized resolvers like {{INVOKE_SKILL:plan-ceo-review}}.
- Widen ResolverFn type to accept optional args?: string[]
- Update RESOLVERS record to use ResolverFn type
- Both replacement and unresolved-check regexes updated
- Fully backward compatible: existing {{WORD}} patterns unchanged
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -259,15 +259,18 @@ function processTemplate(tmplPath: string, host: Host = 'claude'): { outputPath:
|
|||||||
|
|
||||||
const ctx: TemplateContext = { skillName, tmplPath, benefitsFrom, host, paths: HOST_PATHS[host], preambleTier };
|
const ctx: TemplateContext = { skillName, tmplPath, benefitsFrom, host, paths: HOST_PATHS[host], preambleTier };
|
||||||
|
|
||||||
// Replace placeholders
|
// Replace placeholders (supports parameterized: {{NAME:arg1:arg2}})
|
||||||
let content = tmplContent.replace(/\{\{(\w+)\}\}/g, (match, name) => {
|
let content = tmplContent.replace(/\{\{(\w+(?::[^}]+)?)\}\}/g, (match, fullKey) => {
|
||||||
const resolver = RESOLVERS[name];
|
const parts = fullKey.split(':');
|
||||||
if (!resolver) throw new Error(`Unknown placeholder {{${name}}} in ${relTmplPath}`);
|
const resolverName = parts[0];
|
||||||
return resolver(ctx);
|
const args = parts.slice(1);
|
||||||
|
const resolver = RESOLVERS[resolverName];
|
||||||
|
if (!resolver) throw new Error(`Unknown placeholder {{${resolverName}}} in ${relTmplPath}`);
|
||||||
|
return args.length > 0 ? resolver(ctx, args) : resolver(ctx);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check for any remaining unresolved placeholders
|
// Check for any remaining unresolved placeholders
|
||||||
const remaining = content.match(/\{\{(\w+)\}\}/g);
|
const remaining = content.match(/\{\{(\w+(?::[^}]+)?)\}\}/g);
|
||||||
if (remaining) {
|
if (remaining) {
|
||||||
throw new Error(`Unresolved placeholders in ${relTmplPath}: ${remaining.join(', ')}`);
|
throw new Error(`Unresolved placeholders in ${relTmplPath}: ${remaining.join(', ')}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* Each resolver takes a TemplateContext and returns the replacement string.
|
* Each resolver takes a TemplateContext and returns the replacement string.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { TemplateContext } from './types';
|
import type { TemplateContext, ResolverFn } from './types';
|
||||||
|
|
||||||
// Domain modules
|
// Domain modules
|
||||||
import { generatePreamble } from './preamble';
|
import { generatePreamble } from './preamble';
|
||||||
@@ -13,8 +13,9 @@ import { generateDesignMethodology, generateDesignHardRules, generateDesignOutsi
|
|||||||
import { generateTestBootstrap, generateTestCoverageAuditPlan, generateTestCoverageAuditShip, generateTestCoverageAuditReview } from './testing';
|
import { generateTestBootstrap, generateTestCoverageAuditPlan, generateTestCoverageAuditShip, generateTestCoverageAuditReview } from './testing';
|
||||||
import { generateReviewDashboard, generatePlanFileReviewReport, generateSpecReviewLoop, generateBenefitsFrom, generateCodexSecondOpinion, generateAdversarialStep, generateCodexPlanReview, generatePlanCompletionAuditShip, generatePlanCompletionAuditReview, generatePlanVerificationExec } from './review';
|
import { generateReviewDashboard, generatePlanFileReviewReport, generateSpecReviewLoop, generateBenefitsFrom, generateCodexSecondOpinion, generateAdversarialStep, generateCodexPlanReview, generatePlanCompletionAuditShip, generatePlanCompletionAuditReview, generatePlanVerificationExec } from './review';
|
||||||
import { generateSlugEval, generateSlugSetup, generateBaseBranchDetect, generateDeployBootstrap, generateQAMethodology, generateCoAuthorTrailer } from './utility';
|
import { generateSlugEval, generateSlugSetup, generateBaseBranchDetect, generateDeployBootstrap, generateQAMethodology, generateCoAuthorTrailer } from './utility';
|
||||||
|
import { generateInvokeSkill } from './composition';
|
||||||
|
|
||||||
export const RESOLVERS: Record<string, (ctx: TemplateContext) => string> = {
|
export const RESOLVERS: Record<string, ResolverFn> = {
|
||||||
SLUG_EVAL: generateSlugEval,
|
SLUG_EVAL: generateSlugEval,
|
||||||
SLUG_SETUP: generateSlugSetup,
|
SLUG_SETUP: generateSlugSetup,
|
||||||
COMMAND_REFERENCE: generateCommandReference,
|
COMMAND_REFERENCE: generateCommandReference,
|
||||||
@@ -48,4 +49,5 @@ export const RESOLVERS: Record<string, (ctx: TemplateContext) => string> = {
|
|||||||
PLAN_COMPLETION_AUDIT_REVIEW: generatePlanCompletionAuditReview,
|
PLAN_COMPLETION_AUDIT_REVIEW: generatePlanCompletionAuditReview,
|
||||||
PLAN_VERIFICATION_EXEC: generatePlanVerificationExec,
|
PLAN_VERIFICATION_EXEC: generatePlanVerificationExec,
|
||||||
CO_AUTHOR_TRAILER: generateCoAuthorTrailer,
|
CO_AUTHOR_TRAILER: generateCoAuthorTrailer,
|
||||||
|
INVOKE_SKILL: generateInvokeSkill,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -33,3 +33,6 @@ export interface TemplateContext {
|
|||||||
paths: HostPaths;
|
paths: HostPaths;
|
||||||
preambleTier?: number; // 1-4, controls which preamble sections are included
|
preambleTier?: number; // 1-4, controls which preamble sections are included
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Resolver function signature. args is populated for parameterized placeholders like {{INVOKE_SKILL:name}}. */
|
||||||
|
export type ResolverFn = (ctx: TemplateContext, args?: string[]) => string;
|
||||||
|
|||||||
Reference in New Issue
Block a user