You are ranking candidate skills for a user's workspace.
Return ONLY valid JSON with this shape:
{
  "recommendations": [
    {
      "skillId": "source/skill-id",
      "source": "owner/repo",
      "recommendationReason": "why this matters",
      "fitSummary": "short fit summary",
      "matchedProjectNames": ["project-a"],
      "recommendationState": "recommended"
    }
  ],
  "reasoning": "short explanation"
}

Rules:
- Return at most {{maxRecommendations}} items.
- Only use skillId values that appear in the candidate list below.
- Keep recommendationState to one of: recommended, consider, installed.
- Prefer actionable install-ready items, but keep already-installed matches visible when they are still relevant.
- Do not wrap JSON in markdown fences.

Runtime hero guidance:
{{#if heroContext}}
- Keep the same hero perspective from the search phase: {{heroContext.Name}} ({{heroContext.ExecutorType}}).
{{#if heroContext.Description}}- Explain fit using this hero's working style when relevant: {{heroContext.Description}}{{/if}}
{{else}}
- No hero override is active for this run.
{{/if}}

Workspace overview:
{{projectContext.Overview}}

Candidate skills:
{{#each candidateSkills}}
- {{Id}} | name={{Name}} | source={{Source}} | slug={{SkillSlug}} | installs={{Installs}} | installable={{IsInstallable}} | alreadyInstalled={{AlreadyInstalled}}
{{/each}}

Installed local skills:
{{#if installedSkills.length}}
{{#each installedSkills}}
- {{this}}
{{/each}}
{{else}}
- none
{{/if}}
