rulesync 9.8.0 → 10.1.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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const require_import = require("../import-l0o1jVdc.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)
@@ -1204,11 +1204,13 @@ const DERIVED_PATHS_NOT_GITIGNORED = /* @__PURE__ */ new Set([
1204
1204
  "**/.antigravity/settings.json",
1205
1205
  "**/.claude/settings.json",
1206
1206
  "**/.claude/settings.local.json",
1207
+ "**/.codex/config.toml",
1207
1208
  "**/.devin/config.json",
1208
1209
  "**/.factory/settings.json",
1209
- "**/.gemini/settings.json",
1210
+ "**/.grok/config.toml",
1211
+ "**/.vibe/config.toml",
1212
+ "**/reasonix.toml",
1210
1213
  "**/.zed/settings.json",
1211
- "**/.warp/settings.toml",
1212
1214
  "**/kilo.json",
1213
1215
  "**/kilo.jsonc",
1214
1216
  "**/opencode.json"
@@ -1224,7 +1226,6 @@ const supportsProject = (factory) => {
1224
1226
  };
1225
1227
  const getProjectPaths = (factory) => factory.class.getSettablePaths({ global: false });
1226
1228
  const pushEntry = (entries, target, feature, entry) => {
1227
- if (DERIVED_PATHS_NOT_GITIGNORED.has(entry)) return;
1228
1229
  entries.push({
1229
1230
  target,
1230
1231
  feature,
@@ -1293,7 +1294,8 @@ const DERIVED_FEATURES = [
1293
1294
  "permissions",
1294
1295
  "ignore"
1295
1296
  ];
1296
- const deriveAllGitignoreEntries = () => DERIVED_FEATURES.flatMap((feature) => deriveFeatureGitignoreEntries(feature));
1297
+ const deriveAllGitignoreEntriesUnfiltered = () => DERIVED_FEATURES.flatMap((feature) => deriveFeatureGitignoreEntries(feature));
1298
+ const deriveAllGitignoreEntries = () => deriveAllGitignoreEntriesUnfiltered().filter((tag) => !DERIVED_PATHS_NOT_GITIGNORED.has(tag.entry));
1297
1299
  //#endregion
1298
1300
  //#region src/cli/commands/gitignore-entries.ts
1299
1301
  const normalizeGitignoreEntryTargets = (target) => {
@@ -1425,6 +1427,11 @@ const GITIGNORE_ENTRY_REGISTRY = [
1425
1427
  target: "codexcli",
1426
1428
  feature: "ignore",
1427
1429
  entry: "**/.codexignore"
1430
+ },
1431
+ {
1432
+ target: "codexcli",
1433
+ feature: "permissions",
1434
+ entry: `**/${require_import.CODEXCLI_DIR}/rules/`
1428
1435
  }
1429
1436
  ],
1430
1437
  ...deriveAllGitignoreEntries(),
@@ -1499,50 +1506,103 @@ const resolveGitignoreEntries = (params) => {
1499
1506
  //#endregion
1500
1507
  //#region src/cli/commands/gitignore.ts
1501
1508
  const RULESYNC_HEADER = "# Generated by Rulesync";
1509
+ const RULESYNC_FOOTER = "# End of Rulesync";
1502
1510
  const LEGACY_RULESYNC_HEADER = "# Generated by rulesync - AI tool configuration files";
1503
1511
  const isRulesyncHeader = (line) => {
1504
1512
  const trimmed = line.trim();
1505
1513
  return trimmed === RULESYNC_HEADER || trimmed === LEGACY_RULESYNC_HEADER;
1506
1514
  };
1515
+ const isRulesyncFooter = (line) => {
1516
+ return line.trim() === RULESYNC_FOOTER;
1517
+ };
1507
1518
  const isRulesyncEntry = (line) => {
1508
1519
  const trimmed = line.trim();
1509
- if (trimmed === "" || isRulesyncHeader(line)) return false;
1520
+ if (trimmed === "" || isRulesyncHeader(line) || isRulesyncFooter(line)) return false;
1510
1521
  return ALL_GITIGNORE_ENTRIES.includes(trimmed);
1511
1522
  };
1523
+ const findRulesyncFooterIndex = (lines, start) => {
1524
+ for (let index = start; index < lines.length; index++) {
1525
+ const line = lines[index] ?? "";
1526
+ if (isRulesyncFooter(line)) return index;
1527
+ if (isRulesyncHeader(line)) return -1;
1528
+ }
1529
+ return -1;
1530
+ };
1531
+ const skipLegacyRulesyncBlock = (lines, headerIndex) => {
1532
+ let index = headerIndex + 1;
1533
+ let consecutiveEmptyLines = 0;
1534
+ while (index < lines.length) {
1535
+ const line = lines[index] ?? "";
1536
+ if (line.trim() === "") {
1537
+ consecutiveEmptyLines++;
1538
+ index++;
1539
+ if (consecutiveEmptyLines >= 2) break;
1540
+ continue;
1541
+ }
1542
+ if (isRulesyncEntry(line)) {
1543
+ consecutiveEmptyLines = 0;
1544
+ index++;
1545
+ continue;
1546
+ }
1547
+ break;
1548
+ }
1549
+ return index;
1550
+ };
1512
1551
  const removeExistingRulesyncEntries = (content) => {
1513
1552
  const lines = content.split("\n");
1514
1553
  const filteredLines = [];
1515
- let inRulesyncBlock = false;
1516
- let consecutiveEmptyLines = 0;
1517
- for (const line of lines) {
1518
- const trimmed = line.trim();
1554
+ let index = 0;
1555
+ while (index < lines.length) {
1556
+ const line = lines[index] ?? "";
1519
1557
  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
- }
1530
- continue;
1531
- }
1532
- if (isRulesyncEntry(line)) {
1533
- consecutiveEmptyLines = 0;
1558
+ const footerIndex = findRulesyncFooterIndex(lines, index + 1);
1559
+ if (footerIndex !== -1) {
1560
+ index = footerIndex + 1;
1534
1561
  continue;
1535
1562
  }
1536
- inRulesyncBlock = false;
1537
- consecutiveEmptyLines = 0;
1563
+ index = skipLegacyRulesyncBlock(lines, index);
1564
+ continue;
1565
+ }
1566
+ if (isRulesyncEntry(line)) {
1567
+ index++;
1568
+ continue;
1538
1569
  }
1539
- if (isRulesyncEntry(line)) continue;
1540
1570
  filteredLines.push(line);
1571
+ index++;
1541
1572
  }
1542
1573
  let result = filteredLines.join("\n");
1543
1574
  while (result.endsWith("\n\n")) result = result.slice(0, -1);
1544
1575
  return result;
1545
1576
  };
1577
+ const extractRulesyncManagedEntries = (content) => {
1578
+ const lines = content.split("\n");
1579
+ const managed = [];
1580
+ let index = 0;
1581
+ const collectBlockLines = (start, end) => {
1582
+ for (const blockLine of lines.slice(start, end)) {
1583
+ const trimmed = blockLine.trim();
1584
+ if (trimmed !== "") managed.push(trimmed);
1585
+ }
1586
+ };
1587
+ while (index < lines.length) {
1588
+ const line = lines[index] ?? "";
1589
+ if (isRulesyncHeader(line)) {
1590
+ const footerIndex = findRulesyncFooterIndex(lines, index + 1);
1591
+ if (footerIndex !== -1) {
1592
+ collectBlockLines(index + 1, footerIndex);
1593
+ index = footerIndex + 1;
1594
+ continue;
1595
+ }
1596
+ const legacyEnd = skipLegacyRulesyncBlock(lines, index);
1597
+ collectBlockLines(index + 1, legacyEnd);
1598
+ index = legacyEnd;
1599
+ continue;
1600
+ }
1601
+ if (isRulesyncEntry(line)) managed.push(line.trim());
1602
+ index++;
1603
+ }
1604
+ return managed;
1605
+ };
1546
1606
  const groupEntriesByDestination = ({ entries, resolveDestination }) => {
1547
1607
  const gitignore = /* @__PURE__ */ new Set();
1548
1608
  const gitattributes = /* @__PURE__ */ new Set();
@@ -1579,21 +1639,29 @@ const gitignoreCommand = async (logger, options) => {
1579
1639
  let content = "";
1580
1640
  if (await require_import.fileExists(filePath)) content = await require_import.readFileContent(filePath);
1581
1641
  const cleanedContent = removeExistingRulesyncEntries(content);
1582
- const existingEntries = new Set(content.split("\n").map((line) => line.trim()).filter((line) => line !== "" && !isRulesyncHeader(line)));
1642
+ const entrySet = new Set(entries);
1643
+ const entriesRemoved = [...new Set(extractRulesyncManagedEntries(content).filter((entry) => !entrySet.has(entry)))];
1644
+ const existingEntries = new Set(content.split("\n").map((line) => line.trim()).filter((line) => line !== "" && !isRulesyncHeader(line) && !isRulesyncFooter(line)));
1583
1645
  const alreadyExistedEntries = entries.filter((entry) => existingEntries.has(entry));
1584
1646
  const entriesToAdd = entries.filter((entry) => !existingEntries.has(entry));
1585
- const rulesyncBlock = [RULESYNC_HEADER, ...entries].join("\n");
1647
+ const rulesyncBlock = [
1648
+ RULESYNC_HEADER,
1649
+ ...entries,
1650
+ RULESYNC_FOOTER
1651
+ ].join("\n");
1586
1652
  const newContent = entries.length === 0 ? cleanedContent.trim() ? `${cleanedContent.trimEnd()}\n` : "" : cleanedContent.trim() ? `${cleanedContent.trimEnd()}\n\n${rulesyncBlock}\n` : `${rulesyncBlock}\n`;
1587
1653
  if (content === newContent) return {
1588
1654
  updated: false,
1589
1655
  alreadyExistedEntries,
1590
- entriesToAdd: []
1656
+ entriesToAdd: [],
1657
+ entriesRemoved: []
1591
1658
  };
1592
1659
  await require_import.writeFileContent(filePath, newContent);
1593
1660
  return {
1594
1661
  updated: true,
1595
1662
  alreadyExistedEntries,
1596
- entriesToAdd
1663
+ entriesToAdd,
1664
+ entriesRemoved
1597
1665
  };
1598
1666
  };
1599
1667
  const gitignoreResult = await updateRulesyncFile({
@@ -1619,6 +1687,12 @@ const gitignoreCommand = async (logger, options) => {
1619
1687
  logger.captureData("gitignorePath", gitignorePath);
1620
1688
  logger.captureData("gitattributesPath", gitattributesPath);
1621
1689
  logger.captureData("alreadyExisted", [...gitignoreResult.alreadyExistedEntries, ...gitattributesResult.alreadyExistedEntries]);
1690
+ logger.captureData("entriesRemoved", gitignoreResult.entriesRemoved);
1691
+ }
1692
+ if (gitignoreResult.entriesRemoved.length > 0) {
1693
+ logger.warn("The following entries were removed from the rulesync-managed block in .gitignore and are no longer gitignored by rulesync:");
1694
+ for (const entry of gitignoreResult.entriesRemoved) logger.warn(` ${entry}`);
1695
+ logger.warn("Review these paths before committing — user-managed settings files may contain secrets.");
1622
1696
  }
1623
1697
  if (gitignoreResult.updated) logger.success("Updated .gitignore with rulesync entries:");
1624
1698
  else logger.success(".gitignore is already up to date");
@@ -1715,7 +1789,8 @@ async function createConfigFile() {
1715
1789
  "commands",
1716
1790
  "subagents",
1717
1791
  "skills",
1718
- "hooks"
1792
+ "hooks",
1793
+ "permissions"
1719
1794
  ],
1720
1795
  outputRoots: ["."],
1721
1796
  delete: true,
@@ -1859,6 +1934,25 @@ Keep the summary concise and ready to reuse in future tasks.`
1859
1934
  ]
1860
1935
  }
1861
1936
  }
1937
+ ` };
1938
+ const samplePermissionsFile = { content: `{
1939
+ "$schema": "${require_import.RULESYNC_PERMISSIONS_SCHEMA_URL}",
1940
+ "permission": {
1941
+ "bash": {
1942
+ "git status": "allow",
1943
+ "git diff": "allow",
1944
+ "ls *": "allow",
1945
+ "rm -rf *": "deny",
1946
+ "*": "ask"
1947
+ },
1948
+ "edit": {
1949
+ "src/**": "allow"
1950
+ },
1951
+ "read": {
1952
+ ".env": "deny"
1953
+ }
1954
+ }
1955
+ }
1862
1956
  ` };
1863
1957
  const rulePaths = require_import.RulesyncRule.getSettablePaths();
1864
1958
  const mcpPaths = require_import.RulesyncMcp.getSettablePaths();
@@ -1867,6 +1961,7 @@ Keep the summary concise and ready to reuse in future tasks.`
1867
1961
  const skillPaths = require_import.RulesyncSkill.getSettablePaths();
1868
1962
  const ignorePaths = require_import.RulesyncIgnore.getSettablePaths();
1869
1963
  const hooksPaths = require_import.RulesyncHooks.getSettablePaths();
1964
+ const permissionsPaths = require_import.RulesyncPermissions.getSettablePaths();
1870
1965
  await require_import.ensureDir(rulePaths.recommended.relativeDirPath);
1871
1966
  await require_import.ensureDir(mcpPaths.recommended.relativeDirPath);
1872
1967
  await require_import.ensureDir(commandPaths.relativeDirPath);
@@ -1889,6 +1984,8 @@ Keep the summary concise and ready to reuse in future tasks.`
1889
1984
  results.push(await writeIfNotExists(ignoreFilepath, sampleIgnoreFile.content));
1890
1985
  const hooksFilepath = (0, node_path.join)(hooksPaths.relativeDirPath, hooksPaths.relativeFilePath);
1891
1986
  results.push(await writeIfNotExists(hooksFilepath, sampleHooksFile.content));
1987
+ const permissionsFilepath = (0, node_path.join)(permissionsPaths.relativeDirPath, permissionsPaths.relativeFilePath);
1988
+ results.push(await writeIfNotExists(permissionsFilepath, samplePermissionsFile.content));
1892
1989
  return results;
1893
1990
  }
1894
1991
  async function writeIfNotExists(path, content) {
@@ -1930,7 +2027,7 @@ async function initCommand(logger) {
1930
2027
  }
1931
2028
  logger.success("rulesync initialized successfully!");
1932
2029
  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}`);
2030
+ 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
2031
  logger.info("2. Run 'rulesync generate' to create configuration files");
1935
2032
  }
1936
2033
  //#endregion
@@ -4740,6 +4837,8 @@ async function putHooksFile({ content }) {
4740
4837
  try {
4741
4838
  const outputRoot = process.cwd();
4742
4839
  const paths = require_import.RulesyncHooks.getSettablePaths();
4840
+ const jsoncRelativePath = (0, node_path.join)(paths.jsonc.relativeDirPath, paths.jsonc.relativeFilePath);
4841
+ 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
4842
  const relativeDirPath = paths.relativeDirPath;
4744
4843
  const relativeFilePath = paths.relativeFilePath;
4745
4844
  const fullPath = (0, node_path.join)(outputRoot, relativeDirPath, relativeFilePath);
@@ -4768,6 +4867,7 @@ async function deleteHooksFile() {
4768
4867
  const outputRoot = process.cwd();
4769
4868
  const paths = require_import.RulesyncHooks.getSettablePaths();
4770
4869
  await require_import.removeFile((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath));
4870
+ await require_import.removeFile((0, node_path.join)(outputRoot, paths.jsonc.relativeDirPath, paths.jsonc.relativeFilePath));
4771
4871
  return { relativePathFromCwd: (0, node_path.join)(paths.relativeDirPath, paths.relativeFilePath) };
4772
4872
  } catch (error) {
4773
4873
  throw new Error(`Failed to delete hooks file (${require_import.RULESYNC_HOOKS_RELATIVE_FILE_PATH}): ${require_import.formatError(error)}`, { cause: error });
@@ -5018,6 +5118,8 @@ async function putMcpFile({ content }) {
5018
5118
  try {
5019
5119
  const outputRoot = process.cwd();
5020
5120
  const paths = require_import.RulesyncMcp.getSettablePaths();
5121
+ const jsoncRelativePath = (0, node_path.join)(paths.jsonc.relativeDirPath, paths.jsonc.relativeFilePath);
5122
+ 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
5123
  const relativeDirPath = paths.recommended.relativeDirPath;
5022
5124
  const relativeFilePath = paths.recommended.relativeFilePath;
5023
5125
  const fullPath = (0, node_path.join)(outputRoot, relativeDirPath, relativeFilePath);
@@ -5048,6 +5150,7 @@ async function deleteMcpFile() {
5048
5150
  const recommendedPath = (0, node_path.join)(outputRoot, paths.recommended.relativeDirPath, paths.recommended.relativeFilePath);
5049
5151
  const legacyPath = (0, node_path.join)(outputRoot, paths.legacy.relativeDirPath, paths.legacy.relativeFilePath);
5050
5152
  await require_import.removeFile(recommendedPath);
5153
+ await require_import.removeFile((0, node_path.join)(outputRoot, paths.jsonc.relativeDirPath, paths.jsonc.relativeFilePath));
5051
5154
  await require_import.removeFile(legacyPath);
5052
5155
  return { relativePathFromCwd: (0, node_path.join)(paths.recommended.relativeDirPath, paths.recommended.relativeFilePath) };
5053
5156
  } catch (error) {
@@ -5124,6 +5227,8 @@ async function putPermissionsFile({ content }) {
5124
5227
  try {
5125
5228
  const outputRoot = process.cwd();
5126
5229
  const paths = require_import.RulesyncPermissions.getSettablePaths();
5230
+ const jsoncRelativePath = (0, node_path.join)(paths.jsonc.relativeDirPath, paths.jsonc.relativeFilePath);
5231
+ 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
5232
  const relativeDirPath = paths.relativeDirPath;
5128
5233
  const relativeFilePath = paths.relativeFilePath;
5129
5234
  const fullPath = (0, node_path.join)(outputRoot, relativeDirPath, relativeFilePath);
@@ -5152,6 +5257,7 @@ async function deletePermissionsFile() {
5152
5257
  const outputRoot = process.cwd();
5153
5258
  const paths = require_import.RulesyncPermissions.getSettablePaths();
5154
5259
  await require_import.removeFile((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath));
5260
+ await require_import.removeFile((0, node_path.join)(outputRoot, paths.jsonc.relativeDirPath, paths.jsonc.relativeFilePath));
5155
5261
  return { relativePathFromCwd: (0, node_path.join)(paths.relativeDirPath, paths.relativeFilePath) };
5156
5262
  } catch (error) {
5157
5263
  throw new Error(`Failed to delete permissions file (${require_import.RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH}): ${require_import.formatError(error)}`, { cause: error });
@@ -6450,7 +6556,7 @@ function wrapCommand$1({ name, errorCode, handler, getVersion, loggerFactory = c
6450
6556
  }
6451
6557
  //#endregion
6452
6558
  //#region src/cli/index.ts
6453
- const getVersion = () => "9.8.0";
6559
+ const getVersion = () => "10.1.0";
6454
6560
  function wrapCommand(name, errorCode, handler) {
6455
6561
  return wrapCommand$1({
6456
6562
  name,