rulesync 16.30.1 → 16.30.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const require_import = require("../import-WJZi59fO.cjs");
2
+ const require_import = require("../import-auwKp4Mm.cjs");
3
3
  let commander = require("commander");
4
4
  let zod_mini = require("zod/mini");
5
5
  let node_fs_promises = require("node:fs/promises");
@@ -4307,11 +4307,11 @@ async function convertCommand(logger, options) {
4307
4307
  */
4308
4308
  const DOCS_CONTENT = {
4309
4309
  "api/programmatic-api": "# Programmatic API\n\nRulesync can be used as a library in your Node.js/TypeScript projects. The `generate`, `importFromTool`, and `convertFromTool` functions are available as named exports.\n\n```typescript\nimport { convertFromTool, generate, importFromTool } from \"rulesync\";\n\n// Generate configurations\nconst result = await generate({\n targets: [\"claudecode\", \"cursor\"],\n features: [\"rules\", \"mcp\"],\n});\nconsole.log(`Generated ${result.rulesCount} rules, ${result.mcpCount} MCP configs`);\n\n// Import existing tool configurations into .rulesync/\nconst importResult = await importFromTool({\n target: \"claudecode\",\n features: [\"rules\", \"commands\"],\n});\nconsole.log(`Imported ${importResult.rulesCount} rules`);\n\n// Convert configurations between AI tools without writing intermediate .rulesync/ files\ntry {\n const convertResult = await convertFromTool({\n from: \"claudecode\",\n to: [\"cursor\", \"copilot\"],\n features: [\"rules\"],\n });\n console.log(`Converted ${convertResult.rulesCount} rule file(s)`);\n} catch (error) {\n // Thrown when `from` is empty, `to` is empty, `to` includes `from`,\n // a source file cannot be parsed, or write fails.\n console.error(\"convert failed:\", error);\n}\n```\n\n## `generate(options?)`\n\nGenerates configuration files for the specified targets and features.\n\n| Option | Type | Default | Description |\n| ------------------- | -------------- | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `targets` | `ToolTarget[]` | from config file | Tools to generate configurations for |\n| `features` | `Feature[]` | from config file | Features to generate |\n| `outputRoots` | `string[]` | `[process.cwd()]` | Output root directories to generate files into |\n| `inputRoots` | `string[]` | `[<cwd>/.rulesync]` | Ordered list of rulesync source-tree directories (e.g. `.rulesync`, `.rulesync.local`). Each entry is a source tree itself — no `.rulesync/` join is applied. The first root is required; later roots are optional overlays and may be absent. Output still goes to each `outputRoots` entry; only the input source root is redirected. Later entries override earlier ones for the same relative source path. Cannot be combined with `inputRoot`. Mirrors the CLI's `--input-roots`. |\n| `inputRoot` | `string` | `process.cwd()` | **Deprecated.** PARENT directory of a `.rulesync/` source tree; kept as a backward-compatibility alias that expands internally to `inputRoots: [join(inputRoot, \".rulesync\")]`. Prefer `inputRoots` and point it directly at your source tree(s). Cannot be combined with `inputRoots`. Mirrors the CLI's `--input-root`. |\n| `configPath` | `string` | auto-detected | Path to `rulesync.jsonc` |\n| `verbose` | `boolean` | `false` | Enable verbose logging |\n| `silent` | `boolean` | `true` | Suppress all output |\n| `delete` | `boolean` | from config file | Delete existing files before generating |\n| `global` | `boolean` | `false` | Generate global (user scope) configurations |\n| `simulateCommands` | `boolean` | `false` | Generate simulated commands |\n| `simulateSubagents` | `boolean` | `false` | Generate simulated subagents |\n| `simulateSkills` | `boolean` | `false` | Generate simulated skills |\n| `dryRun` | `boolean` | `false` | Show changes without writing files |\n| `check` | `boolean` | `false` | Exit with code 1 if files are not up to date |\n\n> **Unreadable sources do not throw here.** Unlike the CLI, which exits\n> non-zero, `generate()` resolves and reports the problem on the result:\n> `sourceLoadFailed` is `true` and `sourceLoadFailedFeatures` names the features\n> whose `.rulesync/` source could not be read. Their counts are `0`, exactly like\n> a feature that had nothing to write, so check the flag rather than the counts\n> before treating a run as successful. (Those features also keep their existing\n> generated files: `delete` skips their orphan sweep.)\n\n## `importFromTool(options)`\n\nImports existing tool configurations into `.rulesync/` directory.\n\n| Option | Type | Default | Description |\n| ------------ | ------------ | ---------------- | ----------------------------------------- |\n| `target` | `ToolTarget` | (required) | Tool to import configurations from |\n| `features` | `Feature[]` | from config file | Features to import |\n| `configPath` | `string` | auto-detected | Path to `rulesync.jsonc` |\n| `verbose` | `boolean` | `false` | Enable verbose logging |\n| `silent` | `boolean` | `true` | Suppress all output |\n| `global` | `boolean` | `false` | Import global (user scope) configurations |\n\n## `convertFromTool(options)`\n\nConverts configuration files between AI tools without writing intermediate `.rulesync/` files to disk.\n\n| Option | Type | Default | Description |\n| ------------ | -------------- | ------------- | ------------------------------------------------------------------------------------------- |\n| `from` | `ToolTarget` | (required) | Source tool to convert configurations from |\n| `to` | `ToolTarget[]` | (required) | Destination tools to convert to |\n| `features` | `Feature[]` | `[\"*\"]` | Features to convert. Matches CLI behavior and overrides any `features` in `rulesync.jsonc`. |\n| `configPath` | `string` | auto-detected | Path to `rulesync.jsonc` |\n| `verbose` | `boolean` | `false` | Enable verbose logging |\n| `silent` | `boolean` | `true` | Suppress all output |\n| `global` | `boolean` | `false` | Convert global (user scope) configurations |\n| `dryRun` | `boolean` | `false` | Show changes without writing files |\n",
4310
- faq: "# FAQ\n\n## `rulesync generate` doesn't produce what I expect\n\nRun `rulesync doctor` first. It performs read-only diagnostics on `rulesync.jsonc` and `rulesync.local.jsonc` and reports problems the generator silently tolerates — most importantly misspelled or unknown configuration keys (the config schema is non-strict, so a typo like `\"target\"` instead of `\"targets\"` is otherwise ignored and generation quietly falls back to defaults). See the [Doctor Command](./reference/cli-commands.md#doctor-command) reference for the full list of checks.\n\n## The generated `.mcp.json` doesn't work properly in Claude Code\n\nYou can try adding the following to `.claude/settings.json` or `.claude/settings.local.json`:\n\n```diff\n{\n+ \"enableAllProjectMcpServers\": true\n}\n```\n\nAccording to [the documentation](https://code.claude.com/docs/en/settings), this means:\n\n> Automatically approve all MCP servers defined in project .mcp.json files\n\n## Google Antigravity doesn't load rules when `.agents` directories are in `.gitignore`\n\nGoogle Antigravity has a known limitation where it won't load rules, workflows, and skills if the `.agents/rules/`, `.agents/workflows/`, and `.agents/skills/` directories are listed in `.gitignore`, even with \"Agent Gitignore Access\" enabled.\n\n> **Note:** Antigravity 2.0 uses the plural `.agents/` directory by default (the `antigravity-ide` and `antigravity-cli` targets).\n\n**Workaround:** Instead of adding these directories to `.gitignore`, add them to `.git/info/exclude`:\n\n```bash\n# Remove from .gitignore (if present)\n# **/.agents/rules/\n# **/.agents/workflows/\n# **/.agents/skills/\n\n# Add to .git/info/exclude\necho \"**/.agents/rules/\" >> .git/info/exclude\necho \"**/.agents/workflows/\" >> .git/info/exclude\necho \"**/.agents/skills/\" >> .git/info/exclude\n```\n\n`.git/info/exclude` works like `.gitignore` but is local-only, so it won't affect Antigravity's ability to load the rules while still excluding these directories from Git.\n\nNote: `.git/info/exclude` can't be shared with your team since it's not committed to the repository.\n\n## Codex CLI denies SSH agent access, temp-dir writes, or reading its own config with a generated permissions profile\n\nThe `[permissions.rulesync]` profile that rulesync generates into `.codex/config.toml` extends Codex CLI's `:workspace` baseline. That baseline is deliberately conservative, so day-to-day development can still hit permission denials: `git push`/`git fetch` over SSH cannot reach the SSH agent socket, some build tools fail without a writable temp dir, and Codex may be blocked from reading its own `~/.codex` configuration.\n\nrulesync emits the `.git` write carve-out for you (`\".git/**\" = \"write\"` under `:workspace_roots`; opt out with the `codexcli.git_write_rules: false` override). The whole subtree — including `.git/config` — is writable, because everyday commands such as `git remote add`, `git push -u`, and local-scope `git config` write to the repository config; users who want stricter isolation can add their own `read` override (e.g. `read: { \".git/config\": \"allow\" }`) in the canonical permissions. Everything below, however, depends on your environment or workflow, so rulesync does not add it by default. Where to put each piece differs, because the two tables are managed differently:\n\n**Network settings: edit `.codex/config.toml` directly.** Network settings are out of rulesync's management scope by design, keeping you free to edit them. rulesync preserves user-authored network keys when it regenerates the file — `network.enabled` (as long as the profile carries no rulesync-managed allow domains) and unknown keys such as `dangerously_allow_all_unix_sockets` are carried forward verbatim, with a warning so they stay visible:\n\n```toml\n[permissions.rulesync.network]\nenabled = true\n# Simplest option: allow all unix sockets. Codex names this \"dangerously_*\"\n# because it is broad, but it avoids hardcoding an env-dependent socket path.\ndangerously_allow_all_unix_sockets = true\n\n# Stricter alternative: allow only the SSH agent socket.\n# Replace the path with the actual value of $SSH_AUTH_SOCK on your machine;\n# Codex does not expand environment variables in these keys.\n# [permissions.rulesync.network.unix_sockets]\n# \"/path/to/ssh-agent.sock\" = \"allow\"\n```\n\n**Filesystem entries: author them in `.rulesync/permissions.jsonc`, not in `config.toml`.** The profile's `filesystem` table is fully managed — hand-written entries there are replaced on the next `rulesync generate`. Add the rules to the canonical config instead (use the tool-scoped `codexcli.permission` block so they do not leak into other tools' outputs) and regenerate:\n\n```jsonc\n{\n \"permission\": {\n // ...your shared rules...\n },\n \"codexcli\": {\n \"permission\": {\n \"write\": {\n \".\": \"allow\",\n \".git/**\": \"allow\",\n \".agents/**\": \"allow\",\n \".codex/**\": \"allow\",\n \":root\": \"allow\",\n \":minimal\": \"allow\",\n \":tmpdir\": \"allow\",\n \":slash_tmp\": \"allow\",\n },\n \"read\": { \"~/.codex/**\": \"allow\", \"~/.codex/auth.json\": \"deny\" },\n },\n },\n}\n```\n\nNote that this example is intentionally permissive: the `\":root\"` + `\":minimal\"` write pair grants the sandbox full disk write access — see the trade-off in the entry list below for narrower alternatives.\n\nThis generates into the profile as `\".\" = \"write\"`, `\".git/**\" = \"write\"`, `\".agents/**\" = \"write\"`, and `\".codex/**\" = \"write\"` under `:workspace_roots`, plus `\":root\" = \"write\"`, `\":minimal\" = \"write\"`, `\":tmpdir\" = \"write\"`, `\":slash_tmp\" = \"write\"`, `\"~/.codex/**\" = \"read\"`, and `\"~/.codex/auth.json\" = \"deny\"`, and round-trips through `rulesync import` — with two exceptions. First, `\".git/**\" = \"write\"` matches rulesync's default carve-out exactly, so import skips it (it is re-added on every generate); if you later opt out with `codexcli.git_write_rules: false` after an import, re-author the `\".git/**\": \"allow\"` write rule in the canonical config. Second, `\":minimal\"` is never imported regardless of its value — rulesync treats it as its fixed `\"read\"` baseline — so after an import, re-author the `\":minimal\": \"allow\"` write rule as well or the next generate silently drops back to `\":minimal\" = \"read\"`. Note that a tool-scoped category replaces the shared one wholesale for Codex CLI: if your shared `permission` block already has `read`/`write` rules that should also apply to Codex CLI, repeat them inside `codexcli.permission`.\n\nWhat each entry does:\n\n- **Unix socket access**: `git push`/`git fetch` over SSH needs the agent socket. `dangerously_allow_all_unix_sockets = true` is the simple, environment-independent option; a per-socket `unix_sockets` allow entry with the resolved `$SSH_AUTH_SOCK` path is the stricter one.\n- **`.` / `.git/**` / `.agents/**` / `.codex/**` write**: the practical write set for the workspace itself. `\".\"` spells out the workspace-subtree write access the `:workspace` baseline already grants (a tool-scoped category replaces the shared block wholesale, so keeping it explicit avoids surprises), and `\".git/**\"` matches the carve-out rulesync emits by default anyway. `.agents/**` and `.codex/**` genuinely add access: Codex's `:workspace` baseline keeps `.git`, `.agents`, and `.codex` read-only inside workspace roots, so without these rules a Codex session cannot update agent files or its own project-level config — for example, running `rulesync generate` inside a session would be denied when writing `.agents/` or `.codex/` outputs. Trade-off: the baseline keeps those two directories read-only precisely so a sandboxed session cannot rewrite its own configuration — with `.codex/**` writable, a compromised or prompt-injected session could relax `.codex/config.toml` (approval policy, permission profiles, MCP servers) for its next run, and with `.agents/**` writable it could persist injected instructions into rule/skill files. Drop these two entries if your workflow does not need in-session writes there.\n- **`:root` / `:minimal` write**: package runners such as `npx {package}` unpack into the npm cache under the home directory (`~/.npm/_npx`), and many dev tools write to home-directory caches (`~/.cache`, `~/.local`, corepack/pnpm stores); the `:workspace` baseline denies these writes, which breaks the commands outright. `\":root\" = \"write\"` alone is not enough: rulesync emits `\":minimal\" = \"read\"` by default (the platform-default system paths needed for sandboxed command execution), and Codex treats that entry as narrowing the broader `:root` grant — the policy no longer qualifies for full disk write access, so writes that `:root` appears to allow can still be denied. Raise `\":minimal\"` to write alongside `\":root\"` to get the intended effect. Trade-off: the pair grants the sandbox full disk write access, including platform system paths — a compromised or prompt-injected session could then modify shell startup files, `PATH` binaries, or system configuration outside the workspace, effectively neutralizing the sandbox's write isolation. Prefer narrower home-directory patterns instead (e.g. `\"~/.npm/**\"`, `\"~/.cache/**\"`) unless you specifically need system-wide writes, at the cost of chasing each tool's cache path.\n- **`:tmpdir` / `:slash_tmp` write**: many build tools require a writable temp directory (`$TMPDIR` and `/tmp` respectively).\n- **`~/.codex/**` read with `auth.json` deny**: Codex can read its own configuration tree while your credentials stay protected. Tilde paths are expanded by Codex itself, so no manual `$HOME` resolution is needed.\n- **`glob_scan_max_depth`**: no need to add it — rulesync emits the Codex default (`8`) automatically whenever the generated workspace-root rules contain unbounded `**` patterns (the default `.git/**` carve-out already is one).\n\nSee the [Codex permissions reference](https://developers.openai.com/codex/permissions) for the full path and network syntax.\n\n## Generated rule files create noise in pull request diffs\n\nBecause many AI coding tools (Claude Code, Cursor, Copilot, Antigravity, etc.) need to read their rule files directly from the working tree, the files rulesync generates are intentionally not `.gitignore`d. On repositories with many targets, the generated files can dominate a pull request diff and make code review harder.\n\n**Workaround:** Add the generated paths to `.gitattributes` with the [`linguist-generated`](https://docs.github.com/en/repositories/working-with-files/managing-files/customizing-how-changed-files-appear-on-github#marking-files-as-generated) attribute. GitHub's PR UI will then collapse those files by default while still keeping them visible and loadable by the tools themselves.\n\nExample `.gitattributes` for a repo that uses `.agent/`, Claude Code, Cursor, and Copilot targets:\n\n```\n.agent/rules/** linguist-generated\n.agent/skills/** linguist-generated\n.agent/workflows/** linguist-generated\nCLAUDE.md linguist-generated\n.cursor/rules/** linguist-generated\n.github/copilot-instructions.md linguist-generated\n```\n\nAdjust the list to match the targets you have configured. These entries only affect how GitHub displays the files in diffs — they don't change how Git tracks them, and they don't interfere with the tools reading the rules.\n",
4310
+ faq: "# FAQ\n\n## `rulesync generate` doesn't produce what I expect\n\nRun `rulesync doctor` first. It performs read-only diagnostics on `rulesync.jsonc` and `rulesync.local.jsonc` and reports problems the generator silently tolerates — most importantly misspelled or unknown configuration keys (the config schema is non-strict, so a typo like `\"target\"` instead of `\"targets\"` is otherwise ignored and generation quietly falls back to defaults). See the [Doctor Command](./reference/cli-commands.md#doctor-command) reference for the full list of checks.\n\n## The generated `.mcp.json` doesn't work properly in Claude Code\n\nYou can try adding the following to `.claude/settings.json` or `.claude/settings.local.json`:\n\n```diff\n{\n+ \"enableAllProjectMcpServers\": true\n}\n```\n\nAccording to [the documentation](https://code.claude.com/docs/en/settings), this means:\n\n> Automatically approve all MCP servers defined in project .mcp.json files\n\n## Google Antigravity doesn't load rules when `.agents` directories are in `.gitignore`\n\nGoogle Antigravity has a known limitation where it won't load rules, workflows, and skills if the `.agents/rules/`, `.agents/workflows/`, and `.agents/skills/` directories are listed in `.gitignore`, even with \"Agent Gitignore Access\" enabled.\n\n> **Note:** Antigravity 2.0 uses the plural `.agents/` directory by default (the `antigravity-ide` and `antigravity-cli` targets).\n\n**Workaround:** Instead of adding these directories to `.gitignore`, add them to `.git/info/exclude`:\n\n```bash\n# Remove from .gitignore (if present)\n# **/.agents/rules/\n# **/.agents/workflows/\n# **/.agents/skills/\n\n# Add to .git/info/exclude\necho \"**/.agents/rules/\" >> .git/info/exclude\necho \"**/.agents/workflows/\" >> .git/info/exclude\necho \"**/.agents/skills/\" >> .git/info/exclude\n```\n\n`.git/info/exclude` works like `.gitignore` but is local-only, so it won't affect Antigravity's ability to load the rules while still excluding these directories from Git.\n\nNote: `.git/info/exclude` can't be shared with your team since it's not committed to the repository.\n\n## Codex CLI denies SSH agent access, temp-dir writes, or reading its own config with a generated permissions profile\n\nThe `[permissions.rulesync]` profile that rulesync generates into `.codex/config.toml` extends Codex CLI's `:workspace` baseline. That baseline is deliberately conservative, so day-to-day development can still hit permission denials: `git push`/`git fetch` over SSH cannot reach the SSH agent socket, some build tools fail without a writable temp dir, and Codex may be blocked from reading its own `~/.codex` configuration.\n\nrulesync emits the `.git` write carve-out for you (`\".git/**\" = \"write\"` under `:workspace_roots`; opt out with the `codexcli.git_write_rules: false` override). The whole subtree — including `.git/config` — is writable, because everyday commands such as `git remote add`, `git push -u`, and local-scope `git config` write to the repository config; users who want stricter isolation can add their own `read` override (e.g. `read: { \".git/config\": \"allow\" }`) in the canonical permissions. Everything below, however, depends on your environment or workflow, so rulesync does not add it by default. Where to put each piece differs, because the two tables are managed differently:\n\n**Network settings: edit `.codex/config.toml` directly.** Network settings are out of rulesync's management scope by design, keeping you free to edit them. rulesync preserves user-authored network keys when it regenerates the file — `network.enabled` (as long as the profile carries no rulesync-managed allow domains) and unknown keys such as `dangerously_allow_all_unix_sockets` are carried forward verbatim, with a warning so they stay visible:\n\n```toml\n[permissions.rulesync.network]\nenabled = true\n# Simplest option: allow all unix sockets. Codex names this \"dangerously_*\"\n# because it is broad, but it avoids hardcoding an env-dependent socket path.\ndangerously_allow_all_unix_sockets = true\n\n# Stricter alternative: allow only the SSH agent socket.\n# Replace the path with the actual value of $SSH_AUTH_SOCK on your machine;\n# Codex does not expand environment variables in these keys.\n# [permissions.rulesync.network.unix_sockets]\n# \"/path/to/ssh-agent.sock\" = \"allow\"\n```\n\n**Filesystem entries: author them in `.rulesync/permissions.jsonc`, not in `config.toml`.** The profile's `filesystem` table is fully managed — hand-written entries there are replaced on the next `rulesync generate`. Add the rules to the canonical config instead (use the tool-scoped `codexcli.permission` block so they do not leak into other tools' outputs) and regenerate:\n\n```jsonc\n{\n \"permission\": {\n // ...your shared rules...\n },\n \"codexcli\": {\n \"permission\": {\n \"write\": {\n \".\": \"allow\",\n \".git/**\": \"allow\",\n \".agents/**\": \"allow\",\n \".codex/**\": \"allow\",\n \":root\": \"allow\",\n \":minimal\": \"allow\",\n \":tmpdir\": \"allow\",\n \":slash_tmp\": \"allow\",\n },\n \"read\": { \"~/.codex/**\": \"allow\", \"~/.codex/auth.json\": \"deny\" },\n },\n },\n}\n```\n\nNote that this example is intentionally permissive: the `\":root\"` + `\":minimal\"` write pair grants the sandbox full disk write access — see the trade-off in the entry list below for narrower alternatives.\n\nThis generates into the profile as `\".\" = \"write\"`, `\".git/**\" = \"write\"`, `\".agents/**\" = \"write\"`, and `\".codex/**\" = \"write\"` under `:workspace_roots`, plus `\":root\" = \"write\"`, `\":minimal\" = \"write\"`, `\":tmpdir\" = \"write\"`, `\":slash_tmp\" = \"write\"`, `\"~/.codex/**\" = \"read\"`, and `\"~/.codex/auth.json\" = \"deny\"`, and round-trips through `rulesync import` — with two exceptions. First, `\".git/**\" = \"write\"` matches rulesync's default carve-out exactly, so import skips it (it is re-added on every generate); if you later opt out with `codexcli.git_write_rules: false` after an import, re-author the `\".git/**\": \"allow\"` write rule in the canonical config. Second, `\":minimal\"` is never imported regardless of its value — rulesync treats it as its fixed `\"read\"` baseline — so after an import, re-author the `\":minimal\": \"allow\"` write rule as well or the next generate silently drops back to `\":minimal\" = \"read\"`. Note that a tool-scoped category replaces the shared one wholesale for Codex CLI: if your shared `permission` block already has `read`/`write` rules that should also apply to Codex CLI, repeat them inside `codexcli.permission`.\n\nWhat each entry does:\n\n- **Unix socket access**: `git push`/`git fetch` over SSH needs the agent socket. `dangerously_allow_all_unix_sockets = true` is the simple, environment-independent option; a per-socket `unix_sockets` allow entry with the resolved `$SSH_AUTH_SOCK` path is the stricter one.\n- **`.` / `.git/**` / `.agents/**` / `.codex/**` write**: the practical write set for the workspace itself. `\".\"` spells out the workspace-subtree write access the `:workspace` baseline already grants (a tool-scoped category replaces the shared block wholesale, so keeping it explicit avoids surprises), and `\".git/**\"` matches the carve-out rulesync emits by default anyway. `.agents/**` and `.codex/**` genuinely add access: Codex's `:workspace` baseline keeps `.git`, `.agents`, and `.codex` read-only inside workspace roots, so without these rules a Codex session cannot update agent files or its own project-level config — for example, running `rulesync generate` inside a session would be denied when writing `.agents/` or `.codex/` outputs. Trade-off: the baseline keeps those two directories read-only precisely so a sandboxed session cannot rewrite its own configuration — with `.codex/**` writable, a compromised or prompt-injected session could relax `.codex/config.toml` (approval policy, permission profiles, MCP servers) for its next run, and with `.agents/**` writable it could persist injected instructions into rule/skill files. Drop these two entries if your workflow does not need in-session writes there.\n- **`:root` / `:minimal` write**: package runners such as `npx {package}` unpack into the npm cache under the home directory (`~/.npm/_npx`), and many dev tools write to home-directory caches (`~/.cache`, `~/.local`, corepack/pnpm stores); the `:workspace` baseline denies these writes, which breaks the commands outright. `\":root\" = \"write\"` alone is not enough: rulesync emits `\":minimal\" = \"read\"` by default (the platform-default system paths needed for sandboxed command execution), and Codex treats that entry as narrowing the broader `:root` grant — the policy no longer qualifies for full disk write access, so writes that `:root` appears to allow can still be denied. Raise `\":minimal\"` to write alongside `\":root\"` to get the intended effect. Trade-off: the pair grants the sandbox full disk write access, including platform system paths — a compromised or prompt-injected session could then modify shell startup files, `PATH` binaries, or system configuration outside the workspace, effectively neutralizing the sandbox's write isolation. Prefer narrower home-directory patterns instead (e.g. `\"~/.npm/**\"`, `\"~/.cache/**\"`) unless you specifically need system-wide writes, at the cost of chasing each tool's cache path.\n- **`:tmpdir` / `:slash_tmp` write**: many build tools require a writable temp directory (`$TMPDIR` and `/tmp` respectively).\n- **`~/.codex/**` read with `auth.json` deny**: Codex can read its own configuration tree while your credentials stay protected. Tilde paths are expanded by Codex itself, so no manual `$HOME` resolution is needed.\n- **`glob_scan_max_depth`**: no need to add it — rulesync emits the Codex default (`8`) automatically whenever the generated workspace-root rules contain unbounded `**` patterns (the default `.git/**` carve-out already is one).\n\nSee the [Codex permissions reference](https://developers.openai.com/codex/permissions) for the full path and network syntax.\n\n## Generated rule files create noise in pull request diffs\n\nBecause many AI coding tools (Claude Code, Cursor, Copilot, Antigravity, etc.) need to read their rule files directly from the working tree, the files rulesync generates are intentionally not `.gitignore`d. On repositories with many targets, the generated files can dominate a pull request diff and make code review harder.\n\n**Workaround:** Add the generated paths to `.gitattributes` with the [`linguist-generated`](https://docs.github.com/en/repositories/working-with-files/managing-files/customizing-how-changed-files-appear-on-github#marking-files-as-generated) attribute. GitHub's PR UI will then collapse those files by default while still keeping them visible and loadable by the tools themselves.\n\nExample `.gitattributes` for a repo that uses `.agent/`, Claude Code, Cursor, and Copilot targets:\n\n```\n.agent/rules/** linguist-generated\n.agent/skills/** linguist-generated\n.agent/workflows/** linguist-generated\nCLAUDE.md linguist-generated\n.cursor/rules/** linguist-generated\n.github/copilot-instructions.md linguist-generated\n```\n\nAdjust the list to match the targets you have configured. These entries only affect how GitHub displays the files in diffs — they don't change how Git tracks them, and they don't interfere with the tools reading the rules.\n\n## How do I keep many repositories in sync with a shared source?\n\nRulesync stops at the repository boundary. A consumer repository declares its `sources` in `rulesync.jsonc`, pins what it resolved in `rulesync.lock`, and moves forward only when someone runs `rulesync install --update` there — the same model as an npm or Bun lockfile. Nothing in the tool schedules that run, watches the shared repository, or walks other clones, so with fifteen consumers the layer above the lockfile is yours to shape, and either of the two obvious shapes works:\n\n- **On demand.** Run `rulesync install --update && rulesync generate` in a repository when you want it to pick up the shared changes, review the diff, and commit the lockfile with the regenerated files.\n- **Scheduled.** A cron-triggered CI job that runs the same two commands and opens a pull request when the lockfile changed is an ordinary way to drive rulesync; nothing in the tool assumes the update is manual. Compare the lockfile with `resolvedAt` ignored (the `-I` flag needs Git 2.30 or newer): `--update` stamps a fresh timestamp on every source it re-resolves, so the file changes even when no `resolvedRef` did.\n\n ```bash\n rulesync install --update && rulesync generate\n git diff --quiet -I '\"resolvedAt\"' -- rulesync.lock rulesync-npm.lock.json || echo \"shared source moved: open a pull request\"\n ```\n\nKeep `rulesync doctor --strict && rulesync install --frozen && rulesync generate --check` in the consumer's CI either way; that is what guards a repository whose lockfile has fallen behind its own `rulesync.jsonc` or whose generated files have drifted, independent of how updates are triggered.\n\nThere is no read-only command that reports how far a lockfile is behind its source. `generate --dry-run` covers generation, not source resolution, and `install --frozen` checks that the lockfile covers the declared sources, not that it is current. To see which repositories are behind without changing anything, compare the `resolvedRef` in each repository's `rulesync.lock` with the head of the branch its `requestedRef` names (`gh api repos/<owner>/<repo>/commits/<branch> --jq .sha` for a GitHub source; npm sources in `rulesync-npm.lock.json` record a `resolvedVersion` instead), or run the scheduled job above with the commit step removed and read its diff.\n\nUpdating many repositories at once is a loop over clones that does, per repository, exactly what a single one does: skip a dirty working tree, run `rulesync install --update && rulesync generate`, run the CI guard, and commit. Rulesync does not orchestrate that loop, and the shared repository does not have to know who consumes it.\n",
4311
4311
  "getting-started/installation": "# Installation\n\n## Package Managers\n\n```bash\nnpm install -g rulesync\n\n# And then\nrulesync --version\nrulesync --help\n```\n\n## Homebrew (macOS and Linux)\n\nrulesync ships a self-contained [Homebrew](https://brew.sh/) tap inside this\nrepository. Because the repository is not named `homebrew-rulesync`, you must use\nthe two-argument `brew tap <name> <url>` form to add it — the auto-tap shorthand\n`brew install dyoshikawa/rulesync/rulesync` cannot resolve it on its own:\n\n```bash\nbrew tap dyoshikawa/rulesync https://github.com/dyoshikawa/rulesync\nbrew install rulesync\n\n# And then\nrulesync --version\n```\n\nThe formula installs the prebuilt binary for your platform (macOS/Linux, arm64\nand x64), so it does not depend on a Node.js runtime. It is updated as part of\neach release. Homebrew does not support Windows; use npm or the\nsingle-binary download below there.\n\n## Single Binary\n\nDownload pre-built binaries from the [latest release](https://github.com/dyoshikawa/rulesync/releases/latest). These binaries are built using [Bun's single-file executable bundler](https://bun.sh/docs/bundler/executables).\n\n**Quick Install (Linux/macOS - No sudo required):**\n\n```bash\ncurl -fsSL https://github.com/dyoshikawa/rulesync/releases/latest/download/install.sh | bash\n```\n\nOptions:\n\n- Install specific version: `curl -fsSL https://github.com/dyoshikawa/rulesync/releases/latest/download/install.sh | bash -s -- v6.4.0`\n- Custom directory: `RULESYNC_HOME=~/.local curl -fsSL https://github.com/dyoshikawa/rulesync/releases/latest/download/install.sh | bash`\n\n::: details Manual installation (requires sudo)\n\n### Linux (x64)\n\n```bash\ncurl -L https://github.com/dyoshikawa/rulesync/releases/latest/download/rulesync-linux-x64 -o rulesync && \\\n chmod +x rulesync && \\\n sudo mv rulesync /usr/local/bin/\n```\n\n### Linux (ARM64)\n\n```bash\ncurl -L https://github.com/dyoshikawa/rulesync/releases/latest/download/rulesync-linux-arm64 -o rulesync && \\\n chmod +x rulesync && \\\n sudo mv rulesync /usr/local/bin/\n```\n\n### macOS (Apple Silicon)\n\n```bash\ncurl -L https://github.com/dyoshikawa/rulesync/releases/latest/download/rulesync-darwin-arm64 -o rulesync && \\\n chmod +x rulesync && \\\n sudo mv rulesync /usr/local/bin/\n```\n\n### macOS (Intel)\n\n```bash\ncurl -L https://github.com/dyoshikawa/rulesync/releases/latest/download/rulesync-darwin-x64 -o rulesync && \\\n chmod +x rulesync && \\\n sudo mv rulesync /usr/local/bin/\n```\n\n### Windows (x64)\n\n```powershell\nInvoke-WebRequest -Uri \"https://github.com/dyoshikawa/rulesync/releases/latest/download/rulesync-windows-x64.exe\" -OutFile \"rulesync.exe\"; `\n Move-Item rulesync.exe C:\\Windows\\System32\\\n```\n\nOr using curl (if available):\n\n```bash\ncurl -L https://github.com/dyoshikawa/rulesync/releases/latest/download/rulesync-windows-x64.exe -o rulesync.exe && \\\n mv rulesync.exe /path/to/your/bin/\n```\n\n### Verify checksums\n\n```bash\ncurl -L https://github.com/dyoshikawa/rulesync/releases/latest/download/SHA256SUMS -o SHA256SUMS\n\n# Linux/macOS\nsha256sum -c SHA256SUMS\n\n# Windows (PowerShell)\n# Download SHA256SUMS file first, then verify:\nGet-FileHash rulesync.exe -Algorithm SHA256 | ForEach-Object {\n $actual = $_.Hash.ToLower()\n $expected = (Get-Content SHA256SUMS | Select-String \"rulesync-windows-x64.exe\").ToString().Split()[0]\n if ($actual -eq $expected) { \"✓ Checksum verified\" } else { \"✗ Checksum mismatch\" }\n}\n```\n\n### Verify build provenance\n\nRelease binaries carry [GitHub Artifact Attestations](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations), so you can check that the file you downloaded really was built by this repository's release workflow. This needs the [GitHub CLI](https://cli.github.com/) v2.49.0 or later, which is where `gh attestation` was introduced, and a signed-in CLI (`gh auth login`) — verification queries the API even for a public repository.\n\n```bash\n# Linux/macOS — the path the steps above installed the binary to\ngh attestation verify /usr/local/bin/rulesync \\\n --repo dyoshikawa/rulesync \\\n --signer-workflow dyoshikawa/rulesync/.github/workflows/publish-assets.yml\n```\n\n```powershell\n# Windows\ngh attestation verify C:\\Windows\\System32\\rulesync.exe `\n --repo dyoshikawa/rulesync `\n --signer-workflow dyoshikawa/rulesync/.github/workflows/publish-assets.yml\n```\n\nPass the path you actually installed the binary to. The command identifies the file by its contents, not by its name, so renaming it during installation — which the steps above do — does not affect verification; a binary installed by `install.sh` or Homebrew is the same file and verifies the same way. `--repo` alone only proves the attestation came from this repository, so `--signer-workflow` is included to pin the workflow that signed it.\n\nThis covers the release binaries. The npm package carries npm's own provenance attestation instead, which is checked with `npm audit signatures` rather than `gh attestation verify`.\n\n:::\n",
4312
4312
  "getting-started/quick-start": "# Quick Start\n\n## New Project\n\n```bash\n# Install rulesync globally\nnpm install -g rulesync\n\n# Create necessary directories, sample rule files, and configuration file\nrulesync init\n\n# Install official skills (recommended)\nrulesync fetch dyoshikawa/rulesync\n\n# Or add skill sources to rulesync.jsonc and run 'rulesync install' (see \"Declarative Skill Sources\")\n```\n\n## Existing AI Tool Configurations\n\nIf you already have AI tool configurations:\n\n```bash\n# Import existing files (to .rulesync/**/*)\nrulesync import --targets claudecode # From CLAUDE.md\nrulesync import --targets cursor # From .cursorrules\nrulesync import --targets copilot # From .github/copilot-instructions.md\nrulesync import --targets claudecode --features rules,mcp,commands,subagents\n\n# And more tool supports\n\n# Generate unified configurations with all features\nrulesync generate --targets \"*\" --features \"*\"\n```\n\n## Quick Commands\n\nFor a comprehensive list of all commands and options, see [CLI Commands](/reference/cli-commands).\n",
4313
4313
  "guide/case-studies": "# Case Studies\n\nRulesync is trusted by leading companies and recognized by the industry:\n\n- **Anthropic Official Customer Story**: [Classmethod Inc. - Improving AI coding tool consistency with Rulesync](https://claude.com/customers/classmethod)\n- **Asoview Inc.**: [Adopting Rulesync for unified AI development rules](https://tech.asoview.co.jp/entry/2025/12/06/100000)\n- **KAKEHASHI Tech Blog**: [Building multilingual systems for the LLM era with a monorepo and a \"living specification\"](https://kakehashi-dev.hatenablog.com/entry/2025/12/08/110000)\n- **Cloudflare**: [Adopting Rulesync for AI coding assistant configuration](https://github.com/cloudflare/cloudflare-docs/pull/28232)\n- **Ripple**: [Migrating agent rule management to Rulesync](https://github.com/Ripple-TS/ripple/commit/114bcf791c957ab5d43fcc6369515b59b866ce80)\n- **VOICEVOX**: [Adding Rulesync to unify AI coding assistant rules](https://github.com/VOICEVOX/voicevox/pull/2918)\n- **Effect**: [Managing agent rules with Rulesync](https://github.com/Effect-TS/effect-smol/pull/986)\n- **AG Grid**: [Syncing shared AI rules via Rulesync](https://github.com/ag-grid/ag-grid/pull/13044)\n- **Red Hat Developer Hub**: [Adding Rulesync to synchronize AI Assistant rules](https://github.com/redhat-developer/rhdh/pull/3707)\n",
4314
- "guide/configuration": "# Configuration\n\nYou can configure Rulesync by creating a `rulesync.jsonc` file in the root of your project.\n\n## JSON Schema Support\n\nRulesync provides a JSON Schema for editor validation and autocompletion. Add the `$schema` property to your `rulesync.jsonc`:\n\n```jsonc\n// rulesync.jsonc\n{\n \"$schema\": \"https://github.com/dyoshikawa/rulesync/releases/latest/download/config-schema.json\",\n \"targets\": [\"claudecode\"],\n \"features\": [\"rules\"],\n}\n```\n\n## Configuration Options\n\nExample:\n\n```jsonc\n// rulesync.jsonc\n{\n \"$schema\": \"https://github.com/dyoshikawa/rulesync/releases/latest/download/config-schema.json\",\n\n // List of tools to generate configurations for. You can specify \"*\" to generate all tools.\n \"targets\": [\"cursor\", \"claudecode\", \"opencode\", \"codexcli\"],\n\n // Features to generate. You can specify \"*\" to generate all features.\n \"features\": [\"rules\", \"mcp\", \"commands\", \"subagents\", \"hooks\", \"permissions\"],\n\n // Output root directories to generate files into.\n // Basically, you can specify `[\".\"]` only.\n // However, for example, if your project is a monorepo and you have to launch the AI agent at each package directory, you can specify multiple output roots.\n \"outputRoots\": [\".\"],\n\n // Delete existing files before generating\n \"delete\": true,\n\n // Verbose output\n \"verbose\": false,\n\n // Silent mode - suppress all output (except errors)\n \"silent\": false,\n\n // Advanced options\n \"global\": false, // Generate for global(user scope) configuration files\n \"simulateCommands\": false, // Generate simulated commands\n \"simulateSubagents\": false, // Generate simulated subagents\n \"simulateSkills\": false, // Generate simulated skills\n\n // Keep hook handlers Rulesync did not write when regenerating a tool's hooks\n // file. By default the generated hooks replace the destination's hook list\n // wholesale, so a handler another tool (or a person) added by hand is lost on\n // the next `generate`. Turning this on keeps such handlers and appends the\n // generated ones. It applies to Claude Code, Codex CLI and Cursor, in both\n // project and global scope; the Claude Code *plugin* bundle always replaces,\n // because Rulesync owns that directory outright. There is no CLI flag: this\n // is a project policy, not a per-invocation one.\n //\n // To stay able to retract a hook it did write, Rulesync records what it\n // generated in a `.rulesync-hooks-lock.json` next to each hooks file (e.g.\n // `.claude/.rulesync-hooks-lock.json`). Commit it alongside the generated\n // hooks. A handler listed there that the sources no longer define is removed;\n // anything else is kept. The very first run after opting in has no lock yet,\n // so a hook Rulesync wrote before is kept once and retracted from the run\n // after that.\n \"preserveUnownedHooks\": false,\n\n // Derive `agentsmd.subprojectPath` from each non-root rule's `globs`, so a\n // rule with `globs: [\"packages/api/**/*\"]` is written as\n // `packages/api/AGENTS.md` (nested AGENTS.md) by the targets that nest,\n // instead of `.agents/memories/<rule>.md`. The directory is the leading\n // wildcard-free part every glob shares; a rule whose globs have none (e.g.\n // `[\"src/**/*.ts\", \"test/**/*.ts\"]`) or disagree silently keeps its default\n // placement. Only a rule that sets `agentsmd: { subprojectPath: \"auto\" }`\n // itself is warned about when nothing can be derived; that value also opts\n // a single rule in regardless of this option, and `agentsmd: {\n // subprojectPath: \"\" }` opts a single rule out. An explicit directory\n // always wins and `root: true` rules never nest.\n // Turning it on moves existing outputs, so run `generate --delete` once.\n \"deriveSubprojectPathFromGlobs\": false,\n\n // Naming for command files flattened for tools without subdirectory\n // command support (e.g. Cursor): \"basename\" (default) keeps only the\n // filename, so `pj/test.md` and `ops/test.md` collide and the last one\n // wins; \"path\" joins the directory segments into the filename\n // (`pj/test.md` -> `pj-test.md`), which reduces collisions but cannot\n // rule them out (a literal `pj-test.md` also maps to `pj-test.md`); the\n // collision warning still applies.\n // Tools that support subdirectories (e.g. Claude Code) are unaffected.\n // Note: switching from \"basename\" to \"path\" renames the generated files\n // (e.g. `test.md` -> `pj-test.md`); run `rulesync generate` with\n // `delete: true` (or `--delete`) once after switching, otherwise the\n // stale old flat-named files remain alongside the new ones.\n \"flattenedCommandNaming\": \"basename\",\n\n // Language the AI should answer in. Omit it to say nothing about language.\n // See the \"Response Language\" section for what each tool receives.\n // \"language\": \"ja\",\n\n // When true (default), `rulesync gitignore` only emits entries for the\n // tools listed in `targets`. Set to false to emit entries for all supported\n // tools regardless of `targets`.\n //\n // Note: Entries for `agentsmd` (AGENTS.md and related paths) are always\n // appended even when `gitignoreTargetsOnly` is true and `agentsmd` is\n // absent from `targets`. AGENTS.md is a de facto standard read by many AI\n // tools regardless of the target set, so its gitignore entries are emitted\n // unconditionally to prevent accidental commits of generated rule files.\n \"gitignoreTargetsOnly\": true,\n\n // Declarative rule and skill sources — installed via 'rulesync install'\n // See the \"Declarative Sources\" section for details.\n // \"sources\": [\n // { \"source\": \"owner/repo\" },\n // { \"source\": \"org/repo\", \"skills\": [\"specific-skill\"] },\n // { \"source\": \"org/standards\", \"rules\": [\"testing-guidelines\"] },\n // ],\n}\n```\n\n## Per-Target Features\n\nThe `targets` option accepts both an array and an object format. Use the\nobject format when you want to declare per-target feature configuration in\na single place — the object keys are the target tools, and each value\ncarries the features to generate for that tool:\n\n```jsonc\n// rulesync.jsonc\n{\n \"targets\": {\n \"claudecode\": [\"rules\", \"commands\"],\n \"cursor\": [\"rules\", \"mcp\"],\n \"copilot\": [\"rules\", \"subagents\"],\n },\n}\n```\n\nIn this example:\n\n- `claudecode` generates rules and commands\n- `cursor` generates rules and MCP configuration\n- `copilot` generates rules and subagents\n\n> **Important:** When `targets` is in object form, the top-level `features`\n> field must be omitted. Declaring both would double-define the target\n> set, so the config loader rejects that combination.\n\nYou can also use `*` (wildcard) inside a target's value to enable every\nfeature for that tool:\n\n```jsonc\n{\n \"targets\": {\n \"claudecode\": [\"*\"], // Generate all features for Claude Code\n \"cursor\": [\"rules\"], // Only rules for Cursor\n },\n}\n```\n\n### Per-feature options\n\nSome features accept additional configuration. To pass options through, use\nthe object form for a target's value instead of an array. Each feature key\nmaps to either `true`/`false` (enable/disable) or an options object.\n\n```jsonc\n{\n \"gitignoreDestination\": \"gitignore\",\n \"targets\": {\n \"claudecode\": {\n \"gitignoreDestination\": \"gitattributes\",\n \"rules\": { \"ruleDiscoveryMode\": \"explicit\" },\n \"ignore\": {\n \"fileMode\": \"local\",\n \"gitignoreDestination\": \"gitignore\",\n },\n },\n },\n}\n```\n\n`gitignoreDestination` controls where `rulesync gitignore` writes path entries.\nYou can set it:\n\n- at **root level** (`gitignoreDestination`)\n- at **tool level** (`targets.<tool>.gitignoreDestination`)\n- or at **tool × feature level**\n (`targets.<tool>.<feature>.gitignoreDestination`)\n\nAllowed values:\n\n- `\"gitignore\"` (default)\n- `\"gitattributes\"`\n\nPriority is **more specific wins**:\n\n1. tool × feature level\n2. tool level\n3. root level\n4. default (`\"gitignore\"`)\n\nThe current per-feature options are:\n\n| Target | Feature | Option | Values | Default |\n| ------------ | -------- | ---------------------- | ------------------------------------------------------------------------------ | ------------- |\n| `claudecode` | `rules` | `ruleDiscoveryMode` | `\"none\"` / `\"explicit\"` | tool default |\n| any | `rules` | `includeLocalRoot` | `true` / `false` (when `false`, `localRoot` rules are skipped for this target) | `true` |\n| `claudecode` | `ignore` | `fileMode` | `\"shared\"` (settings.json) / `\"local\"` (settings.local.json) | `\"shared\"` |\n| any | any | `gitignoreDestination` | `\"gitignore\"` / `\"gitattributes\"` | `\"gitignore\"` |\n\nSee [`docs/reference/file-formats.md`](../reference/file-formats.md#where-ignore-patterns-are-written-per-tool)\nfor the rationale behind the Claude Code default and when to switch to\n`\"local\"`.\n\n## Response Language\n\nThe root `language` key steers the language the AI answers in. It accepts one of `en`, `ja`, `zh-CN`, `zh-TW`, `ko`, `fr`, `de`, `es`, `pt-BR`, `ru`, has no CLI flag, and can be overridden per developer from `rulesync.local.jsonc`. When it is omitted, Rulesync says nothing about language; `en` is therefore an explicit instruction, not the default.\n\n```jsonc\n// rulesync.jsonc\n{\n \"language\": \"ja\",\n}\n```\n\nWhat the `rules` feature generates from it depends on the tool:\n\n- **Claude Code** has a native `language` setting, so the key is written as `\"language\": \"japanese\"` into `.claude/settings.local.json` (project scope) or `~/.claude/settings.json` (global scope, since Claude Code reads no `~/.claude/settings.local.json`) and `CLAUDE.md` is left as it is. Only the `language` key is touched; everything else in the settings file is preserved, and the file is never created or modified while the key is unset. Removing `language` later does not retract the key already written: the settings file is shared with your own configuration, so Rulesync never deletes from it, and the value stays until you remove it by hand.\n- **Every other tool** gets the instruction appended to the generated root rule file — the file built from your `root: true` rule (`AGENTS.md`, `GEMINI.md`, `.github/copilot-instructions.md`, `.cursor/rules/overview.mdc`, and so on). Nested rules never carry it, and neither does a `localRoot: true` rule's separate personal file. For a tool that files every rule side by side (Cursor and the fixed-name targets) with more than one `root: true` rule, only the file built from the first root rule carries the block. The block is separated from your content by a thematic break:\n\n ```md\n ---\n\n You must always answer in Japanese. On the other hand, reasoning (thinking) should be in English to improve token efficiency.\n ```\n\n `rulesync import` recognizes the block for every supported language and strips it (with a warning, since the instruction is not carried into `.rulesync/rules/` — the `language` key in `rulesync.jsonc` is what keeps it), so importing a generated file and generating again yields one block rather than two. `rulesync convert` re-adds the block to the destination's root file when `language` is set.\n\n## Local Configuration\n\nRulesync supports a local configuration file (`rulesync.local.jsonc`) for machine-specific or developer-specific settings. This file is automatically added to `.gitignore` by `rulesync gitignore` and should not be committed to the repository.\n\n**Configuration Priority** (highest to lowest):\n\n1. CLI options\n2. `rulesync.local.jsonc`\n3. `rulesync.jsonc`\n4. Default values\n\nExample usage:\n\n```jsonc\n// rulesync.local.jsonc (not committed to git)\n{\n \"$schema\": \"https://github.com/dyoshikawa/rulesync/releases/latest/download/config-schema.json\",\n // Override targets for local development\n \"targets\": [\"claudecode\"],\n // Enable verbose output for debugging\n \"verbose\": true,\n}\n```\n\n## Target Order and File Conflicts\n\nWhen multiple targets write to the same output file, **the last target in the array wins**. This is the \"last-wins\" behavior.\n\nFor example, both `agentsmd` and `opencode` generate `AGENTS.md`:\n\n```jsonc\n{\n // opencode wins because it comes last\n \"targets\": [\"agentsmd\", \"opencode\"],\n \"features\": [\"rules\"],\n}\n```\n\nIn this case:\n\n1. `agentsmd` generates `AGENTS.md` first\n2. `opencode` generates `AGENTS.md` second, overwriting the previous file\n\nIf you want `agentsmd`'s output instead, reverse the order:\n\n```jsonc\n{\n // agentsmd wins because it comes last\n \"targets\": [\"opencode\", \"agentsmd\"],\n \"features\": [\"rules\"],\n}\n```\n",
4314
+ "guide/configuration": "# Configuration\n\nYou can configure Rulesync by creating a `rulesync.jsonc` file in the root of your project.\n\n## JSON Schema Support\n\nRulesync provides a JSON Schema for editor validation and autocompletion. Add the `$schema` property to your `rulesync.jsonc`:\n\n```jsonc\n// rulesync.jsonc\n{\n \"$schema\": \"https://github.com/dyoshikawa/rulesync/releases/latest/download/config-schema.json\",\n \"targets\": [\"claudecode\"],\n \"features\": [\"rules\"],\n}\n```\n\n## Configuration Options\n\nExample:\n\n```jsonc\n// rulesync.jsonc\n{\n \"$schema\": \"https://github.com/dyoshikawa/rulesync/releases/latest/download/config-schema.json\",\n\n // List of tools to generate configurations for. You can specify \"*\" to generate all tools.\n \"targets\": [\"cursor\", \"claudecode\", \"opencode\", \"codexcli\"],\n\n // Features to generate. You can specify \"*\" to generate all features.\n \"features\": [\"rules\", \"mcp\", \"commands\", \"subagents\", \"hooks\", \"permissions\"],\n\n // Output root directories to generate files into.\n // Basically, you can specify `[\".\"]` only.\n // However, for example, if your project is a monorepo and you have to launch the AI agent at each package directory, you can specify multiple output roots.\n \"outputRoots\": [\".\"],\n\n // Delete existing files before generating\n \"delete\": true,\n\n // Verbose output\n \"verbose\": false,\n\n // Silent mode - suppress all output (except errors)\n \"silent\": false,\n\n // Advanced options\n \"global\": false, // Generate for global(user scope) configuration files\n \"simulateCommands\": false, // Generate simulated commands\n \"simulateSubagents\": false, // Generate simulated subagents\n \"simulateSkills\": false, // Generate simulated skills\n\n // Keep hook handlers Rulesync did not write when regenerating a tool's hooks\n // file. By default the generated hooks replace the destination's hook list\n // wholesale, so a handler another tool (or a person) added by hand is lost on\n // the next `generate`. Turning this on keeps such handlers and appends the\n // generated ones. It applies to Claude Code, Codex CLI and Cursor, in both\n // project and global scope; the Claude Code *plugin* bundle always replaces,\n // because Rulesync owns that directory outright. There is no CLI flag: this\n // is a project policy, not a per-invocation one.\n //\n // To stay able to retract a hook it did write, Rulesync records what it\n // generated in a `.rulesync-hooks-lock.json` next to each hooks file (e.g.\n // `.claude/.rulesync-hooks-lock.json`). Commit it alongside the generated\n // hooks. A handler listed there that the sources no longer define is removed;\n // anything else is kept. The very first run after opting in has no lock yet,\n // so a hook Rulesync wrote before is kept once and retracted from the run\n // after that.\n \"preserveUnownedHooks\": false,\n\n // Derive `agentsmd.subprojectPath` from each non-root rule's `globs`, so a\n // rule with `globs: [\"packages/api/**/*\"]` is written as\n // `packages/api/AGENTS.md` (nested AGENTS.md) by the targets that nest,\n // instead of `.agents/memories/<rule>.md`. The directory is the leading\n // wildcard-free part every glob shares; a rule whose globs have none (e.g.\n // `[\"src/**/*.ts\", \"test/**/*.ts\"]`) or disagree silently keeps its default\n // placement. Only a rule that sets `agentsmd: { subprojectPath: \"auto\" }`\n // itself is warned about when nothing can be derived; that value also opts\n // a single rule in regardless of this option, and `agentsmd: {\n // subprojectPath: \"\" }` opts a single rule out. An explicit directory\n // always wins and `root: true` rules never nest.\n // Turning it on moves existing outputs, so run `generate --delete` once.\n \"deriveSubprojectPathFromGlobs\": false,\n\n // Naming for command files flattened for tools without subdirectory\n // command support (e.g. Cursor): \"basename\" (default) keeps only the\n // filename, so `pj/test.md` and `ops/test.md` collide and the last one\n // wins; \"path\" joins the directory segments into the filename\n // (`pj/test.md` -> `pj-test.md`), which reduces collisions but cannot\n // rule them out (a literal `pj-test.md` also maps to `pj-test.md`); the\n // collision warning still applies.\n // Tools that support subdirectories (e.g. Claude Code) are unaffected.\n // Note: switching from \"basename\" to \"path\" renames the generated files\n // (e.g. `test.md` -> `pj-test.md`); run `rulesync generate` with\n // `delete: true` (or `--delete`) once after switching, otherwise the\n // stale old flat-named files remain alongside the new ones.\n \"flattenedCommandNaming\": \"basename\",\n\n // Language the AI should answer in. Omit it to say nothing about language.\n // See the \"Response Language\" section for what each tool receives.\n // \"language\": \"ja\",\n\n // When true (default), `rulesync gitignore` only emits entries for the\n // tools listed in `targets`. Set to false to emit entries for all supported\n // tools regardless of `targets`.\n //\n // Note: Entries for `agentsmd` (AGENTS.md and related paths) are always\n // appended even when `gitignoreTargetsOnly` is true and `agentsmd` is\n // absent from `targets`. AGENTS.md is a de facto standard read by many AI\n // tools regardless of the target set, so its gitignore entries are emitted\n // unconditionally to prevent accidental commits of generated rule files.\n \"gitignoreTargetsOnly\": true,\n\n // Declarative rule and skill sources — installed via 'rulesync install'\n // See the \"Declarative Sources\" section for details.\n // \"sources\": [\n // { \"source\": \"owner/repo\" },\n // { \"source\": \"org/repo\", \"skills\": [\"specific-skill\"] },\n // { \"source\": \"org/standards\", \"rules\": [\"testing-guidelines\"] },\n // ],\n}\n```\n\n## Per-Target Features\n\nThe `targets` option accepts both an array and an object format. Use the\nobject format when you want to declare per-target feature configuration in\na single place — the object keys are the target tools, and each value\ncarries the features to generate for that tool:\n\n```jsonc\n// rulesync.jsonc\n{\n \"targets\": {\n \"claudecode\": [\"rules\", \"commands\"],\n \"cursor\": [\"rules\", \"mcp\"],\n \"copilot\": [\"rules\", \"subagents\"],\n },\n}\n```\n\nIn this example:\n\n- `claudecode` generates rules and commands\n- `cursor` generates rules and MCP configuration\n- `copilot` generates rules and subagents\n\n> **Important:** When `targets` is in object form, the top-level `features`\n> field must be omitted. Declaring both would double-define the target\n> set, so the config loader rejects that combination.\n\nYou can also use `*` (wildcard) inside a target's value to enable every\nfeature for that tool:\n\n```jsonc\n{\n \"targets\": {\n \"claudecode\": [\"*\"], // Generate all features for Claude Code\n \"cursor\": [\"rules\"], // Only rules for Cursor\n },\n}\n```\n\n### Per-feature options\n\nSome features accept additional configuration. To pass options through, use\nthe object form for a target's value instead of an array. Each feature key\nmaps to either `true`/`false` (enable/disable) or an options object.\n\n```jsonc\n{\n \"gitignoreDestination\": \"gitignore\",\n \"targets\": {\n \"claudecode\": {\n \"gitignoreDestination\": \"gitattributes\",\n \"rules\": { \"ruleDiscoveryMode\": \"explicit\" },\n \"ignore\": {\n \"fileMode\": \"local\",\n \"gitignoreDestination\": \"gitignore\",\n },\n },\n },\n}\n```\n\n`gitignoreDestination` controls where `rulesync gitignore` writes path entries.\nYou can set it:\n\n- at **root level** (`gitignoreDestination`)\n- at **tool level** (`targets.<tool>.gitignoreDestination`)\n- or at **tool × feature level**\n (`targets.<tool>.<feature>.gitignoreDestination`)\n\nAllowed values:\n\n- `\"gitignore\"` (default)\n- `\"gitattributes\"`\n\nPriority is **more specific wins**:\n\n1. tool × feature level\n2. tool level\n3. root level\n4. default (`\"gitignore\"`)\n\nThe current per-feature options are:\n\n| Target | Feature | Option | Values | Default |\n| ------------ | -------- | ---------------------- | ------------------------------------------------------------------------------ | ------------- |\n| `claudecode` | `rules` | `ruleDiscoveryMode` | `\"none\"` / `\"explicit\"` | tool default |\n| any | `rules` | `includeLocalRoot` | `true` / `false` (when `false`, `localRoot` rules are skipped for this target) | `true` |\n| `claudecode` | `ignore` | `fileMode` | `\"shared\"` (settings.json) / `\"local\"` (settings.local.json) | `\"shared\"` |\n| any | any | `gitignoreDestination` | `\"gitignore\"` / `\"gitattributes\"` | `\"gitignore\"` |\n\nSee [`docs/reference/file-formats.md`](../reference/file-formats.md#where-ignore-patterns-are-written-per-tool)\nfor the rationale behind the Claude Code default and when to switch to\n`\"local\"`.\n\n## Response Language\n\nThe root `language` key steers the language the AI answers in. It accepts one of `en`, `ja`, `zh-CN`, `zh-TW`, `ko`, `fr`, `de`, `es`, `pt-BR`, `ru`, has no CLI flag, and can be overridden per developer from `rulesync.local.jsonc`. When it is omitted, Rulesync says nothing about language; `en` is therefore an explicit instruction, not the default.\n\n```jsonc\n// rulesync.jsonc\n{\n \"language\": \"ja\",\n}\n```\n\nWhat the `rules` feature generates from it depends on the tool:\n\n- **Claude Code** has a native `language` setting, so the key is written as `\"language\": \"japanese\"` into `.claude/settings.local.json` (project scope) or `~/.claude/settings.json` (global scope, since Claude Code reads no `~/.claude/settings.local.json`) and `CLAUDE.md` is left as it is. Only the `language` key is touched; everything else in the settings file is preserved, and the file is never created or modified while the key is unset. Removing `language` later does not retract the key already written: the settings file is shared with your own configuration, so Rulesync never deletes from it, and the value stays until you remove it by hand.\n- **Every other tool** gets the instruction appended to the generated root rule file — the file built from your `root: true` rule (`AGENTS.md`, `GEMINI.md`, `.github/copilot-instructions.md`, `.cursor/rules/overview.mdc`, and so on). Nested rules never carry it, and neither does a `localRoot: true` rule's separate personal file. For a tool that files every rule side by side (Cursor and the fixed-name targets) with more than one `root: true` rule, only the file built from the first root rule carries the block. The block is separated from your content by a thematic break:\n\n ```md\n ---\n\n You must always answer in Japanese. On the other hand, reasoning (thinking) should be in English to improve token efficiency.\n ```\n\n `rulesync import` recognizes the block for every supported language and strips it (with a warning, since the instruction is not carried into `.rulesync/rules/` — the `language` key in `rulesync.jsonc` is what keeps it), so importing a generated file and generating again yields one block rather than two. `rulesync convert` re-adds the block to the destination's root file when `language` is set.\n\n## Local Configuration\n\nRulesync supports a local configuration file (`rulesync.local.jsonc`) for machine-specific or developer-specific settings. This file is automatically added to `.gitignore` by `rulesync gitignore` and should not be committed to the repository.\n\n**Configuration Priority** (highest to lowest):\n\n1. CLI options\n2. `rulesync.local.jsonc`\n3. `rulesync.jsonc`\n4. Default values\n\nExample usage:\n\n```jsonc\n// rulesync.local.jsonc (not committed to git)\n{\n \"$schema\": \"https://github.com/dyoshikawa/rulesync/releases/latest/download/config-schema.json\",\n // Override targets for local development\n \"targets\": [\"claudecode\"],\n // Enable verbose output for debugging\n \"verbose\": true,\n}\n```\n\n## Target Order and File Conflicts\n\nWhen multiple targets write to the same output file, **the last target in the array wins**. This is the \"last-wins\" behavior.\n\nFor example, both `agentsmd` and `opencode` generate `AGENTS.md`:\n\n```jsonc\n{\n // opencode wins because it comes last\n \"targets\": [\"agentsmd\", \"opencode\"],\n \"features\": [\"rules\"],\n}\n```\n\nIn this case:\n\n1. `agentsmd` generates `AGENTS.md` first\n2. `opencode` generates `AGENTS.md` second, overwriting the previous file\n\nIf you want `agentsmd`'s output instead, reverse the order:\n\n```jsonc\n{\n // agentsmd wins because it comes last\n \"targets\": [\"opencode\", \"agentsmd\"],\n \"features\": [\"rules\"],\n}\n```\n\nThe order matters most when a target that folds every rule into its root file shares that file with a target that keeps non-root rules in separate files. `codexcli` folds all rules into `AGENTS.md`, while `roo` and `zoocode` write only the root rule to `AGENTS.md` and put the rest under `.roo/rules/`. With `[\"codexcli\", \"zoocode\"]`, `zoocode` overwrites `AGENTS.md` with the root rule alone, so Codex CLI silently loses every non-root rule. `rulesync generate` warns when this happens; list the folding target last to keep the folded content:\n\n```jsonc\n{\n // codexcli wins AGENTS.md, so its folded rules survive;\n // zoocode still gets its own files under .roo/rules/\n \"targets\": [\"zoocode\", \"codexcli\"],\n \"features\": [\"rules\"],\n}\n```\n\nIn this order Roo Code and ZooCode see the non-root rules twice — folded into `AGENTS.md` and again under `.roo/rules/` — so prefer it when a complete `AGENTS.md` for Codex CLI matters more than that duplication.\n\nThe object form of `targets` follows the same rule using its key order.\n",
4315
4315
  "guide/declarative-sources": "# Declarative Sources\n\nRulesync can fetch rules and skills from external repositories using the `install` command. Instead of manually running `fetch` for each source, declare it in your `rulesync.jsonc` and run `rulesync install` to resolve and fetch its selected artifacts. Then `rulesync generate` processes them as curated inputs. Typical workflow: `rulesync install && rulesync generate`.\n\nTo add one source without editing JSONC by hand, run `rulesync add <source>`. It preserves existing comments, appends the source entry, installs it, and updates the appropriate lockfile:\n\n```bash\nrulesync add anthropics/skills --skills skill-creator\n\n# Add one rule without selecting any skills\nrulesync add acme/ai-standards --rules testing-guidelines\n```\n\nThe command fetches only the source being added. Existing sources must already be locked and installed; run `rulesync install` first when they are not. If the new source fails, Rulesync restores the manifest, source lockfiles, curated rules, and curated skills to their previous state.\n\n## Configuration\n\nAdd a `sources` array to your `rulesync.jsonc`:\n\n```jsonc\n{\n \"$schema\": \"https://github.com/dyoshikawa/rulesync/releases/latest/download/config-schema.json\",\n \"targets\": [\"copilot\", \"claudecode\"],\n \"features\": [\"rules\", \"skills\"],\n \"sources\": [\n // Fetch all skills from a GitHub repository (default transport)\n { \"source\": \"owner/repo\" },\n\n // Fetch only specific skills by name\n { \"source\": \"anthropics/skills\", \"skills\": [\"skill-creator\"] },\n\n // Fetch only specific .md rules from rules/ (no skills)\n {\n \"source\": \"acme/ai-standards\",\n \"rules\": [\"testing-guidelines\", \"typescript-conventions\"],\n },\n\n // Rules and skills can be selected from the same source\n {\n \"source\": \"acme/ai-assets\",\n \"rules\": [\"*\"],\n \"rulesPath\": \"exports/rules\",\n \"skills\": [\"review-pr\"],\n \"path\": \"exports/skills\",\n },\n\n // With ref pinning and subdirectory path (same syntax as fetch command)\n { \"source\": \"owner/repo@v1.0.0:path/to/skills\" },\n\n // Git transport — works with any git remote (Azure DevOps, Bitbucket, etc.)\n {\n \"source\": \"https://dev.azure.com/org/project/_git/repo\",\n \"transport\": \"git\",\n \"ref\": \"main\",\n \"path\": \"exports/skills\",\n },\n\n // Git transport with a local repository\n { \"source\": \"file:///path/to/local/repo\", \"transport\": \"git\" },\n\n // Git transport against a single-skill repo whose SKILL.md is at the root\n {\n \"source\": \"https://github.com/feature-sliced/skills\",\n \"transport\": \"git\",\n \"path\": \".\",\n },\n\n // npm transport (EXPERIMENTAL) — fetch a package from an npm-compatible\n // registry (npmjs.org, JFrog Artifactory, Sonatype Nexus, Verdaccio, ...)\n {\n \"source\": \"@acme/skill-package\",\n \"transport\": \"npm\",\n \"registry\": \"https://acme.jfrog.io/artifactory/api/npm/npm-local/\",\n \"tokenEnv\": \"ACME_REGISTRY_TOKEN\",\n },\n ],\n}\n```\n\nEach entry in `sources` accepts:\n\n| Property | Type | Description |\n| ----------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `source` | `string` | Repository source. For GitHub transport: `owner/repo` or `owner/repo@ref:path`. For git transport: a full git URL. For npm transport: a package name (`pkg` or `@scope/pkg`). |\n| `skills` | `string[]` | Optional skill names to fetch. `\"*\"` selects all skills. When both `skills` and `rules` are omitted, all skills are fetched for backward compatibility. |\n| `rules` | `string[]` | Optional rule names to fetch. Names may include or omit `.md`; `\"*\"` selects every direct `.md` file under `rulesPath`. Setting only `rules` fetches no skills. |\n| `transport` | `string` | `\"github\"` (default) uses the GitHub REST API. `\"git\"` uses git CLI and works with any git remote. `\"npm\"` (experimental) fetches a package from an npm-compatible registry. |\n| `ref` | `string` | Branch, tag, or ref to fetch from. Defaults to the remote's default branch. For GitHub transport, use the `@ref` source syntax. For npm transport: an exact version or dist-tag (defaults to `latest`). |\n| `path` | `string` | Path to the skills directory within the repository. Defaults to `\"skills\"`. Set to `\"\"`, `\".\"`, or `\"./\"` to target the entire repository root (see note below). For GitHub transport, use the `:path` source syntax. |\n| `rulesPath` | `string` | Path to the rules directory within the repository or package. Defaults to `\"rules\"`. This is independent from the skills-only `path` field. |\n| `registry` | `string` | npm transport only. Base URL of the npm-compatible registry. Defaults to `https://registry.npmjs.org`. |\n| `tokenEnv` | `string` | npm transport only. Name of the environment variable holding the registry token. Defaults to `NPM_TOKEN`. |\n\nRules are flat source files: only direct `.md` children of `rulesPath` are discovered. Nested rule files are not installed. Fetched rules are written to `.rulesync/rules/.curated/<rule-name>.md`; during generation they behave as if they were ordinary files directly under `.rulesync/rules/`.\n\n> **Repository-root paths (`path: \".\"`):** When `path` is `\"\"`, `\".\"`, or `\"./\"` (with the `git` transport), rulesync disables sparse-checkout and fetches the **entire** repository tree, then groups each top-level directory as a skill. This is useful for single-skill repositories whose `SKILL.md` lives at the repo root (`<repo>/SKILL.md`) rather than under a `skills/` container. Because the whole tree is fetched, prefer a narrower `path` for large repositories; the fetch is still bounded by rulesync's file-count, total-size, and depth limits.\n\n## npm Transport (Experimental)\n\n> [!WARNING]\n> The `npm` transport is **experimental**. Its configuration surface and lockfile format may change in a future release.\n\nThe `npm` transport fetches skills from any registry that implements the npm registry API. Because JFrog Artifactory, Sonatype Nexus, Verdaccio, GitHub Packages, and similar private registries all expose an npm-compatible API, a single transport with a configurable `registry` URL covers them all. This lets enterprises whose build environments cannot reach public GitHub distribute skills internally as npm packages.\n\nHow a package is fetched:\n\n1. The package metadata (packument) is fetched from `<registry>/<package>` using the abbreviated `application/vnd.npm.install-v1+json` form.\n2. The declared `ref` (an **exact version** or a **dist-tag** such as `latest` or `beta` — semver ranges are not supported) is resolved to a concrete version.\n3. The version's tarball is downloaded and verified against the registry's `dist.integrity` / `dist.shasum` metadata.\n4. The tarball is extracted **in memory** with a hardened minimal tar reader: only regular files are materialized (symlinks, hardlinks, and device entries are skipped), path traversal is rejected, and extraction is capped at 10,000 files / 100 MB to prevent decompression bombs.\n\nPackage layout: skills are discovered the same way as for the git transports. Skill directories under `skills/` (or the configured `path`) are installed as `.rulesync/skills/.curated/<name>/`. Direct `.md` files under `rules/` (or the configured `rulesPath`) can be selected with `rules` and are installed under `.rulesync/rules/.curated/`. A single-skill package with `SKILL.md` at the package root is installed as one skill named after the package's base name (`@acme/my-skill` installs as `my-skill`); note that this root fallback installs the package's root-level files only, so prefer the `skills/<name>/` layout for skills that carry subdirectories such as `references/`.\n\nAuthentication uses a bearer token from an environment variable: `NPM_TOKEN` by default, or the variable named by the per-source `tokenEnv` field. The token is sent as `Authorization: Bearer <token>` to the registry (and to the tarball host only when it matches the registry host). `.npmrc` files are intentionally **not** read.\n\nResolved versions are pinned in `rulesync-npm.lock.json` (next to `rulesync.lock`), which records the resolved version, the tarball integrity, and per-artifact content hashes. Commit it for reproducible installs; `--update` and `--frozen` behave the same as for git sources.\n\n## How It Works\n\nWhen `rulesync install` runs and `sources` is configured:\n\n1. **Lockfile resolution** — Each source's ref is resolved to a commit SHA and stored in `rulesync.lock` (at the project root). On subsequent runs the exact locked SHA is checked out for deterministic builds. npm-transport sources are pinned in a separate `rulesync-npm.lock.json` (resolved version + tarball integrity).\n2. **Remote artifact listing** — The configured skills and rules directories are listed from the remote source.\n3. **Filtering** — Only the names selected by `skills` and `rules` are fetched. Omitting both fields retains the historical behavior of fetching all skills.\n4. **Precedence rules**:\n - **Local inputs win within one source tree** — Rules and skills outside `.curated/` take precedence over a same-named curated artifact in that input root. Across multiple `inputRoots`, root order remains primary: a later root replaces an earlier root's effective artifact even when the later artifact is curated.\n - **First-declared source wins** — If two sources provide an artifact with the same name, the one declared first in the `sources` array is used.\n5. **Output** — Fetched rules are written to `.rulesync/rules/.curated/<rule-name>.md`; fetched skills are written to `.rulesync/skills/.curated/<skill-name>/`. Both directories are automatically added to `.gitignore` by `rulesync gitignore`.\n\n## Install Modes\n\n`rulesync install` supports three install modes via `--mode <mode>`:\n\n| Mode | Manifest input | Lockfile | Output layout |\n| ---------- | ---------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ |\n| `rulesync` | `rulesync.jsonc` `sources` | `rulesync.lock` (+ `rulesync-npm.lock.json` for npm sources) | `.rulesync/rules/.curated/<name>.md`, `.rulesync/skills/.curated/<name>/` (then re-emitted by `rulesync generate`) |\n| `apm` | `apm.yml` `dependencies.apm` | `rulesync-apm.lock.yaml` | `.github/instructions/`, `.github/skills/` (APM v1 layout) |\n| `gh` | `rulesync.jsonc` `sources` | `rulesync-gh.lock.yaml` | Per-agent / per-scope dirs (matching `gh skill install`) |\n\nWhen `--mode` is omitted, rulesync defaults to `rulesync` mode. If `apm.yml` is present and `sources` is also defined, you must pass `--mode apm` or `--mode rulesync` to disambiguate.\n\n### `--mode gh` — gh-skill-install–compatible layout\n\n`--mode gh` reads the same `sources` array from `rulesync.jsonc` but writes each discovered skill into the agent-specific directory expected by `gh skill install`. Each source supports two extra fields:\n\n| Property | Type | Default | Description |\n| -------- | -------- | ---------------- | ----------------------------------------------------------------------------------------- |\n| `agent` | `string` | `github-copilot` | One of `github-copilot`, `claude-code`, `cursor`, `codex`, `gemini`, `antigravity`. |\n| `scope` | `string` | `project` | `project` writes inside the project root; `user` writes inside the user's home directory. |\n\nAgent → install directory mapping:\n\n| Agent | Project scope (relative to project root) | User scope (relative to home) |\n| ---------------- | ---------------------------------------- | ----------------------------- |\n| `github-copilot` | `.agents/skills` | `.copilot/skills` |\n| `claude-code` | `.claude/skills` | `.claude/skills` |\n| `cursor` | `.agents/skills` | `.cursor/skills` |\n| `codex` | `.agents/skills` | `.agents/skills` |\n| `gemini` | `.agents/skills` | `.gemini/skills` |\n| `antigravity` | `.agents/skills` | `.gemini/antigravity/skills` |\n\nFor each skill discovered as `skills/<name>/SKILL.md` in the remote repository, rulesync deploys the entire skill directory to `<install-dir>/<name>/` and injects a provenance frontmatter block (`source`, `repository`, `ref`) into the deployed `SKILL.md`. The lockfile `rulesync-gh.lock.yaml` records one entry per `(source, agent, scope, skill)` tuple.\n\nPer-source field support in `--mode gh`:\n\n| Field | Status |\n| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------- |\n| `source` | Required. Must resolve to a GitHub repository (`owner/repo`, `owner/repo@ref`, or an `https://github.com/...` URL). |\n| `skills` | Optional. When set, only the listed skill names are installed; remote skills not in the list are skipped, and missing names log a warning. |\n| `rules` | **Rejected.** Declarative rules are supported only in `--mode rulesync`. |\n| `rulesPath` | **Rejected.** Declarative rules are supported only in `--mode rulesync`. |\n| `ref` | Optional. Pins a tag, branch, or commit SHA. When omitted, gh mode resolves to the latest release's tag, falling back to the default branch. |\n| `agent` | Optional. Defaults to `github-copilot`. See the agent table above. |\n| `scope` | Optional. Defaults to `project`. |\n| `transport` | **Rejected.** gh mode is GitHub-only and does not honor the `git` transport. Drop the field or switch to `--mode rulesync`. |\n| `path` | **Rejected.** The remote layout is fixed to `skills/<name>/SKILL.md`. Repositories that store skills elsewhere are not supported in gh mode. |\n\nThe remote repository must use the layout `skills/<name>/SKILL.md` (one directory per skill, each containing a `SKILL.md`). Other layouts are not auto-discovered.\n\nExample `rulesync.jsonc`:\n\n```jsonc\n{\n \"targets\": [\"claudecode\"],\n \"features\": [\"rules\"],\n \"sources\": [\n // Default: agent=github-copilot, scope=project -> .agents/skills/git-commit/\n { \"source\": \"acme/skills\", \"skills\": [\"git-commit\"] },\n\n // Same source, deployed for Claude Code at user scope -> ~/.claude/skills/git-commit/\n {\n \"source\": \"acme/skills\",\n \"skills\": [\"git-commit\"],\n \"agent\": \"claude-code\",\n \"scope\": \"user\",\n },\n ],\n}\n```\n\nRun with `npx rulesync install --mode gh`.\n\n## CLI Options\n\nThe `install` command accepts these flags:\n\n| Flag | Description |\n| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| `--mode <mode>` | Install mode: `rulesync` (default), `apm`, or `gh`. See **Install Modes** above. |\n| `--update` | Force re-resolve all source refs, ignoring the lockfile (useful to pull new updates). |\n| `--frozen` | Fail if a lockfile is missing or does not cover declared sources and their skill and rule selections. Fetches missing locked artifacts without updating the lockfile. Useful for CI. |\n| `--token <token>` | GitHub token for private repositories. |\n\n```bash\n# Install rules and skills using locked refs\nrulesync install\n\n# Force update to latest refs\nrulesync install --update\n\n# Strict CI mode — fail if lockfile doesn't cover all sources and selections\nrulesync install --frozen\n\n# Install then generate\nrulesync install && rulesync generate\n\n# Skip source installation — just don't run install\nrulesync generate\n```\n\n## Lockfile\n\nThe lockfile at `rulesync.lock` (at the project root) records the resolved commit SHA, the skill and rule selections each entry was written for, and per-artifact integrity hashes for each source so that builds are reproducible. Rulesync verifies cached rule content against these hashes before reusing it. It is safe to commit this file. An example:\n\n```json\n{\n \"lockfileVersion\": 1,\n \"sources\": {\n \"owner/skill-repo\": {\n \"requestedRef\": \"main\",\n \"resolvedRef\": \"abc123def456...\",\n \"resolvedAt\": \"2025-01-15T12:00:00.000Z\",\n \"skills\": {\n \"my-skill\": { \"integrity\": \"sha256-abcdef...\" },\n \"another-skill\": { \"integrity\": \"sha256-123456...\" }\n },\n \"skillSelection\": [\"*\"],\n \"rules\": {\n \"testing-guidelines\": { \"integrity\": \"sha256-789abc...\" }\n },\n \"ruleSelection\": [\"*\"],\n \"rulesPath\": \"rules\",\n \"resolvedRuleNames\": [\"testing-guidelines\"]\n }\n }\n}\n```\n\nTo update locked refs, run `rulesync install --update`.\n\nChanging a source's `skills` or `rules` selection in `rulesync.jsonc` (for example, adding a skill name to an explicit list, or switching to `\"*\"`) is picked up by the next plain `rulesync install`: the entry is refetched at its locked ref and the lockfile records the new selection. Under `--frozen`, a selection the lockfile does not cover fails the install instead. A lockfile written before `skillSelection` was recorded is fetched again once, at its locked ref, by the next plain `rulesync install`, which then records the selection; commit the updated lockfile so `--frozen` installs keep reusing the cache.\n\nnpm-transport sources (experimental) are pinned in a separate `rulesync-npm.lock.json`, because they lock a resolved package version and tarball integrity instead of a commit SHA:\n\n```json\n{\n \"lockfileVersion\": 1,\n \"sources\": {\n \"@acme/skill-package\": {\n \"registry\": \"https://acme.jfrog.io/artifactory/api/npm/npm-local\",\n \"requestedVersion\": \"latest\",\n \"resolvedVersion\": \"1.2.3\",\n \"integrity\": \"sha512-...\",\n \"resolvedAt\": \"2026-01-15T12:00:00.000Z\",\n \"skills\": {\n \"my-skill\": { \"integrity\": \"sha256-abcdef...\" }\n },\n \"skillSelection\": [\"my-skill\"],\n \"rules\": {\n \"testing-guidelines\": { \"integrity\": \"sha256-789abc...\" }\n },\n \"ruleSelection\": [\"testing-guidelines\"],\n \"rulesPath\": \"rules\",\n \"resolvedRuleNames\": [\"testing-guidelines\"]\n }\n }\n}\n```\n\nIt is safe (and recommended) to commit this file as well.\n\n## Authentication\n\nGitHub transport uses the `GITHUB_TOKEN` or `GH_TOKEN` environment variable for authentication. This is required for private repositories and recommended for better rate limits. Git transport relies on your local git credential configuration (SSH keys, credential helpers, etc.). npm transport (experimental) uses the `NPM_TOKEN` environment variable, or the variable named by the per-source `tokenEnv` field; `.npmrc` files are not read.\n\n```bash\n# Using environment variable\nexport GITHUB_TOKEN=ghp_xxxx\nnpx rulesync install\n\n# Or using GitHub CLI\nGITHUB_TOKEN=$(gh auth token) npx rulesync install\n```\n\n> [!TIP]\n> The `install` command also accepts a `--token` flag for explicit authentication: `rulesync install --token ghp_xxxx`.\n\n## Curated vs Local Inputs\n\n| Location | Type | Precedence within one root | Committed to Git |\n| ------------------------------------ | ------- | -------------------------- | ---------------- |\n| `.rulesync/skills/<name>/` | Local | Higher | Yes |\n| `.rulesync/skills/.curated/<name>/` | Curated | Lower | No (gitignored) |\n| `.rulesync/rules/<name>.md` | Local | Higher | Yes |\n| `.rulesync/rules/.curated/<name>.md` | Curated | Lower | No (gitignored) |\n\nWhen a local and curated artifact in the same source tree share a name, the local artifact is used and the remote one is not fetched. With multiple input roots, this per-root selection happens before the roots are merged in order; see [Separate Input Root](./separate-input-root.md#merge-rules-per-feature).\n",
4316
4316
  "guide/dry-run": "# Dry Run\n\nRulesync provides two dry run options for the `generate` command that allow you to see what changes would be made without actually writing files:\n\n## `--dry-run`\n\nShow what would be written or deleted without actually writing any files. Changes are displayed with a `[DRY RUN]` prefix.\n\n```bash\nrulesync generate --dry-run --targets claudecode --features rules\n```\n\n## `--check`\n\nSame as `--dry-run`, but exits with code 1 if files are not up to date. This is useful for CI/CD pipelines to verify that generated files are committed.\n\n```bash\n# In your CI pipeline\nrulesync generate --check --targets \"*\" --features \"*\"\necho $? # 0 if up to date, 1 if changes needed\n```\n\n> [!NOTE]\n> `--dry-run` and `--check` cannot be used together.\n",
4317
4317
  "guide/global-mode": "# Global Mode\n\nYou can use global mode via Rulesync by enabling `--global` option. It can also be called as user scope mode.\n\nCurrently, supports rules generation for Claude Code, GitHub Copilot, and OpenCode. Import for global files is supported for rules and commands. Command generation in global mode remains Claude Code only.\n\n1. Create an any name directory. For example, if you prefer `~/.aiglobal`, run the following command.\n\n ```bash\n mkdir -p ~/.aiglobal\n ```\n\n2. Initialize files for global files in the directory.\n\n ```bash\n cd ~/.aiglobal\n rulesync init\n ```\n\n3. Edit `~/.aiglobal/rulesync.jsonc` to enable global mode.\n\n ```jsonc\n {\n \"global\": true,\n }\n ```\n\n4. Edit `~/.aiglobal/.rulesync/rules/overview.md` to your preferences.\n\n ```md\n ---\n root: true\n ---\n\n # The Project Overview\n\n ...\n ```\n\n5. Generate rules for global settings.\n\n ```bash\n # Run in the `~/.aiglobal` directory\n rulesync generate\n ```\n\n> [!NOTE]\n> Currently, when in the directory enabled global mode:\n>\n> - `rulesync.jsonc` only supports `global`, `features`, `delete` and `verbose`. `Features` can be set `\"rules\"` and `\"commands\"`. Other parameters are ignored.\n> - Multiple `root: true` files can target the same tool in project and global modes. Compatible root or plain-Markdown single-file outputs are combined with a blank line between files. Local rules are composed first in lexicographic source file path order, followed by non-overridden `.curated/` rules in lexicographic order, so filename prefixes control composition order within each set. Distinct native paths remain separate. Explicitly supported plain-Markdown modular path collisions are combined (fragments whose generated output carries its own frontmatter block stay separate), unsafe collisions involving a source root rule fail, and other exact or case-insensitive modular collisions warn that the last write wins wherever their paths collide.\n> - Only Claude Code is supported for global mode commands.\n",
@@ -12948,7 +12948,7 @@ function wrapCommand$1({ name, errorCode, handler, getVersion, loggerFactory = c
12948
12948
  }
12949
12949
  //#endregion
12950
12950
  //#region src/cli/program.ts
12951
- const getVersion = () => "16.30.1";
12951
+ const getVersion = () => "16.30.2";
12952
12952
  const FEATURES_HELP = `${require_import.ALL_FEATURES.join(",")}; ignore is deprecated, use permissions`;
12953
12953
  function wrapCommand(name, errorCode, handler) {
12954
12954
  return wrapCommand$1({
package/dist/cli/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { $ as RulesyncCheck, $t as ALL_TOOL_TARGETS, A as CLAUDECODE_SKILLS_DIR_PATH, An as RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH, At as ensureDir, B as RulesyncSkill, Bn as quoteForLog, Bt as pathEscapesRoot, C as ChecksProcessor, Cn as RULESYNC_PERMISSIONS_FILE_NAME, Ct as applyFileMode, D as CLAUDECODE_LOCAL_RULE_FILE_NAME, Dn as RULESYNC_RELATIVE_DIR_PATH, Dt as checkPathTraversal, E as CLAUDECODE_DIR, En as RULESYNC_PERMISSIONS_SCHEMA_URL, Et as assertWritablePathInsideRoot, F as AUGMENTCODE_DIR, Fn as DEPRECATED_FEATURE_REPLACEMENTS, Ft as isFileSystemError, G as RulesyncMcp, Gt as removeFile, H as RulesyncRule, Hn as stripControlCharactersKeepingLineFeeds, Ht as readFileContentOrNull, I as AUGMENTCODE_SETTINGS_LOCAL_FILE_NAME, In as formatError, It as isSymlink, J as getRulesyncSourceCandidates, Jt as resolvePath, K as RulesyncIgnore, Kt as removeFileStrict, L as getLocalSkillDirNames, Ln as truncateText, Lt as listDirectoryEntryNames, M as FACTORYDROID_SETTINGS_LOCAL_FILE_NAME, Mn as parseCommaSeparatedList, Mt as getFileSize, N as caseFoldIdentity, Nn as ALL_FEATURES, Nt as getHomeDirectory, O as CLAUDECODE_MEMORIES_DIR_NAME, On as RULESYNC_RULES_RELATIVE_DIR_PATH, Ot as createTempDirectory, P as groupSpellingsByCaseFoldedIdentity, Pn as ALL_FEATURES_WITH_WILDCARD, Pt as isFileNotFoundError, Q as RulesyncCommandFrontmatterSchema, Qt as writeFileContent, R as RulesyncSubagent, Rn as hasDeceptiveHiddenCharacters, Rt as listFilePathsRecursively, S as QWENCODE_LOCAL_RULE_FILE_NAME, Sn as RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH, St as ErrorCodes, T as CODEXCLI_DIR, Tn as RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH, Tt as assertTreeContainsNoSymlinks, U as RulesyncRuleFrontmatterSchema, Un as stripHiddenCharacters, Ut as removeDirectory, V as RulesyncSkillFrontmatterSchema, Vn as stripControlCharacters, Vt as readFileContent, W as RulesyncPermissions, Wt as removeDirectoryStrict, X as parseJsonc, Xt as toPosixPath, Y as resolveRulesyncSourceWritePath, Yt as runWithDirectoryRollback, Z as RulesyncCommand, Zt as writeFileBuffer, _ as IgnoreProcessor, _n as RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH, _t as warnOnConflictingFlags, a as getProcessorRegistryEntry, an as RULESYNC_AIIGNORE_FILE_NAME, at as ConfigResolver, b as CommandsProcessor, bn as RULESYNC_MCP_RELATIVE_FILE_PATH, bt as withWarnOnceScope, c as RulesProcessor, cn as RULESYNC_COMMANDS_RELATIVE_DIR_PATH, ct as CONFLICTING_TARGET_PAIRS, d as CODEBUDDY_DIR, dn as RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH, dt as SourceEntrySchema, en as ALL_TOOL_TARGETS_WITH_WILDCARD, et as RulesyncCheckFrontmatterSchema, f as CODEBUDDY_LOCAL_RULE_FILE_NAME, fn as RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH, ft as findControlCharacter, g as McpProcessor, gn as RULESYNC_IGNORE_RELATIVE_FILE_PATH, gt as fallbackLogger, h as shortenToWidth, hn as RULESYNC_HOOKS_RELATIVE_FILE_PATH, ht as WarningCollectingLogger, i as inspectInputRoots, in as MAX_FILE_SIZE, it as SKILL_FILE_NAME$1, j as FACTORYDROID_DIR, jn as RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, jt as fileExists, k as CLAUDECODE_SETTINGS_LOCAL_FILE_NAME, kn as RULESYNC_SKILLS_RELATIVE_DIR_PATH, kt as directoryExists, l as SubagentsProcessor, ln as RULESYNC_CONFIG_RELATIVE_FILE_PATH, lt as ConfigFileSchema, m as displayWidthOf, mn as RULESYNC_HOOKS_LEGACY_FILE_NAME, mt as JsonLogger, n as formatSourceLoadFailure, nn as ToolTargetSchema, nt as loadYaml, o as convertFromTool, on as RULESYNC_AIIGNORE_RELATIVE_FILE_PATH, ot as mergeInputRootConfigs, p as ELLIPSIS_WIDTH, pn as RULESYNC_HOOKS_FILE_NAME, pt as ConsoleLogger, q as RulesyncHooks, qt as removeTempDirectory, r as generate, rn as CURATED_RULES_FEATURE_SUBDIR, rt as SHARED_USER_MANAGED_CONFIG_PATHS, s as isPackagingToolTarget, sn as RULESYNC_CHECKS_RELATIVE_DIR_PATH, st as resolveEffectiveInputRoots, t as importFromTool, tn as PACKAGING_TOOL_TARGETS, tt as stringifyFrontmatter, u as SkillsProcessor, un as RULESYNC_CONFIG_SCHEMA_URL, ut as GITIGNORE_DESTINATION_KEY, v as CRUSH_LOCAL_RULE_FILE_NAME, vn as RULESYNC_MCP_FILE_NAME, vt as withFallbackLoggerTarget, w as CODEXCLI_BASH_RULES_FILE_NAME, wn as RULESYNC_PERMISSIONS_LEGACY_FILE_NAME, wt as assertDirectoryIfExists, x as QWENCODE_DIR, xn as RULESYNC_MCP_SCHEMA_URL, xt as CLIError, y as HooksProcessor, yn as RULESYNC_MCP_LEGACY_FILE_NAME, yt as resetRunWarningState, z as RulesyncSubagentFrontmatterSchema, zn as hasEnclosingMarkOutsideKeycap, zt as listSubdirectoryNames } from "../import-BDlmyTcr.js";
2
+ import { $ as RulesyncCheck, $t as ALL_TOOL_TARGETS, A as CLAUDECODE_SKILLS_DIR_PATH, An as RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH, At as ensureDir, B as RulesyncSkill, Bn as quoteForLog, Bt as pathEscapesRoot, C as ChecksProcessor, Cn as RULESYNC_PERMISSIONS_FILE_NAME, Ct as applyFileMode, D as CLAUDECODE_LOCAL_RULE_FILE_NAME, Dn as RULESYNC_RELATIVE_DIR_PATH, Dt as checkPathTraversal, E as CLAUDECODE_DIR, En as RULESYNC_PERMISSIONS_SCHEMA_URL, Et as assertWritablePathInsideRoot, F as AUGMENTCODE_DIR, Fn as DEPRECATED_FEATURE_REPLACEMENTS, Ft as isFileSystemError, G as RulesyncMcp, Gt as removeFile, H as RulesyncRule, Hn as stripControlCharactersKeepingLineFeeds, Ht as readFileContentOrNull, I as AUGMENTCODE_SETTINGS_LOCAL_FILE_NAME, In as formatError, It as isSymlink, J as getRulesyncSourceCandidates, Jt as resolvePath, K as RulesyncIgnore, Kt as removeFileStrict, L as getLocalSkillDirNames, Ln as truncateText, Lt as listDirectoryEntryNames, M as FACTORYDROID_SETTINGS_LOCAL_FILE_NAME, Mn as parseCommaSeparatedList, Mt as getFileSize, N as caseFoldIdentity, Nn as ALL_FEATURES, Nt as getHomeDirectory, O as CLAUDECODE_MEMORIES_DIR_NAME, On as RULESYNC_RULES_RELATIVE_DIR_PATH, Ot as createTempDirectory, P as groupSpellingsByCaseFoldedIdentity, Pn as ALL_FEATURES_WITH_WILDCARD, Pt as isFileNotFoundError, Q as RulesyncCommandFrontmatterSchema, Qt as writeFileContent, R as RulesyncSubagent, Rn as hasDeceptiveHiddenCharacters, Rt as listFilePathsRecursively, S as QWENCODE_LOCAL_RULE_FILE_NAME, Sn as RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH, St as ErrorCodes, T as CODEXCLI_DIR, Tn as RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH, Tt as assertTreeContainsNoSymlinks, U as RulesyncRuleFrontmatterSchema, Un as stripHiddenCharacters, Ut as removeDirectory, V as RulesyncSkillFrontmatterSchema, Vn as stripControlCharacters, Vt as readFileContent, W as RulesyncPermissions, Wt as removeDirectoryStrict, X as parseJsonc, Xt as toPosixPath, Y as resolveRulesyncSourceWritePath, Yt as runWithDirectoryRollback, Z as RulesyncCommand, Zt as writeFileBuffer, _ as IgnoreProcessor, _n as RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH, _t as warnOnConflictingFlags, a as getProcessorRegistryEntry, an as RULESYNC_AIIGNORE_FILE_NAME, at as ConfigResolver, b as CommandsProcessor, bn as RULESYNC_MCP_RELATIVE_FILE_PATH, bt as withWarnOnceScope, c as RulesProcessor, cn as RULESYNC_COMMANDS_RELATIVE_DIR_PATH, ct as CONFLICTING_TARGET_PAIRS, d as CODEBUDDY_DIR, dn as RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH, dt as SourceEntrySchema, en as ALL_TOOL_TARGETS_WITH_WILDCARD, et as RulesyncCheckFrontmatterSchema, f as CODEBUDDY_LOCAL_RULE_FILE_NAME, fn as RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH, ft as findControlCharacter, g as McpProcessor, gn as RULESYNC_IGNORE_RELATIVE_FILE_PATH, gt as fallbackLogger, h as shortenToWidth, hn as RULESYNC_HOOKS_RELATIVE_FILE_PATH, ht as WarningCollectingLogger, i as inspectInputRoots, in as MAX_FILE_SIZE, it as SKILL_FILE_NAME$1, j as FACTORYDROID_DIR, jn as RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, jt as fileExists, k as CLAUDECODE_SETTINGS_LOCAL_FILE_NAME, kn as RULESYNC_SKILLS_RELATIVE_DIR_PATH, kt as directoryExists, l as SubagentsProcessor, ln as RULESYNC_CONFIG_RELATIVE_FILE_PATH, lt as ConfigFileSchema, m as displayWidthOf, mn as RULESYNC_HOOKS_LEGACY_FILE_NAME, mt as JsonLogger, n as formatSourceLoadFailure, nn as ToolTargetSchema, nt as loadYaml, o as convertFromTool, on as RULESYNC_AIIGNORE_RELATIVE_FILE_PATH, ot as mergeInputRootConfigs, p as ELLIPSIS_WIDTH, pn as RULESYNC_HOOKS_FILE_NAME, pt as ConsoleLogger, q as RulesyncHooks, qt as removeTempDirectory, r as generate, rn as CURATED_RULES_FEATURE_SUBDIR, rt as SHARED_USER_MANAGED_CONFIG_PATHS, s as isPackagingToolTarget, sn as RULESYNC_CHECKS_RELATIVE_DIR_PATH, st as resolveEffectiveInputRoots, t as importFromTool, tn as PACKAGING_TOOL_TARGETS, tt as stringifyFrontmatter, u as SkillsProcessor, un as RULESYNC_CONFIG_SCHEMA_URL, ut as GITIGNORE_DESTINATION_KEY, v as CRUSH_LOCAL_RULE_FILE_NAME, vn as RULESYNC_MCP_FILE_NAME, vt as withFallbackLoggerTarget, w as CODEXCLI_BASH_RULES_FILE_NAME, wn as RULESYNC_PERMISSIONS_LEGACY_FILE_NAME, wt as assertDirectoryIfExists, x as QWENCODE_DIR, xn as RULESYNC_MCP_SCHEMA_URL, xt as CLIError, y as HooksProcessor, yn as RULESYNC_MCP_LEGACY_FILE_NAME, yt as resetRunWarningState, z as RulesyncSubagentFrontmatterSchema, zn as hasEnclosingMarkOutsideKeycap, zt as listSubdirectoryNames } from "../import-BknrMODR.js";
3
3
  import { Command } from "commander";
4
4
  import { nonnegative, optional, refine, z } from "zod/mini";
5
5
  import { cp, lstat, mkdtemp, readdir, realpath, rm, rmdir, stat } from "node:fs/promises";
@@ -4304,11 +4304,11 @@ async function convertCommand(logger, options) {
4304
4304
  */
4305
4305
  const DOCS_CONTENT = {
4306
4306
  "api/programmatic-api": "# Programmatic API\n\nRulesync can be used as a library in your Node.js/TypeScript projects. The `generate`, `importFromTool`, and `convertFromTool` functions are available as named exports.\n\n```typescript\nimport { convertFromTool, generate, importFromTool } from \"rulesync\";\n\n// Generate configurations\nconst result = await generate({\n targets: [\"claudecode\", \"cursor\"],\n features: [\"rules\", \"mcp\"],\n});\nconsole.log(`Generated ${result.rulesCount} rules, ${result.mcpCount} MCP configs`);\n\n// Import existing tool configurations into .rulesync/\nconst importResult = await importFromTool({\n target: \"claudecode\",\n features: [\"rules\", \"commands\"],\n});\nconsole.log(`Imported ${importResult.rulesCount} rules`);\n\n// Convert configurations between AI tools without writing intermediate .rulesync/ files\ntry {\n const convertResult = await convertFromTool({\n from: \"claudecode\",\n to: [\"cursor\", \"copilot\"],\n features: [\"rules\"],\n });\n console.log(`Converted ${convertResult.rulesCount} rule file(s)`);\n} catch (error) {\n // Thrown when `from` is empty, `to` is empty, `to` includes `from`,\n // a source file cannot be parsed, or write fails.\n console.error(\"convert failed:\", error);\n}\n```\n\n## `generate(options?)`\n\nGenerates configuration files for the specified targets and features.\n\n| Option | Type | Default | Description |\n| ------------------- | -------------- | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `targets` | `ToolTarget[]` | from config file | Tools to generate configurations for |\n| `features` | `Feature[]` | from config file | Features to generate |\n| `outputRoots` | `string[]` | `[process.cwd()]` | Output root directories to generate files into |\n| `inputRoots` | `string[]` | `[<cwd>/.rulesync]` | Ordered list of rulesync source-tree directories (e.g. `.rulesync`, `.rulesync.local`). Each entry is a source tree itself — no `.rulesync/` join is applied. The first root is required; later roots are optional overlays and may be absent. Output still goes to each `outputRoots` entry; only the input source root is redirected. Later entries override earlier ones for the same relative source path. Cannot be combined with `inputRoot`. Mirrors the CLI's `--input-roots`. |\n| `inputRoot` | `string` | `process.cwd()` | **Deprecated.** PARENT directory of a `.rulesync/` source tree; kept as a backward-compatibility alias that expands internally to `inputRoots: [join(inputRoot, \".rulesync\")]`. Prefer `inputRoots` and point it directly at your source tree(s). Cannot be combined with `inputRoots`. Mirrors the CLI's `--input-root`. |\n| `configPath` | `string` | auto-detected | Path to `rulesync.jsonc` |\n| `verbose` | `boolean` | `false` | Enable verbose logging |\n| `silent` | `boolean` | `true` | Suppress all output |\n| `delete` | `boolean` | from config file | Delete existing files before generating |\n| `global` | `boolean` | `false` | Generate global (user scope) configurations |\n| `simulateCommands` | `boolean` | `false` | Generate simulated commands |\n| `simulateSubagents` | `boolean` | `false` | Generate simulated subagents |\n| `simulateSkills` | `boolean` | `false` | Generate simulated skills |\n| `dryRun` | `boolean` | `false` | Show changes without writing files |\n| `check` | `boolean` | `false` | Exit with code 1 if files are not up to date |\n\n> **Unreadable sources do not throw here.** Unlike the CLI, which exits\n> non-zero, `generate()` resolves and reports the problem on the result:\n> `sourceLoadFailed` is `true` and `sourceLoadFailedFeatures` names the features\n> whose `.rulesync/` source could not be read. Their counts are `0`, exactly like\n> a feature that had nothing to write, so check the flag rather than the counts\n> before treating a run as successful. (Those features also keep their existing\n> generated files: `delete` skips their orphan sweep.)\n\n## `importFromTool(options)`\n\nImports existing tool configurations into `.rulesync/` directory.\n\n| Option | Type | Default | Description |\n| ------------ | ------------ | ---------------- | ----------------------------------------- |\n| `target` | `ToolTarget` | (required) | Tool to import configurations from |\n| `features` | `Feature[]` | from config file | Features to import |\n| `configPath` | `string` | auto-detected | Path to `rulesync.jsonc` |\n| `verbose` | `boolean` | `false` | Enable verbose logging |\n| `silent` | `boolean` | `true` | Suppress all output |\n| `global` | `boolean` | `false` | Import global (user scope) configurations |\n\n## `convertFromTool(options)`\n\nConverts configuration files between AI tools without writing intermediate `.rulesync/` files to disk.\n\n| Option | Type | Default | Description |\n| ------------ | -------------- | ------------- | ------------------------------------------------------------------------------------------- |\n| `from` | `ToolTarget` | (required) | Source tool to convert configurations from |\n| `to` | `ToolTarget[]` | (required) | Destination tools to convert to |\n| `features` | `Feature[]` | `[\"*\"]` | Features to convert. Matches CLI behavior and overrides any `features` in `rulesync.jsonc`. |\n| `configPath` | `string` | auto-detected | Path to `rulesync.jsonc` |\n| `verbose` | `boolean` | `false` | Enable verbose logging |\n| `silent` | `boolean` | `true` | Suppress all output |\n| `global` | `boolean` | `false` | Convert global (user scope) configurations |\n| `dryRun` | `boolean` | `false` | Show changes without writing files |\n",
4307
- faq: "# FAQ\n\n## `rulesync generate` doesn't produce what I expect\n\nRun `rulesync doctor` first. It performs read-only diagnostics on `rulesync.jsonc` and `rulesync.local.jsonc` and reports problems the generator silently tolerates — most importantly misspelled or unknown configuration keys (the config schema is non-strict, so a typo like `\"target\"` instead of `\"targets\"` is otherwise ignored and generation quietly falls back to defaults). See the [Doctor Command](./reference/cli-commands.md#doctor-command) reference for the full list of checks.\n\n## The generated `.mcp.json` doesn't work properly in Claude Code\n\nYou can try adding the following to `.claude/settings.json` or `.claude/settings.local.json`:\n\n```diff\n{\n+ \"enableAllProjectMcpServers\": true\n}\n```\n\nAccording to [the documentation](https://code.claude.com/docs/en/settings), this means:\n\n> Automatically approve all MCP servers defined in project .mcp.json files\n\n## Google Antigravity doesn't load rules when `.agents` directories are in `.gitignore`\n\nGoogle Antigravity has a known limitation where it won't load rules, workflows, and skills if the `.agents/rules/`, `.agents/workflows/`, and `.agents/skills/` directories are listed in `.gitignore`, even with \"Agent Gitignore Access\" enabled.\n\n> **Note:** Antigravity 2.0 uses the plural `.agents/` directory by default (the `antigravity-ide` and `antigravity-cli` targets).\n\n**Workaround:** Instead of adding these directories to `.gitignore`, add them to `.git/info/exclude`:\n\n```bash\n# Remove from .gitignore (if present)\n# **/.agents/rules/\n# **/.agents/workflows/\n# **/.agents/skills/\n\n# Add to .git/info/exclude\necho \"**/.agents/rules/\" >> .git/info/exclude\necho \"**/.agents/workflows/\" >> .git/info/exclude\necho \"**/.agents/skills/\" >> .git/info/exclude\n```\n\n`.git/info/exclude` works like `.gitignore` but is local-only, so it won't affect Antigravity's ability to load the rules while still excluding these directories from Git.\n\nNote: `.git/info/exclude` can't be shared with your team since it's not committed to the repository.\n\n## Codex CLI denies SSH agent access, temp-dir writes, or reading its own config with a generated permissions profile\n\nThe `[permissions.rulesync]` profile that rulesync generates into `.codex/config.toml` extends Codex CLI's `:workspace` baseline. That baseline is deliberately conservative, so day-to-day development can still hit permission denials: `git push`/`git fetch` over SSH cannot reach the SSH agent socket, some build tools fail without a writable temp dir, and Codex may be blocked from reading its own `~/.codex` configuration.\n\nrulesync emits the `.git` write carve-out for you (`\".git/**\" = \"write\"` under `:workspace_roots`; opt out with the `codexcli.git_write_rules: false` override). The whole subtree — including `.git/config` — is writable, because everyday commands such as `git remote add`, `git push -u`, and local-scope `git config` write to the repository config; users who want stricter isolation can add their own `read` override (e.g. `read: { \".git/config\": \"allow\" }`) in the canonical permissions. Everything below, however, depends on your environment or workflow, so rulesync does not add it by default. Where to put each piece differs, because the two tables are managed differently:\n\n**Network settings: edit `.codex/config.toml` directly.** Network settings are out of rulesync's management scope by design, keeping you free to edit them. rulesync preserves user-authored network keys when it regenerates the file — `network.enabled` (as long as the profile carries no rulesync-managed allow domains) and unknown keys such as `dangerously_allow_all_unix_sockets` are carried forward verbatim, with a warning so they stay visible:\n\n```toml\n[permissions.rulesync.network]\nenabled = true\n# Simplest option: allow all unix sockets. Codex names this \"dangerously_*\"\n# because it is broad, but it avoids hardcoding an env-dependent socket path.\ndangerously_allow_all_unix_sockets = true\n\n# Stricter alternative: allow only the SSH agent socket.\n# Replace the path with the actual value of $SSH_AUTH_SOCK on your machine;\n# Codex does not expand environment variables in these keys.\n# [permissions.rulesync.network.unix_sockets]\n# \"/path/to/ssh-agent.sock\" = \"allow\"\n```\n\n**Filesystem entries: author them in `.rulesync/permissions.jsonc`, not in `config.toml`.** The profile's `filesystem` table is fully managed — hand-written entries there are replaced on the next `rulesync generate`. Add the rules to the canonical config instead (use the tool-scoped `codexcli.permission` block so they do not leak into other tools' outputs) and regenerate:\n\n```jsonc\n{\n \"permission\": {\n // ...your shared rules...\n },\n \"codexcli\": {\n \"permission\": {\n \"write\": {\n \".\": \"allow\",\n \".git/**\": \"allow\",\n \".agents/**\": \"allow\",\n \".codex/**\": \"allow\",\n \":root\": \"allow\",\n \":minimal\": \"allow\",\n \":tmpdir\": \"allow\",\n \":slash_tmp\": \"allow\",\n },\n \"read\": { \"~/.codex/**\": \"allow\", \"~/.codex/auth.json\": \"deny\" },\n },\n },\n}\n```\n\nNote that this example is intentionally permissive: the `\":root\"` + `\":minimal\"` write pair grants the sandbox full disk write access — see the trade-off in the entry list below for narrower alternatives.\n\nThis generates into the profile as `\".\" = \"write\"`, `\".git/**\" = \"write\"`, `\".agents/**\" = \"write\"`, and `\".codex/**\" = \"write\"` under `:workspace_roots`, plus `\":root\" = \"write\"`, `\":minimal\" = \"write\"`, `\":tmpdir\" = \"write\"`, `\":slash_tmp\" = \"write\"`, `\"~/.codex/**\" = \"read\"`, and `\"~/.codex/auth.json\" = \"deny\"`, and round-trips through `rulesync import` — with two exceptions. First, `\".git/**\" = \"write\"` matches rulesync's default carve-out exactly, so import skips it (it is re-added on every generate); if you later opt out with `codexcli.git_write_rules: false` after an import, re-author the `\".git/**\": \"allow\"` write rule in the canonical config. Second, `\":minimal\"` is never imported regardless of its value — rulesync treats it as its fixed `\"read\"` baseline — so after an import, re-author the `\":minimal\": \"allow\"` write rule as well or the next generate silently drops back to `\":minimal\" = \"read\"`. Note that a tool-scoped category replaces the shared one wholesale for Codex CLI: if your shared `permission` block already has `read`/`write` rules that should also apply to Codex CLI, repeat them inside `codexcli.permission`.\n\nWhat each entry does:\n\n- **Unix socket access**: `git push`/`git fetch` over SSH needs the agent socket. `dangerously_allow_all_unix_sockets = true` is the simple, environment-independent option; a per-socket `unix_sockets` allow entry with the resolved `$SSH_AUTH_SOCK` path is the stricter one.\n- **`.` / `.git/**` / `.agents/**` / `.codex/**` write**: the practical write set for the workspace itself. `\".\"` spells out the workspace-subtree write access the `:workspace` baseline already grants (a tool-scoped category replaces the shared block wholesale, so keeping it explicit avoids surprises), and `\".git/**\"` matches the carve-out rulesync emits by default anyway. `.agents/**` and `.codex/**` genuinely add access: Codex's `:workspace` baseline keeps `.git`, `.agents`, and `.codex` read-only inside workspace roots, so without these rules a Codex session cannot update agent files or its own project-level config — for example, running `rulesync generate` inside a session would be denied when writing `.agents/` or `.codex/` outputs. Trade-off: the baseline keeps those two directories read-only precisely so a sandboxed session cannot rewrite its own configuration — with `.codex/**` writable, a compromised or prompt-injected session could relax `.codex/config.toml` (approval policy, permission profiles, MCP servers) for its next run, and with `.agents/**` writable it could persist injected instructions into rule/skill files. Drop these two entries if your workflow does not need in-session writes there.\n- **`:root` / `:minimal` write**: package runners such as `npx {package}` unpack into the npm cache under the home directory (`~/.npm/_npx`), and many dev tools write to home-directory caches (`~/.cache`, `~/.local`, corepack/pnpm stores); the `:workspace` baseline denies these writes, which breaks the commands outright. `\":root\" = \"write\"` alone is not enough: rulesync emits `\":minimal\" = \"read\"` by default (the platform-default system paths needed for sandboxed command execution), and Codex treats that entry as narrowing the broader `:root` grant — the policy no longer qualifies for full disk write access, so writes that `:root` appears to allow can still be denied. Raise `\":minimal\"` to write alongside `\":root\"` to get the intended effect. Trade-off: the pair grants the sandbox full disk write access, including platform system paths — a compromised or prompt-injected session could then modify shell startup files, `PATH` binaries, or system configuration outside the workspace, effectively neutralizing the sandbox's write isolation. Prefer narrower home-directory patterns instead (e.g. `\"~/.npm/**\"`, `\"~/.cache/**\"`) unless you specifically need system-wide writes, at the cost of chasing each tool's cache path.\n- **`:tmpdir` / `:slash_tmp` write**: many build tools require a writable temp directory (`$TMPDIR` and `/tmp` respectively).\n- **`~/.codex/**` read with `auth.json` deny**: Codex can read its own configuration tree while your credentials stay protected. Tilde paths are expanded by Codex itself, so no manual `$HOME` resolution is needed.\n- **`glob_scan_max_depth`**: no need to add it — rulesync emits the Codex default (`8`) automatically whenever the generated workspace-root rules contain unbounded `**` patterns (the default `.git/**` carve-out already is one).\n\nSee the [Codex permissions reference](https://developers.openai.com/codex/permissions) for the full path and network syntax.\n\n## Generated rule files create noise in pull request diffs\n\nBecause many AI coding tools (Claude Code, Cursor, Copilot, Antigravity, etc.) need to read their rule files directly from the working tree, the files rulesync generates are intentionally not `.gitignore`d. On repositories with many targets, the generated files can dominate a pull request diff and make code review harder.\n\n**Workaround:** Add the generated paths to `.gitattributes` with the [`linguist-generated`](https://docs.github.com/en/repositories/working-with-files/managing-files/customizing-how-changed-files-appear-on-github#marking-files-as-generated) attribute. GitHub's PR UI will then collapse those files by default while still keeping them visible and loadable by the tools themselves.\n\nExample `.gitattributes` for a repo that uses `.agent/`, Claude Code, Cursor, and Copilot targets:\n\n```\n.agent/rules/** linguist-generated\n.agent/skills/** linguist-generated\n.agent/workflows/** linguist-generated\nCLAUDE.md linguist-generated\n.cursor/rules/** linguist-generated\n.github/copilot-instructions.md linguist-generated\n```\n\nAdjust the list to match the targets you have configured. These entries only affect how GitHub displays the files in diffs — they don't change how Git tracks them, and they don't interfere with the tools reading the rules.\n",
4307
+ faq: "# FAQ\n\n## `rulesync generate` doesn't produce what I expect\n\nRun `rulesync doctor` first. It performs read-only diagnostics on `rulesync.jsonc` and `rulesync.local.jsonc` and reports problems the generator silently tolerates — most importantly misspelled or unknown configuration keys (the config schema is non-strict, so a typo like `\"target\"` instead of `\"targets\"` is otherwise ignored and generation quietly falls back to defaults). See the [Doctor Command](./reference/cli-commands.md#doctor-command) reference for the full list of checks.\n\n## The generated `.mcp.json` doesn't work properly in Claude Code\n\nYou can try adding the following to `.claude/settings.json` or `.claude/settings.local.json`:\n\n```diff\n{\n+ \"enableAllProjectMcpServers\": true\n}\n```\n\nAccording to [the documentation](https://code.claude.com/docs/en/settings), this means:\n\n> Automatically approve all MCP servers defined in project .mcp.json files\n\n## Google Antigravity doesn't load rules when `.agents` directories are in `.gitignore`\n\nGoogle Antigravity has a known limitation where it won't load rules, workflows, and skills if the `.agents/rules/`, `.agents/workflows/`, and `.agents/skills/` directories are listed in `.gitignore`, even with \"Agent Gitignore Access\" enabled.\n\n> **Note:** Antigravity 2.0 uses the plural `.agents/` directory by default (the `antigravity-ide` and `antigravity-cli` targets).\n\n**Workaround:** Instead of adding these directories to `.gitignore`, add them to `.git/info/exclude`:\n\n```bash\n# Remove from .gitignore (if present)\n# **/.agents/rules/\n# **/.agents/workflows/\n# **/.agents/skills/\n\n# Add to .git/info/exclude\necho \"**/.agents/rules/\" >> .git/info/exclude\necho \"**/.agents/workflows/\" >> .git/info/exclude\necho \"**/.agents/skills/\" >> .git/info/exclude\n```\n\n`.git/info/exclude` works like `.gitignore` but is local-only, so it won't affect Antigravity's ability to load the rules while still excluding these directories from Git.\n\nNote: `.git/info/exclude` can't be shared with your team since it's not committed to the repository.\n\n## Codex CLI denies SSH agent access, temp-dir writes, or reading its own config with a generated permissions profile\n\nThe `[permissions.rulesync]` profile that rulesync generates into `.codex/config.toml` extends Codex CLI's `:workspace` baseline. That baseline is deliberately conservative, so day-to-day development can still hit permission denials: `git push`/`git fetch` over SSH cannot reach the SSH agent socket, some build tools fail without a writable temp dir, and Codex may be blocked from reading its own `~/.codex` configuration.\n\nrulesync emits the `.git` write carve-out for you (`\".git/**\" = \"write\"` under `:workspace_roots`; opt out with the `codexcli.git_write_rules: false` override). The whole subtree — including `.git/config` — is writable, because everyday commands such as `git remote add`, `git push -u`, and local-scope `git config` write to the repository config; users who want stricter isolation can add their own `read` override (e.g. `read: { \".git/config\": \"allow\" }`) in the canonical permissions. Everything below, however, depends on your environment or workflow, so rulesync does not add it by default. Where to put each piece differs, because the two tables are managed differently:\n\n**Network settings: edit `.codex/config.toml` directly.** Network settings are out of rulesync's management scope by design, keeping you free to edit them. rulesync preserves user-authored network keys when it regenerates the file — `network.enabled` (as long as the profile carries no rulesync-managed allow domains) and unknown keys such as `dangerously_allow_all_unix_sockets` are carried forward verbatim, with a warning so they stay visible:\n\n```toml\n[permissions.rulesync.network]\nenabled = true\n# Simplest option: allow all unix sockets. Codex names this \"dangerously_*\"\n# because it is broad, but it avoids hardcoding an env-dependent socket path.\ndangerously_allow_all_unix_sockets = true\n\n# Stricter alternative: allow only the SSH agent socket.\n# Replace the path with the actual value of $SSH_AUTH_SOCK on your machine;\n# Codex does not expand environment variables in these keys.\n# [permissions.rulesync.network.unix_sockets]\n# \"/path/to/ssh-agent.sock\" = \"allow\"\n```\n\n**Filesystem entries: author them in `.rulesync/permissions.jsonc`, not in `config.toml`.** The profile's `filesystem` table is fully managed — hand-written entries there are replaced on the next `rulesync generate`. Add the rules to the canonical config instead (use the tool-scoped `codexcli.permission` block so they do not leak into other tools' outputs) and regenerate:\n\n```jsonc\n{\n \"permission\": {\n // ...your shared rules...\n },\n \"codexcli\": {\n \"permission\": {\n \"write\": {\n \".\": \"allow\",\n \".git/**\": \"allow\",\n \".agents/**\": \"allow\",\n \".codex/**\": \"allow\",\n \":root\": \"allow\",\n \":minimal\": \"allow\",\n \":tmpdir\": \"allow\",\n \":slash_tmp\": \"allow\",\n },\n \"read\": { \"~/.codex/**\": \"allow\", \"~/.codex/auth.json\": \"deny\" },\n },\n },\n}\n```\n\nNote that this example is intentionally permissive: the `\":root\"` + `\":minimal\"` write pair grants the sandbox full disk write access — see the trade-off in the entry list below for narrower alternatives.\n\nThis generates into the profile as `\".\" = \"write\"`, `\".git/**\" = \"write\"`, `\".agents/**\" = \"write\"`, and `\".codex/**\" = \"write\"` under `:workspace_roots`, plus `\":root\" = \"write\"`, `\":minimal\" = \"write\"`, `\":tmpdir\" = \"write\"`, `\":slash_tmp\" = \"write\"`, `\"~/.codex/**\" = \"read\"`, and `\"~/.codex/auth.json\" = \"deny\"`, and round-trips through `rulesync import` — with two exceptions. First, `\".git/**\" = \"write\"` matches rulesync's default carve-out exactly, so import skips it (it is re-added on every generate); if you later opt out with `codexcli.git_write_rules: false` after an import, re-author the `\".git/**\": \"allow\"` write rule in the canonical config. Second, `\":minimal\"` is never imported regardless of its value — rulesync treats it as its fixed `\"read\"` baseline — so after an import, re-author the `\":minimal\": \"allow\"` write rule as well or the next generate silently drops back to `\":minimal\" = \"read\"`. Note that a tool-scoped category replaces the shared one wholesale for Codex CLI: if your shared `permission` block already has `read`/`write` rules that should also apply to Codex CLI, repeat them inside `codexcli.permission`.\n\nWhat each entry does:\n\n- **Unix socket access**: `git push`/`git fetch` over SSH needs the agent socket. `dangerously_allow_all_unix_sockets = true` is the simple, environment-independent option; a per-socket `unix_sockets` allow entry with the resolved `$SSH_AUTH_SOCK` path is the stricter one.\n- **`.` / `.git/**` / `.agents/**` / `.codex/**` write**: the practical write set for the workspace itself. `\".\"` spells out the workspace-subtree write access the `:workspace` baseline already grants (a tool-scoped category replaces the shared block wholesale, so keeping it explicit avoids surprises), and `\".git/**\"` matches the carve-out rulesync emits by default anyway. `.agents/**` and `.codex/**` genuinely add access: Codex's `:workspace` baseline keeps `.git`, `.agents`, and `.codex` read-only inside workspace roots, so without these rules a Codex session cannot update agent files or its own project-level config — for example, running `rulesync generate` inside a session would be denied when writing `.agents/` or `.codex/` outputs. Trade-off: the baseline keeps those two directories read-only precisely so a sandboxed session cannot rewrite its own configuration — with `.codex/**` writable, a compromised or prompt-injected session could relax `.codex/config.toml` (approval policy, permission profiles, MCP servers) for its next run, and with `.agents/**` writable it could persist injected instructions into rule/skill files. Drop these two entries if your workflow does not need in-session writes there.\n- **`:root` / `:minimal` write**: package runners such as `npx {package}` unpack into the npm cache under the home directory (`~/.npm/_npx`), and many dev tools write to home-directory caches (`~/.cache`, `~/.local`, corepack/pnpm stores); the `:workspace` baseline denies these writes, which breaks the commands outright. `\":root\" = \"write\"` alone is not enough: rulesync emits `\":minimal\" = \"read\"` by default (the platform-default system paths needed for sandboxed command execution), and Codex treats that entry as narrowing the broader `:root` grant — the policy no longer qualifies for full disk write access, so writes that `:root` appears to allow can still be denied. Raise `\":minimal\"` to write alongside `\":root\"` to get the intended effect. Trade-off: the pair grants the sandbox full disk write access, including platform system paths — a compromised or prompt-injected session could then modify shell startup files, `PATH` binaries, or system configuration outside the workspace, effectively neutralizing the sandbox's write isolation. Prefer narrower home-directory patterns instead (e.g. `\"~/.npm/**\"`, `\"~/.cache/**\"`) unless you specifically need system-wide writes, at the cost of chasing each tool's cache path.\n- **`:tmpdir` / `:slash_tmp` write**: many build tools require a writable temp directory (`$TMPDIR` and `/tmp` respectively).\n- **`~/.codex/**` read with `auth.json` deny**: Codex can read its own configuration tree while your credentials stay protected. Tilde paths are expanded by Codex itself, so no manual `$HOME` resolution is needed.\n- **`glob_scan_max_depth`**: no need to add it — rulesync emits the Codex default (`8`) automatically whenever the generated workspace-root rules contain unbounded `**` patterns (the default `.git/**` carve-out already is one).\n\nSee the [Codex permissions reference](https://developers.openai.com/codex/permissions) for the full path and network syntax.\n\n## Generated rule files create noise in pull request diffs\n\nBecause many AI coding tools (Claude Code, Cursor, Copilot, Antigravity, etc.) need to read their rule files directly from the working tree, the files rulesync generates are intentionally not `.gitignore`d. On repositories with many targets, the generated files can dominate a pull request diff and make code review harder.\n\n**Workaround:** Add the generated paths to `.gitattributes` with the [`linguist-generated`](https://docs.github.com/en/repositories/working-with-files/managing-files/customizing-how-changed-files-appear-on-github#marking-files-as-generated) attribute. GitHub's PR UI will then collapse those files by default while still keeping them visible and loadable by the tools themselves.\n\nExample `.gitattributes` for a repo that uses `.agent/`, Claude Code, Cursor, and Copilot targets:\n\n```\n.agent/rules/** linguist-generated\n.agent/skills/** linguist-generated\n.agent/workflows/** linguist-generated\nCLAUDE.md linguist-generated\n.cursor/rules/** linguist-generated\n.github/copilot-instructions.md linguist-generated\n```\n\nAdjust the list to match the targets you have configured. These entries only affect how GitHub displays the files in diffs — they don't change how Git tracks them, and they don't interfere with the tools reading the rules.\n\n## How do I keep many repositories in sync with a shared source?\n\nRulesync stops at the repository boundary. A consumer repository declares its `sources` in `rulesync.jsonc`, pins what it resolved in `rulesync.lock`, and moves forward only when someone runs `rulesync install --update` there — the same model as an npm or Bun lockfile. Nothing in the tool schedules that run, watches the shared repository, or walks other clones, so with fifteen consumers the layer above the lockfile is yours to shape, and either of the two obvious shapes works:\n\n- **On demand.** Run `rulesync install --update && rulesync generate` in a repository when you want it to pick up the shared changes, review the diff, and commit the lockfile with the regenerated files.\n- **Scheduled.** A cron-triggered CI job that runs the same two commands and opens a pull request when the lockfile changed is an ordinary way to drive rulesync; nothing in the tool assumes the update is manual. Compare the lockfile with `resolvedAt` ignored (the `-I` flag needs Git 2.30 or newer): `--update` stamps a fresh timestamp on every source it re-resolves, so the file changes even when no `resolvedRef` did.\n\n ```bash\n rulesync install --update && rulesync generate\n git diff --quiet -I '\"resolvedAt\"' -- rulesync.lock rulesync-npm.lock.json || echo \"shared source moved: open a pull request\"\n ```\n\nKeep `rulesync doctor --strict && rulesync install --frozen && rulesync generate --check` in the consumer's CI either way; that is what guards a repository whose lockfile has fallen behind its own `rulesync.jsonc` or whose generated files have drifted, independent of how updates are triggered.\n\nThere is no read-only command that reports how far a lockfile is behind its source. `generate --dry-run` covers generation, not source resolution, and `install --frozen` checks that the lockfile covers the declared sources, not that it is current. To see which repositories are behind without changing anything, compare the `resolvedRef` in each repository's `rulesync.lock` with the head of the branch its `requestedRef` names (`gh api repos/<owner>/<repo>/commits/<branch> --jq .sha` for a GitHub source; npm sources in `rulesync-npm.lock.json` record a `resolvedVersion` instead), or run the scheduled job above with the commit step removed and read its diff.\n\nUpdating many repositories at once is a loop over clones that does, per repository, exactly what a single one does: skip a dirty working tree, run `rulesync install --update && rulesync generate`, run the CI guard, and commit. Rulesync does not orchestrate that loop, and the shared repository does not have to know who consumes it.\n",
4308
4308
  "getting-started/installation": "# Installation\n\n## Package Managers\n\n```bash\nnpm install -g rulesync\n\n# And then\nrulesync --version\nrulesync --help\n```\n\n## Homebrew (macOS and Linux)\n\nrulesync ships a self-contained [Homebrew](https://brew.sh/) tap inside this\nrepository. Because the repository is not named `homebrew-rulesync`, you must use\nthe two-argument `brew tap <name> <url>` form to add it — the auto-tap shorthand\n`brew install dyoshikawa/rulesync/rulesync` cannot resolve it on its own:\n\n```bash\nbrew tap dyoshikawa/rulesync https://github.com/dyoshikawa/rulesync\nbrew install rulesync\n\n# And then\nrulesync --version\n```\n\nThe formula installs the prebuilt binary for your platform (macOS/Linux, arm64\nand x64), so it does not depend on a Node.js runtime. It is updated as part of\neach release. Homebrew does not support Windows; use npm or the\nsingle-binary download below there.\n\n## Single Binary\n\nDownload pre-built binaries from the [latest release](https://github.com/dyoshikawa/rulesync/releases/latest). These binaries are built using [Bun's single-file executable bundler](https://bun.sh/docs/bundler/executables).\n\n**Quick Install (Linux/macOS - No sudo required):**\n\n```bash\ncurl -fsSL https://github.com/dyoshikawa/rulesync/releases/latest/download/install.sh | bash\n```\n\nOptions:\n\n- Install specific version: `curl -fsSL https://github.com/dyoshikawa/rulesync/releases/latest/download/install.sh | bash -s -- v6.4.0`\n- Custom directory: `RULESYNC_HOME=~/.local curl -fsSL https://github.com/dyoshikawa/rulesync/releases/latest/download/install.sh | bash`\n\n::: details Manual installation (requires sudo)\n\n### Linux (x64)\n\n```bash\ncurl -L https://github.com/dyoshikawa/rulesync/releases/latest/download/rulesync-linux-x64 -o rulesync && \\\n chmod +x rulesync && \\\n sudo mv rulesync /usr/local/bin/\n```\n\n### Linux (ARM64)\n\n```bash\ncurl -L https://github.com/dyoshikawa/rulesync/releases/latest/download/rulesync-linux-arm64 -o rulesync && \\\n chmod +x rulesync && \\\n sudo mv rulesync /usr/local/bin/\n```\n\n### macOS (Apple Silicon)\n\n```bash\ncurl -L https://github.com/dyoshikawa/rulesync/releases/latest/download/rulesync-darwin-arm64 -o rulesync && \\\n chmod +x rulesync && \\\n sudo mv rulesync /usr/local/bin/\n```\n\n### macOS (Intel)\n\n```bash\ncurl -L https://github.com/dyoshikawa/rulesync/releases/latest/download/rulesync-darwin-x64 -o rulesync && \\\n chmod +x rulesync && \\\n sudo mv rulesync /usr/local/bin/\n```\n\n### Windows (x64)\n\n```powershell\nInvoke-WebRequest -Uri \"https://github.com/dyoshikawa/rulesync/releases/latest/download/rulesync-windows-x64.exe\" -OutFile \"rulesync.exe\"; `\n Move-Item rulesync.exe C:\\Windows\\System32\\\n```\n\nOr using curl (if available):\n\n```bash\ncurl -L https://github.com/dyoshikawa/rulesync/releases/latest/download/rulesync-windows-x64.exe -o rulesync.exe && \\\n mv rulesync.exe /path/to/your/bin/\n```\n\n### Verify checksums\n\n```bash\ncurl -L https://github.com/dyoshikawa/rulesync/releases/latest/download/SHA256SUMS -o SHA256SUMS\n\n# Linux/macOS\nsha256sum -c SHA256SUMS\n\n# Windows (PowerShell)\n# Download SHA256SUMS file first, then verify:\nGet-FileHash rulesync.exe -Algorithm SHA256 | ForEach-Object {\n $actual = $_.Hash.ToLower()\n $expected = (Get-Content SHA256SUMS | Select-String \"rulesync-windows-x64.exe\").ToString().Split()[0]\n if ($actual -eq $expected) { \"✓ Checksum verified\" } else { \"✗ Checksum mismatch\" }\n}\n```\n\n### Verify build provenance\n\nRelease binaries carry [GitHub Artifact Attestations](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations), so you can check that the file you downloaded really was built by this repository's release workflow. This needs the [GitHub CLI](https://cli.github.com/) v2.49.0 or later, which is where `gh attestation` was introduced, and a signed-in CLI (`gh auth login`) — verification queries the API even for a public repository.\n\n```bash\n# Linux/macOS — the path the steps above installed the binary to\ngh attestation verify /usr/local/bin/rulesync \\\n --repo dyoshikawa/rulesync \\\n --signer-workflow dyoshikawa/rulesync/.github/workflows/publish-assets.yml\n```\n\n```powershell\n# Windows\ngh attestation verify C:\\Windows\\System32\\rulesync.exe `\n --repo dyoshikawa/rulesync `\n --signer-workflow dyoshikawa/rulesync/.github/workflows/publish-assets.yml\n```\n\nPass the path you actually installed the binary to. The command identifies the file by its contents, not by its name, so renaming it during installation — which the steps above do — does not affect verification; a binary installed by `install.sh` or Homebrew is the same file and verifies the same way. `--repo` alone only proves the attestation came from this repository, so `--signer-workflow` is included to pin the workflow that signed it.\n\nThis covers the release binaries. The npm package carries npm's own provenance attestation instead, which is checked with `npm audit signatures` rather than `gh attestation verify`.\n\n:::\n",
4309
4309
  "getting-started/quick-start": "# Quick Start\n\n## New Project\n\n```bash\n# Install rulesync globally\nnpm install -g rulesync\n\n# Create necessary directories, sample rule files, and configuration file\nrulesync init\n\n# Install official skills (recommended)\nrulesync fetch dyoshikawa/rulesync\n\n# Or add skill sources to rulesync.jsonc and run 'rulesync install' (see \"Declarative Skill Sources\")\n```\n\n## Existing AI Tool Configurations\n\nIf you already have AI tool configurations:\n\n```bash\n# Import existing files (to .rulesync/**/*)\nrulesync import --targets claudecode # From CLAUDE.md\nrulesync import --targets cursor # From .cursorrules\nrulesync import --targets copilot # From .github/copilot-instructions.md\nrulesync import --targets claudecode --features rules,mcp,commands,subagents\n\n# And more tool supports\n\n# Generate unified configurations with all features\nrulesync generate --targets \"*\" --features \"*\"\n```\n\n## Quick Commands\n\nFor a comprehensive list of all commands and options, see [CLI Commands](/reference/cli-commands).\n",
4310
4310
  "guide/case-studies": "# Case Studies\n\nRulesync is trusted by leading companies and recognized by the industry:\n\n- **Anthropic Official Customer Story**: [Classmethod Inc. - Improving AI coding tool consistency with Rulesync](https://claude.com/customers/classmethod)\n- **Asoview Inc.**: [Adopting Rulesync for unified AI development rules](https://tech.asoview.co.jp/entry/2025/12/06/100000)\n- **KAKEHASHI Tech Blog**: [Building multilingual systems for the LLM era with a monorepo and a \"living specification\"](https://kakehashi-dev.hatenablog.com/entry/2025/12/08/110000)\n- **Cloudflare**: [Adopting Rulesync for AI coding assistant configuration](https://github.com/cloudflare/cloudflare-docs/pull/28232)\n- **Ripple**: [Migrating agent rule management to Rulesync](https://github.com/Ripple-TS/ripple/commit/114bcf791c957ab5d43fcc6369515b59b866ce80)\n- **VOICEVOX**: [Adding Rulesync to unify AI coding assistant rules](https://github.com/VOICEVOX/voicevox/pull/2918)\n- **Effect**: [Managing agent rules with Rulesync](https://github.com/Effect-TS/effect-smol/pull/986)\n- **AG Grid**: [Syncing shared AI rules via Rulesync](https://github.com/ag-grid/ag-grid/pull/13044)\n- **Red Hat Developer Hub**: [Adding Rulesync to synchronize AI Assistant rules](https://github.com/redhat-developer/rhdh/pull/3707)\n",
4311
- "guide/configuration": "# Configuration\n\nYou can configure Rulesync by creating a `rulesync.jsonc` file in the root of your project.\n\n## JSON Schema Support\n\nRulesync provides a JSON Schema for editor validation and autocompletion. Add the `$schema` property to your `rulesync.jsonc`:\n\n```jsonc\n// rulesync.jsonc\n{\n \"$schema\": \"https://github.com/dyoshikawa/rulesync/releases/latest/download/config-schema.json\",\n \"targets\": [\"claudecode\"],\n \"features\": [\"rules\"],\n}\n```\n\n## Configuration Options\n\nExample:\n\n```jsonc\n// rulesync.jsonc\n{\n \"$schema\": \"https://github.com/dyoshikawa/rulesync/releases/latest/download/config-schema.json\",\n\n // List of tools to generate configurations for. You can specify \"*\" to generate all tools.\n \"targets\": [\"cursor\", \"claudecode\", \"opencode\", \"codexcli\"],\n\n // Features to generate. You can specify \"*\" to generate all features.\n \"features\": [\"rules\", \"mcp\", \"commands\", \"subagents\", \"hooks\", \"permissions\"],\n\n // Output root directories to generate files into.\n // Basically, you can specify `[\".\"]` only.\n // However, for example, if your project is a monorepo and you have to launch the AI agent at each package directory, you can specify multiple output roots.\n \"outputRoots\": [\".\"],\n\n // Delete existing files before generating\n \"delete\": true,\n\n // Verbose output\n \"verbose\": false,\n\n // Silent mode - suppress all output (except errors)\n \"silent\": false,\n\n // Advanced options\n \"global\": false, // Generate for global(user scope) configuration files\n \"simulateCommands\": false, // Generate simulated commands\n \"simulateSubagents\": false, // Generate simulated subagents\n \"simulateSkills\": false, // Generate simulated skills\n\n // Keep hook handlers Rulesync did not write when regenerating a tool's hooks\n // file. By default the generated hooks replace the destination's hook list\n // wholesale, so a handler another tool (or a person) added by hand is lost on\n // the next `generate`. Turning this on keeps such handlers and appends the\n // generated ones. It applies to Claude Code, Codex CLI and Cursor, in both\n // project and global scope; the Claude Code *plugin* bundle always replaces,\n // because Rulesync owns that directory outright. There is no CLI flag: this\n // is a project policy, not a per-invocation one.\n //\n // To stay able to retract a hook it did write, Rulesync records what it\n // generated in a `.rulesync-hooks-lock.json` next to each hooks file (e.g.\n // `.claude/.rulesync-hooks-lock.json`). Commit it alongside the generated\n // hooks. A handler listed there that the sources no longer define is removed;\n // anything else is kept. The very first run after opting in has no lock yet,\n // so a hook Rulesync wrote before is kept once and retracted from the run\n // after that.\n \"preserveUnownedHooks\": false,\n\n // Derive `agentsmd.subprojectPath` from each non-root rule's `globs`, so a\n // rule with `globs: [\"packages/api/**/*\"]` is written as\n // `packages/api/AGENTS.md` (nested AGENTS.md) by the targets that nest,\n // instead of `.agents/memories/<rule>.md`. The directory is the leading\n // wildcard-free part every glob shares; a rule whose globs have none (e.g.\n // `[\"src/**/*.ts\", \"test/**/*.ts\"]`) or disagree silently keeps its default\n // placement. Only a rule that sets `agentsmd: { subprojectPath: \"auto\" }`\n // itself is warned about when nothing can be derived; that value also opts\n // a single rule in regardless of this option, and `agentsmd: {\n // subprojectPath: \"\" }` opts a single rule out. An explicit directory\n // always wins and `root: true` rules never nest.\n // Turning it on moves existing outputs, so run `generate --delete` once.\n \"deriveSubprojectPathFromGlobs\": false,\n\n // Naming for command files flattened for tools without subdirectory\n // command support (e.g. Cursor): \"basename\" (default) keeps only the\n // filename, so `pj/test.md` and `ops/test.md` collide and the last one\n // wins; \"path\" joins the directory segments into the filename\n // (`pj/test.md` -> `pj-test.md`), which reduces collisions but cannot\n // rule them out (a literal `pj-test.md` also maps to `pj-test.md`); the\n // collision warning still applies.\n // Tools that support subdirectories (e.g. Claude Code) are unaffected.\n // Note: switching from \"basename\" to \"path\" renames the generated files\n // (e.g. `test.md` -> `pj-test.md`); run `rulesync generate` with\n // `delete: true` (or `--delete`) once after switching, otherwise the\n // stale old flat-named files remain alongside the new ones.\n \"flattenedCommandNaming\": \"basename\",\n\n // Language the AI should answer in. Omit it to say nothing about language.\n // See the \"Response Language\" section for what each tool receives.\n // \"language\": \"ja\",\n\n // When true (default), `rulesync gitignore` only emits entries for the\n // tools listed in `targets`. Set to false to emit entries for all supported\n // tools regardless of `targets`.\n //\n // Note: Entries for `agentsmd` (AGENTS.md and related paths) are always\n // appended even when `gitignoreTargetsOnly` is true and `agentsmd` is\n // absent from `targets`. AGENTS.md is a de facto standard read by many AI\n // tools regardless of the target set, so its gitignore entries are emitted\n // unconditionally to prevent accidental commits of generated rule files.\n \"gitignoreTargetsOnly\": true,\n\n // Declarative rule and skill sources — installed via 'rulesync install'\n // See the \"Declarative Sources\" section for details.\n // \"sources\": [\n // { \"source\": \"owner/repo\" },\n // { \"source\": \"org/repo\", \"skills\": [\"specific-skill\"] },\n // { \"source\": \"org/standards\", \"rules\": [\"testing-guidelines\"] },\n // ],\n}\n```\n\n## Per-Target Features\n\nThe `targets` option accepts both an array and an object format. Use the\nobject format when you want to declare per-target feature configuration in\na single place — the object keys are the target tools, and each value\ncarries the features to generate for that tool:\n\n```jsonc\n// rulesync.jsonc\n{\n \"targets\": {\n \"claudecode\": [\"rules\", \"commands\"],\n \"cursor\": [\"rules\", \"mcp\"],\n \"copilot\": [\"rules\", \"subagents\"],\n },\n}\n```\n\nIn this example:\n\n- `claudecode` generates rules and commands\n- `cursor` generates rules and MCP configuration\n- `copilot` generates rules and subagents\n\n> **Important:** When `targets` is in object form, the top-level `features`\n> field must be omitted. Declaring both would double-define the target\n> set, so the config loader rejects that combination.\n\nYou can also use `*` (wildcard) inside a target's value to enable every\nfeature for that tool:\n\n```jsonc\n{\n \"targets\": {\n \"claudecode\": [\"*\"], // Generate all features for Claude Code\n \"cursor\": [\"rules\"], // Only rules for Cursor\n },\n}\n```\n\n### Per-feature options\n\nSome features accept additional configuration. To pass options through, use\nthe object form for a target's value instead of an array. Each feature key\nmaps to either `true`/`false` (enable/disable) or an options object.\n\n```jsonc\n{\n \"gitignoreDestination\": \"gitignore\",\n \"targets\": {\n \"claudecode\": {\n \"gitignoreDestination\": \"gitattributes\",\n \"rules\": { \"ruleDiscoveryMode\": \"explicit\" },\n \"ignore\": {\n \"fileMode\": \"local\",\n \"gitignoreDestination\": \"gitignore\",\n },\n },\n },\n}\n```\n\n`gitignoreDestination` controls where `rulesync gitignore` writes path entries.\nYou can set it:\n\n- at **root level** (`gitignoreDestination`)\n- at **tool level** (`targets.<tool>.gitignoreDestination`)\n- or at **tool × feature level**\n (`targets.<tool>.<feature>.gitignoreDestination`)\n\nAllowed values:\n\n- `\"gitignore\"` (default)\n- `\"gitattributes\"`\n\nPriority is **more specific wins**:\n\n1. tool × feature level\n2. tool level\n3. root level\n4. default (`\"gitignore\"`)\n\nThe current per-feature options are:\n\n| Target | Feature | Option | Values | Default |\n| ------------ | -------- | ---------------------- | ------------------------------------------------------------------------------ | ------------- |\n| `claudecode` | `rules` | `ruleDiscoveryMode` | `\"none\"` / `\"explicit\"` | tool default |\n| any | `rules` | `includeLocalRoot` | `true` / `false` (when `false`, `localRoot` rules are skipped for this target) | `true` |\n| `claudecode` | `ignore` | `fileMode` | `\"shared\"` (settings.json) / `\"local\"` (settings.local.json) | `\"shared\"` |\n| any | any | `gitignoreDestination` | `\"gitignore\"` / `\"gitattributes\"` | `\"gitignore\"` |\n\nSee [`docs/reference/file-formats.md`](../reference/file-formats.md#where-ignore-patterns-are-written-per-tool)\nfor the rationale behind the Claude Code default and when to switch to\n`\"local\"`.\n\n## Response Language\n\nThe root `language` key steers the language the AI answers in. It accepts one of `en`, `ja`, `zh-CN`, `zh-TW`, `ko`, `fr`, `de`, `es`, `pt-BR`, `ru`, has no CLI flag, and can be overridden per developer from `rulesync.local.jsonc`. When it is omitted, Rulesync says nothing about language; `en` is therefore an explicit instruction, not the default.\n\n```jsonc\n// rulesync.jsonc\n{\n \"language\": \"ja\",\n}\n```\n\nWhat the `rules` feature generates from it depends on the tool:\n\n- **Claude Code** has a native `language` setting, so the key is written as `\"language\": \"japanese\"` into `.claude/settings.local.json` (project scope) or `~/.claude/settings.json` (global scope, since Claude Code reads no `~/.claude/settings.local.json`) and `CLAUDE.md` is left as it is. Only the `language` key is touched; everything else in the settings file is preserved, and the file is never created or modified while the key is unset. Removing `language` later does not retract the key already written: the settings file is shared with your own configuration, so Rulesync never deletes from it, and the value stays until you remove it by hand.\n- **Every other tool** gets the instruction appended to the generated root rule file — the file built from your `root: true` rule (`AGENTS.md`, `GEMINI.md`, `.github/copilot-instructions.md`, `.cursor/rules/overview.mdc`, and so on). Nested rules never carry it, and neither does a `localRoot: true` rule's separate personal file. For a tool that files every rule side by side (Cursor and the fixed-name targets) with more than one `root: true` rule, only the file built from the first root rule carries the block. The block is separated from your content by a thematic break:\n\n ```md\n ---\n\n You must always answer in Japanese. On the other hand, reasoning (thinking) should be in English to improve token efficiency.\n ```\n\n `rulesync import` recognizes the block for every supported language and strips it (with a warning, since the instruction is not carried into `.rulesync/rules/` — the `language` key in `rulesync.jsonc` is what keeps it), so importing a generated file and generating again yields one block rather than two. `rulesync convert` re-adds the block to the destination's root file when `language` is set.\n\n## Local Configuration\n\nRulesync supports a local configuration file (`rulesync.local.jsonc`) for machine-specific or developer-specific settings. This file is automatically added to `.gitignore` by `rulesync gitignore` and should not be committed to the repository.\n\n**Configuration Priority** (highest to lowest):\n\n1. CLI options\n2. `rulesync.local.jsonc`\n3. `rulesync.jsonc`\n4. Default values\n\nExample usage:\n\n```jsonc\n// rulesync.local.jsonc (not committed to git)\n{\n \"$schema\": \"https://github.com/dyoshikawa/rulesync/releases/latest/download/config-schema.json\",\n // Override targets for local development\n \"targets\": [\"claudecode\"],\n // Enable verbose output for debugging\n \"verbose\": true,\n}\n```\n\n## Target Order and File Conflicts\n\nWhen multiple targets write to the same output file, **the last target in the array wins**. This is the \"last-wins\" behavior.\n\nFor example, both `agentsmd` and `opencode` generate `AGENTS.md`:\n\n```jsonc\n{\n // opencode wins because it comes last\n \"targets\": [\"agentsmd\", \"opencode\"],\n \"features\": [\"rules\"],\n}\n```\n\nIn this case:\n\n1. `agentsmd` generates `AGENTS.md` first\n2. `opencode` generates `AGENTS.md` second, overwriting the previous file\n\nIf you want `agentsmd`'s output instead, reverse the order:\n\n```jsonc\n{\n // agentsmd wins because it comes last\n \"targets\": [\"opencode\", \"agentsmd\"],\n \"features\": [\"rules\"],\n}\n```\n",
4311
+ "guide/configuration": "# Configuration\n\nYou can configure Rulesync by creating a `rulesync.jsonc` file in the root of your project.\n\n## JSON Schema Support\n\nRulesync provides a JSON Schema for editor validation and autocompletion. Add the `$schema` property to your `rulesync.jsonc`:\n\n```jsonc\n// rulesync.jsonc\n{\n \"$schema\": \"https://github.com/dyoshikawa/rulesync/releases/latest/download/config-schema.json\",\n \"targets\": [\"claudecode\"],\n \"features\": [\"rules\"],\n}\n```\n\n## Configuration Options\n\nExample:\n\n```jsonc\n// rulesync.jsonc\n{\n \"$schema\": \"https://github.com/dyoshikawa/rulesync/releases/latest/download/config-schema.json\",\n\n // List of tools to generate configurations for. You can specify \"*\" to generate all tools.\n \"targets\": [\"cursor\", \"claudecode\", \"opencode\", \"codexcli\"],\n\n // Features to generate. You can specify \"*\" to generate all features.\n \"features\": [\"rules\", \"mcp\", \"commands\", \"subagents\", \"hooks\", \"permissions\"],\n\n // Output root directories to generate files into.\n // Basically, you can specify `[\".\"]` only.\n // However, for example, if your project is a monorepo and you have to launch the AI agent at each package directory, you can specify multiple output roots.\n \"outputRoots\": [\".\"],\n\n // Delete existing files before generating\n \"delete\": true,\n\n // Verbose output\n \"verbose\": false,\n\n // Silent mode - suppress all output (except errors)\n \"silent\": false,\n\n // Advanced options\n \"global\": false, // Generate for global(user scope) configuration files\n \"simulateCommands\": false, // Generate simulated commands\n \"simulateSubagents\": false, // Generate simulated subagents\n \"simulateSkills\": false, // Generate simulated skills\n\n // Keep hook handlers Rulesync did not write when regenerating a tool's hooks\n // file. By default the generated hooks replace the destination's hook list\n // wholesale, so a handler another tool (or a person) added by hand is lost on\n // the next `generate`. Turning this on keeps such handlers and appends the\n // generated ones. It applies to Claude Code, Codex CLI and Cursor, in both\n // project and global scope; the Claude Code *plugin* bundle always replaces,\n // because Rulesync owns that directory outright. There is no CLI flag: this\n // is a project policy, not a per-invocation one.\n //\n // To stay able to retract a hook it did write, Rulesync records what it\n // generated in a `.rulesync-hooks-lock.json` next to each hooks file (e.g.\n // `.claude/.rulesync-hooks-lock.json`). Commit it alongside the generated\n // hooks. A handler listed there that the sources no longer define is removed;\n // anything else is kept. The very first run after opting in has no lock yet,\n // so a hook Rulesync wrote before is kept once and retracted from the run\n // after that.\n \"preserveUnownedHooks\": false,\n\n // Derive `agentsmd.subprojectPath` from each non-root rule's `globs`, so a\n // rule with `globs: [\"packages/api/**/*\"]` is written as\n // `packages/api/AGENTS.md` (nested AGENTS.md) by the targets that nest,\n // instead of `.agents/memories/<rule>.md`. The directory is the leading\n // wildcard-free part every glob shares; a rule whose globs have none (e.g.\n // `[\"src/**/*.ts\", \"test/**/*.ts\"]`) or disagree silently keeps its default\n // placement. Only a rule that sets `agentsmd: { subprojectPath: \"auto\" }`\n // itself is warned about when nothing can be derived; that value also opts\n // a single rule in regardless of this option, and `agentsmd: {\n // subprojectPath: \"\" }` opts a single rule out. An explicit directory\n // always wins and `root: true` rules never nest.\n // Turning it on moves existing outputs, so run `generate --delete` once.\n \"deriveSubprojectPathFromGlobs\": false,\n\n // Naming for command files flattened for tools without subdirectory\n // command support (e.g. Cursor): \"basename\" (default) keeps only the\n // filename, so `pj/test.md` and `ops/test.md` collide and the last one\n // wins; \"path\" joins the directory segments into the filename\n // (`pj/test.md` -> `pj-test.md`), which reduces collisions but cannot\n // rule them out (a literal `pj-test.md` also maps to `pj-test.md`); the\n // collision warning still applies.\n // Tools that support subdirectories (e.g. Claude Code) are unaffected.\n // Note: switching from \"basename\" to \"path\" renames the generated files\n // (e.g. `test.md` -> `pj-test.md`); run `rulesync generate` with\n // `delete: true` (or `--delete`) once after switching, otherwise the\n // stale old flat-named files remain alongside the new ones.\n \"flattenedCommandNaming\": \"basename\",\n\n // Language the AI should answer in. Omit it to say nothing about language.\n // See the \"Response Language\" section for what each tool receives.\n // \"language\": \"ja\",\n\n // When true (default), `rulesync gitignore` only emits entries for the\n // tools listed in `targets`. Set to false to emit entries for all supported\n // tools regardless of `targets`.\n //\n // Note: Entries for `agentsmd` (AGENTS.md and related paths) are always\n // appended even when `gitignoreTargetsOnly` is true and `agentsmd` is\n // absent from `targets`. AGENTS.md is a de facto standard read by many AI\n // tools regardless of the target set, so its gitignore entries are emitted\n // unconditionally to prevent accidental commits of generated rule files.\n \"gitignoreTargetsOnly\": true,\n\n // Declarative rule and skill sources — installed via 'rulesync install'\n // See the \"Declarative Sources\" section for details.\n // \"sources\": [\n // { \"source\": \"owner/repo\" },\n // { \"source\": \"org/repo\", \"skills\": [\"specific-skill\"] },\n // { \"source\": \"org/standards\", \"rules\": [\"testing-guidelines\"] },\n // ],\n}\n```\n\n## Per-Target Features\n\nThe `targets` option accepts both an array and an object format. Use the\nobject format when you want to declare per-target feature configuration in\na single place — the object keys are the target tools, and each value\ncarries the features to generate for that tool:\n\n```jsonc\n// rulesync.jsonc\n{\n \"targets\": {\n \"claudecode\": [\"rules\", \"commands\"],\n \"cursor\": [\"rules\", \"mcp\"],\n \"copilot\": [\"rules\", \"subagents\"],\n },\n}\n```\n\nIn this example:\n\n- `claudecode` generates rules and commands\n- `cursor` generates rules and MCP configuration\n- `copilot` generates rules and subagents\n\n> **Important:** When `targets` is in object form, the top-level `features`\n> field must be omitted. Declaring both would double-define the target\n> set, so the config loader rejects that combination.\n\nYou can also use `*` (wildcard) inside a target's value to enable every\nfeature for that tool:\n\n```jsonc\n{\n \"targets\": {\n \"claudecode\": [\"*\"], // Generate all features for Claude Code\n \"cursor\": [\"rules\"], // Only rules for Cursor\n },\n}\n```\n\n### Per-feature options\n\nSome features accept additional configuration. To pass options through, use\nthe object form for a target's value instead of an array. Each feature key\nmaps to either `true`/`false` (enable/disable) or an options object.\n\n```jsonc\n{\n \"gitignoreDestination\": \"gitignore\",\n \"targets\": {\n \"claudecode\": {\n \"gitignoreDestination\": \"gitattributes\",\n \"rules\": { \"ruleDiscoveryMode\": \"explicit\" },\n \"ignore\": {\n \"fileMode\": \"local\",\n \"gitignoreDestination\": \"gitignore\",\n },\n },\n },\n}\n```\n\n`gitignoreDestination` controls where `rulesync gitignore` writes path entries.\nYou can set it:\n\n- at **root level** (`gitignoreDestination`)\n- at **tool level** (`targets.<tool>.gitignoreDestination`)\n- or at **tool × feature level**\n (`targets.<tool>.<feature>.gitignoreDestination`)\n\nAllowed values:\n\n- `\"gitignore\"` (default)\n- `\"gitattributes\"`\n\nPriority is **more specific wins**:\n\n1. tool × feature level\n2. tool level\n3. root level\n4. default (`\"gitignore\"`)\n\nThe current per-feature options are:\n\n| Target | Feature | Option | Values | Default |\n| ------------ | -------- | ---------------------- | ------------------------------------------------------------------------------ | ------------- |\n| `claudecode` | `rules` | `ruleDiscoveryMode` | `\"none\"` / `\"explicit\"` | tool default |\n| any | `rules` | `includeLocalRoot` | `true` / `false` (when `false`, `localRoot` rules are skipped for this target) | `true` |\n| `claudecode` | `ignore` | `fileMode` | `\"shared\"` (settings.json) / `\"local\"` (settings.local.json) | `\"shared\"` |\n| any | any | `gitignoreDestination` | `\"gitignore\"` / `\"gitattributes\"` | `\"gitignore\"` |\n\nSee [`docs/reference/file-formats.md`](../reference/file-formats.md#where-ignore-patterns-are-written-per-tool)\nfor the rationale behind the Claude Code default and when to switch to\n`\"local\"`.\n\n## Response Language\n\nThe root `language` key steers the language the AI answers in. It accepts one of `en`, `ja`, `zh-CN`, `zh-TW`, `ko`, `fr`, `de`, `es`, `pt-BR`, `ru`, has no CLI flag, and can be overridden per developer from `rulesync.local.jsonc`. When it is omitted, Rulesync says nothing about language; `en` is therefore an explicit instruction, not the default.\n\n```jsonc\n// rulesync.jsonc\n{\n \"language\": \"ja\",\n}\n```\n\nWhat the `rules` feature generates from it depends on the tool:\n\n- **Claude Code** has a native `language` setting, so the key is written as `\"language\": \"japanese\"` into `.claude/settings.local.json` (project scope) or `~/.claude/settings.json` (global scope, since Claude Code reads no `~/.claude/settings.local.json`) and `CLAUDE.md` is left as it is. Only the `language` key is touched; everything else in the settings file is preserved, and the file is never created or modified while the key is unset. Removing `language` later does not retract the key already written: the settings file is shared with your own configuration, so Rulesync never deletes from it, and the value stays until you remove it by hand.\n- **Every other tool** gets the instruction appended to the generated root rule file — the file built from your `root: true` rule (`AGENTS.md`, `GEMINI.md`, `.github/copilot-instructions.md`, `.cursor/rules/overview.mdc`, and so on). Nested rules never carry it, and neither does a `localRoot: true` rule's separate personal file. For a tool that files every rule side by side (Cursor and the fixed-name targets) with more than one `root: true` rule, only the file built from the first root rule carries the block. The block is separated from your content by a thematic break:\n\n ```md\n ---\n\n You must always answer in Japanese. On the other hand, reasoning (thinking) should be in English to improve token efficiency.\n ```\n\n `rulesync import` recognizes the block for every supported language and strips it (with a warning, since the instruction is not carried into `.rulesync/rules/` — the `language` key in `rulesync.jsonc` is what keeps it), so importing a generated file and generating again yields one block rather than two. `rulesync convert` re-adds the block to the destination's root file when `language` is set.\n\n## Local Configuration\n\nRulesync supports a local configuration file (`rulesync.local.jsonc`) for machine-specific or developer-specific settings. This file is automatically added to `.gitignore` by `rulesync gitignore` and should not be committed to the repository.\n\n**Configuration Priority** (highest to lowest):\n\n1. CLI options\n2. `rulesync.local.jsonc`\n3. `rulesync.jsonc`\n4. Default values\n\nExample usage:\n\n```jsonc\n// rulesync.local.jsonc (not committed to git)\n{\n \"$schema\": \"https://github.com/dyoshikawa/rulesync/releases/latest/download/config-schema.json\",\n // Override targets for local development\n \"targets\": [\"claudecode\"],\n // Enable verbose output for debugging\n \"verbose\": true,\n}\n```\n\n## Target Order and File Conflicts\n\nWhen multiple targets write to the same output file, **the last target in the array wins**. This is the \"last-wins\" behavior.\n\nFor example, both `agentsmd` and `opencode` generate `AGENTS.md`:\n\n```jsonc\n{\n // opencode wins because it comes last\n \"targets\": [\"agentsmd\", \"opencode\"],\n \"features\": [\"rules\"],\n}\n```\n\nIn this case:\n\n1. `agentsmd` generates `AGENTS.md` first\n2. `opencode` generates `AGENTS.md` second, overwriting the previous file\n\nIf you want `agentsmd`'s output instead, reverse the order:\n\n```jsonc\n{\n // agentsmd wins because it comes last\n \"targets\": [\"opencode\", \"agentsmd\"],\n \"features\": [\"rules\"],\n}\n```\n\nThe order matters most when a target that folds every rule into its root file shares that file with a target that keeps non-root rules in separate files. `codexcli` folds all rules into `AGENTS.md`, while `roo` and `zoocode` write only the root rule to `AGENTS.md` and put the rest under `.roo/rules/`. With `[\"codexcli\", \"zoocode\"]`, `zoocode` overwrites `AGENTS.md` with the root rule alone, so Codex CLI silently loses every non-root rule. `rulesync generate` warns when this happens; list the folding target last to keep the folded content:\n\n```jsonc\n{\n // codexcli wins AGENTS.md, so its folded rules survive;\n // zoocode still gets its own files under .roo/rules/\n \"targets\": [\"zoocode\", \"codexcli\"],\n \"features\": [\"rules\"],\n}\n```\n\nIn this order Roo Code and ZooCode see the non-root rules twice — folded into `AGENTS.md` and again under `.roo/rules/` — so prefer it when a complete `AGENTS.md` for Codex CLI matters more than that duplication.\n\nThe object form of `targets` follows the same rule using its key order.\n",
4312
4312
  "guide/declarative-sources": "# Declarative Sources\n\nRulesync can fetch rules and skills from external repositories using the `install` command. Instead of manually running `fetch` for each source, declare it in your `rulesync.jsonc` and run `rulesync install` to resolve and fetch its selected artifacts. Then `rulesync generate` processes them as curated inputs. Typical workflow: `rulesync install && rulesync generate`.\n\nTo add one source without editing JSONC by hand, run `rulesync add <source>`. It preserves existing comments, appends the source entry, installs it, and updates the appropriate lockfile:\n\n```bash\nrulesync add anthropics/skills --skills skill-creator\n\n# Add one rule without selecting any skills\nrulesync add acme/ai-standards --rules testing-guidelines\n```\n\nThe command fetches only the source being added. Existing sources must already be locked and installed; run `rulesync install` first when they are not. If the new source fails, Rulesync restores the manifest, source lockfiles, curated rules, and curated skills to their previous state.\n\n## Configuration\n\nAdd a `sources` array to your `rulesync.jsonc`:\n\n```jsonc\n{\n \"$schema\": \"https://github.com/dyoshikawa/rulesync/releases/latest/download/config-schema.json\",\n \"targets\": [\"copilot\", \"claudecode\"],\n \"features\": [\"rules\", \"skills\"],\n \"sources\": [\n // Fetch all skills from a GitHub repository (default transport)\n { \"source\": \"owner/repo\" },\n\n // Fetch only specific skills by name\n { \"source\": \"anthropics/skills\", \"skills\": [\"skill-creator\"] },\n\n // Fetch only specific .md rules from rules/ (no skills)\n {\n \"source\": \"acme/ai-standards\",\n \"rules\": [\"testing-guidelines\", \"typescript-conventions\"],\n },\n\n // Rules and skills can be selected from the same source\n {\n \"source\": \"acme/ai-assets\",\n \"rules\": [\"*\"],\n \"rulesPath\": \"exports/rules\",\n \"skills\": [\"review-pr\"],\n \"path\": \"exports/skills\",\n },\n\n // With ref pinning and subdirectory path (same syntax as fetch command)\n { \"source\": \"owner/repo@v1.0.0:path/to/skills\" },\n\n // Git transport — works with any git remote (Azure DevOps, Bitbucket, etc.)\n {\n \"source\": \"https://dev.azure.com/org/project/_git/repo\",\n \"transport\": \"git\",\n \"ref\": \"main\",\n \"path\": \"exports/skills\",\n },\n\n // Git transport with a local repository\n { \"source\": \"file:///path/to/local/repo\", \"transport\": \"git\" },\n\n // Git transport against a single-skill repo whose SKILL.md is at the root\n {\n \"source\": \"https://github.com/feature-sliced/skills\",\n \"transport\": \"git\",\n \"path\": \".\",\n },\n\n // npm transport (EXPERIMENTAL) — fetch a package from an npm-compatible\n // registry (npmjs.org, JFrog Artifactory, Sonatype Nexus, Verdaccio, ...)\n {\n \"source\": \"@acme/skill-package\",\n \"transport\": \"npm\",\n \"registry\": \"https://acme.jfrog.io/artifactory/api/npm/npm-local/\",\n \"tokenEnv\": \"ACME_REGISTRY_TOKEN\",\n },\n ],\n}\n```\n\nEach entry in `sources` accepts:\n\n| Property | Type | Description |\n| ----------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `source` | `string` | Repository source. For GitHub transport: `owner/repo` or `owner/repo@ref:path`. For git transport: a full git URL. For npm transport: a package name (`pkg` or `@scope/pkg`). |\n| `skills` | `string[]` | Optional skill names to fetch. `\"*\"` selects all skills. When both `skills` and `rules` are omitted, all skills are fetched for backward compatibility. |\n| `rules` | `string[]` | Optional rule names to fetch. Names may include or omit `.md`; `\"*\"` selects every direct `.md` file under `rulesPath`. Setting only `rules` fetches no skills. |\n| `transport` | `string` | `\"github\"` (default) uses the GitHub REST API. `\"git\"` uses git CLI and works with any git remote. `\"npm\"` (experimental) fetches a package from an npm-compatible registry. |\n| `ref` | `string` | Branch, tag, or ref to fetch from. Defaults to the remote's default branch. For GitHub transport, use the `@ref` source syntax. For npm transport: an exact version or dist-tag (defaults to `latest`). |\n| `path` | `string` | Path to the skills directory within the repository. Defaults to `\"skills\"`. Set to `\"\"`, `\".\"`, or `\"./\"` to target the entire repository root (see note below). For GitHub transport, use the `:path` source syntax. |\n| `rulesPath` | `string` | Path to the rules directory within the repository or package. Defaults to `\"rules\"`. This is independent from the skills-only `path` field. |\n| `registry` | `string` | npm transport only. Base URL of the npm-compatible registry. Defaults to `https://registry.npmjs.org`. |\n| `tokenEnv` | `string` | npm transport only. Name of the environment variable holding the registry token. Defaults to `NPM_TOKEN`. |\n\nRules are flat source files: only direct `.md` children of `rulesPath` are discovered. Nested rule files are not installed. Fetched rules are written to `.rulesync/rules/.curated/<rule-name>.md`; during generation they behave as if they were ordinary files directly under `.rulesync/rules/`.\n\n> **Repository-root paths (`path: \".\"`):** When `path` is `\"\"`, `\".\"`, or `\"./\"` (with the `git` transport), rulesync disables sparse-checkout and fetches the **entire** repository tree, then groups each top-level directory as a skill. This is useful for single-skill repositories whose `SKILL.md` lives at the repo root (`<repo>/SKILL.md`) rather than under a `skills/` container. Because the whole tree is fetched, prefer a narrower `path` for large repositories; the fetch is still bounded by rulesync's file-count, total-size, and depth limits.\n\n## npm Transport (Experimental)\n\n> [!WARNING]\n> The `npm` transport is **experimental**. Its configuration surface and lockfile format may change in a future release.\n\nThe `npm` transport fetches skills from any registry that implements the npm registry API. Because JFrog Artifactory, Sonatype Nexus, Verdaccio, GitHub Packages, and similar private registries all expose an npm-compatible API, a single transport with a configurable `registry` URL covers them all. This lets enterprises whose build environments cannot reach public GitHub distribute skills internally as npm packages.\n\nHow a package is fetched:\n\n1. The package metadata (packument) is fetched from `<registry>/<package>` using the abbreviated `application/vnd.npm.install-v1+json` form.\n2. The declared `ref` (an **exact version** or a **dist-tag** such as `latest` or `beta` — semver ranges are not supported) is resolved to a concrete version.\n3. The version's tarball is downloaded and verified against the registry's `dist.integrity` / `dist.shasum` metadata.\n4. The tarball is extracted **in memory** with a hardened minimal tar reader: only regular files are materialized (symlinks, hardlinks, and device entries are skipped), path traversal is rejected, and extraction is capped at 10,000 files / 100 MB to prevent decompression bombs.\n\nPackage layout: skills are discovered the same way as for the git transports. Skill directories under `skills/` (or the configured `path`) are installed as `.rulesync/skills/.curated/<name>/`. Direct `.md` files under `rules/` (or the configured `rulesPath`) can be selected with `rules` and are installed under `.rulesync/rules/.curated/`. A single-skill package with `SKILL.md` at the package root is installed as one skill named after the package's base name (`@acme/my-skill` installs as `my-skill`); note that this root fallback installs the package's root-level files only, so prefer the `skills/<name>/` layout for skills that carry subdirectories such as `references/`.\n\nAuthentication uses a bearer token from an environment variable: `NPM_TOKEN` by default, or the variable named by the per-source `tokenEnv` field. The token is sent as `Authorization: Bearer <token>` to the registry (and to the tarball host only when it matches the registry host). `.npmrc` files are intentionally **not** read.\n\nResolved versions are pinned in `rulesync-npm.lock.json` (next to `rulesync.lock`), which records the resolved version, the tarball integrity, and per-artifact content hashes. Commit it for reproducible installs; `--update` and `--frozen` behave the same as for git sources.\n\n## How It Works\n\nWhen `rulesync install` runs and `sources` is configured:\n\n1. **Lockfile resolution** — Each source's ref is resolved to a commit SHA and stored in `rulesync.lock` (at the project root). On subsequent runs the exact locked SHA is checked out for deterministic builds. npm-transport sources are pinned in a separate `rulesync-npm.lock.json` (resolved version + tarball integrity).\n2. **Remote artifact listing** — The configured skills and rules directories are listed from the remote source.\n3. **Filtering** — Only the names selected by `skills` and `rules` are fetched. Omitting both fields retains the historical behavior of fetching all skills.\n4. **Precedence rules**:\n - **Local inputs win within one source tree** — Rules and skills outside `.curated/` take precedence over a same-named curated artifact in that input root. Across multiple `inputRoots`, root order remains primary: a later root replaces an earlier root's effective artifact even when the later artifact is curated.\n - **First-declared source wins** — If two sources provide an artifact with the same name, the one declared first in the `sources` array is used.\n5. **Output** — Fetched rules are written to `.rulesync/rules/.curated/<rule-name>.md`; fetched skills are written to `.rulesync/skills/.curated/<skill-name>/`. Both directories are automatically added to `.gitignore` by `rulesync gitignore`.\n\n## Install Modes\n\n`rulesync install` supports three install modes via `--mode <mode>`:\n\n| Mode | Manifest input | Lockfile | Output layout |\n| ---------- | ---------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ |\n| `rulesync` | `rulesync.jsonc` `sources` | `rulesync.lock` (+ `rulesync-npm.lock.json` for npm sources) | `.rulesync/rules/.curated/<name>.md`, `.rulesync/skills/.curated/<name>/` (then re-emitted by `rulesync generate`) |\n| `apm` | `apm.yml` `dependencies.apm` | `rulesync-apm.lock.yaml` | `.github/instructions/`, `.github/skills/` (APM v1 layout) |\n| `gh` | `rulesync.jsonc` `sources` | `rulesync-gh.lock.yaml` | Per-agent / per-scope dirs (matching `gh skill install`) |\n\nWhen `--mode` is omitted, rulesync defaults to `rulesync` mode. If `apm.yml` is present and `sources` is also defined, you must pass `--mode apm` or `--mode rulesync` to disambiguate.\n\n### `--mode gh` — gh-skill-install–compatible layout\n\n`--mode gh` reads the same `sources` array from `rulesync.jsonc` but writes each discovered skill into the agent-specific directory expected by `gh skill install`. Each source supports two extra fields:\n\n| Property | Type | Default | Description |\n| -------- | -------- | ---------------- | ----------------------------------------------------------------------------------------- |\n| `agent` | `string` | `github-copilot` | One of `github-copilot`, `claude-code`, `cursor`, `codex`, `gemini`, `antigravity`. |\n| `scope` | `string` | `project` | `project` writes inside the project root; `user` writes inside the user's home directory. |\n\nAgent → install directory mapping:\n\n| Agent | Project scope (relative to project root) | User scope (relative to home) |\n| ---------------- | ---------------------------------------- | ----------------------------- |\n| `github-copilot` | `.agents/skills` | `.copilot/skills` |\n| `claude-code` | `.claude/skills` | `.claude/skills` |\n| `cursor` | `.agents/skills` | `.cursor/skills` |\n| `codex` | `.agents/skills` | `.agents/skills` |\n| `gemini` | `.agents/skills` | `.gemini/skills` |\n| `antigravity` | `.agents/skills` | `.gemini/antigravity/skills` |\n\nFor each skill discovered as `skills/<name>/SKILL.md` in the remote repository, rulesync deploys the entire skill directory to `<install-dir>/<name>/` and injects a provenance frontmatter block (`source`, `repository`, `ref`) into the deployed `SKILL.md`. The lockfile `rulesync-gh.lock.yaml` records one entry per `(source, agent, scope, skill)` tuple.\n\nPer-source field support in `--mode gh`:\n\n| Field | Status |\n| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------- |\n| `source` | Required. Must resolve to a GitHub repository (`owner/repo`, `owner/repo@ref`, or an `https://github.com/...` URL). |\n| `skills` | Optional. When set, only the listed skill names are installed; remote skills not in the list are skipped, and missing names log a warning. |\n| `rules` | **Rejected.** Declarative rules are supported only in `--mode rulesync`. |\n| `rulesPath` | **Rejected.** Declarative rules are supported only in `--mode rulesync`. |\n| `ref` | Optional. Pins a tag, branch, or commit SHA. When omitted, gh mode resolves to the latest release's tag, falling back to the default branch. |\n| `agent` | Optional. Defaults to `github-copilot`. See the agent table above. |\n| `scope` | Optional. Defaults to `project`. |\n| `transport` | **Rejected.** gh mode is GitHub-only and does not honor the `git` transport. Drop the field or switch to `--mode rulesync`. |\n| `path` | **Rejected.** The remote layout is fixed to `skills/<name>/SKILL.md`. Repositories that store skills elsewhere are not supported in gh mode. |\n\nThe remote repository must use the layout `skills/<name>/SKILL.md` (one directory per skill, each containing a `SKILL.md`). Other layouts are not auto-discovered.\n\nExample `rulesync.jsonc`:\n\n```jsonc\n{\n \"targets\": [\"claudecode\"],\n \"features\": [\"rules\"],\n \"sources\": [\n // Default: agent=github-copilot, scope=project -> .agents/skills/git-commit/\n { \"source\": \"acme/skills\", \"skills\": [\"git-commit\"] },\n\n // Same source, deployed for Claude Code at user scope -> ~/.claude/skills/git-commit/\n {\n \"source\": \"acme/skills\",\n \"skills\": [\"git-commit\"],\n \"agent\": \"claude-code\",\n \"scope\": \"user\",\n },\n ],\n}\n```\n\nRun with `npx rulesync install --mode gh`.\n\n## CLI Options\n\nThe `install` command accepts these flags:\n\n| Flag | Description |\n| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| `--mode <mode>` | Install mode: `rulesync` (default), `apm`, or `gh`. See **Install Modes** above. |\n| `--update` | Force re-resolve all source refs, ignoring the lockfile (useful to pull new updates). |\n| `--frozen` | Fail if a lockfile is missing or does not cover declared sources and their skill and rule selections. Fetches missing locked artifacts without updating the lockfile. Useful for CI. |\n| `--token <token>` | GitHub token for private repositories. |\n\n```bash\n# Install rules and skills using locked refs\nrulesync install\n\n# Force update to latest refs\nrulesync install --update\n\n# Strict CI mode — fail if lockfile doesn't cover all sources and selections\nrulesync install --frozen\n\n# Install then generate\nrulesync install && rulesync generate\n\n# Skip source installation — just don't run install\nrulesync generate\n```\n\n## Lockfile\n\nThe lockfile at `rulesync.lock` (at the project root) records the resolved commit SHA, the skill and rule selections each entry was written for, and per-artifact integrity hashes for each source so that builds are reproducible. Rulesync verifies cached rule content against these hashes before reusing it. It is safe to commit this file. An example:\n\n```json\n{\n \"lockfileVersion\": 1,\n \"sources\": {\n \"owner/skill-repo\": {\n \"requestedRef\": \"main\",\n \"resolvedRef\": \"abc123def456...\",\n \"resolvedAt\": \"2025-01-15T12:00:00.000Z\",\n \"skills\": {\n \"my-skill\": { \"integrity\": \"sha256-abcdef...\" },\n \"another-skill\": { \"integrity\": \"sha256-123456...\" }\n },\n \"skillSelection\": [\"*\"],\n \"rules\": {\n \"testing-guidelines\": { \"integrity\": \"sha256-789abc...\" }\n },\n \"ruleSelection\": [\"*\"],\n \"rulesPath\": \"rules\",\n \"resolvedRuleNames\": [\"testing-guidelines\"]\n }\n }\n}\n```\n\nTo update locked refs, run `rulesync install --update`.\n\nChanging a source's `skills` or `rules` selection in `rulesync.jsonc` (for example, adding a skill name to an explicit list, or switching to `\"*\"`) is picked up by the next plain `rulesync install`: the entry is refetched at its locked ref and the lockfile records the new selection. Under `--frozen`, a selection the lockfile does not cover fails the install instead. A lockfile written before `skillSelection` was recorded is fetched again once, at its locked ref, by the next plain `rulesync install`, which then records the selection; commit the updated lockfile so `--frozen` installs keep reusing the cache.\n\nnpm-transport sources (experimental) are pinned in a separate `rulesync-npm.lock.json`, because they lock a resolved package version and tarball integrity instead of a commit SHA:\n\n```json\n{\n \"lockfileVersion\": 1,\n \"sources\": {\n \"@acme/skill-package\": {\n \"registry\": \"https://acme.jfrog.io/artifactory/api/npm/npm-local\",\n \"requestedVersion\": \"latest\",\n \"resolvedVersion\": \"1.2.3\",\n \"integrity\": \"sha512-...\",\n \"resolvedAt\": \"2026-01-15T12:00:00.000Z\",\n \"skills\": {\n \"my-skill\": { \"integrity\": \"sha256-abcdef...\" }\n },\n \"skillSelection\": [\"my-skill\"],\n \"rules\": {\n \"testing-guidelines\": { \"integrity\": \"sha256-789abc...\" }\n },\n \"ruleSelection\": [\"testing-guidelines\"],\n \"rulesPath\": \"rules\",\n \"resolvedRuleNames\": [\"testing-guidelines\"]\n }\n }\n}\n```\n\nIt is safe (and recommended) to commit this file as well.\n\n## Authentication\n\nGitHub transport uses the `GITHUB_TOKEN` or `GH_TOKEN` environment variable for authentication. This is required for private repositories and recommended for better rate limits. Git transport relies on your local git credential configuration (SSH keys, credential helpers, etc.). npm transport (experimental) uses the `NPM_TOKEN` environment variable, or the variable named by the per-source `tokenEnv` field; `.npmrc` files are not read.\n\n```bash\n# Using environment variable\nexport GITHUB_TOKEN=ghp_xxxx\nnpx rulesync install\n\n# Or using GitHub CLI\nGITHUB_TOKEN=$(gh auth token) npx rulesync install\n```\n\n> [!TIP]\n> The `install` command also accepts a `--token` flag for explicit authentication: `rulesync install --token ghp_xxxx`.\n\n## Curated vs Local Inputs\n\n| Location | Type | Precedence within one root | Committed to Git |\n| ------------------------------------ | ------- | -------------------------- | ---------------- |\n| `.rulesync/skills/<name>/` | Local | Higher | Yes |\n| `.rulesync/skills/.curated/<name>/` | Curated | Lower | No (gitignored) |\n| `.rulesync/rules/<name>.md` | Local | Higher | Yes |\n| `.rulesync/rules/.curated/<name>.md` | Curated | Lower | No (gitignored) |\n\nWhen a local and curated artifact in the same source tree share a name, the local artifact is used and the remote one is not fetched. With multiple input roots, this per-root selection happens before the roots are merged in order; see [Separate Input Root](./separate-input-root.md#merge-rules-per-feature).\n",
4313
4313
  "guide/dry-run": "# Dry Run\n\nRulesync provides two dry run options for the `generate` command that allow you to see what changes would be made without actually writing files:\n\n## `--dry-run`\n\nShow what would be written or deleted without actually writing any files. Changes are displayed with a `[DRY RUN]` prefix.\n\n```bash\nrulesync generate --dry-run --targets claudecode --features rules\n```\n\n## `--check`\n\nSame as `--dry-run`, but exits with code 1 if files are not up to date. This is useful for CI/CD pipelines to verify that generated files are committed.\n\n```bash\n# In your CI pipeline\nrulesync generate --check --targets \"*\" --features \"*\"\necho $? # 0 if up to date, 1 if changes needed\n```\n\n> [!NOTE]\n> `--dry-run` and `--check` cannot be used together.\n",
4314
4314
  "guide/global-mode": "# Global Mode\n\nYou can use global mode via Rulesync by enabling `--global` option. It can also be called as user scope mode.\n\nCurrently, supports rules generation for Claude Code, GitHub Copilot, and OpenCode. Import for global files is supported for rules and commands. Command generation in global mode remains Claude Code only.\n\n1. Create an any name directory. For example, if you prefer `~/.aiglobal`, run the following command.\n\n ```bash\n mkdir -p ~/.aiglobal\n ```\n\n2. Initialize files for global files in the directory.\n\n ```bash\n cd ~/.aiglobal\n rulesync init\n ```\n\n3. Edit `~/.aiglobal/rulesync.jsonc` to enable global mode.\n\n ```jsonc\n {\n \"global\": true,\n }\n ```\n\n4. Edit `~/.aiglobal/.rulesync/rules/overview.md` to your preferences.\n\n ```md\n ---\n root: true\n ---\n\n # The Project Overview\n\n ...\n ```\n\n5. Generate rules for global settings.\n\n ```bash\n # Run in the `~/.aiglobal` directory\n rulesync generate\n ```\n\n> [!NOTE]\n> Currently, when in the directory enabled global mode:\n>\n> - `rulesync.jsonc` only supports `global`, `features`, `delete` and `verbose`. `Features` can be set `\"rules\"` and `\"commands\"`. Other parameters are ignored.\n> - Multiple `root: true` files can target the same tool in project and global modes. Compatible root or plain-Markdown single-file outputs are combined with a blank line between files. Local rules are composed first in lexicographic source file path order, followed by non-overridden `.curated/` rules in lexicographic order, so filename prefixes control composition order within each set. Distinct native paths remain separate. Explicitly supported plain-Markdown modular path collisions are combined (fragments whose generated output carries its own frontmatter block stay separate), unsafe collisions involving a source root rule fail, and other exact or case-insensitive modular collisions warn that the last write wins wherever their paths collide.\n> - Only Claude Code is supported for global mode commands.\n",
@@ -12945,7 +12945,7 @@ function wrapCommand$1({ name, errorCode, handler, getVersion, loggerFactory = c
12945
12945
  }
12946
12946
  //#endregion
12947
12947
  //#region src/cli/program.ts
12948
- const getVersion = () => "16.30.1";
12948
+ const getVersion = () => "16.30.2";
12949
12949
  const FEATURES_HELP = `${ALL_FEATURES.join(",")}; ignore is deprecated, use permissions`;
12950
12950
  function wrapCommand(name, errorCode, handler) {
12951
12951
  return wrapCommand$1({