rulesync 9.7.0 → 10.0.0

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.
package/README.md CHANGED
@@ -95,7 +95,7 @@ The tables below show whether each tool supports a given feature (✅ = supporte
95
95
  | GitHub Copilot CLI | ✅ | | ✅ | | ✅ | ✅ | ✅ | |
96
96
  | Goose | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
97
97
  | Hermes Agent | ✅ | | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
98
- | Grok CLI | ✅ | | ✅ | | ✅ | ✅ | | ✅ |
98
+ | Grok CLI | ✅ | | ✅ | | ✅ | ✅ || ✅ |
99
99
  | Cursor | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
100
100
  | deepagents-cli | ✅ | | ✅ | | ✅ | ✅ | ✅ | |
101
101
  | Factory Droid | ✅ | | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const require_import = require("../import-D5f_X4-A.cjs");
2
+ const require_import = require("../import-Dcj5HiOE.cjs");
3
3
  let commander = require("commander");
4
4
  let zod_mini = require("zod/mini");
5
5
  let node_path = require("node:path");
@@ -542,9 +542,9 @@ const FEATURE_PATHS = {
542
542
  subagents: ["subagents"],
543
543
  skills: ["skills"],
544
544
  ignore: [require_import.RULESYNC_AIIGNORE_FILE_NAME],
545
- mcp: [require_import.RULESYNC_MCP_FILE_NAME],
546
- hooks: [require_import.RULESYNC_HOOKS_FILE_NAME],
547
- permissions: [require_import.RULESYNC_PERMISSIONS_FILE_NAME]
545
+ mcp: [require_import.RULESYNC_MCP_FILE_NAME, require_import.RULESYNC_MCP_JSONC_FILE_NAME],
546
+ hooks: [require_import.RULESYNC_HOOKS_FILE_NAME, require_import.RULESYNC_HOOKS_JSONC_FILE_NAME],
547
+ permissions: [require_import.RULESYNC_PERMISSIONS_FILE_NAME, require_import.RULESYNC_PERMISSIONS_JSONC_FILE_NAME]
548
548
  };
549
549
  /**
550
550
  * Check if target is a tool target (not rulesync)
@@ -1425,6 +1425,11 @@ const GITIGNORE_ENTRY_REGISTRY = [
1425
1425
  target: "codexcli",
1426
1426
  feature: "ignore",
1427
1427
  entry: "**/.codexignore"
1428
+ },
1429
+ {
1430
+ target: "codexcli",
1431
+ feature: "permissions",
1432
+ entry: `**/${require_import.CODEXCLI_DIR}/rules/`
1428
1433
  }
1429
1434
  ],
1430
1435
  ...deriveAllGitignoreEntries(),
