feat(hosts): /land-and-deploy and /modernize run only when typed on Claude Code and Cursor (C1) (#3808)

* feat(hosts): manual-only /land-and-deploy and /modernize on Claude Code and Cursor (C1, GST-804)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(routing): host-neutral router wording, update repo CLAUDE.md and the onboarding fixture for the manual-only skills

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(gen): escape conditionalFields keys, anchor values, skip keys the template already sets

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(modernize): trim always-on description to stay under the 8000-token context budget

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
Garry Tanandcapy-ai[bot] authored and GitHub committed 2026-11-04 03:33:48 +00:00
1 parent 92f22ea87e
commit 242e918bcb
17 files changed
+104 -38

No files matched your search

+12 -16
View File
@@ -486,13 +486,21 @@ policy:
/**
* Transform frontmatter for external hosts.
* Claude: strips `sensitive:` field (only Factory uses it).
* Claude: strips template-only fields, adds disable-model-invocation to manual_only skills.
* Codex: keeps name + description only, enforces 1024-char limit.
* Factory: keeps name + description + user-invocable, conditionally adds disable-model-invocation.
*/
function transformFrontmatter(content: string, host: Host): string {
const hostConfig = getHostConfig(host);
const fm = hostConfig.frontmatter;
const templateFrontmatter = content.startsWith('---\n') ? content.slice(4, content.indexOf('\n---', 4)) : '';
const escape = (s: unknown) => String(s).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const conditionalLines = (fm.conditionalFields || [])
.filter(rule => Object.entries(rule.if).every(([k, v]) => new RegExp(`^${escape(k)}:[ \\t]*${escape(v)}[ \\t]*$`, 'm').test(templateFrontmatter)))
.flatMap(rule => Object.entries(rule.add))
.filter(([key]) => !new RegExp(`^${escape(key)}:`, 'm').test(templateFrontmatter))
.map(([key, value]) => `${key}: ${value}\n`)
.join('');
if (fm.mode === 'denylist') {
// Denylist mode: strip listed fields, keep everything else
@@ -503,7 +511,7 @@ function transformFrontmatter(content: string, host: Host): string {
content = content.replace(new RegExp(`^${field}:\\s*.*\\n`, 'm'), '');
}
}
return content;
return conditionalLines ? content.replace('\n---\n', () => `\n${conditionalLines}---\n`) : content;
}
// Allowlist mode: reconstruct frontmatter with only allowed fields
@@ -546,19 +554,7 @@ function transformFrontmatter(content: string, host: Host): string {
}
}
// Add conditional fields
if (fm.conditionalFields) {
for (const rule of fm.conditionalFields) {
const match = Object.entries(rule.if).every(([k, v]) =>
new RegExp(`^${k}:\\s*${v}`, 'm').test(frontmatter)
);
if (match) {
for (const [key, value] of Object.entries(rule.add)) {
newFm += `${key}: ${value}\n`;
}
}
}
}
newFm += conditionalLines;
// Preserve additional keepFields beyond name and description
if (fm.keepFields) {
@@ -853,7 +849,7 @@ function processTemplate(tmplPath: string, host: Host, options: RenderOptions):
// metadata gets the updated description with voice triggers included.
const postProcessDescription = extractNameAndDescription(content).description;
// For Claude: strip sensitive: field (only Factory uses it)
// For Claude: strip template-only fields, add conditional fields
// For external hosts: route output, transform frontmatter, rewrite paths
let symlinkLoop = false;
let metadata: { outputPath: string; content: string } | undefined;