external/ag-shared/prompts/guides/setup-prompts.md
This guide covers the setup-prompts.sh script and rulesync patching.
The setup-prompts.sh script (external/ag-shared/scripts/setup-prompts/setup-prompts.sh) detects installed AI coding tools and generates configuration using rulesync.
Patches are stored in external/ag-shared/prompts/patches/ and symlinked from patches/ for patch-package to apply them.
Rulesync's findFilesByGlobs() function uses item.isDirectory() and item.isFile() to filter results, but these return false for symlinks - even when the symlink target is a directory/file.
Symptom: Skills, commands, or subagents that are symlinks in .rulesync/ don't appear in the generated output (e.g., .claude/skills/).
Diagnosis:
# Check if symlinks are being detected as directories
node -e "
const { globSync } = require('glob');
const items = globSync('.rulesync/skills/*', { withFileTypes: true });
items.forEach(item => {
console.log(item.name, 'isDir:', item.isDirectory(), 'isSymlink:', item.isSymbolicLink());
});
"
Fix: The rulesync patch must check item.isSymbolicLink() and use statSync() to determine the target type:
case "dir":
return validItems.filter((item) => {
if (item.isDirectory()) return true;
if (item.isSymbolicLink()) {
try {
return statSync(join(item.parentPath, item.name)).isDirectory();
} catch {
return false;
}
}
return false;
}).map((item) => join(item.parentPath, item.name));
fromRulesyncSkill() for New Frontmatter FieldsRulesync's fromRulesyncSkill() methods explicitly construct new frontmatter objects, picking only known fields (name, description, tool-specific extras). The looseObject schema preserves unknown fields through parsing, but the conversion code discards them. To propagate a rulesync-level field to tool-specific output, you must patch each tool's fromRulesyncSkill() — there is no passthrough.
Tool-specific skill classes with fromRulesyncSkill():
name, description, allowed-toolsname, descriptionname, description, licensename, description via fromRulesyncSkillDefault()disable-model-invocation)disable-model-invocation: true is a de facto standard shared by Claude Code, Cursor, and GitHub Copilot (VS Code agent). It is NOT part of the Agent Skills open standard (agentskills.io).
| Tool | Supports per-skill invocation control? | Mechanism |
|---|---|---|
| Claude Code | Yes | disable-model-invocation: true in SKILL.md |
| Cursor | Yes | disable-model-invocation: true in SKILL.md |
| GitHub Copilot | Yes (VS Code agent) | disable-model-invocation: true in SKILL.md |
| Codex CLI | Yes (different) | allow_implicit_invocation: false in agents yaml |
| Gemini CLI / Cline / OpenCode | No | No per-skill control |
In rulesync source files, use invocable: user-only — the patched fromRulesyncSkill() methods translate this to disable-model-invocation: true for Claude Code, Cursor, and Copilot.
The --postinstall flag triggers stash_agents_md() and restore_agents_md() functions which:
This prevents rulesync from creating noise in AGENTS.md while preserving intentional user edits.
After modifying node_modules/rulesync/dist/index.js:
npx patch-package rulesync
This updates patches/rulesync+*.patch (which symlinks to external/ag-shared/prompts/patches/).
verify-rulesync.sh builds an expected file inventory and content-verifies each file. Skill directories can contain arbitrary .md resource files (templates, page guides) beyond SKILL.md, _*.md helpers, and *.sh scripts. Both build_expected_inventory() and verify_content() must glob *.md (excluding SKILL.md) rather than _*.md to capture all of them.
When using the inverted reference pattern (shared core + thin wrapper), follow these rules to avoid silent breakage.
Heading names in the wrapper are referenced by exact name in the core. Adding suffixes like (REQUIRED) or other annotations to heading text in templates or guides will break the exact-name contract silently at runtime. Treat section headings as API identifiers — rename them only with the same care as renaming a function.
When migrating product-specific prompts to shared cores, bash scripts and procedural subsections are often generic despite appearing product-specific (they only contain path references that can be parameterised). Always diff the original vs genericised output section-by-section to catch accidentally dropped content.