@@ -1499,45 +1504,69 @@ const resolveGitignoreEntries = (params) => {
1499
1504
  //#endregion
1500
1505
  //#region src/cli/commands/gitignore.ts
1501
1506
  const RULESYNC_HEADER = "# Generated by Rulesync";
1507
+ const RULESYNC_FOOTER = "# End of Rulesync";
1502
1508
  const LEGACY_RULESYNC_HEADER = "# Generated by rulesync - AI tool configuration files";
1503
1509
  const isRulesyncHeader = (line) => {
1504
1510
  const trimmed = line.trim();
1505
1511
  return trimmed === RULESYNC_HEADER || trimmed === LEGACY_RULESYNC_HEADER;
1506
1512
  };
1513
+ const isRulesyncFooter = (line) => {
1514
+ return line.trim() === RULESYNC_FOOTER;
1515
+ };
1507
1516
  const isRulesyncEntry = (line) => {
1508
1517
  const trimmed = line.trim();
1509
- if (trimmed === "" || isRulesyncHeader(line)) return false;
1518
+ if (trimmed === "" || isRulesyncHeader(line) || isRulesyncFooter(line)) return false;
1510
1519
  return ALL_GITIGNORE_ENTRIES.includes(trimmed);
1511
1520
  };
1521
+ const findRulesyncFooterIndex = (lines, start) => {
1522
+ for (let index = start; index < lines.length; index++) {
1523
+ const line = lines[index] ?? "";
1524
+ if (isRulesyncFooter(line)) return index;
1525
+ if (isRulesyncHeader(line)) return -1;
1526
+ }
1527
+ return -1;
1528
+ };
1529
+ const skipLegacyRulesyncBlock = (lines, headerIndex) => {
1530
+ let index = headerIndex + 1;
1531
+ let consecutiveEmptyLines = 0;
1532
+ while (index < lines.length) {
1533
+ const line = lines[index] ?? "";
1534
+ if (line.trim() === "") {
1535
+ consecutiveEmptyLines++;
1536
+ index++;
1537
+ if (consecutiveEmptyLines >= 2) break;
1538
+ continue;
1539
+ }
1540
+ if (isRulesyncEntry(line)) {
1541
+ consecutiveEmptyLines = 0;
1542
+ index++;
1543
+ continue;
1544
+ }
1545
+ break;
1546
+ }
1547
+ return index;
1548
+ };
1512
1549
  const removeExistingRulesyncEntries = (content) => {
1513
1550
  const lines = content.split("\n");
1514
1551
  const filteredLines = [];
1515
- let inRulesyncBlock = false;
1516
- let consecutiveEmptyLines = 0;
1517
- for (const line of lines) {
1518
- const trimmed = line.trim();
1552
+ let index = 0;
1553
+ while (index < lines.length) {
1554
+ const line = lines[index] ?? "";
1519
1555
  if (isRulesyncHeader(line)) {
1520
- inRulesyncBlock = true;
1521
- continue;
1522
- }
1523
- if (inRulesyncBlock) {
1524
- if (trimmed === "") {
1525
- consecutiveEmptyLines++;
1526
- if (consecutiveEmptyLines >= 2) {
1527
- inRulesyncBlock = false;
1528
- consecutiveEmptyLines = 0;
1529
- }
1556
+ const footerIndex = findRulesyncFooterIndex(lines, index + 1);
1557
+ if (footerIndex !== -1) {
1558
+ index = footerIndex + 1;
1530
1559
  continue;
1531
1560
  }
1532
- if (isRulesyncEntry(line)) {
1533
- consecutiveEmptyLines = 0;
1534
- continue;
1535
- }
1536
- inRulesyncBlock = false;
1537
- consecutiveEmptyLines = 0;
1561
+ index = skipLegacyRulesyncBlock(lines, index);
1562
+ continue;
1563
+ }
1564
+ if (isRulesyncEntry(line)) {
1565
+ index++;
1566
+ continue;
1538
1567
  }
1539
- if (isRulesyncEntry(line)) continue;
1540
1568
  filteredLines.push(line);
1569
+ index++;
1541
1570
  }
1542
1571
  let result = filteredLines.join("\n");
1543
1572
  while (result.endsWith("\n\n")) result = result.slice(0, -1);
@@ -1579,10 +1608,14 @@ const gitignoreCommand = async (logger, options) => {
1579
1608
  let content = "";
1580
1609
  if (await require_import.fileExists(filePath)) content = await require_import.readFileContent(filePath);
1581
1610
  const cleanedContent = removeExistingRulesyncEntries(content);
1582
- const existingEntries = new Set(content.split("\n").map((line) => line.trim()).filter((line) => line !== "" && !isRulesyncHeader(line)));
1611
+ const existingEntries = new Set(content.split("\n").map((line) => line.trim()).filter((line) => line !== "" && !isRulesyncHeader(line) && !isRulesyncFooter(line)));
1583
1612
  const alreadyExistedEntries = entries.filter((entry) => existingEntries.has(entry));
1584
1613
  const entriesToAdd = entries.filter((entry) => !existingEntries.has(entry));
1585
- const rulesyncBlock = [RULESYNC_HEADER, ...entries].join("\n");
1614
+ const rulesyncBlock = [
1615
+ RULESYNC_HEADER,
1616
+ ...entries,
1617
+ RULESYNC_FOOTER
1618
+ ].join("\n");
1586
1619
  const newContent = entries.length === 0 ? cleanedContent.trim() ? `${cleanedContent.trimEnd()}\n` : "" : cleanedContent.trim() ? `${cleanedContent.trimEnd()}\n\n${rulesyncBlock}\n` : `${rulesyncBlock}\n`;
1587
1620
  if (content === newContent) return {
1588
1621
  updated: false,
@@ -1715,7 +1748,8 @@ async function createConfigFile() {
1715
1748
  "commands",
1716
1749
  "subagents",
1717
1750
  "skills",
1718
- "hooks"
1751
+ "hooks",
1752
+ "permissions"
1719
1753
  ],
1720
1754
  outputRoots: ["."],
1721
1755
  delete: true,
@@ -1859,6 +1893,25 @@ Keep the summary concise and ready to reuse in future tasks.`
1859
1893
  ]
1860
1894
  }
1861
1895
  }
1896
+ ` };
1897
+ const samplePermissionsFile = { content: `{
1898
+ "$schema": "${require_import.RULESYNC_PERMISSIONS_SCHEMA_URL}",
1899
+ "permission": {
1900
+ "bash": {
1901
+ "git status": "allow",
1902
+ "git diff": "allow",
1903
+ "ls *": "allow",
1904
+ "rm -rf *": "deny",
1905
+ "*": "ask"
1906
+ },
1907
+ "edit": {
1908
+ "src/**": "allow"
1909
+ },
1910
+ "read": {
1911
+ ".env": "deny"
1912
+ }
1913
+ }
1914
+ }
1862
1915
  ` };
1863
1916
  const rulePaths = require_import.RulesyncRule.getSettablePaths();
1864
1917
  const mcpPaths = require_import.RulesyncMcp.getSettablePaths();
@@ -1867,6 +1920,7 @@ Keep the summary concise and ready to reuse in future tasks.`
1867
1920
  const skillPaths = require_import.RulesyncSkill.getSettablePaths();
1868
1921
  const ignorePaths = require_import.RulesyncIgnore.getSettablePaths();
1869
1922
  const hooksPaths = require_import.RulesyncHooks.getSettablePaths();
1923
+ const permissionsPaths = require_import.RulesyncPermissions.getSettablePaths();
1870
1924
  await require_import.ensureDir(rulePaths.recommended.relativeDirPath);
1871
1925
  await require_import.ensureDir(mcpPaths.recommended.relativeDirPath);
1872
1926
  await require_import.ensureDir(commandPaths.relativeDirPath);
@@ -1889,6 +1943,8 @@ Keep the summary concise and ready to reuse in future tasks.`
1889
1943
  results.push(await writeIfNotExists(ignoreFilepath, sampleIgnoreFile.content));
1890
1944
  const hooksFilepath = (0, node_path.join)(hooksPaths.relativeDirPath, hooksPaths.relativeFilePath);
1891
1945
  results.push(await writeIfNotExists(hooksFilepath, sampleHooksFile.content));
1946
+ const permissionsFilepath = (0, node_path.join)(permissionsPaths.relativeDirPath, permissionsPaths.relativeFilePath);
1947
+ results.push(await writeIfNotExists(permissionsFilepath, samplePermissionsFile.content));
1892
1948
  return results;
1893
1949
  }
1894
1950
  async function writeIfNotExists(path, content) {
@@ -1930,7 +1986,7 @@ async function initCommand(logger) {
1930
1986
  }
1931
1987
  logger.success("rulesync initialized successfully!");
1932
1988
  logger.info("Next steps:");
1933
- logger.info(`1. Edit ${require_import.RULESYNC_RELATIVE_DIR_PATH}/**/*.md, ${require_import.RULESYNC_RELATIVE_DIR_PATH}/skills/*/${require_import.SKILL_FILE_NAME}, ${require_import.RULESYNC_MCP_RELATIVE_FILE_PATH}, ${require_import.RULESYNC_HOOKS_RELATIVE_FILE_PATH} and ${require_import.RULESYNC_AIIGNORE_RELATIVE_FILE_PATH}`);
1989
+ logger.info(`1. Edit ${require_import.RULESYNC_RELATIVE_DIR_PATH}/**/*.md, ${require_import.RULESYNC_RELATIVE_DIR_PATH}/skills/*/${require_import.SKILL_FILE_NAME}, ${require_import.RULESYNC_MCP_RELATIVE_FILE_PATH}, ${require_import.RULESYNC_HOOKS_RELATIVE_FILE_PATH}, ${require_import.RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH} and ${require_import.RULESYNC_AIIGNORE_RELATIVE_FILE_PATH}`);
1934
1990
  logger.info("2. Run 'rulesync generate' to create configuration files");
1935
1991
  }
1936
1992
  //#endregion
@@ -4740,6 +4796,8 @@ async function putHooksFile({ content }) {
4740
4796
  try {
4741
4797
  const outputRoot = process.cwd();
4742
4798
  const paths = require_import.RulesyncHooks.getSettablePaths();
4799
+ const jsoncRelativePath = (0, node_path.join)(paths.jsonc.relativeDirPath, paths.jsonc.relativeFilePath);
4800
+ if (await require_import.fileExists((0, node_path.join)(outputRoot, jsoncRelativePath))) throw new Error(`${jsoncRelativePath} exists and takes precedence over ${require_import.RULESYNC_HOOKS_RELATIVE_FILE_PATH}. Edit ${jsoncRelativePath} directly instead.`);
4743
4801
  const relativeDirPath = paths.relativeDirPath;
4744
4802
  const relativeFilePath = paths.relativeFilePath;
4745
4803
  const fullPath = (0, node_path.join)(outputRoot, relativeDirPath, relativeFilePath);
@@ -4768,6 +4826,7 @@ async function deleteHooksFile() {
4768
4826
  const outputRoot = process.cwd();
4769
4827
  const paths = require_import.RulesyncHooks.getSettablePaths();
4770
4828
  await require_import.removeFile((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath));
4829
+ await require_import.removeFile((0, node_path.join)(outputRoot, paths.jsonc.relativeDirPath, paths.jsonc.relativeFilePath));
4771
4830
  return { relativePathFromCwd: (0, node_path.join)(paths.relativeDirPath, paths.relativeFilePath) };
4772
4831
  } catch (error) {
4773
4832
  throw new Error(`Failed to delete hooks file (${require_import.RULESYNC_HOOKS_RELATIVE_FILE_PATH}): ${require_import.formatError(error)}`, { cause: error });
@@ -5018,6 +5077,8 @@ async function putMcpFile({ content }) {
5018
5077
  try {
5019
5078
  const outputRoot = process.cwd();
5020
5079
  const paths = require_import.RulesyncMcp.getSettablePaths();
5080
+ const jsoncRelativePath = (0, node_path.join)(paths.jsonc.relativeDirPath, paths.jsonc.relativeFilePath);
5081
+ if (await require_import.fileExists((0, node_path.join)(outputRoot, jsoncRelativePath))) throw new Error(`${jsoncRelativePath} exists and takes precedence over ${require_import.RULESYNC_MCP_RELATIVE_FILE_PATH}. Edit ${jsoncRelativePath} directly instead.`);
5021
5082
  const relativeDirPath = paths.recommended.relativeDirPath;
5022
5083
  const relativeFilePath = paths.recommended.relativeFilePath;
5023
5084
  const fullPath = (0, node_path.join)(outputRoot, relativeDirPath, relativeFilePath);
@@ -5048,6 +5109,7 @@ async function deleteMcpFile() {
5048
5109
  const recommendedPath = (0, node_path.join)(outputRoot, paths.recommended.relativeDirPath, paths.recommended.relativeFilePath);
5049
5110
  const legacyPath = (0, node_path.join)(outputRoot, paths.legacy.relativeDirPath, paths.legacy.relativeFilePath);
5050
5111
  await require_import.removeFile(recommendedPath);
5112
+ await require_import.removeFile((0, node_path.join)(outputRoot, paths.jsonc.relativeDirPath, paths.jsonc.relativeFilePath));
5051
5113
  await require_import.removeFile(legacyPath);
5052
5114
  return { relativePathFromCwd: (0, node_path.join)(paths.recommended.relativeDirPath, paths.recommended.relativeFilePath) };
5053
5115
  } catch (error) {
@@ -5124,6 +5186,8 @@ async function putPermissionsFile({ content }) {
5124
5186
  try {
5125
5187
  const outputRoot = process.cwd();
5126
5188
  const paths = require_import.RulesyncPermissions.getSettablePaths();
5189
+ const jsoncRelativePath = (0, node_path.join)(paths.jsonc.relativeDirPath, paths.jsonc.relativeFilePath);
5190
+ if (await require_import.fileExists((0, node_path.join)(outputRoot, jsoncRelativePath))) throw new Error(`${jsoncRelativePath} exists and takes precedence over ${require_import.RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH}. Edit ${jsoncRelativePath} directly instead.`);
5127
5191
  const relativeDirPath = paths.relativeDirPath;
5128
5192
  const relativeFilePath = paths.relativeFilePath;
5129
5193
  const fullPath = (0, node_path.join)(outputRoot, relativeDirPath, relativeFilePath);
@@ -5152,6 +5216,7 @@ async function deletePermissionsFile() {
5152
5216
  const outputRoot = process.cwd();
5153
5217
  const paths = require_import.RulesyncPermissions.getSettablePaths();
5154
5218
  await require_import.removeFile((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath));
5219
+ await require_import.removeFile((0, node_path.join)(outputRoot, paths.jsonc.relativeDirPath, paths.jsonc.relativeFilePath));
5155
5220
  return { relativePathFromCwd: (0, node_path.join)(paths.relativeDirPath, paths.relativeFilePath) };
5156
5221
  } catch (error) {
5157
5222
  throw new Error(`Failed to delete permissions file (${require_import.RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH}): ${require_import.formatError(error)}`, { cause: error });
@@ -6450,7 +6515,7 @@ function wrapCommand$1({ name, errorCode, handler, getVersion, loggerFactory = c
6450
6515
  }
6451
6516
  //#endregion
6452
6517
  //#region src/cli/index.ts
6453
- const getVersion = () => "9.7.0";
6518
+ const getVersion = () => "10.0.0";
6454
6519
  function wrapCommand(name, errorCode, handler) {
6455
6520
  return wrapCommand$1({
6456
6521
  name,
package/dist/cli/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { $ as removeTempDirectory, A as RulesyncCommand, B as checkPathTraversal, C as RulesyncHooks, Ct as RULESYNC_RULES_RELATIVE_DIR_PATH, D as CLAUDECODE_MEMORIES_DIR_NAME, Dt as formatError, E as CLAUDECODE_LOCAL_RULE_FILE_NAME, Et as RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, F as findControlCharacter, G as findFilesByGlobs, H as directoryExists, I as ConsoleLogger, J as isSymlink, K as getFileSize, L as JsonLogger, M as stringifyFrontmatter, N as loadYaml, O as CLAUDECODE_SETTINGS_LOCAL_FILE_NAME, Ot as ALL_FEATURES, P as ConfigResolver, Q as removeFile, R as CLIError, S as HooksProcessor, St as RULESYNC_RELATIVE_DIR_PATH, T as CLAUDECODE_DIR, Tt as RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH, U as ensureDir, V as createTempDirectory, W as fileExists, X as readFileContent, Y as listDirectoryFiles, Z as removeDirectory, _ as RulesyncPermissions, _t as RULESYNC_MCP_RELATIVE_FILE_PATH, a as convertFromTool, at as MAX_FILE_SIZE, b as IgnoreProcessor, bt as RULESYNC_PERMISSIONS_FILE_NAME, c as RulesyncRuleFrontmatterSchema, ct as RULESYNC_COMMANDS_RELATIVE_DIR_PATH, d as RulesyncSubagentFrontmatterSchema, dt as RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH, et as toPosixPath, f as SkillsProcessor, ft as RULESYNC_HOOKS_FILE_NAME, g as SKILL_FILE_NAME$1, gt as RULESYNC_MCP_FILE_NAME, h as RulesyncSkillFrontmatterSchema, ht as RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH, i as getProcessorRegistryEntry, it as ToolTargetSchema, j as RulesyncCommandFrontmatterSchema, k as CLAUDECODE_SKILLS_DIR_PATH, kt as ALL_FEATURES_WITH_WILDCARD, l as SubagentsProcessor, lt as RULESYNC_CONFIG_RELATIVE_FILE_PATH, m as RulesyncSkill, mt as RULESYNC_IGNORE_RELATIVE_FILE_PATH, n as checkRulesyncDirExists, nt as ALL_TOOL_TARGETS, o as RulesProcessor, ot as RULESYNC_AIIGNORE_FILE_NAME, p as getLocalSkillDirNames, pt as RULESYNC_HOOKS_RELATIVE_FILE_PATH, q as getHomeDirectory, r as generate, rt as ALL_TOOL_TARGETS_WITH_WILDCARD, s as RulesyncRule, st as RULESYNC_AIIGNORE_RELATIVE_FILE_PATH, t as importFromTool, tt as writeFileContent, u as RulesyncSubagent, ut as RULESYNC_CONFIG_SCHEMA_URL, v as McpProcessor, vt as RULESYNC_MCP_SCHEMA_URL, w as CommandsProcessor, wt as RULESYNC_SKILLS_RELATIVE_DIR_PATH, x as RulesyncIgnore, xt as RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH, y as RulesyncMcp, yt as RULESYNC_OVERVIEW_FILE_NAME, z as ErrorCodes } from "../import-DC9T1JlQ.js";
2
+ import { $ as removeFile, A as CLAUDECODE_SKILLS_DIR_PATH, At as RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH, B as ErrorCodes, C as RulesyncHooks, Ct as RULESYNC_PERMISSIONS_FILE_NAME, D as CLAUDECODE_LOCAL_RULE_FILE_NAME, Dt as RULESYNC_RELATIVE_DIR_PATH, E as CLAUDECODE_DIR, Et as RULESYNC_PERMISSIONS_SCHEMA_URL, F as ConfigResolver, G as fileExists, H as createTempDirectory, I as findControlCharacter, J as getHomeDirectory, K as findFilesByGlobs, L as ConsoleLogger, M as RulesyncCommandFrontmatterSchema, Mt as formatError, N as stringifyFrontmatter, Nt as ALL_FEATURES, O as CLAUDECODE_MEMORIES_DIR_NAME, Ot as RULESYNC_RULES_RELATIVE_DIR_PATH, P as loadYaml, Pt as ALL_FEATURES_WITH_WILDCARD, Q as removeDirectory, R as JsonLogger, S as HooksProcessor, St as RULESYNC_OVERVIEW_FILE_NAME, T as CODEXCLI_DIR, Tt as RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH, U as directoryExists, V as checkPathTraversal, W as ensureDir, X as listDirectoryFiles, Y as isSymlink, Z as readFileContent, _ as RulesyncPermissions, _t as RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH, a as convertFromTool, at as ToolTargetSchema, b as IgnoreProcessor, bt as RULESYNC_MCP_RELATIVE_FILE_PATH, c as RulesyncRuleFrontmatterSchema, ct as RULESYNC_AIIGNORE_RELATIVE_FILE_PATH, d as RulesyncSubagentFrontmatterSchema, dt as RULESYNC_CONFIG_SCHEMA_URL, et as removeTempDirectory, f as SkillsProcessor, ft as RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH, g as SKILL_FILE_NAME$1, gt as RULESYNC_IGNORE_RELATIVE_FILE_PATH, h as RulesyncSkillFrontmatterSchema, ht as RULESYNC_HOOKS_RELATIVE_FILE_PATH, i as getProcessorRegistryEntry, it as ALL_TOOL_TARGETS_WITH_WILDCARD, j as RulesyncCommand, jt as RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, k as CLAUDECODE_SETTINGS_LOCAL_FILE_NAME, kt as RULESYNC_SKILLS_RELATIVE_DIR_PATH, l as SubagentsProcessor, lt as RULESYNC_COMMANDS_RELATIVE_DIR_PATH, m as RulesyncSkill, mt as RULESYNC_HOOKS_JSONC_FILE_NAME, n as checkRulesyncDirExists, nt as writeFileContent, o as RulesProcessor, ot as MAX_FILE_SIZE, p as getLocalSkillDirNames, pt as RULESYNC_HOOKS_FILE_NAME, q as getFileSize, r as generate, rt as ALL_TOOL_TARGETS, s as RulesyncRule, st as RULESYNC_AIIGNORE_FILE_NAME, t as importFromTool, tt as toPosixPath, u as RulesyncSubagent, ut as RULESYNC_CONFIG_RELATIVE_FILE_PATH, v as McpProcessor, vt as RULESYNC_MCP_FILE_NAME, w as CommandsProcessor, wt as RULESYNC_PERMISSIONS_JSONC_FILE_NAME, x as RulesyncIgnore, xt as RULESYNC_MCP_SCHEMA_URL, y as RulesyncMcp, yt as RULESYNC_MCP_JSONC_FILE_NAME, z as CLIError } from "../import-B02AyWkQ.js";
3
3
  import { Command } from "commander";
4
4
  import { nonnegative, optional, refine, z } from "zod/mini";
5
5
  import * as path$1 from "node:path";
@@ -540,9 +540,9 @@ const FEATURE_PATHS = {
540
540
  subagents: ["subagents"],
541
541
  skills: ["skills"],
542
542
  ignore: [RULESYNC_AIIGNORE_FILE_NAME],
543
- mcp: [RULESYNC_MCP_FILE_NAME],
544
- hooks: [RULESYNC_HOOKS_FILE_NAME],
545
- permissions: [RULESYNC_PERMISSIONS_FILE_NAME]
543
+ mcp: [RULESYNC_MCP_FILE_NAME, RULESYNC_MCP_JSONC_FILE_NAME],
544
+ hooks: [RULESYNC_HOOKS_FILE_NAME, RULESYNC_HOOKS_JSONC_FILE_NAME],
545
+ permissions: [RULESYNC_PERMISSIONS_FILE_NAME, RULESYNC_PERMISSIONS_JSONC_FILE_NAME]
546
546
  };
547
547
  /**
548
548
  * Check if target is a tool target (not rulesync)
@@ -1423,6 +1423,11 @@ const GITIGNORE_ENTRY_REGISTRY = [
1423
1423
  target: "codexcli",
1424
1424
  feature: "ignore",
1425
1425
  entry: "**/.codexignore"
1426
+ },
1427
+ {
1428
+ target: "codexcli",
1429
+ feature: "permissions",
1430
+ entry: `**/${CODEXCLI_DIR}/rules/`
1426
1431
  }
1427
1432
  ],
1428
1433
  ...deriveAllGitignoreEntries(),
@@ -1497,45 +1502,69 @@ const resolveGitignoreEntries = (params) => {
1497
1502
  //#endregion
1498
1503
  //#region src/cli/commands/gitignore.ts
1499
1504
  const RULESYNC_HEADER = "# Generated by Rulesync";
1505
+ const RULESYNC_FOOTER = "# End of Rulesync";
1500
1506
  const LEGACY_RULESYNC_HEADER = "# Generated by rulesync - AI tool configuration files";
1501
1507
  const isRulesyncHeader = (line) => {
1502
1508
  const trimmed = line.trim();
1503
1509
  return trimmed === RULESYNC_HEADER || trimmed === LEGACY_RULESYNC_HEADER;
1504
1510
  };
1511
+ const isRulesyncFooter = (line) => {
1512
+ return line.trim() === RULESYNC_FOOTER;
1513
+ };
1505
1514
  const isRulesyncEntry = (line) => {
1506
1515
  const trimmed = line.trim();
1507
- if (trimmed === "" || isRulesyncHeader(line)) return false;
1516
+ if (trimmed === "" || isRulesyncHeader(line) || isRulesyncFooter(line)) return false;
1508
1517
  return ALL_GITIGNORE_ENTRIES.includes(trimmed);
1509
1518
  };
1519
+ const findRulesyncFooterIndex = (lines, start) => {
1520
+ for (let index = start; index < lines.length; index++) {
1521
+ const line = lines[index] ?? "";
1522
+ if (isRulesyncFooter(line)) return index;
1523
+ if (isRulesyncHeader(line)) return -1;
1524
+ }
1525
+ return -1;
1526
+ };
1527
+ const skipLegacyRulesyncBlock = (lines, headerIndex) => {
1528
+ let index = headerIndex + 1;
1529
+ let consecutiveEmptyLines = 0;
1530
+ while (index < lines.length) {
1531
+ const line = lines[index] ?? "";
1532
+ if (line.trim() === "") {
1533
+ consecutiveEmptyLines++;
1534
+ index++;
1535
+ if (consecutiveEmptyLines >= 2) break;
1536
+ continue;
1537
+ }
1538
+ if (isRulesyncEntry(line)) {
1539
+ consecutiveEmptyLines = 0;
1540
+ index++;
1541
+ continue;
1542
+ }
1543
+ break;
1544
+ }
1545
+ return index;
1546
+ };
1510
1547
  const removeExistingRulesyncEntries = (content) => {
1511
1548
  const lines = content.split("\n");
1512
1549
  const filteredLines = [];
1513
- let inRulesyncBlock = false;
1514
- let consecutiveEmptyLines = 0;
1515
- for (const line of lines) {
1516
- const trimmed = line.trim();
1550
+ let index = 0;
1551
+ while (index < lines.length) {
1552
+ const line = lines[index] ?? "";
1517
1553
  if (isRulesyncHeader(line)) {
1518
- inRulesyncBlock = true;
1519
- continue;
1520
- }
1521
- if (inRulesyncBlock) {
1522
- if (trimmed === "") {
1523
- consecutiveEmptyLines++;
1524
- if (consecutiveEmptyLines >= 2) {
1525
- inRulesyncBlock = false;
1526
- consecutiveEmptyLines = 0;
1527
- }
1554
+ const footerIndex = findRulesyncFooterIndex(lines, index + 1);
1555
+ if (footerIndex !== -1) {
1556
+ index = footerIndex + 1;
1528
1557
  continue;
1529
1558
  }
1530
- if (isRulesyncEntry(line)) {
1531
- consecutiveEmptyLines = 0;
1532
- continue;
1533
- }
1534
- inRulesyncBlock = false;
1535
- consecutiveEmptyLines = 0;
1559
+ index = skipLegacyRulesyncBlock(lines, index);
1560
+ continue;
1561
+ }
1562
+ if (isRulesyncEntry(line)) {
1563
+ index++;
1564
+ continue;
1536
1565
  }
1537
- if (isRulesyncEntry(line)) continue;
1538
1566
  filteredLines.push(line);
1567
+ index++;
1539
1568
  }
1540
1569
  let result = filteredLines.join("\n");
1541
1570
  while (result.endsWith("\n\n")) result = result.slice(0, -1);
@@ -1577,10 +1606,14 @@ const gitignoreCommand = async (logger, options) => {
1577
1606
  let content = "";
1578
1607
  if (await fileExists(filePath)) content = await readFileContent(filePath);
1579
1608
  const cleanedContent = removeExistingRulesyncEntries(content);
1580
- const existingEntries = new Set(content.split("\n").map((line) => line.trim()).filter((line) => line !== "" && !isRulesyncHeader(line)));
1609
+ const existingEntries = new Set(content.split("\n").map((line) => line.trim()).filter((line) => line !== "" && !isRulesyncHeader(line) && !isRulesyncFooter(line)));
1581
1610
  const alreadyExistedEntries = entries.filter((entry) => existingEntries.has(entry));
1582
1611
  const entriesToAdd = entries.filter((entry) => !existingEntries.has(entry));
1583
- const rulesyncBlock = [RULESYNC_HEADER, ...entries].join("\n");
1612
+ const rulesyncBlock = [
1613
+ RULESYNC_HEADER,
1614
+ ...entries,
1615
+ RULESYNC_FOOTER
1616
+ ].join("\n");
1584
1617
  const newContent = entries.length === 0 ? cleanedContent.trim() ? `${cleanedContent.trimEnd()}\n` : "" : cleanedContent.trim() ? `${cleanedContent.trimEnd()}\n\n${rulesyncBlock}\n` : `${rulesyncBlock}\n`;
1585
1618
  if (content === newContent) return {
1586
1619
  updated: false,
@@ -1713,7 +1746,8 @@ async function createConfigFile() {
1713
1746
  "commands",
1714
1747
  "subagents",
1715
1748
  "skills",
1716
- "hooks"
1749
+ "hooks",
1750
+ "permissions"
1717
1751
  ],
1718
1752
  outputRoots: ["."],
1719
1753
  delete: true,
@@ -1857,6 +1891,25 @@ Keep the summary concise and ready to reuse in future tasks.`
1857
1891
  ]
1858
1892
  }
1859
1893
  }
1894
+ ` };
1895
+ const samplePermissionsFile = { content: `{
1896
+ "$schema": "${RULESYNC_PERMISSIONS_SCHEMA_URL}",
1897
+ "permission": {
1898
+ "bash": {
1899
+ "git status": "allow",
1900
+ "git diff": "allow",
1901
+ "ls *": "allow",
1902
+ "rm -rf *": "deny",
1903
+ "*": "ask"
1904
+ },
1905
+ "edit": {
1906
+ "src/**": "allow"
1907
+ },
1908
+ "read": {
1909
+ ".env": "deny"
1910
+ }
1911
+ }
1912
+ }
1860
1913
  ` };
1861
1914
  const rulePaths = RulesyncRule.getSettablePaths();
1862
1915
  const mcpPaths = RulesyncMcp.getSettablePaths();
@@ -1865,6 +1918,7 @@ Keep the summary concise and ready to reuse in future tasks.`
1865
1918
  const skillPaths = RulesyncSkill.getSettablePaths();
1866
1919
  const ignorePaths = RulesyncIgnore.getSettablePaths();
1867
1920
  const hooksPaths = RulesyncHooks.getSettablePaths();
1921
+ const permissionsPaths = RulesyncPermissions.getSettablePaths();
1868
1922
  await ensureDir(rulePaths.recommended.relativeDirPath);
1869
1923
  await ensureDir(mcpPaths.recommended.relativeDirPath);
1870
1924
  await ensureDir(commandPaths.relativeDirPath);
@@ -1887,6 +1941,8 @@ Keep the summary concise and ready to reuse in future tasks.`
1887
1941
  results.push(await writeIfNotExists(ignoreFilepath, sampleIgnoreFile.content));
1888
1942
  const hooksFilepath = join(hooksPaths.relativeDirPath, hooksPaths.relativeFilePath);
1889
1943
  results.push(await writeIfNotExists(hooksFilepath, sampleHooksFile.content));
1944
+ const permissionsFilepath = join(permissionsPaths.relativeDirPath, permissionsPaths.relativeFilePath);
1945
+ results.push(await writeIfNotExists(permissionsFilepath, samplePermissionsFile.content));
1890
1946
  return results;
1891
1947
  }
1892
1948
  async function writeIfNotExists(path, content) {
@@ -1928,7 +1984,7 @@ async function initCommand(logger) {
1928
1984
  }
1929
1985
  logger.success("rulesync initialized successfully!");
1930
1986
  logger.info("Next steps:");
1931
- logger.info(`1. Edit ${RULESYNC_RELATIVE_DIR_PATH}/**/*.md, ${RULESYNC_RELATIVE_DIR_PATH}/skills/*/${SKILL_FILE_NAME$1}, ${RULESYNC_MCP_RELATIVE_FILE_PATH}, ${RULESYNC_HOOKS_RELATIVE_FILE_PATH} and ${RULESYNC_AIIGNORE_RELATIVE_FILE_PATH}`);
1987
+ logger.info(`1. Edit ${RULESYNC_RELATIVE_DIR_PATH}/**/*.md, ${RULESYNC_RELATIVE_DIR_PATH}/skills/*/${SKILL_FILE_NAME$1}, ${RULESYNC_MCP_RELATIVE_FILE_PATH}, ${RULESYNC_HOOKS_RELATIVE_FILE_PATH}, ${RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH} and ${RULESYNC_AIIGNORE_RELATIVE_FILE_PATH}`);
1932
1988
  logger.info("2. Run 'rulesync generate' to create configuration files");
1933
1989
  }
1934
1990
  //#endregion
@@ -4738,6 +4794,8 @@ async function putHooksFile({ content }) {
4738
4794
  try {
4739
4795
  const outputRoot = process.cwd();
4740
4796
  const paths = RulesyncHooks.getSettablePaths();
4797
+ const jsoncRelativePath = join(paths.jsonc.relativeDirPath, paths.jsonc.relativeFilePath);
4798
+ if (await fileExists(join(outputRoot, jsoncRelativePath))) throw new Error(`${jsoncRelativePath} exists and takes precedence over ${RULESYNC_HOOKS_RELATIVE_FILE_PATH}. Edit ${jsoncRelativePath} directly instead.`);
4741
4799
  const relativeDirPath = paths.relativeDirPath;
4742
4800
  const relativeFilePath = paths.relativeFilePath;
4743
4801
  const fullPath = join(outputRoot, relativeDirPath, relativeFilePath);
@@ -4766,6 +4824,7 @@ async function deleteHooksFile() {
4766
4824
  const outputRoot = process.cwd();
4767
4825
  const paths = RulesyncHooks.getSettablePaths();
4768
4826
  await removeFile(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath));
4827
+ await removeFile(join(outputRoot, paths.jsonc.relativeDirPath, paths.jsonc.relativeFilePath));
4769
4828
  return { relativePathFromCwd: join(paths.relativeDirPath, paths.relativeFilePath) };
4770
4829
  } catch (error) {
4771
4830
  throw new Error(`Failed to delete hooks file (${RULESYNC_HOOKS_RELATIVE_FILE_PATH}): ${formatError(error)}`, { cause: error });
@@ -5016,6 +5075,8 @@ async function putMcpFile({ content }) {
5016
5075
  try {
5017
5076
  const outputRoot = process.cwd();
5018
5077
  const paths = RulesyncMcp.getSettablePaths();
5078
+ const jsoncRelativePath = join(paths.jsonc.relativeDirPath, paths.jsonc.relativeFilePath);
5079
+ if (await fileExists(join(outputRoot, jsoncRelativePath))) throw new Error(`${jsoncRelativePath} exists and takes precedence over ${RULESYNC_MCP_RELATIVE_FILE_PATH}. Edit ${jsoncRelativePath} directly instead.`);
5019
5080
  const relativeDirPath = paths.recommended.relativeDirPath;
5020
5081
  const relativeFilePath = paths.recommended.relativeFilePath;
5021
5082
  const fullPath = join(outputRoot, relativeDirPath, relativeFilePath);
@@ -5046,6 +5107,7 @@ async function deleteMcpFile() {
5046
5107
  const recommendedPath = join(outputRoot, paths.recommended.relativeDirPath, paths.recommended.relativeFilePath);
5047
5108
  const legacyPath = join(outputRoot, paths.legacy.relativeDirPath, paths.legacy.relativeFilePath);
5048
5109
  await removeFile(recommendedPath);
5110
+ await removeFile(join(outputRoot, paths.jsonc.relativeDirPath, paths.jsonc.relativeFilePath));
5049
5111
  await removeFile(legacyPath);
5050
5112
  return { relativePathFromCwd: join(paths.recommended.relativeDirPath, paths.recommended.relativeFilePath) };
5051
5113
  } catch (error) {
@@ -5122,6 +5184,8 @@ async function putPermissionsFile({ content }) {
5122
5184
  try {
5123
5185
  const outputRoot = process.cwd();
5124
5186
  const paths = RulesyncPermissions.getSettablePaths();
5187
+ const jsoncRelativePath = join(paths.jsonc.relativeDirPath, paths.jsonc.relativeFilePath);
5188
+ if (await fileExists(join(outputRoot, jsoncRelativePath))) throw new Error(`${jsoncRelativePath} exists and takes precedence over ${RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH}. Edit ${jsoncRelativePath} directly instead.`);
5125
5189
  const relativeDirPath = paths.relativeDirPath;
5126
5190
  const relativeFilePath = paths.relativeFilePath;
5127
5191
  const fullPath = join(outputRoot, relativeDirPath, relativeFilePath);
@@ -5150,6 +5214,7 @@ async function deletePermissionsFile() {
5150
5214
  const outputRoot = process.cwd();
5151
5215
  const paths = RulesyncPermissions.getSettablePaths();
5152
5216
  await removeFile(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath));
5217
+ await removeFile(join(outputRoot, paths.jsonc.relativeDirPath, paths.jsonc.relativeFilePath));
5153
5218
  return { relativePathFromCwd: join(paths.relativeDirPath, paths.relativeFilePath) };
5154
5219
  } catch (error) {
5155
5220
  throw new Error(`Failed to delete permissions file (${RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH}): ${formatError(error)}`, { cause: error });
@@ -6448,7 +6513,7 @@ function wrapCommand$1({ name, errorCode, handler, getVersion, loggerFactory = c
6448
6513
  }
6449
6514
  //#endregion
6450
6515
  //#region src/cli/index.ts
6451
- const getVersion = () => "9.7.0";
6516
+ const getVersion = () => "10.0.0";
6452
6517
  function wrapCommand(name, errorCode, handler) {
6453
6518
  return wrapCommand$1({
6454
6519
  name,