diff --git a/packages/cli/src/services/BundledSkillLoader.test.ts b/packages/cli/src/services/BundledSkillLoader.test.ts index e7cc268e7..d4277d60f 100644 --- a/packages/cli/src/services/BundledSkillLoader.test.ts +++ b/packages/cli/src/services/BundledSkillLoader.test.ts @@ -127,6 +127,65 @@ describe('BundledSkillLoader', () => { expect(commands.map((c) => c.name)).toEqual(['review', 'deploy']); }); + it('should resolve {{model}} template variable in skill body', async () => { + const skill = makeSkill({ + body: 'Review by {{model}} via Qwen Code', + }); + mockSkillManager.listSkills.mockResolvedValue([skill]); + (mockConfig as Record).getModel = vi + .fn() + .mockReturnValue('qwen3-coder'); + + const loader = new BundledSkillLoader(mockConfig); + const commands = await loader.loadCommands(signal); + const result = await commands[0].action!( + { invocation: { raw: '/review', args: '' } } as never, + '', + ); + + expect(result).toEqual({ + type: 'submit_prompt', + content: [{ text: 'Review by qwen3-coder via Qwen Code' }], + }); + }); + + it('should use "unknown" when model is not available for {{model}}', async () => { + const skill = makeSkill({ + body: 'Review by {{model}}', + }); + mockSkillManager.listSkills.mockResolvedValue([skill]); + // No getModel on config + + const loader = new BundledSkillLoader(mockConfig); + const commands = await loader.loadCommands(signal); + const result = await commands[0].action!( + { invocation: { raw: '/review', args: '' } } as never, + '', + ); + + expect(result).toEqual({ + type: 'submit_prompt', + content: [{ text: 'Review by unknown' }], + }); + }); + + it('should not modify skill body without {{model}} template', async () => { + const skill = makeSkill({ body: 'No template here' }); + mockSkillManager.listSkills.mockResolvedValue([skill]); + + const loader = new BundledSkillLoader(mockConfig); + const commands = await loader.loadCommands(signal); + const result = await commands[0].action!( + { invocation: { raw: '/review', args: '' } } as never, + '', + ); + + expect(result).toEqual({ + type: 'submit_prompt', + content: [{ text: 'No template here' }], + }); + }); + it('should hide skills with cron allowedTools when cron is disabled', async () => { const skills = [ makeSkill({ name: 'review', description: 'Review code' }), diff --git a/packages/cli/src/services/BundledSkillLoader.ts b/packages/cli/src/services/BundledSkillLoader.ts index b0fa68467..6ad11d0ab 100644 --- a/packages/cli/src/services/BundledSkillLoader.ts +++ b/packages/cli/src/services/BundledSkillLoader.ts @@ -59,12 +59,19 @@ export class BundledSkillLoader implements ICommandLoader { description: skill.description, kind: CommandKind.SKILL, action: async (context, _args): Promise => { + // Resolve template variables in skill body (e.g., {{model}}) + let body = skill.body; + if (body.includes('{{model}}')) { + const modelId = + (typeof this.config?.getModel === 'function' + ? this.config.getModel() + : undefined) ?? 'unknown'; + body = body.replaceAll('{{model}}', modelId); + } + const content = context.invocation?.args - ? appendToLastTextPart( - [{ text: skill.body }], - context.invocation.raw, - ) - : [{ text: skill.body }]; + ? appendToLastTextPart([{ text: body }], context.invocation.raw) + : [{ text: body }]; return { type: 'submit_prompt', diff --git a/packages/core/src/skills/bundled/review/SKILL.md b/packages/core/src/skills/bundled/review/SKILL.md index 9b91a153b..dbf0a613a 100644 --- a/packages/core/src/skills/bundled/review/SKILL.md +++ b/packages/core/src/skills/bundled/review/SKILL.md @@ -379,7 +379,7 @@ If posting an inline comment fails (e.g., line not part of the diff, auth error) - Include the severity tag (Critical/Suggestion) at the start of each comment - Include the suggested fix in the comment body when available -After posting all inline comments, use `write_file` to create `/tmp/qwen-review-{target}-summary.txt` with the summary text, then submit the review using the action that matches the **pre-fix verdict** from Step 3 (if autofix was applied, use the original verdict since the remote PR hasn't been updated yet): +After posting all inline comments, use `write_file` to create `/tmp/qwen-review-{target}-summary.txt` with the summary text. Append a model attribution footer at the end of the summary: `_Reviewed by {{model}} via Qwen Code /review_`. Then submit the review using the action that matches the **pre-fix verdict** from Step 3 (if autofix was applied, use the original verdict since the remote PR hasn't been updated yet): ```bash # Submit review with the matching action: