rulesync 11.0.0 → 13.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.
@@ -37,6 +37,7 @@ let gray_matter = require("gray-matter");
37
37
  gray_matter = __toESM(gray_matter, 1);
38
38
  let js_yaml = require("js-yaml");
39
39
  let es_toolkit_object = require("es-toolkit/object");
40
+ let node_crypto = require("node:crypto");
40
41
  let _toon_format_toon = require("@toon-format/toon");
41
42
  //#region src/types/features.ts
42
43
  const ALL_FEATURES = [
@@ -47,7 +48,8 @@ const ALL_FEATURES = [
47
48
  "commands",
48
49
  "skills",
49
50
  "hooks",
50
- "permissions"
51
+ "permissions",
52
+ "checks"
51
53
  ];
52
54
  const ALL_FEATURES_WITH_WILDCARD = [...ALL_FEATURES, "*"];
53
55
  const FeatureSchema = zod_mini.z.enum(ALL_FEATURES);
@@ -114,25 +116,26 @@ function formatError(error) {
114
116
  }
115
117
  //#endregion
116
118
  //#region src/constants/rulesync-paths.ts
117
- const { join: join$251 } = node_path.posix;
119
+ const { join: join$256 } = node_path.posix;
118
120
  const RULESYNC_CONFIG_RELATIVE_FILE_PATH = "rulesync.jsonc";
119
121
  const RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH = "rulesync.local.jsonc";
120
122
  const RULESYNC_RELATIVE_DIR_PATH = ".rulesync";
121
- const RULESYNC_RULES_RELATIVE_DIR_PATH = join$251(RULESYNC_RELATIVE_DIR_PATH, "rules");
122
- const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$251(RULESYNC_RELATIVE_DIR_PATH, "commands");
123
- const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$251(RULESYNC_RELATIVE_DIR_PATH, "subagents");
124
- const RULESYNC_MCP_RELATIVE_FILE_PATH = join$251(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
125
- const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$251(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
126
- const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$251(RULESYNC_RELATIVE_DIR_PATH, "permissions.json");
127
- const RULESYNC_MCP_JSONC_RELATIVE_FILE_PATH = join$251(RULESYNC_RELATIVE_DIR_PATH, "mcp.jsonc");
128
- const RULESYNC_HOOKS_JSONC_RELATIVE_FILE_PATH = join$251(RULESYNC_RELATIVE_DIR_PATH, "hooks.jsonc");
129
- const RULESYNC_PERMISSIONS_JSONC_RELATIVE_FILE_PATH = join$251(RULESYNC_RELATIVE_DIR_PATH, "permissions.jsonc");
123
+ const RULESYNC_RULES_RELATIVE_DIR_PATH = join$256(RULESYNC_RELATIVE_DIR_PATH, "rules");
124
+ const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$256(RULESYNC_RELATIVE_DIR_PATH, "commands");
125
+ const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$256(RULESYNC_RELATIVE_DIR_PATH, "subagents");
126
+ const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$256(RULESYNC_RELATIVE_DIR_PATH, "checks");
127
+ const RULESYNC_MCP_RELATIVE_FILE_PATH = join$256(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
128
+ const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$256(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
129
+ const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$256(RULESYNC_RELATIVE_DIR_PATH, "permissions.json");
130
+ const RULESYNC_MCP_JSONC_RELATIVE_FILE_PATH = join$256(RULESYNC_RELATIVE_DIR_PATH, "mcp.jsonc");
131
+ const RULESYNC_HOOKS_JSONC_RELATIVE_FILE_PATH = join$256(RULESYNC_RELATIVE_DIR_PATH, "hooks.jsonc");
132
+ const RULESYNC_PERMISSIONS_JSONC_RELATIVE_FILE_PATH = join$256(RULESYNC_RELATIVE_DIR_PATH, "permissions.jsonc");
130
133
  const RULESYNC_AIIGNORE_FILE_NAME = ".aiignore";
131
- const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$251(RULESYNC_RELATIVE_DIR_PATH, ".aiignore");
134
+ const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$256(RULESYNC_RELATIVE_DIR_PATH, ".aiignore");
132
135
  const RULESYNC_IGNORE_RELATIVE_FILE_PATH = ".rulesyncignore";
133
136
  const RULESYNC_OVERVIEW_FILE_NAME = "overview.md";
134
- const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$251(RULESYNC_RELATIVE_DIR_PATH, "skills");
135
- const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$251(RULESYNC_SKILLS_RELATIVE_DIR_PATH, ".curated");
137
+ const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$256(RULESYNC_RELATIVE_DIR_PATH, "skills");
138
+ const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$256(RULESYNC_SKILLS_RELATIVE_DIR_PATH, ".curated");
136
139
  const RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync.lock";
137
140
  const RULESYNC_MCP_FILE_NAME = "mcp.json";
138
141
  const RULESYNC_HOOKS_FILE_NAME = "hooks.json";
@@ -289,6 +292,7 @@ const subagentsProcessorToolTargetTuple = [
289
292
  "kiro-ide",
290
293
  "opencode",
291
294
  "qwencode",
295
+ "reasonix",
292
296
  "roo",
293
297
  "rovodev",
294
298
  "takt",
@@ -385,6 +389,7 @@ const permissionsProcessorToolTargetTuple = [
385
389
  "warp",
386
390
  "zed"
387
391
  ];
392
+ const checksProcessorToolTargetTuple = ["amp"];
388
393
  //#endregion
389
394
  //#region src/types/tool-targets.ts
390
395
  const ALL_TOOL_TARGETS = [...new Set([
@@ -395,7 +400,8 @@ const ALL_TOOL_TARGETS = [...new Set([
395
400
  subagentsProcessorToolTargetTuple,
396
401
  skillsProcessorToolTargetTuple,
397
402
  hooksProcessorToolTargetTuple,
398
- permissionsProcessorToolTargetTuple
403
+ permissionsProcessorToolTargetTuple,
404
+ checksProcessorToolTargetTuple
399
405
  ].flat())];
400
406
  const ALL_TOOL_TARGETS_WITH_WILDCARD = [...ALL_TOOL_TARGETS, "*"];
401
407
  const ToolTargetSchema = zod_mini.z.enum(ALL_TOOL_TARGETS);
@@ -1469,7 +1475,7 @@ function isPlainObject$1(value) {
1469
1475
  /**
1470
1476
  * Type guard to check if a value is an array of strings.
1471
1477
  */
1472
- function isStringArray(value) {
1478
+ function isStringArray$1(value) {
1473
1479
  return Array.isArray(value) && value.every((item) => typeof item === "string");
1474
1480
  }
1475
1481
  //#endregion
@@ -1717,13 +1723,17 @@ var FeatureProcessor = class {
1717
1723
  }
1718
1724
  };
1719
1725
  //#endregion
1720
- //#region src/constants/agentsmd-paths.ts
1721
- const AGENTSMD_DIR = ".agents";
1722
- const AGENTSMD_MEMORIES_DIR_PATH = (0, node_path.join)(AGENTSMD_DIR, "memories");
1723
- const AGENTSMD_COMMANDS_DIR_PATH = (0, node_path.join)(AGENTSMD_DIR, "commands");
1724
- const AGENTSMD_SKILLS_DIR_PATH = (0, node_path.join)(AGENTSMD_DIR, "skills");
1725
- const AGENTSMD_SUBAGENTS_DIR_PATH = (0, node_path.join)(AGENTSMD_DIR, "subagents");
1726
- const AGENTSMD_RULE_FILE_NAME = "AGENTS.md";
1726
+ //#region src/constants/amp-paths.ts
1727
+ const AMP_DIR = ".amp";
1728
+ const AMP_GLOBAL_DIR = (0, node_path.join)(".config", "amp");
1729
+ const AMP_AGENTS_DIR = ".agents";
1730
+ const AMP_SKILLS_PROJECT_DIR = (0, node_path.join)(AMP_AGENTS_DIR, "skills");
1731
+ const AMP_SKILLS_GLOBAL_DIR = (0, node_path.join)(".config", "agents", "skills");
1732
+ const AMP_CHECKS_PROJECT_DIR = (0, node_path.join)(AMP_AGENTS_DIR, "checks");
1733
+ const AMP_CHECKS_GLOBAL_DIR = (0, node_path.join)(AMP_GLOBAL_DIR, "checks");
1734
+ const AMP_RULE_FILE_NAME = "AGENTS.md";
1735
+ const AMP_SETTINGS_FILE_NAME = "settings.json";
1736
+ const AMP_SETTINGS_JSONC_FILE_NAME = "settings.jsonc";
1727
1737
  //#endregion
1728
1738
  //#region src/types/ai-file.ts
1729
1739
  var AiFile = class {
@@ -1797,6 +1807,414 @@ var AiFile = class {
1797
1807
  }
1798
1808
  };
1799
1809
  //#endregion
1810
+ //#region src/types/rulesync-file.ts
1811
+ var RulesyncFile = class extends AiFile {
1812
+ static async fromFile(_params) {
1813
+ throw new Error("Please implement this method in the subclass.");
1814
+ }
1815
+ };
1816
+ //#endregion
1817
+ //#region src/features/checks/rulesync-check.ts
1818
+ const RulesyncCheckFrontmatterSchema = zod_mini.z.looseObject({
1819
+ targets: zod_mini.z._default(RulesyncTargetsSchema, ["*"]),
1820
+ description: zod_mini.z.optional(zod_mini.z.string()),
1821
+ severity: zod_mini.z.optional(zod_mini.z.enum([
1822
+ "low",
1823
+ "medium",
1824
+ "high",
1825
+ "critical"
1826
+ ])),
1827
+ tools: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string()))
1828
+ });
1829
+ var RulesyncCheck = class RulesyncCheck extends RulesyncFile {
1830
+ frontmatter;
1831
+ body;
1832
+ constructor({ frontmatter, body, ...rest }) {
1833
+ const parseResult = RulesyncCheckFrontmatterSchema.safeParse(frontmatter);
1834
+ if (!parseResult.success && rest.validate !== false) throw new Error(`Invalid frontmatter in ${(0, node_path.join)(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(parseResult.error)}`);
1835
+ const parsedFrontmatter = parseResult.success ? {
1836
+ ...frontmatter,
1837
+ ...parseResult.data
1838
+ } : {
1839
+ ...frontmatter,
1840
+ targets: frontmatter?.targets ?? ["*"]
1841
+ };
1842
+ super({
1843
+ ...rest,
1844
+ fileContent: stringifyFrontmatter(body, parsedFrontmatter)
1845
+ });
1846
+ this.frontmatter = parsedFrontmatter;
1847
+ this.body = body;
1848
+ }
1849
+ static getSettablePaths() {
1850
+ return { relativeDirPath: RULESYNC_CHECKS_RELATIVE_DIR_PATH };
1851
+ }
1852
+ getFrontmatter() {
1853
+ return this.frontmatter;
1854
+ }
1855
+ getBody() {
1856
+ return this.body;
1857
+ }
1858
+ validate() {
1859
+ if (!this.frontmatter) return {
1860
+ success: true,
1861
+ error: null
1862
+ };
1863
+ const result = RulesyncCheckFrontmatterSchema.safeParse(this.frontmatter);
1864
+ if (result.success) return {
1865
+ success: true,
1866
+ error: null
1867
+ };
1868
+ else return {
1869
+ success: false,
1870
+ error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${(0, node_path.join)(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
1871
+ };
1872
+ }
1873
+ static async fromFile({ outputRoot = process.cwd(), relativeFilePath }) {
1874
+ const filePath = (0, node_path.join)(outputRoot, RULESYNC_CHECKS_RELATIVE_DIR_PATH, relativeFilePath);
1875
+ const { frontmatter, body: content, hasFrontmatter } = parseFrontmatter(await readFileContent(filePath), filePath);
1876
+ if (!hasFrontmatter) throw new Error(`Missing frontmatter in ${filePath}. Rulesync files must begin with a YAML frontmatter block delimited by '---'.`);
1877
+ const result = RulesyncCheckFrontmatterSchema.safeParse(frontmatter);
1878
+ if (!result.success) throw new Error(`Invalid frontmatter in ${relativeFilePath}: ${formatError(result.error)}`);
1879
+ const filename = (0, node_path.basename)(relativeFilePath);
1880
+ return new RulesyncCheck({
1881
+ outputRoot,
1882
+ relativeDirPath: this.getSettablePaths().relativeDirPath,
1883
+ relativeFilePath: filename,
1884
+ frontmatter: result.data,
1885
+ body: content.trim()
1886
+ });
1887
+ }
1888
+ };
1889
+ //#endregion
1890
+ //#region src/types/tool-file.ts
1891
+ var ToolFile = class extends AiFile {};
1892
+ //#endregion
1893
+ //#region src/features/checks/tool-check.ts
1894
+ var ToolCheck = class extends ToolFile {
1895
+ static getSettablePaths(_options = {}) {
1896
+ throw new Error("Please implement this method in the subclass.");
1897
+ }
1898
+ static async fromFile(_params) {
1899
+ throw new Error("Please implement this method in the subclass.");
1900
+ }
1901
+ /**
1902
+ * Create a minimal instance for deletion purposes.
1903
+ * This method does not read or parse file content, making it safe to use
1904
+ * even when files have old/incompatible formats.
1905
+ */
1906
+ static forDeletion(_params) {
1907
+ throw new Error("Please implement this method in the subclass.");
1908
+ }
1909
+ static fromRulesyncCheck(_params) {
1910
+ throw new Error("Please implement this method in the subclass.");
1911
+ }
1912
+ static isTargetedByRulesyncCheck(_rulesyncCheck) {
1913
+ throw new Error("Please implement this method in the subclass.");
1914
+ }
1915
+ static isTargetedByRulesyncCheckDefault({ rulesyncCheck, toolTarget }) {
1916
+ const targets = rulesyncCheck.getFrontmatter().targets;
1917
+ if (!targets) return true;
1918
+ if (targets.includes("*")) return true;
1919
+ if (targets.includes(toolTarget)) return true;
1920
+ return false;
1921
+ }
1922
+ static filterToolSpecificSection(rawSection, excludeFields) {
1923
+ const filtered = {};
1924
+ for (const [key, value] of Object.entries(rawSection)) if (!excludeFields.includes(key)) filtered[key] = value;
1925
+ return filtered;
1926
+ }
1927
+ };
1928
+ //#endregion
1929
+ //#region src/features/checks/amp-check.ts
1930
+ const AmpCheckFrontmatterSchema = zod_mini.z.looseObject({
1931
+ name: zod_mini.z.string(),
1932
+ description: zod_mini.z.optional(zod_mini.z.string()),
1933
+ "severity-default": zod_mini.z.optional(zod_mini.z.enum([
1934
+ "low",
1935
+ "medium",
1936
+ "high",
1937
+ "critical"
1938
+ ])),
1939
+ tools: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string()))
1940
+ });
1941
+ /**
1942
+ * Represents an Amp code review check.
1943
+ *
1944
+ * Amp natively reads code review checks as Markdown files with YAML frontmatter,
1945
+ * scoped to the project (`.agents/checks/`) and user-wide (`~/.config/amp/checks/`).
1946
+ * Each check runs as a per-check subagent during code review.
1947
+ * @see https://ampcode.com/manual
1948
+ */
1949
+ var AmpCheck = class AmpCheck extends ToolCheck {
1950
+ frontmatter;
1951
+ body;
1952
+ constructor({ frontmatter, body, fileContent, ...rest }) {
1953
+ if (rest.validate !== false) {
1954
+ const result = AmpCheckFrontmatterSchema.safeParse(frontmatter);
1955
+ if (!result.success) throw new Error(`Invalid frontmatter in ${(0, node_path.join)(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
1956
+ }
1957
+ super({
1958
+ ...rest,
1959
+ fileContent: fileContent ?? stringifyFrontmatter(body, frontmatter)
1960
+ });
1961
+ this.frontmatter = frontmatter;
1962
+ this.body = body;
1963
+ }
1964
+ static getSettablePaths({ global = false } = {}) {
1965
+ return { relativeDirPath: global ? AMP_CHECKS_GLOBAL_DIR : AMP_CHECKS_PROJECT_DIR };
1966
+ }
1967
+ getFrontmatter() {
1968
+ return this.frontmatter;
1969
+ }
1970
+ getBody() {
1971
+ return this.body;
1972
+ }
1973
+ toRulesyncCheck() {
1974
+ const { name: _name, description, "severity-default": severityDefault, tools, ...restFields } = this.frontmatter;
1975
+ return new RulesyncCheck({
1976
+ outputRoot: ".",
1977
+ frontmatter: {
1978
+ targets: ["*"],
1979
+ ...description !== void 0 && { description },
1980
+ ...severityDefault !== void 0 && { severity: severityDefault },
1981
+ ...tools !== void 0 && { tools },
1982
+ ...restFields
1983
+ },
1984
+ body: this.body,
1985
+ relativeDirPath: RULESYNC_CHECKS_RELATIVE_DIR_PATH,
1986
+ relativeFilePath: this.getRelativeFilePath(),
1987
+ validate: true
1988
+ });
1989
+ }
1990
+ static fromRulesyncCheck({ outputRoot = process.cwd(), rulesyncCheck, validate = true, global = false }) {
1991
+ const rulesyncFrontmatter = rulesyncCheck.getFrontmatter();
1992
+ const relativeFilePath = rulesyncCheck.getRelativeFilePath();
1993
+ const name = (0, node_path.basename)(relativeFilePath, ".md");
1994
+ const { targets: _targets, description, severity, tools, ...restFields } = rulesyncFrontmatter;
1995
+ const toolTargetKeys = new Set(ALL_TOOL_TARGETS);
1996
+ const passthroughFields = Object.fromEntries(Object.entries(restFields).filter(([key]) => !toolTargetKeys.has(key) && key !== "name"));
1997
+ const ampSection = this.filterToolSpecificSection(rulesyncFrontmatter.amp ?? {}, ["name"]);
1998
+ const rawAmpFrontmatter = {
1999
+ name,
2000
+ ...description !== void 0 && { description },
2001
+ ...severity !== void 0 && { "severity-default": severity },
2002
+ ...tools !== void 0 && { tools },
2003
+ ...passthroughFields,
2004
+ ...ampSection
2005
+ };
2006
+ const result = AmpCheckFrontmatterSchema.safeParse(rawAmpFrontmatter);
2007
+ if (!result.success) throw new Error(`Invalid amp check frontmatter in ${relativeFilePath}: ${formatError(result.error)}`);
2008
+ const ampFrontmatter = result.data;
2009
+ const body = rulesyncCheck.getBody();
2010
+ const fileContent = stringifyFrontmatter(body, ampFrontmatter);
2011
+ const paths = this.getSettablePaths({ global });
2012
+ return new AmpCheck({
2013
+ outputRoot,
2014
+ frontmatter: ampFrontmatter,
2015
+ body,
2016
+ relativeDirPath: paths.relativeDirPath,
2017
+ relativeFilePath,
2018
+ fileContent,
2019
+ validate
2020
+ });
2021
+ }
2022
+ validate() {
2023
+ if (!this.frontmatter) return {
2024
+ success: true,
2025
+ error: null
2026
+ };
2027
+ const result = AmpCheckFrontmatterSchema.safeParse(this.frontmatter);
2028
+ if (result.success) return {
2029
+ success: true,
2030
+ error: null
2031
+ };
2032
+ else return {
2033
+ success: false,
2034
+ error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${(0, node_path.join)(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
2035
+ };
2036
+ }
2037
+ static isTargetedByRulesyncCheck(rulesyncCheck) {
2038
+ return this.isTargetedByRulesyncCheckDefault({
2039
+ rulesyncCheck,
2040
+ toolTarget: "amp"
2041
+ });
2042
+ }
2043
+ static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
2044
+ const paths = this.getSettablePaths({ global });
2045
+ const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, relativeFilePath);
2046
+ const fileContent = await readFileContent(filePath);
2047
+ const { frontmatter, body: content } = parseFrontmatter(fileContent, filePath);
2048
+ const result = AmpCheckFrontmatterSchema.safeParse(frontmatter);
2049
+ if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
2050
+ return new AmpCheck({
2051
+ outputRoot,
2052
+ relativeDirPath: paths.relativeDirPath,
2053
+ relativeFilePath,
2054
+ frontmatter: result.data,
2055
+ body: content.trim(),
2056
+ fileContent,
2057
+ validate
2058
+ });
2059
+ }
2060
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
2061
+ return new AmpCheck({
2062
+ outputRoot,
2063
+ relativeDirPath,
2064
+ relativeFilePath,
2065
+ frontmatter: { name: "" },
2066
+ body: "",
2067
+ fileContent: "",
2068
+ validate: false
2069
+ });
2070
+ }
2071
+ };
2072
+ //#endregion
2073
+ //#region src/features/checks/checks-processor.ts
2074
+ const ChecksProcessorToolTargetSchema = zod_mini.z.enum(checksProcessorToolTargetTuple);
2075
+ /**
2076
+ * Factory Map mapping tool targets to their check factories.
2077
+ * Using Map to preserve insertion order for consistent iteration.
2078
+ */
2079
+ const toolCheckFactories = /* @__PURE__ */ new Map([["amp", {
2080
+ class: AmpCheck,
2081
+ meta: {
2082
+ supportsGlobal: true,
2083
+ filePattern: "*.md"
2084
+ }
2085
+ }]]);
2086
+ const defaultGetFactory$6 = (target) => {
2087
+ const factory = toolCheckFactories.get(target);
2088
+ if (!factory) throw new Error(`Unsupported tool target: ${target}`);
2089
+ return factory;
2090
+ };
2091
+ const allToolTargetKeys$5 = [...toolCheckFactories.keys()];
2092
+ const checksProcessorToolTargets = allToolTargetKeys$5;
2093
+ const checksProcessorToolTargetsGlobal = allToolTargetKeys$5.filter((target) => {
2094
+ return toolCheckFactories.get(target)?.meta.supportsGlobal ?? false;
2095
+ });
2096
+ var ChecksProcessor = class extends FeatureProcessor {
2097
+ toolTarget;
2098
+ global;
2099
+ getFactory;
2100
+ constructor({ outputRoot = process.cwd(), inputRoot = process.cwd(), toolTarget, global = false, getFactory = defaultGetFactory$6, dryRun = false, logger }) {
2101
+ super({
2102
+ outputRoot,
2103
+ inputRoot,
2104
+ dryRun,
2105
+ logger
2106
+ });
2107
+ const result = ChecksProcessorToolTargetSchema.safeParse(toolTarget);
2108
+ if (!result.success) throw new Error(`Invalid tool target for ChecksProcessor: ${toolTarget}. ${formatError(result.error)}`);
2109
+ this.toolTarget = result.data;
2110
+ this.global = global;
2111
+ this.getFactory = getFactory;
2112
+ }
2113
+ async convertRulesyncFilesToToolFiles(rulesyncFiles) {
2114
+ const rulesyncChecks = rulesyncFiles.filter((file) => file instanceof RulesyncCheck);
2115
+ const factory = this.getFactory(this.toolTarget);
2116
+ return rulesyncChecks.filter((rulesyncCheck) => factory.class.isTargetedByRulesyncCheck(rulesyncCheck)).map((rulesyncCheck) => factory.class.fromRulesyncCheck({
2117
+ outputRoot: this.outputRoot,
2118
+ relativeDirPath: RulesyncCheck.getSettablePaths().relativeDirPath,
2119
+ rulesyncCheck,
2120
+ global: this.global
2121
+ }));
2122
+ }
2123
+ async convertToolFilesToRulesyncFiles(toolFiles) {
2124
+ return toolFiles.filter((file) => file instanceof ToolCheck).map((toolCheck) => toolCheck.toRulesyncCheck());
2125
+ }
2126
+ /**
2127
+ * Implementation of abstract method from Processor
2128
+ * Load and parse rulesync check files from .rulesync/checks/ directory
2129
+ */
2130
+ async loadRulesyncFiles() {
2131
+ const checksDir = (0, node_path.join)(this.inputRoot, RulesyncCheck.getSettablePaths().relativeDirPath);
2132
+ if (!await directoryExists(checksDir)) {
2133
+ this.logger.debug(`Rulesync checks directory not found: ${checksDir}`);
2134
+ return [];
2135
+ }
2136
+ const mdFiles = (await listDirectoryFiles(checksDir)).filter((file) => file.endsWith(".md"));
2137
+ if (mdFiles.length === 0) {
2138
+ this.logger.debug(`No markdown files found in rulesync checks directory: ${checksDir}`);
2139
+ return [];
2140
+ }
2141
+ this.logger.debug(`Found ${mdFiles.length} check files in ${checksDir}`);
2142
+ const rulesyncChecks = [];
2143
+ for (const mdFile of mdFiles) {
2144
+ const filepath = (0, node_path.join)(checksDir, mdFile);
2145
+ try {
2146
+ const rulesyncCheck = await RulesyncCheck.fromFile({
2147
+ outputRoot: this.inputRoot,
2148
+ relativeFilePath: mdFile,
2149
+ validate: true
2150
+ });
2151
+ rulesyncChecks.push(rulesyncCheck);
2152
+ this.logger.debug(`Successfully loaded check: ${mdFile}`);
2153
+ } catch (error) {
2154
+ this.logger.warn(`Failed to load check file ${filepath}: ${formatError(error)}`);
2155
+ continue;
2156
+ }
2157
+ }
2158
+ this.logger.debug(`Successfully loaded ${rulesyncChecks.length} rulesync checks`);
2159
+ return rulesyncChecks;
2160
+ }
2161
+ /**
2162
+ * Implementation of abstract method from Processor
2163
+ * Load tool-specific check files and parse them into ToolCheck instances
2164
+ */
2165
+ async loadToolFiles({ forDeletion = false } = {}) {
2166
+ const factory = this.getFactory(this.toolTarget);
2167
+ const paths = factory.class.getSettablePaths({ global: this.global });
2168
+ const baseDir = (0, node_path.join)(this.outputRoot, paths.relativeDirPath);
2169
+ const checkFilePaths = await findFilesByGlobs((0, node_path.join)(baseDir, factory.meta.filePattern));
2170
+ const toRelativeFilePath = (path) => (0, node_path.relative)(baseDir, path);
2171
+ if (forDeletion) return checkFilePaths.map((path) => factory.class.forDeletion({
2172
+ outputRoot: this.outputRoot,
2173
+ relativeDirPath: paths.relativeDirPath,
2174
+ relativeFilePath: toRelativeFilePath(path),
2175
+ global: this.global
2176
+ })).filter((check) => check.isDeletable());
2177
+ const loaded = await Promise.all(checkFilePaths.map((path) => factory.class.fromFile({
2178
+ outputRoot: this.outputRoot,
2179
+ relativeDirPath: paths.relativeDirPath,
2180
+ relativeFilePath: toRelativeFilePath(path),
2181
+ global: this.global
2182
+ })));
2183
+ this.logger.debug(`Successfully loaded ${loaded.length} ${this.toolTarget} checks from ${paths.relativeDirPath}`);
2184
+ return loaded;
2185
+ }
2186
+ /**
2187
+ * Implementation of abstract method from FeatureProcessor
2188
+ * Return the tool targets that this processor supports
2189
+ */
2190
+ static getToolTargets({ global = false } = {}) {
2191
+ if (global) return [...checksProcessorToolTargetsGlobal];
2192
+ return [...checksProcessorToolTargets];
2193
+ }
2194
+ static getToolTargetsSimulated() {
2195
+ return [];
2196
+ }
2197
+ /**
2198
+ * Get the factory for a specific tool target.
2199
+ * This is a static version of the internal getFactory for external use.
2200
+ * @param target - The tool target. Must be a valid ChecksProcessorToolTarget.
2201
+ * @returns The factory for the target, or undefined if not found.
2202
+ */
2203
+ static getFactory(target) {
2204
+ const result = ChecksProcessorToolTargetSchema.safeParse(target);
2205
+ if (!result.success) return;
2206
+ return toolCheckFactories.get(result.data);
2207
+ }
2208
+ };
2209
+ //#endregion
2210
+ //#region src/constants/agentsmd-paths.ts
2211
+ const AGENTSMD_DIR = ".agents";
2212
+ const AGENTSMD_MEMORIES_DIR_PATH = (0, node_path.join)(AGENTSMD_DIR, "memories");
2213
+ const AGENTSMD_COMMANDS_DIR_PATH = (0, node_path.join)(AGENTSMD_DIR, "commands");
2214
+ const AGENTSMD_SKILLS_DIR_PATH = (0, node_path.join)(AGENTSMD_DIR, "skills");
2215
+ const AGENTSMD_SUBAGENTS_DIR_PATH = (0, node_path.join)(AGENTSMD_DIR, "subagents");
2216
+ const AGENTSMD_RULE_FILE_NAME = "AGENTS.md";
2217
+ //#endregion
1800
2218
  //#region src/features/commands/tool-command.ts
1801
2219
  /**
1802
2220
  * Abstract base class for AI development tool-specific command formats.
@@ -2044,13 +2462,6 @@ const AntigravityCommandFrontmatterSchema = zod_mini.z.looseObject({
2044
2462
  ...AntigravityWorkflowFrontmatterSchema.shape
2045
2463
  });
2046
2464
  //#endregion
2047
- //#region src/types/rulesync-file.ts
2048
- var RulesyncFile = class extends AiFile {
2049
- static async fromFile(_params) {
2050
- throw new Error("Please implement this method in the subclass.");
2051
- }
2052
- };
2053
- //#endregion
2054
2465
  //#region src/features/commands/rulesync-command.ts
2055
2466
  const RulesyncCommandFrontmatterSchema = zod_mini.z.looseObject({
2056
2467
  targets: zod_mini.z._default(RulesyncTargetsSchema, ["*"]),
@@ -3111,135 +3522,118 @@ var CursorCommand = class CursorCommand extends ToolCommand {
3111
3522
  //#endregion
3112
3523
  //#region src/constants/devin-paths.ts
3113
3524
  const DEVIN_DIR = ".devin";
3114
- const CODEIUM_WINDSURF_DIR = (0, node_path.join)(".codeium", "windsurf");
3115
- const DEVIN_WORKFLOWS_DIR_PATH = (0, node_path.join)(DEVIN_DIR, "workflows");
3116
3525
  const DEVIN_SKILLS_DIR_PATH = (0, node_path.join)(DEVIN_DIR, "skills");
3117
3526
  const DEVIN_AGENTS_DIR_PATH = (0, node_path.join)(DEVIN_DIR, "agents");
3118
3527
  const DEVIN_GLOBAL_CONFIG_DIR_PATH = (0, node_path.join)(".config", "devin");
3119
3528
  const DEVIN_GLOBAL_AGENTS_DIR_PATH = (0, node_path.join)(DEVIN_GLOBAL_CONFIG_DIR_PATH, "agents");
3120
3529
  const DEVIN_GLOBAL_SKILLS_DIR_PATH = (0, node_path.join)(DEVIN_GLOBAL_CONFIG_DIR_PATH, "skills");
3121
- const CODEIUM_WINDSURF_GLOBAL_WORKFLOWS_DIR_PATH = (0, node_path.join)(CODEIUM_WINDSURF_DIR, "global_workflows");
3122
3530
  const DEVIN_CONFIG_FILE_NAME = "config.json";
3123
3531
  const DEVIN_HOOKS_V1_FILE_NAME = "hooks.v1.json";
3124
3532
  const DEVIN_GLOBAL_AGENTS_FILE_NAME = "AGENTS.md";
3125
3533
  const DEVIN_IGNORE_FILE_NAME = ".devinignore";
3126
3534
  const DEVIN_LEGACY_IGNORE_FILE_NAME = ".codeiumignore";
3127
3535
  //#endregion
3536
+ //#region src/constants/general.ts
3537
+ const SKILL_FILE_NAME$1 = "SKILL.md";
3538
+ //#endregion
3539
+ //#region src/features/commands/command-skill-ownership.ts
3540
+ /**
3541
+ * Slug used for the per-command `<slug>/SKILL.md` directory when a tool's
3542
+ * commands are emitted onto its skills surface (Devin, Hermes Agent).
3543
+ */
3544
+ function commandSlug(relativeFilePath) {
3545
+ return (0, node_path.basename)(relativeFilePath, ".md").replace(/[^a-zA-Z0-9_-]/g, "-");
3546
+ }
3547
+ /**
3548
+ * Whether a rulesync command exists whose slug matches `dirName`.
3549
+ *
3550
+ * Used by the skills-surface `isDirOwned` hooks of tools whose commands are
3551
+ * emitted as `<slug>/SKILL.md` into the skills tree: a directory matching a
3552
+ * current command slug is owned by the commands feature, so the skills
3553
+ * feature must neither import it as a skill nor delete it as an orphan
3554
+ * skill. Once the command is removed from `.rulesync/commands/`, the
3555
+ * directory stops matching and the skills feature cleans it up as a regular
3556
+ * orphan.
3557
+ */
3558
+ async function rulesyncCommandSlugExists({ inputRoot, dirName }) {
3559
+ return (await findFilesByGlobs((0, node_path.join)(inputRoot, RULESYNC_COMMANDS_RELATIVE_DIR_PATH, "**", "*.md"))).some((filePath) => commandSlug((0, node_path.basename)(filePath)) === dirName);
3560
+ }
3561
+ //#endregion
3128
3562
  //#region src/features/commands/devin-command.ts
3129
- const DevinCommandFrontmatterSchema = zod_mini.z.looseObject({ description: zod_mini.z.optional(zod_mini.z.string()) });
3563
+ function commandSkillContent$1(rulesyncCommand) {
3564
+ const slug = commandSlug(rulesyncCommand.getRelativeFilePath());
3565
+ const description = rulesyncCommand.getFrontmatter().description ?? `${slug} command`;
3566
+ return stringifyFrontmatter(rulesyncCommand.getBody().trim(), {
3567
+ name: slug,
3568
+ description
3569
+ });
3570
+ }
3130
3571
  /**
3131
- * Represents a Devin (Cascade, now Devin Desktop) workflow command.
3132
- * Devin supports workflows in both project mode under .devin/workflows/
3133
- * (preferred since the Devin Desktop rebrand; .devin/workflows/ is the
3134
- * legacy fallback the tool still reads) and global mode under
3135
- * ~/.codeium/windsurf/global_workflows/ (unchanged by the rebrand).
3572
+ * Represents a Devin slash command, emitted as a Devin Skill.
3573
+ *
3574
+ * Devin's extensibility docs no longer document a standalone
3575
+ * workflows/commands component reusable prompts invoked as slash commands
3576
+ * are Skills (`/name`). Commands are therefore emitted onto the native skills
3577
+ * surface, one `SKILL.md` per command: `.devin/skills/<slug>/SKILL.md`
3578
+ * (project) and `~/.config/devin/skills/<slug>/SKILL.md` (global). The legacy
3579
+ * Windsurf/Cascade-era `.devin/workflows/` and
3580
+ * `~/.codeium/windsurf/global_workflows/` locations are no longer emitted.
3581
+ *
3582
+ * Import and deletion are intentionally no-ops for this target: the skills
3583
+ * feature owns the `.devin/skills/` tree, so importing it as commands would
3584
+ * double-import every skill (mirrors the Hermes Agent commands target).
3585
+ *
3586
+ * @see https://docs.devin.ai/cli/extensibility
3587
+ * @see https://docs.devin.ai/cli/extensibility/skills/overview
3136
3588
  */
3137
3589
  var DevinCommand = class DevinCommand extends ToolCommand {
3138
- frontmatter;
3139
- body;
3140
- constructor({ frontmatter, body, ...rest }) {
3141
- if (rest.validate) {
3142
- const result = DevinCommandFrontmatterSchema.safeParse(frontmatter);
3143
- if (!result.success) throw new Error(`Invalid frontmatter in ${(0, node_path.join)(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
3144
- }
3145
- super({
3146
- ...rest,
3147
- fileContent: stringifyFrontmatter(body, frontmatter)
3590
+ static isTargetedByRulesyncCommand(rulesyncCommand) {
3591
+ return this.isTargetedByRulesyncCommandDefault({
3592
+ rulesyncCommand,
3593
+ toolTarget: "devin"
3148
3594
  });
3149
- this.frontmatter = frontmatter;
3150
- this.body = body;
3151
3595
  }
3152
3596
  static getSettablePaths({ global = false } = {}) {
3153
- if (global) return { relativeDirPath: CODEIUM_WINDSURF_GLOBAL_WORKFLOWS_DIR_PATH };
3154
- return { relativeDirPath: DEVIN_WORKFLOWS_DIR_PATH };
3155
- }
3156
- getBody() {
3157
- return this.body;
3158
- }
3159
- getFrontmatter() {
3160
- return this.frontmatter;
3597
+ return { relativeDirPath: global ? DEVIN_GLOBAL_SKILLS_DIR_PATH : DEVIN_SKILLS_DIR_PATH };
3161
3598
  }
3162
- toRulesyncCommand() {
3163
- const { description, ...restFields } = this.frontmatter;
3164
- const rulesyncFrontmatter = {
3165
- targets: ["*"],
3166
- description,
3167
- ...Object.keys(restFields).length > 0 && { devin: restFields }
3168
- };
3169
- const fileContent = stringifyFrontmatter(this.body, rulesyncFrontmatter);
3170
- return new RulesyncCommand({
3171
- outputRoot: process.cwd(),
3172
- frontmatter: rulesyncFrontmatter,
3173
- body: this.body,
3174
- relativeDirPath: RulesyncCommand.getSettablePaths().relativeDirPath,
3175
- relativeFilePath: this.relativeFilePath,
3176
- fileContent,
3177
- validate: true
3178
- });
3179
- }
3180
- static fromRulesyncCommand({ outputRoot = process.cwd(), rulesyncCommand, validate = true, global = false }) {
3181
- const rulesyncFrontmatter = rulesyncCommand.getFrontmatter();
3182
- const devinFields = rulesyncFrontmatter.devin ?? {};
3183
- const devinFrontmatter = {
3184
- description: rulesyncFrontmatter.description,
3185
- ...devinFields
3186
- };
3187
- const body = rulesyncCommand.getBody();
3188
- const paths = this.getSettablePaths({ global });
3189
- return new DevinCommand({
3190
- outputRoot,
3191
- frontmatter: devinFrontmatter,
3192
- body,
3193
- relativeDirPath: paths.relativeDirPath,
3194
- relativeFilePath: rulesyncCommand.getRelativeFilePath(),
3195
- validate
3599
+ constructor({ slug, ...params }) {
3600
+ super({
3601
+ ...params,
3602
+ ...slug !== void 0 && {
3603
+ relativeDirPath: (0, node_path.join)(params.relativeDirPath, slug),
3604
+ relativeFilePath: "SKILL.md"
3605
+ }
3196
3606
  });
3197
3607
  }
3198
3608
  validate() {
3199
- if (!this.frontmatter) return {
3200
- success: true,
3201
- error: null
3202
- };
3203
- const result = DevinCommandFrontmatterSchema.safeParse(this.frontmatter);
3204
- if (result.success) return {
3609
+ return {
3205
3610
  success: true,
3206
3611
  error: null
3207
3612
  };
3208
- else return {
3209
- success: false,
3210
- error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${(0, node_path.join)(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
3211
- };
3212
3613
  }
3213
- static isTargetedByRulesyncCommand(rulesyncCommand) {
3214
- return this.isTargetedByRulesyncCommandDefault({
3215
- rulesyncCommand,
3216
- toolTarget: "devin"
3614
+ toRulesyncCommand() {
3615
+ const slug = (0, node_path.basename)((0, node_path.dirname)(this.getRelativePathFromCwd()));
3616
+ const { frontmatter, body } = parseFrontmatter(this.getFileContent(), this.getFilePath());
3617
+ const description = typeof frontmatter.description === "string" ? frontmatter.description : void 0;
3618
+ return new RulesyncCommand({
3619
+ relativeDirPath: RULESYNC_COMMANDS_RELATIVE_DIR_PATH,
3620
+ relativeFilePath: `${slug}.md`,
3621
+ frontmatter: { description },
3622
+ body: body.trimStart()
3217
3623
  });
3218
3624
  }
3219
- static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
3220
- const paths = this.getSettablePaths({ global });
3221
- const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, relativeFilePath);
3222
- const { frontmatter, body: content } = parseFrontmatter(await readFileContent(filePath), filePath);
3223
- const result = DevinCommandFrontmatterSchema.safeParse(frontmatter);
3224
- if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
3625
+ static fromRulesyncCommand({ outputRoot, rulesyncCommand, global = false }) {
3626
+ const paths = DevinCommand.getSettablePaths({ global });
3225
3627
  return new DevinCommand({
3226
3628
  outputRoot,
3227
3629
  relativeDirPath: paths.relativeDirPath,
3228
- relativeFilePath,
3229
- frontmatter: result.data,
3230
- body: content.trim(),
3231
- validate
3630
+ relativeFilePath: SKILL_FILE_NAME$1,
3631
+ slug: commandSlug(rulesyncCommand.getRelativeFilePath()),
3632
+ fileContent: commandSkillContent$1(rulesyncCommand)
3232
3633
  });
3233
3634
  }
3234
- static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
3235
- return new DevinCommand({
3236
- outputRoot,
3237
- relativeDirPath,
3238
- relativeFilePath,
3239
- frontmatter: { description: "" },
3240
- body: "",
3241
- validate: false
3242
- });
3635
+ getFileContent() {
3636
+ return this.fileContent;
3243
3637
  }
3244
3638
  };
3245
3639
  //#endregion
@@ -3568,10 +3962,7 @@ const HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_MANIFEST_PATH = (0, node_path.join)(
3568
3962
  const HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_INIT_PATH = (0, node_path.join)(HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_DIR_PATH, "__init__.py");
3569
3963
  //#endregion
3570
3964
  //#region src/features/commands/hermesagent-command.ts
3571
- const SKILL_FILE_NAME$1 = "SKILL.md";
3572
- function commandSlug(relativeFilePath) {
3573
- return (0, node_path.basename)(relativeFilePath, ".md").replace(/[^a-zA-Z0-9_-]/g, "-");
3574
- }
3965
+ const SKILL_FILE_NAME = "SKILL.md";
3575
3966
  function commandSkillContent(rulesyncCommand) {
3576
3967
  const slug = commandSlug(rulesyncCommand.getRelativeFilePath());
3577
3968
  const description = rulesyncCommand.getFrontmatter().description ?? `${slug} command`;
@@ -3588,7 +3979,7 @@ var HermesagentCommand = class HermesagentCommand extends ToolCommand {
3588
3979
  static getSettablePaths({ slug = "command" } = {}) {
3589
3980
  return {
3590
3981
  relativeDirPath: (0, node_path.join)(HERMESAGENT_SKILLS_DIR_PATH, slug),
3591
- relativeFilePath: SKILL_FILE_NAME$1
3982
+ relativeFilePath: SKILL_FILE_NAME
3592
3983
  };
3593
3984
  }
3594
3985
  constructor({ slug, ...params }) {
@@ -3619,7 +4010,7 @@ var HermesagentCommand = class HermesagentCommand extends ToolCommand {
3619
4010
  return new HermesagentCommand({
3620
4011
  outputRoot,
3621
4012
  relativeDirPath: "",
3622
- relativeFilePath: SKILL_FILE_NAME$1,
4013
+ relativeFilePath: SKILL_FILE_NAME,
3623
4014
  slug: commandSlug(rulesyncCommand.getRelativeFilePath()),
3624
4015
  fileContent: commandSkillContent(rulesyncCommand)
3625
4016
  });
@@ -4854,6 +5245,7 @@ const PI_AGENT_SKILLS_DIR_PATH = (0, node_path.join)(PI_AGENT_DIR, "skills");
4854
5245
  const PI_PROMPTS_DIR_PATH = (0, node_path.join)(".pi", "prompts");
4855
5246
  const PI_SKILLS_DIR_PATH = (0, node_path.join)(".pi", "skills");
4856
5247
  const PI_RULE_FILE_NAME = "AGENTS.md";
5248
+ const PI_APPEND_SYSTEM_FILE_NAME = "APPEND_SYSTEM.md";
4857
5249
  //#endregion
4858
5250
  //#region src/features/commands/pi-command.ts
4859
5251
  /**
@@ -5118,6 +5510,9 @@ const REASONIX_SETTINGS_FILE_NAME = "settings.json";
5118
5510
  const REASONIX_COMMANDS_DIR_PATH = (0, node_path.join)(REASONIX_DIR, "commands");
5119
5511
  const REASONIX_RULE_FILE_NAME = "REASONIX.md";
5120
5512
  const REASONIX_SKILLS_DIR_PATH = (0, node_path.join)(REASONIX_DIR, "skills");
5513
+ const REASONIX_SUBAGENTS_DIR_PATH = REASONIX_SKILLS_DIR_PATH;
5514
+ const REASONIX_SUBAGENT_INVOCATION = "manual";
5515
+ const REASONIX_SUBAGENT_RUN_AS = "subagent";
5121
5516
  //#endregion
5122
5517
  //#region src/features/commands/reasonix-command.ts
5123
5518
  /**
@@ -5385,9 +5780,6 @@ const ROVODEV_AGENTS_SKILLS_DIR_PATH = (0, node_path.join)(".agents", "skills");
5385
5780
  const ROVODEV_PROMPTS_FILE_NAME = "prompts.yml";
5386
5781
  const ROVODEV_PROMPTS_DIR_PATH = (0, node_path.join)(ROVODEV_DIR, "prompts");
5387
5782
  //#endregion
5388
- //#region src/types/tool-file.ts
5389
- var ToolFile = class extends AiFile {};
5390
- //#endregion
5391
5783
  //#region src/features/commands/rovodev-command.ts
5392
5784
  /**
5393
5785
  * Rovo Dev CLI "saved prompts": a file-based custom-command surface made of a
@@ -5876,7 +6268,8 @@ const toolCommandFactories = /* @__PURE__ */ new Map([
5876
6268
  supportsProject: false,
5877
6269
  supportsGlobal: true,
5878
6270
  isSimulated: false,
5879
- supportsSubdirectory: true
6271
+ supportsSubdirectory: true,
6272
+ skipToolFileScan: true
5880
6273
  }
5881
6274
  }],
5882
6275
  ["junie", {
@@ -6006,7 +6399,8 @@ const toolCommandFactories = /* @__PURE__ */ new Map([
6006
6399
  supportsProject: true,
6007
6400
  supportsGlobal: true,
6008
6401
  isSimulated: false,
6009
- supportsSubdirectory: false
6402
+ supportsSubdirectory: false,
6403
+ skipToolFileScan: true
6010
6404
  }
6011
6405
  }]
6012
6406
  ]);
@@ -6109,6 +6503,7 @@ var CommandsProcessor = class extends FeatureProcessor {
6109
6503
  */
6110
6504
  async loadToolFiles({ forDeletion = false } = {}) {
6111
6505
  const factory = this.getFactory(this.toolTarget);
6506
+ if (factory.meta.skipToolFileScan) return [];
6112
6507
  const paths = factory.class.getSettablePaths({ global: this.global });
6113
6508
  const outputRootFull = (0, node_path.join)(this.outputRoot, paths.relativeDirPath);
6114
6509
  const commandFilePaths = await findFilesByGlobs(factory.meta.supportsSubdirectory ? (0, node_path.join)(outputRootFull, "**", `*.${factory.meta.extension}`) : (0, node_path.join)(outputRootFull, `*.${factory.meta.extension}`));
@@ -6218,7 +6613,10 @@ const HookDefinitionSchema = zod_mini.z.looseObject({
6218
6613
  type: zod_mini.z.optional(zod_mini.z.enum([
6219
6614
  "command",
6220
6615
  "prompt",
6221
- "http"
6616
+ "http",
6617
+ "agent",
6618
+ "mcp_tool",
6619
+ "function"
6222
6620
  ])),
6223
6621
  url: zod_mini.z.optional(safeString),
6224
6622
  timeout: zod_mini.z.optional(zod_mini.z.number()),
@@ -6231,12 +6629,71 @@ const HookDefinitionSchema = zod_mini.z.looseObject({
6231
6629
  sequential: zod_mini.z.optional(zod_mini.z.boolean()),
6232
6630
  async: zod_mini.z.optional(zod_mini.z.boolean()),
6233
6631
  env: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), safeString)),
6234
- shell: zod_mini.z.optional(safeString),
6632
+ shell: zod_mini.z.optional(zod_mini.z.enum(["bash", "powershell"])),
6235
6633
  statusMessage: zod_mini.z.optional(safeString),
6236
6634
  headers: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), safeString)),
6237
6635
  allowedEnvVars: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
6238
- once: zod_mini.z.optional(zod_mini.z.boolean())
6636
+ once: zod_mini.z.optional(zod_mini.z.boolean()),
6637
+ server: zod_mini.z.optional(safeString),
6638
+ tool: zod_mini.z.optional(safeString),
6639
+ input: zod_mini.z.optional(zod_mini.z.looseObject({})),
6640
+ model: zod_mini.z.optional(safeString)
6239
6641
  });
6642
+ /**
6643
+ * All canonical hook event names.
6644
+ * Each tool supports a subset of these events.
6645
+ */
6646
+ const HOOK_EVENTS = [
6647
+ "sessionStart",
6648
+ "sessionEnd",
6649
+ "preToolUse",
6650
+ "postToolUse",
6651
+ "preModelInvocation",
6652
+ "postModelInvocation",
6653
+ "beforeSubmitPrompt",
6654
+ "stop",
6655
+ "subagentStop",
6656
+ "preCompact",
6657
+ "postCompact",
6658
+ "contextOffload",
6659
+ "postToolUseFailure",
6660
+ "subagentStart",
6661
+ "beforeShellExecution",
6662
+ "afterShellExecution",
6663
+ "beforeMCPExecution",
6664
+ "afterMCPExecution",
6665
+ "beforeReadFile",
6666
+ "afterFileEdit",
6667
+ "beforeAgentResponse",
6668
+ "afterAgentResponse",
6669
+ "afterAgentThought",
6670
+ "beforeTabFileRead",
6671
+ "afterTabFileEdit",
6672
+ "permissionRequest",
6673
+ "notification",
6674
+ "setup",
6675
+ "afterError",
6676
+ "beforeToolSelection",
6677
+ "worktreeCreate",
6678
+ "worktreeRemove",
6679
+ "workspaceOpen",
6680
+ "messageDisplay",
6681
+ "todoCreated",
6682
+ "todoCompleted",
6683
+ "stopFailure",
6684
+ "instructionsLoaded",
6685
+ "userPromptExpansion",
6686
+ "postToolBatch",
6687
+ "permissionDenied",
6688
+ "taskCreated",
6689
+ "taskCompleted",
6690
+ "teammateIdle",
6691
+ "configChange",
6692
+ "cwdChanged",
6693
+ "fileChanged",
6694
+ "elicitation",
6695
+ "elicitationResult"
6696
+ ];
6240
6697
  /** Hook events supported by Cursor. */
6241
6698
  const CURSOR_HOOK_EVENTS = [
6242
6699
  "sessionStart",
@@ -6711,12 +7168,28 @@ const CANONICAL_TO_HERMESAGENT_EVENT_NAMES = {
6711
7168
  */
6712
7169
  const HERMESAGENT_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_HERMESAGENT_EVENT_NAMES).map(([k, v]) => [v, k]));
6713
7170
  const hooksRecordSchema = zod_mini.z.record(zod_mini.z.string(), zod_mini.z.array(HookDefinitionSchema));
7171
+ const HOOK_EVENT_SET = new Set(HOOK_EVENTS);
7172
+ /** Whether `value` is a canonical hook event name. */
7173
+ const isHookEvent = (value) => HOOK_EVENT_SET.has(value);
7174
+ /**
7175
+ * Top-level `hooks` record whose keys must be canonical event names, so typos
7176
+ * are rejected at parse time. Keys are validated with a refinement (instead of
7177
+ * an enum key schema) to keep the inferred type a plain string record.
7178
+ *
7179
+ * The per-tool override blocks below deliberately keep the lenient
7180
+ * `hooksRecordSchema`: some are documented to pass tool-native event keys
7181
+ * through verbatim (e.g. kiro-ide's IDE-only `PostFileSave`/`PreTaskExec`
7182
+ * triggers), which the canonical enum would reject.
7183
+ */
7184
+ const canonicalHooksRecordSchema = zod_mini.z.record(zod_mini.z.string(), zod_mini.z.array(HookDefinitionSchema)).check(zod_mini.z.refine((record) => Object.keys(record).every((key) => HOOK_EVENT_SET.has(key)), { error: (issue) => {
7185
+ return `unknown hook event name(s): ${Object.keys(issue.input ?? {}).filter((key) => !HOOK_EVENT_SET.has(key)).join(", ")}`;
7186
+ } }));
6714
7187
  /**
6715
7188
  * Canonical hooks config (canonical event names in camelCase).
6716
7189
  */
6717
7190
  const HooksConfigSchema = zod_mini.z.looseObject({
6718
7191
  version: zod_mini.z.optional(zod_mini.z.number()),
6719
- hooks: hooksRecordSchema,
7192
+ hooks: canonicalHooksRecordSchema,
6720
7193
  cursor: zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
6721
7194
  claudecode: zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
6722
7195
  copilot: zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
@@ -7153,6 +7626,20 @@ const CANONICAL_TO_GROKCLI_EVENT_NAMES = {
7153
7626
  */
7154
7627
  const GROKCLI_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_GROKCLI_EVENT_NAMES).map(([k, v]) => [v, k]));
7155
7628
  //#endregion
7629
+ //#region src/utils/object.ts
7630
+ /**
7631
+ * Return a shallow copy of `obj` keeping only the entries whose value is
7632
+ * neither `undefined` nor `null`.
7633
+ *
7634
+ * Used to assemble generated config objects without one conditional spread per
7635
+ * optional field (which would otherwise exceed the lint complexity budget).
7636
+ */
7637
+ function compact(obj) {
7638
+ const result = {};
7639
+ for (const [key, value] of Object.entries(obj)) if (value !== void 0 && value !== null) result[key] = value;
7640
+ return result;
7641
+ }
7642
+ //#endregion
7156
7643
  //#region src/features/hooks/tool-hooks-converter.ts
7157
7644
  function isToolMatcherEntry(x) {
7158
7645
  if (x === null || typeof x !== "object") return false;
@@ -7210,6 +7697,25 @@ function importBooleanPassthroughFields({ h, converterConfig }) {
7210
7697
  return Object.fromEntries((converterConfig.booleanPassthroughFields ?? []).filter(({ tool }) => typeof h[tool] === "boolean").map(({ canonical, tool }) => [canonical, h[tool]]));
7211
7698
  }
7212
7699
  /**
7700
+ * Emit the payload fields specific to a hook type — `url`/`headers`/
7701
+ * `allowedEnvVars` for http, `server`/`tool`/`input` for mcp_tool, `model`
7702
+ * for prompt/agent. https://code.claude.com/docs/en/hooks
7703
+ */
7704
+ function emitTypePayloadFields({ def, hookType, converterConfig }) {
7705
+ if (hookType === "http") return compact({
7706
+ url: def.url,
7707
+ headers: def.headers,
7708
+ allowedEnvVars: def.allowedEnvVars
7709
+ });
7710
+ if (hookType === "mcp_tool") return compact({
7711
+ server: def.server,
7712
+ tool: def.tool,
7713
+ input: def.input
7714
+ });
7715
+ if ((hookType === "prompt" || hookType === "agent") && converterConfig.emitsPromptModel) return compact({ model: def.model });
7716
+ return {};
7717
+ }
7718
+ /**
7213
7719
  * Convert the definitions of a single matcher group into tool hook entries,
7214
7720
  * honoring supported hook types and passthrough fields.
7215
7721
  */
@@ -7231,6 +7737,11 @@ function buildToolHooks({ defs, converterConfig }) {
7231
7737
  ...command !== void 0 && command !== null && { command },
7232
7738
  ...def.timeout !== void 0 && def.timeout !== null && { timeout: def.timeout },
7233
7739
  ...def.prompt !== void 0 && def.prompt !== null && { prompt: def.prompt },
7740
+ ...emitTypePayloadFields({
7741
+ def,
7742
+ hookType,
7743
+ converterConfig
7744
+ }),
7234
7745
  ...converterConfig.passthroughFields?.includes("name") && def.name !== void 0 && def.name !== null && { name: def.name },
7235
7746
  ...converterConfig.passthroughFields?.includes("description") && def.description !== void 0 && def.description !== null && { description: def.description }
7236
7747
  });
@@ -7297,6 +7808,46 @@ function stripCommandPrefix({ command, converterConfig }) {
7297
7808
  return cmd;
7298
7809
  }
7299
7810
  /**
7811
+ * Hook types preserved verbatim on import — Claude Code's five documented
7812
+ * handler types. Anything else is coerced to `command` as before.
7813
+ * https://code.claude.com/docs/en/hooks
7814
+ */
7815
+ const IMPORTED_HOOK_TYPES = /* @__PURE__ */ new Set([
7816
+ "command",
7817
+ "prompt",
7818
+ "http",
7819
+ "mcp_tool",
7820
+ "agent"
7821
+ ]);
7822
+ function isImportedHookType(value) {
7823
+ return typeof value === "string" && IMPORTED_HOOK_TYPES.has(value);
7824
+ }
7825
+ function isStringRecord(value) {
7826
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
7827
+ return Object.values(value).every((v) => typeof v === "string");
7828
+ }
7829
+ function isStringArray(value) {
7830
+ return Array.isArray(value) && value.every((v) => typeof v === "string");
7831
+ }
7832
+ /**
7833
+ * Import the payload fields specific to a hook type, type-checking each raw
7834
+ * value before it enters the canonical definition.
7835
+ */
7836
+ function importTypePayloadFields({ h, hookType }) {
7837
+ if (hookType === "http") return {
7838
+ ...typeof h.url === "string" && { url: h.url },
7839
+ ...isStringRecord(h.headers) && { headers: h.headers },
7840
+ ...isStringArray(h.allowedEnvVars) && { allowedEnvVars: h.allowedEnvVars }
7841
+ };
7842
+ if (hookType === "mcp_tool") return {
7843
+ ...typeof h.server === "string" && { server: h.server },
7844
+ ...typeof h.tool === "string" && { tool: h.tool },
7845
+ ...h.input !== null && typeof h.input === "object" && !Array.isArray(h.input) && { input: h.input }
7846
+ };
7847
+ if (hookType === "prompt" || hookType === "agent") return typeof h.model === "string" ? { model: h.model } : {};
7848
+ return {};
7849
+ }
7850
+ /**
7300
7851
  * Convert a single tool hook record into a canonical hook definition.
7301
7852
  */
7302
7853
  function toolHookToCanonical({ h, rawEntry, converterConfig }) {
@@ -7304,7 +7855,7 @@ function toolHookToCanonical({ h, rawEntry, converterConfig }) {
7304
7855
  command: h.command,
7305
7856
  converterConfig
7306
7857
  });
7307
- const hookType = h.type === "command" || h.type === "prompt" ? h.type : "command";
7858
+ const hookType = isImportedHookType(h.type) ? h.type : "command";
7308
7859
  const timeout = typeof h.timeout === "number" ? h.timeout : void 0;
7309
7860
  const prompt = typeof h.prompt === "string" ? h.prompt : void 0;
7310
7861
  return {
@@ -7312,6 +7863,10 @@ function toolHookToCanonical({ h, rawEntry, converterConfig }) {
7312
7863
  ...command !== void 0 && command !== null && { command },
7313
7864
  ...timeout !== void 0 && timeout !== null && { timeout },
7314
7865
  ...prompt !== void 0 && prompt !== null && { prompt },
7866
+ ...importTypePayloadFields({
7867
+ h,
7868
+ hookType
7869
+ }),
7315
7870
  ...converterConfig.passthroughFields?.includes("name") && typeof h.name === "string" && { name: h.name },
7316
7871
  ...converterConfig.passthroughFields?.includes("description") && typeof h.description === "string" && { description: h.description },
7317
7872
  ...importBooleanPassthroughFields({
@@ -7331,6 +7886,32 @@ function toolMatcherEntryToCanonical({ rawEntry, converterConfig }) {
7331
7886
  converterConfig
7332
7887
  }));
7333
7888
  }
7889
+ /**
7890
+ * Assemble the canonical hooks config a tool importer writes to
7891
+ * `.rulesync/hooks.json`.
7892
+ *
7893
+ * The top-level `hooks` record only accepts canonical event names, so any
7894
+ * imported native event key without a canonical mapping is moved under the
7895
+ * importing tool's own override block (`<overrideKey>.hooks`), whose keys stay
7896
+ * lenient. This mirrors the generate direction — override blocks pass
7897
+ * tool-native keys through verbatim — so documented native triggers (e.g.
7898
+ * kiro-ide's `PostFileSave`) survive an import → generate round-trip instead
7899
+ * of failing canonical validation.
7900
+ */
7901
+ function buildImportedHooksConfig({ hooks, overrideKey, version = 1, extraOverride }) {
7902
+ const canonical = {};
7903
+ const native = {};
7904
+ for (const [event, defs] of Object.entries(hooks)) if (isHookEvent(event)) canonical[event] = defs;
7905
+ else native[event] = defs;
7906
+ const override = { ...extraOverride };
7907
+ if (Object.keys(native).length > 0) override.hooks = native;
7908
+ const config = {
7909
+ version,
7910
+ hooks: canonical
7911
+ };
7912
+ if (Object.keys(override).length > 0) config[overrideKey] = override;
7913
+ return config;
7914
+ }
7334
7915
  function toolHooksToCanonical({ hooks, converterConfig }) {
7335
7916
  if (hooks === null || hooks === void 0 || typeof hooks !== "object") return {};
7336
7917
  const canonical = {};
@@ -7501,7 +8082,8 @@ const ANTIGRAVITY_CONVERTER_CONFIG = {
7501
8082
  "preModelInvocation",
7502
8083
  "postModelInvocation",
7503
8084
  "stop"
7504
- ])
8085
+ ]),
8086
+ supportedHookTypes: /* @__PURE__ */ new Set(["command"])
7505
8087
  };
7506
8088
  /**
7507
8089
  * Antigravity's `hooks.json` is keyed by a named hook whose value holds the
@@ -7608,10 +8190,11 @@ var AntigravityHooks = class extends ToolHooks {
7608
8190
  hooks: flattenAntigravityHooks(parsed),
7609
8191
  converterConfig: ANTIGRAVITY_CONVERTER_CONFIG
7610
8192
  });
7611
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
7612
- version: 1,
7613
- hooks
7614
- }, null, 2) });
8193
+ const overrideKey = this.constructor.getOverrideKey();
8194
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
8195
+ hooks,
8196
+ overrideKey
8197
+ }), null, 2) });
7615
8198
  }
7616
8199
  validate() {
7617
8200
  return {
@@ -7732,7 +8315,8 @@ const AUGMENTCODE_CONVERTER_CONFIG = {
7732
8315
  "sessionEnd",
7733
8316
  "stop",
7734
8317
  "notification"
7735
- ])
8318
+ ]),
8319
+ supportedHookTypes: /* @__PURE__ */ new Set(["command"])
7736
8320
  };
7737
8321
  /**
7738
8322
  * AugmentCode (Auggie CLI) lifecycle hooks.
@@ -7814,10 +8398,10 @@ var AugmentcodeHooks = class AugmentcodeHooks extends ToolHooks {
7814
8398
  hooks: settings.hooks,
7815
8399
  converterConfig: AUGMENTCODE_CONVERTER_CONFIG
7816
8400
  });
7817
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
7818
- version: 1,
7819
- hooks
7820
- }, null, 2) });
8401
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
8402
+ hooks,
8403
+ overrideKey: "augmentcode"
8404
+ }), null, 2) });
7821
8405
  }
7822
8406
  validate() {
7823
8407
  return {
@@ -7852,7 +8436,15 @@ const CLAUDE_CONVERTER_CONFIG = {
7852
8436
  "taskCompleted",
7853
8437
  "teammateIdle",
7854
8438
  "cwdChanged"
7855
- ])
8439
+ ]),
8440
+ supportedHookTypes: /* @__PURE__ */ new Set([
8441
+ "command",
8442
+ "prompt",
8443
+ "http",
8444
+ "mcp_tool",
8445
+ "agent"
8446
+ ]),
8447
+ emitsPromptModel: true
7856
8448
  };
7857
8449
  var ClaudecodeHooks = class ClaudecodeHooks extends ToolHooks {
7858
8450
  constructor(params) {
@@ -7917,10 +8509,10 @@ var ClaudecodeHooks = class ClaudecodeHooks extends ToolHooks {
7917
8509
  hooks: settings.hooks,
7918
8510
  converterConfig: CLAUDE_CONVERTER_CONFIG
7919
8511
  });
7920
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
7921
- version: 1,
7922
- hooks
7923
- }, null, 2) });
8512
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
8513
+ hooks,
8514
+ overrideKey: "claudecode"
8515
+ }), null, 2) });
7924
8516
  }
7925
8517
  validate() {
7926
8518
  return {
@@ -8063,10 +8655,10 @@ var CodexcliHooks = class CodexcliHooks extends ToolHooks {
8063
8655
  hooks: parsed.hooks,
8064
8656
  converterConfig: CODEXCLI_CONVERTER_CONFIG
8065
8657
  });
8066
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
8067
- version: 1,
8068
- hooks
8069
- }, null, 2) });
8658
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
8659
+ hooks,
8660
+ overrideKey: "codexcli"
8661
+ }), null, 2) });
8070
8662
  }
8071
8663
  validate() {
8072
8664
  return {
@@ -8234,10 +8826,10 @@ var CopilotHooks = class CopilotHooks extends ToolHooks {
8234
8826
  throw new Error(`Failed to parse Copilot hooks content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
8235
8827
  }
8236
8828
  const hooks = copilotHooksToCanonical(parsed.hooks, options?.logger);
8237
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
8238
- version: 1,
8239
- hooks
8240
- }, null, 2) });
8829
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
8830
+ hooks,
8831
+ overrideKey: "copilot"
8832
+ }), null, 2) });
8241
8833
  }
8242
8834
  validate() {
8243
8835
  return {
@@ -8256,20 +8848,6 @@ var CopilotHooks = class CopilotHooks extends ToolHooks {
8256
8848
  }
8257
8849
  };
8258
8850
  //#endregion
8259
- //#region src/utils/object.ts
8260
- /**
8261
- * Return a shallow copy of `obj` keeping only the entries whose value is
8262
- * neither `undefined` nor `null`.
8263
- *
8264
- * Used to assemble generated config objects without one conditional spread per
8265
- * optional field (which would otherwise exceed the lint complexity budget).
8266
- */
8267
- function compact(obj) {
8268
- const result = {};
8269
- for (const [key, value] of Object.entries(obj)) if (value !== void 0 && value !== null) result[key] = value;
8270
- return result;
8271
- }
8272
- //#endregion
8273
8851
  //#region src/features/hooks/copilotcli-hooks.ts
8274
8852
  /**
8275
8853
  * GitHub Copilot CLI hooks.
@@ -8402,7 +8980,7 @@ function buildCopilotCliEntriesForEvent({ eventName, definitions, canonicalSchem
8402
8980
  ...timeoutPart,
8403
8981
  ...rest
8404
8982
  });
8405
- else entries.push({
8983
+ else if (hookType === "command") entries.push({
8406
8984
  type: "command",
8407
8985
  ...matcherPart,
8408
8986
  ...compact({
@@ -8554,10 +9132,10 @@ var CopilotcliHooks = class CopilotcliHooks extends ToolHooks {
8554
9132
  throw new Error(`Failed to parse Copilot CLI hooks content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
8555
9133
  }
8556
9134
  const hooks = copilotCliHooksToCanonical(parsed.hooks, options?.logger);
8557
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
8558
- version: 1,
8559
- hooks
8560
- }, null, 2) });
9135
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
9136
+ hooks,
9137
+ overrideKey: "copilotcli"
9138
+ }), null, 2) });
8561
9139
  }
8562
9140
  validate() {
8563
9141
  return {
@@ -8612,9 +9190,10 @@ var CursorHooks = class CursorHooks extends ToolHooks {
8612
9190
  ...config.cursor?.hooks
8613
9191
  };
8614
9192
  const mappedHooks = {};
9193
+ const cursorSupportedTypes = /* @__PURE__ */ new Set(["command", "prompt"]);
8615
9194
  for (const [eventName, defs] of Object.entries(mergedHooks)) {
8616
9195
  const cursorEventName = CANONICAL_TO_CURSOR_EVENT_NAMES[eventName] ?? eventName;
8617
- mappedHooks[cursorEventName] = defs.map((def) => ({
9196
+ const mappedDefs = defs.filter((def) => cursorSupportedTypes.has(def.type ?? "command")).map((def) => ({
8618
9197
  ...def.type !== void 0 && def.type !== null && { type: def.type },
8619
9198
  ...def.command !== void 0 && def.command !== null && { command: def.command },
8620
9199
  ...def.timeout !== void 0 && def.timeout !== null && { timeout: def.timeout },
@@ -8623,6 +9202,7 @@ var CursorHooks = class CursorHooks extends ToolHooks {
8623
9202
  ...def.prompt !== void 0 && def.prompt !== null && { prompt: def.prompt },
8624
9203
  ...def.failClosed !== void 0 && def.failClosed !== null && { failClosed: def.failClosed }
8625
9204
  }));
9205
+ if (mappedDefs.length > 0) mappedHooks[cursorEventName] = mappedDefs;
8626
9206
  }
8627
9207
  const cursorConfig = {
8628
9208
  version: config.version ?? 1,
@@ -8649,10 +9229,11 @@ var CursorHooks = class CursorHooks extends ToolHooks {
8649
9229
  canonicalHooks[eventName] = defs;
8650
9230
  }
8651
9231
  const version = parsed.version ?? 1;
8652
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
8653
- version,
8654
- hooks: canonicalHooks
8655
- }, null, 2) });
9232
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
9233
+ hooks: canonicalHooks,
9234
+ overrideKey: "cursor",
9235
+ version
9236
+ }), null, 2) });
8656
9237
  }
8657
9238
  validate() {
8658
9239
  return {
@@ -8707,7 +9288,7 @@ function canonicalToDeepagentsHooks(config) {
8707
9288
  const deepagentsEvent = CANONICAL_TO_DEEPAGENTS_EVENT_NAMES[canonicalEvent];
8708
9289
  if (!deepagentsEvent) continue;
8709
9290
  for (const def of definitions) {
8710
- if (def.type === "prompt") continue;
9291
+ if ((def.type ?? "command") !== "command") continue;
8711
9292
  if (!def.command) continue;
8712
9293
  if (def.matcher) continue;
8713
9294
  entries.push({
@@ -8797,10 +9378,10 @@ var DeepagentsHooks = class DeepagentsHooks extends ToolHooks {
8797
9378
  throw new Error(`Failed to parse deepagents hooks content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
8798
9379
  }
8799
9380
  const hooks = deepagentsToCanonicalHooks(isDeepagentsHooksFile(parsed) ? parsed.hooks : []);
8800
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
8801
- version: 1,
8802
- hooks
8803
- }, null, 2) });
9381
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
9382
+ hooks,
9383
+ overrideKey: "deepagents"
9384
+ }), null, 2) });
8804
9385
  }
8805
9386
  validate() {
8806
9387
  return {
@@ -8929,10 +9510,10 @@ var DevinHooks = class DevinHooks extends ToolHooks {
8929
9510
  hooks: this.getRelativeFilePath() === "config.json" ? isRecord(parsed) && isRecord(parsed.hooks) ? parsed.hooks : {} : parsed,
8930
9511
  converterConfig: DEVIN_CONVERTER_CONFIG
8931
9512
  });
8932
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
8933
- version: 1,
8934
- hooks
8935
- }, null, 2) });
9513
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
9514
+ hooks,
9515
+ overrideKey: "devin"
9516
+ }), null, 2) });
8936
9517
  }
8937
9518
  validate() {
8938
9519
  return {
@@ -8956,7 +9537,8 @@ const FACTORYDROID_CONVERTER_CONFIG = {
8956
9537
  supportedEvents: FACTORYDROID_HOOK_EVENTS,
8957
9538
  canonicalToToolEventNames: CANONICAL_TO_FACTORYDROID_EVENT_NAMES,
8958
9539
  toolToCanonicalEventNames: FACTORYDROID_TO_CANONICAL_EVENT_NAMES,
8959
- projectDirVar: "$FACTORY_PROJECT_DIR"
9540
+ projectDirVar: "$FACTORY_PROJECT_DIR",
9541
+ supportedHookTypes: /* @__PURE__ */ new Set(["command", "prompt"])
8960
9542
  };
8961
9543
  var FactorydroidHooks = class FactorydroidHooks extends ToolHooks {
8962
9544
  constructor(params) {
@@ -9024,10 +9606,10 @@ var FactorydroidHooks = class FactorydroidHooks extends ToolHooks {
9024
9606
  hooks: settings.hooks,
9025
9607
  converterConfig: FACTORYDROID_CONVERTER_CONFIG
9026
9608
  });
9027
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
9028
- version: 1,
9029
- hooks
9030
- }, null, 2) });
9609
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
9610
+ hooks,
9611
+ overrideKey: "factorydroid"
9612
+ }), null, 2) });
9031
9613
  }
9032
9614
  validate() {
9033
9615
  return {
@@ -9118,10 +9700,10 @@ var GooseHooks = class GooseHooks extends ToolHooks {
9118
9700
  hooks: parsed.hooks && typeof parsed.hooks === "object" && !Array.isArray(parsed.hooks) ? Object.fromEntries(Object.entries(parsed.hooks).filter(([eventName]) => Object.hasOwn(GOOSE_TO_CANONICAL_EVENT_NAMES, eventName))) : parsed.hooks,
9119
9701
  converterConfig: GOOSE_CONVERTER_CONFIG
9120
9702
  });
9121
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
9122
- version: 1,
9123
- hooks
9124
- }, null, 2) });
9703
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
9704
+ hooks,
9705
+ overrideKey: "goose"
9706
+ }), null, 2) });
9125
9707
  }
9126
9708
  validate() {
9127
9709
  return {
@@ -9279,10 +9861,10 @@ var GrokcliHooks = class GrokcliHooks extends ToolHooks {
9279
9861
  hooks: isRecord(parsed) && isRecord(parsed.hooks) ? parsed.hooks : {},
9280
9862
  converterConfig: GROKCLI_CONVERTER_CONFIG
9281
9863
  });
9282
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
9283
- version: 1,
9284
- hooks
9285
- }, null, 2) });
9864
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
9865
+ hooks,
9866
+ overrideKey: "grokcli"
9867
+ }), null, 2) });
9286
9868
  }
9287
9869
  validate() {
9288
9870
  return {
@@ -9443,10 +10025,10 @@ var HermesagentHooks = class HermesagentHooks extends ToolHooks {
9443
10025
  format: "yaml",
9444
10026
  fileContent: this.getFileContent()
9445
10027
  }).hooks);
9446
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
9447
- version: 1,
9448
- hooks
9449
- }, null, 2) });
10028
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
10029
+ hooks,
10030
+ overrideKey: "hermesagent"
10031
+ }), null, 2) });
9450
10032
  }
9451
10033
  static fromRulesyncHooks({ outputRoot, rulesyncHooks, logger }) {
9452
10034
  const config = rulesyncHooks.getJson();
@@ -9549,10 +10131,10 @@ var JunieHooks = class JunieHooks extends ToolHooks {
9549
10131
  hooks: settings.hooks,
9550
10132
  converterConfig: JUNIE_CONVERTER_CONFIG
9551
10133
  });
9552
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
9553
- version: 1,
9554
- hooks
9555
- }, null, 2) });
10134
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
10135
+ hooks,
10136
+ overrideKey: "junie"
10137
+ }), null, 2) });
9556
10138
  }
9557
10139
  validate() {
9558
10140
  return {
@@ -9597,7 +10179,7 @@ function collectOpencodeStyleHandlers({ effectiveHooks, eventMap, namedEventHand
9597
10179
  if (!toolEvent) continue;
9598
10180
  const handlers = [];
9599
10181
  for (const def of definitions) {
9600
- if (def.type === "prompt") continue;
10182
+ if ((def.type ?? "command") !== "command") continue;
9601
10183
  if (!def.command) continue;
9602
10184
  handlers.push({
9603
10185
  command: def.command,
@@ -9902,10 +10484,11 @@ var KiroHooks = class KiroHooks extends ToolHooks {
9902
10484
  throw new Error(`Failed to parse Kiro hooks content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
9903
10485
  }
9904
10486
  const hooks = kiroHooksToCanonical(agentConfig.hooks);
9905
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
9906
- version: 1,
9907
- hooks
9908
- }, null, 2) });
10487
+ const overrideKey = this.constructor.getOverrideKey();
10488
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
10489
+ hooks,
10490
+ overrideKey
10491
+ }), null, 2) });
9909
10492
  }
9910
10493
  validate() {
9911
10494
  return {
@@ -10114,10 +10697,10 @@ var KiroIdeHooks = class KiroIdeHooks extends ToolHooks {
10114
10697
  throw new Error(`Failed to parse Kiro IDE hooks content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
10115
10698
  }
10116
10699
  const hooks = kiroIdeHooksToCanonical(parsed.hooks ?? []);
10117
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
10118
- version: 1,
10119
- hooks
10120
- }, null, 2) });
10700
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
10701
+ hooks,
10702
+ overrideKey: "kiro-ide"
10703
+ }), null, 2) });
10121
10704
  }
10122
10705
  validate() {
10123
10706
  return {
@@ -10241,11 +10824,18 @@ function canonicalToQwencodeHooks(config) {
10241
10824
  ...sharedHooks,
10242
10825
  ...config.qwencode?.hooks
10243
10826
  };
10827
+ const qwencodeSupportedTypes = /* @__PURE__ */ new Set([
10828
+ "command",
10829
+ "prompt",
10830
+ "http",
10831
+ "function"
10832
+ ]);
10244
10833
  const qwencode = {};
10245
10834
  for (const [eventName, definitions] of Object.entries(effectiveHooks)) {
10246
10835
  const qwencodeEventName = CANONICAL_TO_QWENCODE_EVENT_NAMES[eventName] ?? eventName;
10247
10836
  const byMatcher = /* @__PURE__ */ new Map();
10248
10837
  for (const def of definitions) {
10838
+ if (!qwencodeSupportedTypes.has(def.type ?? "command")) continue;
10249
10839
  const key = def.matcher ?? "";
10250
10840
  const list = byMatcher.get(key);
10251
10841
  if (list) list.push(def);
@@ -10305,9 +10895,10 @@ function qwencodeMatcherEntryToCanonical(entry) {
10305
10895
  const sequential = entry.sequential === true;
10306
10896
  const matcher = entry.matcher !== void 0 && entry.matcher !== null && entry.matcher !== "" ? entry.matcher : void 0;
10307
10897
  for (const h of hooks) {
10308
- const hookType = h.type === "command" || h.type === "prompt" || h.type === "http" ? h.type : "command";
10898
+ const hookType = h.type === "command" || h.type === "prompt" || h.type === "http" || h.type === "function" ? h.type : "command";
10309
10899
  const isHttp = hookType === "http";
10310
10900
  const isCommand = hookType === "command";
10901
+ const shell = h.shell === "bash" || h.shell === "powershell" ? h.shell : void 0;
10311
10902
  defs.push({
10312
10903
  type: hookType,
10313
10904
  ...compact({
@@ -10319,7 +10910,7 @@ function qwencodeMatcherEntryToCanonical(entry) {
10319
10910
  statusMessage: h.statusMessage,
10320
10911
  async: isCommand ? h.async : void 0,
10321
10912
  env: isCommand ? h.env : void 0,
10322
- shell: isCommand ? h.shell : void 0,
10913
+ shell: isCommand ? shell : void 0,
10323
10914
  headers: isHttp ? h.headers : void 0,
10324
10915
  allowedEnvVars: isHttp ? h.allowedEnvVars : void 0,
10325
10916
  once: isHttp ? h.once : void 0,
@@ -10406,15 +10997,11 @@ var QwencodeHooks = class QwencodeHooks extends ToolHooks {
10406
10997
  } catch (error) {
10407
10998
  throw new Error(`Failed to parse Qwen Code hooks content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
10408
10999
  }
10409
- const hooks = qwencodeHooksToCanonical(settings.hooks);
10410
- const canonical = typeof settings.disableAllHooks === "boolean" ? {
10411
- version: 1,
10412
- hooks,
10413
- qwencode: { disableAllHooks: settings.disableAllHooks }
10414
- } : {
10415
- version: 1,
10416
- hooks
10417
- };
11000
+ const canonical = buildImportedHooksConfig({
11001
+ hooks: qwencodeHooksToCanonical(settings.hooks),
11002
+ overrideKey: "qwencode",
11003
+ ...typeof settings.disableAllHooks === "boolean" && { extraOverride: { disableAllHooks: settings.disableAllHooks } }
11004
+ });
10418
11005
  return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(canonical, null, 2) });
10419
11006
  }
10420
11007
  validate() {
@@ -10576,10 +11163,10 @@ var ReasonixHooks = class ReasonixHooks extends ToolHooks {
10576
11163
  throw new Error(`Failed to parse Reasonix hooks content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
10577
11164
  }
10578
11165
  const hooks = reasonixHooksToCanonical(settings.hooks);
10579
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
10580
- version: 1,
10581
- hooks
10582
- }, null, 2) });
11166
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
11167
+ hooks,
11168
+ overrideKey: "reasonix"
11169
+ }), null, 2) });
10583
11170
  }
10584
11171
  validate() {
10585
11172
  return {
@@ -10802,10 +11389,10 @@ var VibeHooks = class VibeHooks extends ToolHooks {
10802
11389
  throw new Error(`Failed to parse Vibe hooks content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
10803
11390
  }
10804
11391
  const hooks = vibeHooksToCanonical(parsed);
10805
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
10806
- version: 1,
10807
- hooks
10808
- }, null, 2) });
11392
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
11393
+ hooks,
11394
+ overrideKey: "vibe"
11395
+ }), null, 2) });
10809
11396
  }
10810
11397
  validate() {
10811
11398
  try {
@@ -10892,7 +11479,13 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
10892
11479
  supportsImport: true
10893
11480
  },
10894
11481
  supportedEvents: CLAUDE_HOOK_EVENTS,
10895
- supportedHookTypes: ["command", "prompt"],
11482
+ supportedHookTypes: [
11483
+ "command",
11484
+ "prompt",
11485
+ "http",
11486
+ "mcp_tool",
11487
+ "agent"
11488
+ ],
10896
11489
  supportsMatcher: true
10897
11490
  }],
10898
11491
  ["codexcli", {
@@ -11084,7 +11677,12 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
11084
11677
  supportsImport: true
11085
11678
  },
11086
11679
  supportedEvents: QWENCODE_HOOK_EVENTS,
11087
- supportedHookTypes: ["command"],
11680
+ supportedHookTypes: [
11681
+ "command",
11682
+ "prompt",
11683
+ "http",
11684
+ "function"
11685
+ ],
11088
11686
  supportsMatcher: true
11089
11687
  }],
11090
11688
  ["reasonix", {
@@ -12386,16 +12984,6 @@ var IgnoreProcessor = class extends FeatureProcessor {
12386
12984
  }
12387
12985
  };
12388
12986
  //#endregion
12389
- //#region src/constants/amp-paths.ts
12390
- const AMP_DIR = ".amp";
12391
- const AMP_GLOBAL_DIR = (0, node_path.join)(".config", "amp");
12392
- const AMP_AGENTS_DIR = ".agents";
12393
- const AMP_SKILLS_PROJECT_DIR = (0, node_path.join)(AMP_AGENTS_DIR, "skills");
12394
- const AMP_SKILLS_GLOBAL_DIR = (0, node_path.join)(".config", "agents", "skills");
12395
- const AMP_RULE_FILE_NAME = "AGENTS.md";
12396
- const AMP_SETTINGS_FILE_NAME = "settings.json";
12397
- const AMP_SETTINGS_JSONC_FILE_NAME = "settings.jsonc";
12398
- //#endregion
12399
12987
  //#region src/types/mcp.ts
12400
12988
  const EnvVarNameSchema = zod_mini.z.string().check((0, zod_mini.refine)((value) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(value), "envVars entries must be valid environment variable names"));
12401
12989
  const McpServerSchema = zod_mini.z.looseObject({
@@ -13487,10 +14075,19 @@ function mapOauthFromCodex(oauth) {
13487
14075
  }
13488
14076
  const CODEX_MCP_SERVER_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
13489
14077
  function normalizeCodexMcpServerName(name) {
13490
- if (PROTOTYPE_POLLUTION_KEYS.has(name)) return null;
13491
- if (CODEX_MCP_SERVER_NAME_PATTERN.test(name)) return name;
14078
+ if (!PROTOTYPE_POLLUTION_KEYS.has(name) && CODEX_MCP_SERVER_NAME_PATTERN.test(name)) return {
14079
+ codexName: name,
14080
+ usedFallback: false
14081
+ };
13492
14082
  const normalizedName = name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
13493
- return normalizedName && !PROTOTYPE_POLLUTION_KEYS.has(normalizedName) ? normalizedName : null;
14083
+ if (normalizedName && !PROTOTYPE_POLLUTION_KEYS.has(normalizedName)) return {
14084
+ codexName: normalizedName,
14085
+ usedFallback: false
14086
+ };
14087
+ return {
14088
+ codexName: `mcp_${(0, node_crypto.createHash)("sha256").update(name).digest("hex").slice(0, 8)}`,
14089
+ usedFallback: true
14090
+ };
13494
14091
  }
13495
14092
  function convertFromCodexFormat(codexMcp) {
13496
14093
  const result = {};
@@ -13504,7 +14101,7 @@ function convertFromCodexFormat(codexMcp) {
13504
14101
  } else if (key === "oauth" && isRecord(value)) converted[key] = mapOauthFromCodex(value);
13505
14102
  else if (Object.hasOwn(CODEX_TO_RULESYNC_FIELD_MAP, key)) {
13506
14103
  const mappedKey = CODEX_TO_RULESYNC_FIELD_MAP[key];
13507
- if (mappedKey) if (isStringArray(value)) converted[mappedKey] = value;
14104
+ if (mappedKey) if (isStringArray$1(value)) converted[mappedKey] = value;
13508
14105
  else warnWithFallback(void 0, `Ignored malformed array for ${key} in MCP server ${name}`);
13509
14106
  } else converted[key] = value;
13510
14107
  }
@@ -13516,12 +14113,9 @@ function convertToCodexFormat(mcpServers) {
13516
14113
  const result = {};
13517
14114
  const originalNames = /* @__PURE__ */ new Map();
13518
14115
  for (const [name, config] of Object.entries(mcpServers)) {
13519
- const codexName = normalizeCodexMcpServerName(name);
13520
- if (codexName === null) {
13521
- warnWithFallback(void 0, `MCP server "${name}" could not be normalized to a valid Codex MCP server name and was skipped.`);
13522
- continue;
13523
- }
13524
14116
  if (!isRecord(config)) continue;
14117
+ const { codexName, usedFallback } = normalizeCodexMcpServerName(name);
14118
+ if (usedFallback) warnWithFallback(void 0, `MCP server "${name}" cannot be represented as a Codex MCP server name (ASCII [a-zA-Z0-9_-] only), so the stable fallback name "${codexName}" was used. Rename the server in .rulesync/mcp.json to choose a readable Codex name.`);
13525
14119
  const converted = {};
13526
14120
  for (const [key, value] of Object.entries(config)) {
13527
14121
  if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
@@ -13530,12 +14124,12 @@ function convertToCodexFormat(mcpServers) {
13530
14124
  } else if (key === "oauth" && isRecord(value)) converted[key] = mapOauthToCodex(value);
13531
14125
  else if (Object.hasOwn(RULESYNC_TO_CODEX_FIELD_MAP, key)) {
13532
14126
  const mappedKey = RULESYNC_TO_CODEX_FIELD_MAP[key];
13533
- if (mappedKey) if (isStringArray(value)) converted[mappedKey] = value;
14127
+ if (mappedKey) if (isStringArray$1(value)) converted[mappedKey] = value;
13534
14128
  else warnWithFallback(void 0, `[CodexCliMcp] Skipping invalid value type for mapped key '${key}': expected string array, got ${typeof value}`);
13535
14129
  } else converted[key] = value;
13536
14130
  }
13537
14131
  const previousName = originalNames.get(codexName);
13538
- if (previousName !== void 0) warnWithFallback(void 0, `Codex MCP server name collision: "${previousName}" and "${name}" both normalize to "${codexName}". Only the last processed server will be used.`);
14132
+ if (previousName !== void 0) warnWithFallback(void 0, `Codex MCP server name collision: "${previousName}" and "${name}" both normalize to "${codexName}"; "${name}" (processed last) overwrites "${previousName}".`);
13539
14133
  originalNames.set(codexName, name);
13540
14134
  result[codexName] = converted;
13541
14135
  }
@@ -13602,7 +14196,7 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
13602
14196
  const rawServer = isRecord(rawMcpServers) ? rawMcpServers[serverName] : void 0;
13603
14197
  return [serverName, {
13604
14198
  ...serverConfig,
13605
- ...isRecord(rawServer) && isStringArray(rawServer.envVars) ? { envVars: rawServer.envVars } : {}
14199
+ ...isRecord(rawServer) && isStringArray$1(rawServer.envVars) ? { envVars: rawServer.envVars } : {}
13606
14200
  }];
13607
14201
  })));
13608
14202
  const filteredMcpServers = this.removeEmptyEntries(converted);
@@ -14339,11 +14933,11 @@ function applyGooseStdioFields(ext, config) {
14339
14933
  if (Array.isArray(command)) {
14340
14934
  if (typeof command[0] === "string") ext.cmd = command[0];
14341
14935
  const rest = command.slice(1).filter((c) => typeof c === "string");
14342
- const args = isStringArray(config.args) ? config.args : [];
14936
+ const args = isStringArray$1(config.args) ? config.args : [];
14343
14937
  if (rest.length > 0 || args.length > 0) ext.args = [...rest, ...args];
14344
14938
  } else if (typeof command === "string") {
14345
14939
  ext.cmd = command;
14346
- if (isStringArray(config.args)) ext.args = config.args;
14940
+ if (isStringArray$1(config.args)) ext.args = config.args;
14347
14941
  }
14348
14942
  if (isPlainObject$1(config.env)) ext.envs = omitPrototypePollutionKeys(config.env);
14349
14943
  }
@@ -14401,7 +14995,7 @@ function convertFromGooseFormat(extensions) {
14401
14995
  else if (type === "streamable_http") server.type = "http";
14402
14996
  else if (type === "stdio") server.type = "stdio";
14403
14997
  if (typeof ext.cmd === "string") server.command = ext.cmd;
14404
- if (isStringArray(ext.args)) server.args = ext.args;
14998
+ if (isStringArray$1(ext.args)) server.args = ext.args;
14405
14999
  if (isPlainObject$1(ext.envs)) server.env = omitPrototypePollutionKeys(ext.envs);
14406
15000
  if (typeof ext.uri === "string") server.url = ext.uri;
14407
15001
  if (isPlainObject$1(ext.headers)) server.headers = omitPrototypePollutionKeys(ext.headers);
@@ -14421,11 +15015,11 @@ function buildGoosePluginStdioServer(config) {
14421
15015
  if (Array.isArray(command)) {
14422
15016
  if (typeof command[0] === "string") server.command = command[0];
14423
15017
  const rest = command.slice(1).filter((c) => typeof c === "string");
14424
- const args = isStringArray(config.args) ? config.args : [];
15018
+ const args = isStringArray$1(config.args) ? config.args : [];
14425
15019
  if (rest.length > 0 || args.length > 0) server.args = [...rest, ...args];
14426
15020
  } else if (typeof command === "string") {
14427
15021
  server.command = command;
14428
- if (isStringArray(config.args)) server.args = config.args;
15022
+ if (isStringArray$1(config.args)) server.args = config.args;
14429
15023
  }
14430
15024
  if (isPlainObject$1(config.env)) server.env = omitPrototypePollutionKeys(config.env);
14431
15025
  if (typeof config.cwd === "string") server.cwd = config.cwd;
@@ -14738,7 +15332,7 @@ function resolveHermesTimeout(config) {
14738
15332
  */
14739
15333
  function copyHermesAdvancedFields(source, target) {
14740
15334
  if (typeof source.auth === "string") target.auth = source.auth;
14741
- if (typeof source.client_cert === "string" || isStringArray(source.client_cert)) target.client_cert = source.client_cert;
15335
+ if (typeof source.client_cert === "string" || isStringArray$1(source.client_cert)) target.client_cert = source.client_cert;
14742
15336
  if (typeof source.client_key === "string") target.client_key = source.client_key;
14743
15337
  if (typeof source.connect_timeout === "number") target.connect_timeout = source.connect_timeout;
14744
15338
  if (typeof source.supports_parallel_tool_calls === "boolean") target.supports_parallel_tool_calls = source.supports_parallel_tool_calls;
@@ -14757,8 +15351,8 @@ function copyHermesAdvancedFields(source, target) {
14757
15351
  */
14758
15352
  function buildHermesToolsBlock(config) {
14759
15353
  const tools = {};
14760
- if (isStringArray(config.enabledTools)) tools.include = config.enabledTools;
14761
- if (isStringArray(config.disabledTools)) tools.exclude = config.disabledTools;
15354
+ if (isStringArray$1(config.enabledTools)) tools.include = config.enabledTools;
15355
+ if (isStringArray$1(config.disabledTools)) tools.exclude = config.disabledTools;
14762
15356
  if (typeof config.promptsEnabled === "boolean") tools.prompts = config.promptsEnabled;
14763
15357
  if (typeof config.resourcesEnabled === "boolean") tools.resources = config.resourcesEnabled;
14764
15358
  return tools;
@@ -14770,8 +15364,8 @@ function buildHermesToolsBlock(config) {
14770
15364
  * `promptsEnabled`/`resourcesEnabled` top-level toggles.
14771
15365
  */
14772
15366
  function applyHermesToolsBlock(hermesTools, server) {
14773
- if (isStringArray(hermesTools.include)) server.enabledTools = hermesTools.include;
14774
- if (isStringArray(hermesTools.exclude)) server.disabledTools = hermesTools.exclude;
15367
+ if (isStringArray$1(hermesTools.include)) server.enabledTools = hermesTools.include;
15368
+ if (isStringArray$1(hermesTools.exclude)) server.disabledTools = hermesTools.exclude;
14775
15369
  if (typeof hermesTools.prompts === "boolean") server.promptsEnabled = hermesTools.prompts;
14776
15370
  if (typeof hermesTools.resources === "boolean") server.resourcesEnabled = hermesTools.resources;
14777
15371
  }
@@ -14795,11 +15389,11 @@ function convertServerToHermes(config) {
14795
15389
  if (Array.isArray(command)) {
14796
15390
  if (typeof command[0] === "string") out.command = command[0];
14797
15391
  const rest = command.slice(1).filter((c) => typeof c === "string");
14798
- const args = isStringArray(config.args) ? config.args : [];
15392
+ const args = isStringArray$1(config.args) ? config.args : [];
14799
15393
  if (rest.length > 0 || args.length > 0) out.args = [...rest, ...args];
14800
15394
  } else if (typeof command === "string") {
14801
15395
  out.command = command;
14802
- if (isStringArray(config.args)) out.args = config.args;
15396
+ if (isStringArray$1(config.args)) out.args = config.args;
14803
15397
  }
14804
15398
  if (isPlainObject$1(config.env)) out.env = omitPrototypePollutionKeys(config.env);
14805
15399
  } else if (url !== void 0) {
@@ -14847,7 +15441,7 @@ function convertFromHermesFormat(mcpServers) {
14847
15441
  if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord(config)) continue;
14848
15442
  const server = {};
14849
15443
  if (typeof config.command === "string") server.command = config.command;
14850
- if (isStringArray(config.args)) server.args = config.args;
15444
+ if (isStringArray$1(config.args)) server.args = config.args;
14851
15445
  if (isPlainObject$1(config.env)) server.env = omitPrototypePollutionKeys(config.env);
14852
15446
  if (typeof config.url === "string") server.url = config.url;
14853
15447
  if (isPlainObject$1(config.headers)) server.headers = omitPrototypePollutionKeys(config.headers);
@@ -17172,7 +17766,11 @@ const VibePermissionsOverrideSchema = zod_mini.z.looseObject({ permission: zod_m
17172
17766
  */
17173
17767
  const CursorPermissionsOverrideSchema = zod_mini.z.looseObject({
17174
17768
  permission: zod_mini.z.optional(ToolScopedPermissionSchema),
17175
- approvalMode: zod_mini.z.optional(zod_mini.z.string()),
17769
+ approvalMode: zod_mini.z.optional(zod_mini.z.enum([
17770
+ "allowlist",
17771
+ "auto-review",
17772
+ "unrestricted"
17773
+ ])),
17176
17774
  sandbox: zod_mini.z.optional(zod_mini.z.looseObject({}))
17177
17775
  });
17178
17776
  /**
@@ -17260,7 +17858,11 @@ const FactorydroidPermissionsOverrideSchema = zod_mini.z.looseObject({
17260
17858
  */
17261
17859
  const WarpPermissionsOverrideSchema = zod_mini.z.looseObject({
17262
17860
  permission: zod_mini.z.optional(ToolScopedPermissionSchema),
17263
- agent_mode_coding_permissions: zod_mini.z.optional(zod_mini.z.string()),
17861
+ agent_mode_coding_permissions: zod_mini.z.optional(zod_mini.z.enum([
17862
+ "always_ask_before_reading",
17863
+ "always_allow_reading",
17864
+ "allow_reading_specific_files"
17865
+ ])),
17264
17866
  agent_mode_coding_file_read_allowlist: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
17265
17867
  agent_mode_execute_readonly_commands: zod_mini.z.optional(zod_mini.z.boolean())
17266
17868
  });
@@ -17306,7 +17908,11 @@ const JuniePermissionsOverrideSchema = zod_mini.z.looseObject({
17306
17908
  */
17307
17909
  const TaktPermissionsOverrideSchema = zod_mini.z.looseObject({
17308
17910
  permission: zod_mini.z.optional(ToolScopedPermissionSchema),
17309
- step_permission_overrides: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), zod_mini.z.string())),
17911
+ step_permission_overrides: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), zod_mini.z.enum([
17912
+ "readonly",
17913
+ "edit",
17914
+ "full"
17915
+ ]))),
17310
17916
  provider_options: zod_mini.z.optional(zod_mini.z.looseObject({}))
17311
17917
  });
17312
17918
  /**
@@ -17336,9 +17942,15 @@ const AmpPermissionsOverrideSchema = zod_mini.z.looseObject({
17336
17942
  permission: zod_mini.z.optional(ToolScopedPermissionSchema),
17337
17943
  permissions: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.looseObject({
17338
17944
  tool: zod_mini.z.string(),
17339
- action: zod_mini.z.string()
17945
+ action: zod_mini.z.enum([
17946
+ "allow",
17947
+ "ask",
17948
+ "reject",
17949
+ "delegate"
17950
+ ]),
17951
+ context: zod_mini.z.optional(zod_mini.z.enum(["thread", "subagent"]))
17340
17952
  }))),
17341
- mcpPermissions: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.looseObject({}))),
17953
+ mcpPermissions: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.looseObject({ action: zod_mini.z.enum(["allow", "reject"]) }))),
17342
17954
  guardedFiles: zod_mini.z.optional(zod_mini.z.looseObject({ allowlist: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())) })),
17343
17955
  dangerouslyAllowAll: zod_mini.z.optional(zod_mini.z.boolean())
17344
17956
  });
@@ -17363,7 +17975,12 @@ const AmpPermissionsOverrideSchema = zod_mini.z.looseObject({
17363
17975
  */
17364
17976
  const AntigravityCliPermissionsOverrideSchema = zod_mini.z.looseObject({
17365
17977
  permission: zod_mini.z.optional(ToolScopedPermissionSchema),
17366
- toolPermission: zod_mini.z.optional(zod_mini.z.string()),
17978
+ toolPermission: zod_mini.z.optional(zod_mini.z.enum([
17979
+ "request-review",
17980
+ "proceed-in-sandbox",
17981
+ "always-proceed",
17982
+ "strict"
17983
+ ])),
17367
17984
  enableTerminalSandbox: zod_mini.z.optional(zod_mini.z.boolean())
17368
17985
  });
17369
17986
  /**
@@ -17392,7 +18009,14 @@ const AugmentcodePermissionsOverrideSchema = zod_mini.z.looseObject({
17392
18009
  permission: zod_mini.z.optional(ToolScopedPermissionSchema),
17393
18010
  toolPermissions: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.looseObject({
17394
18011
  toolName: zod_mini.z.string(),
17395
- permission: zod_mini.z.looseObject({ type: zod_mini.z.string() })
18012
+ eventType: zod_mini.z.optional(zod_mini.z.enum(["tool-call", "tool-response"])),
18013
+ permission: zod_mini.z.looseObject({ type: zod_mini.z.enum([
18014
+ "allow",
18015
+ "deny",
18016
+ "ask-user",
18017
+ "webhook-policy",
18018
+ "script-policy"
18019
+ ]) })
17396
18020
  })))
17397
18021
  });
17398
18022
  /**
@@ -17482,12 +18106,20 @@ const CodexApprovalsReviewerSchema = zod_mini.z.enum([
17482
18106
  * `[permissions.rulesync]` profile may extend. Codex ships three built-in
17483
18107
  * profiles (`:read-only`, `:workspace`, `:danger-full-access`; the leading
17484
18108
  * colon is reserved for built-ins), but `extends` rejects
17485
- * `:danger-full-access` at config load time, so only the two extendable
17486
- * baselines are accepted here. The value list is exported so the Codex CLI
17487
- * translator derives its import-side baseline check from the same source.
18109
+ * `:danger-full-access` at config load time, so only these two are valid
18110
+ * `extends` parents. The value list is exported so the Codex CLI translator
18111
+ * derives its import-side baseline check from the same source.
17488
18112
  * @see https://learn.chatgpt.com/docs/permissions
17489
18113
  */
17490
- const CODEX_BASE_PERMISSION_PROFILES = [":read-only", ":workspace"];
18114
+ const CODEX_EXTENDABLE_BASELINE_PROFILES = [":read-only", ":workspace"];
18115
+ /**
18116
+ * All accepted `codexcli.base_permission_profile` values. `:danger-full-access`
18117
+ * cannot be an `extends` parent, so selecting it makes rulesync emit
18118
+ * `default_permissions = ":danger-full-access"` directly and skip the managed
18119
+ * `[permissions.rulesync]` profile entirely (there is no sandbox for
18120
+ * filesystem/network rules to refine in that mode).
18121
+ */
18122
+ const CODEX_BASE_PERMISSION_PROFILES = [...CODEX_EXTENDABLE_BASELINE_PROFILES, ":danger-full-access"];
17491
18123
  const CodexBasePermissionProfileSchema = zod_mini.z.enum(CODEX_BASE_PERMISSION_PROFILES);
17492
18124
  /**
17493
18125
  * Codex CLI-scoped permission override.
@@ -17498,10 +18130,14 @@ const CodexBasePermissionProfileSchema = zod_mini.z.enum(CODEX_BASE_PERMISSION_P
17498
18130
  * override whose fields are written verbatim as top-level `.codex/config.toml`
17499
18131
  * keys (the override wins per key; existing sibling keys the user set directly
17500
18132
  * are preserved):
17501
- * - `base_permission_profile` — the built-in profile the managed
17502
- * `[permissions.rulesync]` profile extends (`:read-only` | `:workspace`).
17503
- * Unlike the other keys it is not a top-level config key: it is emitted as
17504
- * the profile's `extends` value. Defaults to `:workspace` when unspecified.
18133
+ * - `base_permission_profile` — the built-in baseline profile (`:read-only` |
18134
+ * `:workspace` | `:danger-full-access`). Unlike the other keys it is not a
18135
+ * top-level config key: the extendable baselines are emitted as the managed
18136
+ * `[permissions.rulesync]` profile's `extends` value, while
18137
+ * `:danger-full-access` (which Codex rejects as an `extends` parent) is
18138
+ * selected directly via `default_permissions` and skips the managed profile
18139
+ * entirely — canonical filesystem/network rules are ignored in that mode.
18140
+ * Defaults to `:workspace` when unspecified.
17505
18141
  * - `approval_policy` — `untrusted` | `on-request` (legacy alias `on-failure`) |
17506
18142
  * `never`, or a `{ granular = { … } }` table (kept verbatim; the granular
17507
18143
  * schema has required fields that are brittle to model as typed keys).
@@ -17521,6 +18157,15 @@ const CodexBasePermissionProfileSchema = zod_mini.z.enum(CODEX_BASE_PERMISSION_P
17521
18157
  * | `guardian_subagent`), or a table for the richer reviewer config.
17522
18158
  * Defaults to `auto_review` when neither the override nor the existing
17523
18159
  * config sets it.
18160
+ * - `git_write_rules` — whether the managed profile's `:workspace_roots` table
18161
+ * emits the default `.git` carve-out (`".git/**" = "write"`). Codex's
18162
+ * `:workspace` baseline makes `.git` read-only, which denies basic git
18163
+ * workflows (commit/stage writes to `.git/index`, `.git/objects`, refs,
18164
+ * logs; everyday commands like `git remote add` or `git push -u` write to
18165
+ * `.git/config`), so the carve-out is emitted by default. Defaults to
18166
+ * `true`; only an explicit `false` suppresses it. Like
18167
+ * `base_permission_profile` it is consumed by the profile builder, not
18168
+ * written as a top-level config key.
17524
18169
  *
17525
18170
  * Two surfaces are deliberately NOT authorable here so the override can never
17526
18171
  * clobber a feature-owned key: `mcp_servers.*` per-MCP gating is owned by the
@@ -17546,7 +18191,8 @@ const CodexcliPermissionsOverrideSchema = zod_mini.z.looseObject({
17546
18191
  /** @deprecated Superseded by `base_permission_profile` (permission profiles). */
17547
18192
  sandbox_workspace_write: zod_mini.z.optional(zod_mini.z.looseObject({})),
17548
18193
  apps: zod_mini.z.optional(zod_mini.z.looseObject({})),
17549
- approvals_reviewer: zod_mini.z.optional(zod_mini.z.union([CodexApprovalsReviewerSchema, zod_mini.z.looseObject({})]))
18194
+ approvals_reviewer: zod_mini.z.optional(zod_mini.z.union([CodexApprovalsReviewerSchema, zod_mini.z.looseObject({})])),
18195
+ git_write_rules: zod_mini.z.optional(zod_mini.z.boolean())
17550
18196
  });
17551
18197
  /**
17552
18198
  * Permissions configuration.
@@ -19515,11 +20161,14 @@ var ClinePermissions = class ClinePermissions extends ToolPermissions {
19515
20161
  const RULESYNC_PROFILE_NAME = "rulesync";
19516
20162
  const CODEX_WORKSPACE_ROOTS_KEY = ":workspace_roots";
19517
20163
  const CODEX_WORKSPACE_BASELINE = ":workspace";
19518
- const CODEX_EXTENDABLE_BASELINES = new Set(CODEX_BASE_PERMISSION_PROFILES);
20164
+ const CODEX_READ_ONLY_BASELINE = ":read-only";
20165
+ const CODEX_DANGER_FULL_ACCESS_BASELINE = ":danger-full-access";
20166
+ const CODEX_EXTENDABLE_BASELINES = new Set(CODEX_EXTENDABLE_BASELINE_PROFILES);
19519
20167
  const CODEX_DEFAULT_APPROVAL_POLICY = "on-request";
19520
20168
  const CODEX_DEFAULT_APPROVALS_REVIEWER = "auto_review";
19521
20169
  const CODEX_GLOB_SCAN_MAX_DEPTH = 8;
19522
20170
  const CODEX_MINIMAL_KEY = ":minimal";
20171
+ const CODEX_GIT_WRITE_RULES = { ".git/**": "write" };
19523
20172
  const GLOBAL_WILDCARD_DOMAIN = "*";
19524
20173
  var CodexcliPermissions = class CodexcliPermissions extends ToolPermissions {
19525
20174
  static getSettablePaths(_options = {}) {
@@ -19547,8 +20196,40 @@ var CodexcliPermissions = class CodexcliPermissions extends ToolPermissions {
19547
20196
  const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
19548
20197
  const existingContent = await readFileContentOrNull(filePath) ?? "";
19549
20198
  const existing = toMutableTable(smol_toml.parse(existingContent || smol_toml.stringify({})));
20199
+ const canonicalConfig = rulesyncPermissions.getJson();
20200
+ if (canonicalConfig.codexcli?.base_permission_profile === CODEX_DANGER_FULL_ACCESS_BASELINE) {
20201
+ const ignoredCategories = Object.entries(canonicalConfig.permission).filter(([category, rules]) => category !== "bash" && Object.keys(rules).length > 0).map(([category]) => category);
20202
+ if (ignoredCategories.length > 0) logger?.warn(`Codex CLI baseline ":danger-full-access" removes the sandbox, so canonical ${ignoredCategories.join("/")} rules are not representable and are ignored for Codex CLI.`);
20203
+ const permissionsTable = toMutableTable(existing.permissions);
20204
+ if (permissionsTable[RULESYNC_PROFILE_NAME] !== void 0) {
20205
+ logger?.warn(`Codex CLI baseline ":danger-full-access" prunes the managed "[permissions.${RULESYNC_PROFILE_NAME}]" profile; any hand-written keys inside it (e.g. network settings) are removed. Move them to a sibling profile or re-add them after switching baselines.`);
20206
+ delete permissionsTable[RULESYNC_PROFILE_NAME];
20207
+ }
20208
+ const overridePatch = computeCodexcliOverridePatch({
20209
+ existing,
20210
+ override: canonicalConfig.codexcli,
20211
+ logger
20212
+ });
20213
+ return new CodexcliPermissions({
20214
+ outputRoot,
20215
+ relativeDirPath: paths.relativeDirPath,
20216
+ relativeFilePath: paths.relativeFilePath,
20217
+ fileContent: applySharedConfigPatch({
20218
+ fileKey: sharedConfigFileKey(paths),
20219
+ feature: "permissions",
20220
+ existingContent,
20221
+ patch: {
20222
+ permissions: Object.keys(permissionsTable).length > 0 ? permissionsTable : void 0,
20223
+ default_permissions: CODEX_DANGER_FULL_ACCESS_BASELINE,
20224
+ ...overridePatch
20225
+ },
20226
+ filePath
20227
+ }),
20228
+ validate
20229
+ });
20230
+ }
19550
20231
  const newProfile = convertRulesyncToCodexProfile({
19551
- config: rulesyncPermissions.getJson(),
20232
+ config: canonicalConfig,
19552
20233
  logger
19553
20234
  });
19554
20235
  const permissionsTable = toMutableTable(existing.permissions);
@@ -19609,6 +20290,7 @@ var CodexcliPermissions = class CodexcliPermissions extends ToolPermissions {
19609
20290
  });
19610
20291
  const override = extractCodexcliOverride(table);
19611
20292
  if (typeof profile?.extends === "string" && CODEX_EXTENDABLE_BASELINES.has(profile.extends)) override.base_permission_profile = profile.extends;
20293
+ if (defaultProfile === CODEX_DANGER_FULL_ACCESS_BASELINE) override.base_permission_profile = CODEX_DANGER_FULL_ACCESS_BASELINE;
19612
20294
  const result = Object.keys(override).length > 0 ? {
19613
20295
  ...config,
19614
20296
  codexcli: override
@@ -19664,16 +20346,10 @@ function convertRulesyncToCodexProfile({ config, logger }) {
19664
20346
  const filesystem = { [CODEX_MINIMAL_KEY]: "read" };
19665
20347
  const workspaceRootFilesystem = {};
19666
20348
  const domains = {};
20349
+ const filesystemCategoryRules = {};
19667
20350
  for (const [toolName, rules] of Object.entries(config.permission)) {
19668
20351
  if (toolName === "read" || toolName === "edit" || toolName === "write") {
19669
- const mapAction = toolName === "read" ? mapReadAction : mapWriteAction;
19670
- for (const [pattern, action] of Object.entries(rules)) addFilesystemRule({
19671
- filesystem,
19672
- workspaceRootFilesystem,
19673
- pattern,
19674
- access: mapAction(action),
19675
- logger
19676
- });
20352
+ filesystemCategoryRules[toolName] = rules;
19677
20353
  continue;
19678
20354
  }
19679
20355
  if (toolName === "webfetch") {
@@ -19686,6 +20362,21 @@ function convertRulesyncToCodexProfile({ config, logger }) {
19686
20362
  }
19687
20363
  logger?.warn(`Codex CLI permissions support only read/edit/write/webfetch categories. Skipping: ${toolName}`);
19688
20364
  }
20365
+ for (const [pattern, access] of mergeFilesystemCategoryRules({
20366
+ categoryRules: filesystemCategoryRules,
20367
+ logger
20368
+ })) addFilesystemRule({
20369
+ filesystem,
20370
+ workspaceRootFilesystem,
20371
+ pattern,
20372
+ access,
20373
+ logger
20374
+ });
20375
+ applyDefaultGitWriteRules({
20376
+ config,
20377
+ filesystem,
20378
+ workspaceRootFilesystem
20379
+ });
19689
20380
  if (Object.keys(workspaceRootFilesystem).length > 0) {
19690
20381
  if (typeof filesystem[CODEX_WORKSPACE_ROOTS_KEY] === "string") logger?.warn(`"${CODEX_WORKSPACE_ROOTS_KEY}" is set as a direct filesystem access rule in the permissions, but it will be overwritten by workspace-root rules. Consider removing the direct "${CODEX_WORKSPACE_ROOTS_KEY}" entry.`);
19691
20382
  if (Object.keys(workspaceRootFilesystem).some((pattern) => pattern.includes("**"))) filesystem.glob_scan_max_depth = CODEX_GLOB_SCAN_MAX_DEPTH;
@@ -19703,6 +20394,12 @@ function convertRulesyncToCodexProfile({ config, logger }) {
19703
20394
  ...network ? { network } : {}
19704
20395
  };
19705
20396
  }
20397
+ function applyDefaultGitWriteRules({ config, filesystem, workspaceRootFilesystem }) {
20398
+ if (config.codexcli?.git_write_rules === false) return;
20399
+ if (config.codexcli?.base_permission_profile === CODEX_READ_ONLY_BASELINE) return;
20400
+ if (typeof filesystem[CODEX_WORKSPACE_ROOTS_KEY] === "string") return;
20401
+ for (const [pattern, access] of Object.entries(CODEX_GIT_WRITE_RULES)) workspaceRootFilesystem[pattern] ??= access;
20402
+ }
19706
20403
  function convertCodexProfileToRulesync({ profile, domainsHadUnknown }) {
19707
20404
  const permission = {};
19708
20405
  if (profile?.filesystem) {
@@ -19714,7 +20411,10 @@ function convertCodexProfileToRulesync({ profile, domainsHadUnknown }) {
19714
20411
  addRulesyncFilesystemRule(permission, pattern, access);
19715
20412
  continue;
19716
20413
  }
19717
- if (isCodexFilesystemRuleTable(access)) for (const [nestedPattern, nestedAccess] of Object.entries(access)) addRulesyncFilesystemRule(permission, nestedPattern, nestedAccess);
20414
+ if (isCodexFilesystemRuleTable(access)) for (const [nestedPattern, nestedAccess] of Object.entries(access)) {
20415
+ if (pattern === CODEX_WORKSPACE_ROOTS_KEY && CODEX_GIT_WRITE_RULES[nestedPattern] === nestedAccess) continue;
20416
+ addRulesyncFilesystemRule(permission, nestedPattern, nestedAccess);
20417
+ }
19718
20418
  }
19719
20419
  }
19720
20420
  if (profile?.network && profile.network.enabled !== false) {
@@ -19756,9 +20456,28 @@ function toCodexProfile(value) {
19756
20456
  }
19757
20457
  function warnAboutPreservedProfileState({ existingProfile, newProfile, existingDomainsHadUnknown, logger }) {
19758
20458
  if (existingProfile !== void 0 && existingProfile.extends !== newProfile.extends) logger?.warn(`Existing "extends" value "${existingProfile.extends ?? "(none)"}" will be replaced by Rulesync-managed "${newProfile.extends ?? "(none)"}".`);
20459
+ warnAboutPreservedNetworkState({
20460
+ existingProfile,
20461
+ newProfile,
20462
+ logger
20463
+ });
20464
+ if (existingDomainsHadUnknown) logger?.warn(`Existing "network.domains" contained unrecognized values. These entries were skipped and will not be imported.`);
20465
+ }
20466
+ function networkHasAllowDomain(network) {
20467
+ return Object.values(network?.domains ?? {}).some((action) => action === "allow");
20468
+ }
20469
+ function warnAboutNetworkEnabledState({ existingProfile, newProfile, logger }) {
20470
+ if (existingProfile?.network?.enabled !== void 0 && newProfile.network?.enabled === void 0 && !networkHasAllowDomain(existingProfile.network)) logger?.warn(`Preserving existing "network.enabled" from config. Review this value manually as it may enable network access beyond the Rulesync-managed domain rules.`);
20471
+ if (existingProfile?.network?.enabled === false && newProfile.network?.enabled === true) logger?.warn(`Existing "network.enabled = false" will be replaced by Rulesync-managed "enabled = true" because the canonical model contains an allow domain.`);
20472
+ }
20473
+ function warnAboutPreservedNetworkState({ existingProfile, newProfile, logger }) {
19759
20474
  if (existingProfile?.network?.unix_sockets !== void 0) logger?.warn(`Preserving existing "network.unix_sockets" from config. Review these entries manually as they may grant broad system access.`);
20475
+ warnAboutNetworkEnabledState({
20476
+ existingProfile,
20477
+ newProfile,
20478
+ logger
20479
+ });
19760
20480
  if (existingProfile?.network?.mode !== void 0) logger?.warn(`Preserving existing "network.mode" from config. Review this value manually as it may grant broader network access than the Rulesync-managed domain rules.`);
19761
- if (existingDomainsHadUnknown) logger?.warn(`Existing "network.domains" contained unrecognized values. These entries were skipped and will not be imported.`);
19762
20481
  }
19763
20482
  const MANAGED_PROFILE_KEYS = /* @__PURE__ */ new Set([
19764
20483
  "description",
@@ -19800,6 +20519,7 @@ function preserveUnmanagedProfileKeys({ rawExistingProfile, profile, logger }) {
19800
20519
  function mergeWithExistingProfile({ newProfile, existingProfile }) {
19801
20520
  if (!existingProfile) return newProfile;
19802
20521
  const mergedNetwork = { ...newProfile.network };
20522
+ if (existingProfile.network?.enabled !== void 0 && mergedNetwork.enabled === void 0 && !networkHasAllowDomain(existingProfile.network)) mergedNetwork.enabled = existingProfile.network.enabled;
19803
20523
  if (existingProfile.network?.mode !== void 0 && mergedNetwork.mode === void 0) mergedNetwork.mode = existingProfile.network.mode;
19804
20524
  if (existingProfile.network?.unix_sockets !== void 0 && mergedNetwork.unix_sockets === void 0) mergedNetwork.unix_sockets = existingProfile.network.unix_sockets;
19805
20525
  const hasNetwork = Object.keys(mergedNetwork).length > 0;
@@ -19851,7 +20571,7 @@ function computeCodexcliOverridePatch({ existing, override, logger }) {
19851
20571
  const patch = {};
19852
20572
  const allowed = new Set(CODEXCLI_OVERRIDE_KEYS);
19853
20573
  for (const [key, value] of Object.entries(override ?? {})) {
19854
- if (key === "base_permission_profile") continue;
20574
+ if (key === "base_permission_profile" || key === "git_write_rules") continue;
19855
20575
  if (!allowed.has(key)) {
19856
20576
  logger?.warn(`Codex CLI permission override key "${key}" is not managed and was skipped. "permissions"/"default_permissions" are owned by the canonical permission model and "mcp_servers" gating by the MCP feature.`);
19857
20577
  continue;
@@ -19932,6 +20652,67 @@ function mapReadAction(action) {
19932
20652
  function mapWriteAction(action) {
19933
20653
  return action === "allow" ? "write" : "deny";
19934
20654
  }
20655
+ /**
20656
+ * Merge the canonical read/edit/write category rules into one Codex access
20657
+ * level per path pattern (Codex models a single `deny` < `read` < `write`
20658
+ * level, with no `ask`).
20659
+ *
20660
+ * - `edit` and `write` collapse onto Codex's write side; when both carry the
20661
+ * same pattern, the more restrictive action wins (`deny` > `ask` > `allow`).
20662
+ * - `read: allow` + write-side `allow` → `"write"`.
20663
+ * - `read: allow` + write-side non-allow → `"read"` (readable but not
20664
+ * writable — exactly what Codex's `"read"` level expresses).
20665
+ * - `read` non-allow → `"deny"` regardless of the write side; a contradictory
20666
+ * write-side `allow` (unreadable but writable is not expressible in Codex)
20667
+ * is warned about.
20668
+ * - Single-category patterns keep the existing mapReadAction/mapWriteAction
20669
+ * mappings.
20670
+ *
20671
+ * Iteration order is read → edit → write with first-seen pattern order, so
20672
+ * the emitted table is stable regardless of the authored category order.
20673
+ * Note the merge is one-way: `"{path}" = "read"` imports back as
20674
+ * `read: allow` only (the explicit write-side deny is implied by Codex's
20675
+ * access level and not re-materialized).
20676
+ */
20677
+ function mergeFilesystemCategoryRules({ categoryRules, logger }) {
20678
+ const readRules = categoryRules.read ?? {};
20679
+ const writeSideRestrictiveness = {
20680
+ deny: 2,
20681
+ ask: 1,
20682
+ allow: 0
20683
+ };
20684
+ const writeSideRules = {};
20685
+ for (const category of ["edit", "write"]) for (const [pattern, action] of Object.entries(categoryRules[category] ?? {})) {
20686
+ const existing = writeSideRules[pattern];
20687
+ if (existing === void 0 || writeSideRestrictiveness[action] > writeSideRestrictiveness[existing]) writeSideRules[pattern] = action;
20688
+ }
20689
+ const patterns = [];
20690
+ const seen = /* @__PURE__ */ new Set();
20691
+ for (const pattern of [...Object.keys(readRules), ...Object.keys(writeSideRules)]) if (!seen.has(pattern)) {
20692
+ seen.add(pattern);
20693
+ patterns.push(pattern);
20694
+ }
20695
+ const merged = [];
20696
+ for (const pattern of patterns) {
20697
+ const readAction = readRules[pattern];
20698
+ const writeAction = writeSideRules[pattern];
20699
+ if (readAction === void 0) {
20700
+ merged.push([pattern, mapWriteAction(writeAction)]);
20701
+ continue;
20702
+ }
20703
+ if (writeAction === void 0) {
20704
+ merged.push([pattern, mapReadAction(readAction)]);
20705
+ continue;
20706
+ }
20707
+ if (readAction === "allow") {
20708
+ merged.push([pattern, writeAction === "allow" ? "write" : "read"]);
20709
+ continue;
20710
+ }
20711
+ if (writeAction === "allow") logger?.warn(`Codex CLI cannot express "writable but not readable": pattern "${pattern}" has read: ${readAction} and a write-side allow. Emitting "deny".`);
20712
+ merged.push([pattern, "deny"]);
20713
+ }
20714
+ return merged;
20715
+ }
19935
20716
  function buildCodexBashRulesContent(config) {
19936
20717
  const bashRules = config.permission.bash ?? {};
19937
20718
  const entries = Object.entries(bashRules);
@@ -20883,7 +21664,7 @@ function convertRulesyncToGoosePermissionConfig({ config, logger }) {
20883
21664
  function convertGoosePermissionConfigToRulesync(userPermission) {
20884
21665
  const permission = {};
20885
21666
  for (const key of GOOSE_PERMISSION_LIST_KEYS) {
20886
- const toolNames = isStringArray(userPermission[key]) ? userPermission[key] : [];
21667
+ const toolNames = isStringArray$1(userPermission[key]) ? userPermission[key] : [];
20887
21668
  const action = GOOSE_LIST_TO_ACTION[key];
20888
21669
  for (const toolName of toolNames) {
20889
21670
  const category = GOOSE_TO_RULESYNC_TOOL_NAME[toolName] ?? toolName;
@@ -21123,7 +21904,7 @@ const ACTION_RANK = {
21123
21904
  * (mirrors the Cursor adapter's preservation of unmanaged types).
21124
21905
  */
21125
21906
  function unmanagedEntries(existingPermission, key) {
21126
- return (isStringArray(existingPermission[key]) ? existingPermission[key] : []).filter((entry) => parseGrokEntry(entry) === null);
21907
+ return (isStringArray$1(existingPermission[key]) ? existingPermission[key] : []).filter((entry) => parseGrokEntry(entry) === null);
21127
21908
  }
21128
21909
  /**
21129
21910
  * Bucket canonical rules into Grok's `[permission]` allow/deny/ask arrays of
@@ -21164,9 +21945,9 @@ function buildGrokPermissionArrays(config, existingPermission, logger) {
21164
21945
  * arrays resolves to the strictest action.
21165
21946
  */
21166
21947
  function parseGrokPermissionArrays(permission) {
21167
- const allow = isStringArray(permission.allow) ? permission.allow : void 0;
21168
- const deny = isStringArray(permission.deny) ? permission.deny : void 0;
21169
- const ask = isStringArray(permission.ask) ? permission.ask : void 0;
21948
+ const allow = isStringArray$1(permission.allow) ? permission.allow : void 0;
21949
+ const deny = isStringArray$1(permission.deny) ? permission.deny : void 0;
21950
+ const ask = isStringArray$1(permission.ask) ? permission.ask : void 0;
21170
21951
  if ((allow?.length ?? 0) + (deny?.length ?? 0) + (ask?.length ?? 0) === 0) return null;
21171
21952
  const result = {};
21172
21953
  const apply = (entries, action) => {
@@ -22947,7 +23728,7 @@ function convertRovodevToolPermissionsToRulesync(toolPermissions) {
22947
23728
  permission[category][CATCH_ALL_PATTERN$1] = value;
22948
23729
  }
22949
23730
  }
22950
- if (isStringArray(toolPermissions.allowedExternalPaths)) for (const path of toolPermissions.allowedExternalPaths) {
23731
+ if (isStringArray$1(toolPermissions.allowedExternalPaths)) for (const path of toolPermissions.allowedExternalPaths) {
22951
23732
  permission.read ??= {};
22952
23733
  permission.read[path] = "allow";
22953
23734
  }
@@ -23507,8 +24288,8 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
23507
24288
  const agents = isRecord(settings.agents) ? settings.agents : {};
23508
24289
  const profiles = isRecord(agents.profiles) ? agents.profiles : {};
23509
24290
  const config = convertWarpToRulesyncPermissions({
23510
- allow: isStringArray(profiles[ALLOWLIST_KEY]) ? profiles[ALLOWLIST_KEY] : [],
23511
- deny: isStringArray(profiles[DENYLIST_KEY]) ? profiles[DENYLIST_KEY] : []
24291
+ allow: isStringArray$1(profiles[ALLOWLIST_KEY]) ? profiles[ALLOWLIST_KEY] : [],
24292
+ deny: isStringArray$1(profiles[DENYLIST_KEY]) ? profiles[DENYLIST_KEY] : []
23512
24293
  });
23513
24294
  const warpOverride = {};
23514
24295
  for (const key of WARP_OVERRIDE_KEYS) if (profiles[key] !== void 0) warpOverride[key] = profiles[key];
@@ -24088,9 +24869,6 @@ var PermissionsProcessor = class extends FeatureProcessor {
24088
24869
  }
24089
24870
  };
24090
24871
  //#endregion
24091
- //#region src/constants/general.ts
24092
- const SKILL_FILE_NAME = "SKILL.md";
24093
- //#endregion
24094
24872
  //#region src/types/ai-dir.ts
24095
24873
  var AiDir = class {
24096
24874
  /**
@@ -24291,10 +25069,10 @@ var ToolSkill = class extends AiDir {
24291
25069
  const settablePaths = getSettablePaths({ global });
24292
25070
  const actualRelativeDirPath = relativeDirPath ?? settablePaths.relativeDirPath;
24293
25071
  const skillDirPath = (0, node_path.join)(outputRoot, actualRelativeDirPath, dirName);
24294
- const skillFilePath = (0, node_path.join)(skillDirPath, SKILL_FILE_NAME);
24295
- if (!await fileExists(skillFilePath)) throw new Error(`${SKILL_FILE_NAME} not found in ${skillDirPath}`);
25072
+ const skillFilePath = (0, node_path.join)(skillDirPath, SKILL_FILE_NAME$1);
25073
+ if (!await fileExists(skillFilePath)) throw new Error(`${SKILL_FILE_NAME$1} not found in ${skillDirPath}`);
24296
25074
  const { frontmatter, body: content } = parseFrontmatter(await readFileContent(skillFilePath), skillFilePath);
24297
- const otherFiles = await this.collectOtherFiles(outputRoot, actualRelativeDirPath, dirName, SKILL_FILE_NAME);
25075
+ const otherFiles = await this.collectOtherFiles(outputRoot, actualRelativeDirPath, dirName, SKILL_FILE_NAME$1);
24298
25076
  return {
24299
25077
  outputRoot,
24300
25078
  relativeDirPath: actualRelativeDirPath,
@@ -24336,7 +25114,7 @@ var SimulatedSkill = class extends ToolSkill {
24336
25114
  relativeDirPath,
24337
25115
  dirName,
24338
25116
  mainFile: {
24339
- name: SKILL_FILE_NAME,
25117
+ name: SKILL_FILE_NAME$1,
24340
25118
  body,
24341
25119
  frontmatter: { ...frontmatter }
24342
25120
  },
@@ -24394,12 +25172,12 @@ var SimulatedSkill = class extends ToolSkill {
24394
25172
  const settablePaths = this.getSettablePaths();
24395
25173
  const actualRelativeDirPath = relativeDirPath ?? settablePaths.relativeDirPath;
24396
25174
  const skillDirPath = (0, node_path.join)(outputRoot, actualRelativeDirPath, dirName);
24397
- const skillFilePath = (0, node_path.join)(skillDirPath, SKILL_FILE_NAME);
24398
- if (!await fileExists(skillFilePath)) throw new Error(`${SKILL_FILE_NAME} not found in ${skillDirPath}`);
25175
+ const skillFilePath = (0, node_path.join)(skillDirPath, SKILL_FILE_NAME$1);
25176
+ if (!await fileExists(skillFilePath)) throw new Error(`${SKILL_FILE_NAME$1} not found in ${skillDirPath}`);
24399
25177
  const { frontmatter, body: content } = parseFrontmatter(await readFileContent(skillFilePath), skillFilePath);
24400
25178
  const result = SimulatedSkillFrontmatterSchema.safeParse(frontmatter);
24401
25179
  if (!result.success) throw new Error(`Invalid frontmatter in ${skillFilePath}: ${formatError(result.error)}`);
24402
- const otherFiles = await this.collectOtherFiles(outputRoot, actualRelativeDirPath, dirName, SKILL_FILE_NAME);
25180
+ const otherFiles = await this.collectOtherFiles(outputRoot, actualRelativeDirPath, dirName, SKILL_FILE_NAME$1);
24403
25181
  return {
24404
25182
  outputRoot,
24405
25183
  relativeDirPath: actualRelativeDirPath,
@@ -24609,7 +25387,7 @@ var RulesyncSkill = class RulesyncSkill extends AiDir {
24609
25387
  relativeDirPath,
24610
25388
  dirName,
24611
25389
  mainFile: {
24612
- name: SKILL_FILE_NAME,
25390
+ name: SKILL_FILE_NAME$1,
24613
25391
  body,
24614
25392
  frontmatter: { ...frontmatter }
24615
25393
  },
@@ -24644,13 +25422,13 @@ var RulesyncSkill = class RulesyncSkill extends AiDir {
24644
25422
  }
24645
25423
  static async fromDir({ outputRoot = process.cwd(), relativeDirPath = RULESYNC_SKILLS_RELATIVE_DIR_PATH, dirName, global = false }) {
24646
25424
  const skillDirPath = (0, node_path.join)(outputRoot, relativeDirPath, dirName);
24647
- const skillFilePath = (0, node_path.join)(skillDirPath, SKILL_FILE_NAME);
24648
- if (!await fileExists(skillFilePath)) throw new Error(`${SKILL_FILE_NAME} not found in ${skillDirPath}`);
25425
+ const skillFilePath = (0, node_path.join)(skillDirPath, SKILL_FILE_NAME$1);
25426
+ if (!await fileExists(skillFilePath)) throw new Error(`${SKILL_FILE_NAME$1} not found in ${skillDirPath}`);
24649
25427
  const { frontmatter, body: content, hasFrontmatter } = parseFrontmatter(await readFileContent(skillFilePath), skillFilePath);
24650
25428
  if (!hasFrontmatter) throw new Error(`Missing frontmatter in ${skillFilePath}. Rulesync files must begin with a YAML frontmatter block delimited by '---'.`);
24651
25429
  const result = RulesyncSkillFrontmatterSchema.safeParse(frontmatter);
24652
25430
  if (!result.success) throw new Error(`Invalid frontmatter in ${skillFilePath}: ${formatError(result.error)}`);
24653
- const otherFiles = await this.collectOtherFiles(outputRoot, relativeDirPath, dirName, SKILL_FILE_NAME);
25431
+ const otherFiles = await this.collectOtherFiles(outputRoot, relativeDirPath, dirName, SKILL_FILE_NAME$1);
24654
25432
  return new RulesyncSkill({
24655
25433
  outputRoot,
24656
25434
  relativeDirPath,
@@ -24690,7 +25468,7 @@ var RovodevSkill = class RovodevSkill extends ToolSkill {
24690
25468
  relativeDirPath,
24691
25469
  dirName,
24692
25470
  mainFile: {
24693
- name: SKILL_FILE_NAME,
25471
+ name: SKILL_FILE_NAME$1,
24694
25472
  body,
24695
25473
  frontmatter: { ...frontmatter }
24696
25474
  },
@@ -24717,7 +25495,7 @@ var RovodevSkill = class RovodevSkill extends ToolSkill {
24717
25495
  validate() {
24718
25496
  if (!this.mainFile) return {
24719
25497
  success: false,
24720
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
25498
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
24721
25499
  };
24722
25500
  const result = RovodevSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
24723
25501
  if (!result.success) return {
@@ -24793,10 +25571,10 @@ var RovodevSkill = class RovodevSkill extends ToolSkill {
24793
25571
  const result = RovodevSkillFrontmatterSchema.safeParse(loaded.frontmatter);
24794
25572
  if (!result.success) {
24795
25573
  const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
24796
- throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
25574
+ throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
24797
25575
  }
24798
25576
  if (result.data.name !== loaded.dirName) {
24799
- const skillFilePath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME);
25577
+ const skillFilePath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME$1);
24800
25578
  throw new Error(`Frontmatter name (${result.data.name}) must match directory name (${loaded.dirName}) in ${skillFilePath}`);
24801
25579
  }
24802
25580
  return new RovodevSkill({
@@ -24961,7 +25739,7 @@ var AgentsSkillsSkill = class AgentsSkillsSkill extends ToolSkill {
24961
25739
  relativeDirPath,
24962
25740
  dirName,
24963
25741
  mainFile: {
24964
- name: SKILL_FILE_NAME,
25742
+ name: SKILL_FILE_NAME$1,
24965
25743
  body,
24966
25744
  frontmatter: { ...frontmatter }
24967
25745
  },
@@ -24985,7 +25763,7 @@ var AgentsSkillsSkill = class AgentsSkillsSkill extends ToolSkill {
24985
25763
  validate() {
24986
25764
  if (!this.mainFile) return {
24987
25765
  success: false,
24988
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
25766
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
24989
25767
  };
24990
25768
  const result = AgentsSkillsSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
24991
25769
  if (!result.success) return {
@@ -25057,7 +25835,7 @@ var AgentsSkillsSkill = class AgentsSkillsSkill extends ToolSkill {
25057
25835
  const result = AgentsSkillsSkillFrontmatterSchema.safeParse(loaded.frontmatter);
25058
25836
  if (!result.success) {
25059
25837
  const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
25060
- throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
25838
+ throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
25061
25839
  }
25062
25840
  return new this({
25063
25841
  outputRoot: loaded.outputRoot,
@@ -25114,7 +25892,7 @@ var AiassistantSkill = class AiassistantSkill extends ToolSkill {
25114
25892
  relativeDirPath,
25115
25893
  dirName,
25116
25894
  mainFile: {
25117
- name: SKILL_FILE_NAME,
25895
+ name: SKILL_FILE_NAME$1,
25118
25896
  body,
25119
25897
  frontmatter: { ...frontmatter }
25120
25898
  },
@@ -25139,7 +25917,7 @@ var AiassistantSkill = class AiassistantSkill extends ToolSkill {
25139
25917
  validate() {
25140
25918
  if (!this.mainFile) return {
25141
25919
  success: false,
25142
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
25920
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
25143
25921
  };
25144
25922
  const result = AiassistantSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
25145
25923
  if (!result.success) return {
@@ -25199,7 +25977,7 @@ var AiassistantSkill = class AiassistantSkill extends ToolSkill {
25199
25977
  const result = AiassistantSkillFrontmatterSchema.safeParse(loaded.frontmatter);
25200
25978
  if (!result.success) {
25201
25979
  const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
25202
- throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
25980
+ throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
25203
25981
  }
25204
25982
  return new AiassistantSkill({
25205
25983
  outputRoot: loaded.outputRoot,
@@ -25250,7 +26028,7 @@ var AmpSkill = class AmpSkill extends ToolSkill {
25250
26028
  relativeDirPath,
25251
26029
  dirName,
25252
26030
  mainFile: {
25253
- name: SKILL_FILE_NAME,
26031
+ name: SKILL_FILE_NAME$1,
25254
26032
  body,
25255
26033
  frontmatter: { ...frontmatter }
25256
26034
  },
@@ -25274,7 +26052,7 @@ var AmpSkill = class AmpSkill extends ToolSkill {
25274
26052
  validate() {
25275
26053
  if (!this.mainFile) return {
25276
26054
  success: false,
25277
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
26055
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
25278
26056
  };
25279
26057
  const result = AmpSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
25280
26058
  if (!result.success) return {
@@ -25334,7 +26112,7 @@ var AmpSkill = class AmpSkill extends ToolSkill {
25334
26112
  const result = AmpSkillFrontmatterSchema.safeParse(loaded.frontmatter);
25335
26113
  if (!result.success) {
25336
26114
  const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
25337
- throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
26115
+ throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
25338
26116
  }
25339
26117
  return new AmpSkill({
25340
26118
  outputRoot: loaded.outputRoot,
@@ -25390,7 +26168,7 @@ var AntigravitySharedSkill = class extends ToolSkill {
25390
26168
  relativeDirPath,
25391
26169
  dirName,
25392
26170
  mainFile: {
25393
- name: SKILL_FILE_NAME,
26171
+ name: SKILL_FILE_NAME$1,
25394
26172
  body,
25395
26173
  frontmatter: { ...frontmatter }
25396
26174
  },
@@ -25423,7 +26201,7 @@ var AntigravitySharedSkill = class extends ToolSkill {
25423
26201
  validate() {
25424
26202
  if (this.mainFile === void 0) return {
25425
26203
  success: false,
25426
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
26204
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
25427
26205
  };
25428
26206
  const result = AntigravitySkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
25429
26207
  if (!result.success) return {
@@ -25483,7 +26261,7 @@ var AntigravitySharedSkill = class extends ToolSkill {
25483
26261
  const result = AntigravitySkillFrontmatterSchema.safeParse(loaded.frontmatter);
25484
26262
  if (!result.success) {
25485
26263
  const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
25486
- throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
26264
+ throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
25487
26265
  }
25488
26266
  return new this({
25489
26267
  outputRoot: loaded.outputRoot,
@@ -25567,7 +26345,7 @@ var AugmentcodeSkill = class AugmentcodeSkill extends ToolSkill {
25567
26345
  relativeDirPath,
25568
26346
  dirName,
25569
26347
  mainFile: {
25570
- name: SKILL_FILE_NAME,
26348
+ name: SKILL_FILE_NAME$1,
25571
26349
  body,
25572
26350
  frontmatter: { ...frontmatter }
25573
26351
  },
@@ -25594,7 +26372,7 @@ var AugmentcodeSkill = class AugmentcodeSkill extends ToolSkill {
25594
26372
  validate() {
25595
26373
  if (this.mainFile === void 0) return {
25596
26374
  success: false,
25597
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
26375
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
25598
26376
  };
25599
26377
  const result = AugmentcodeSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
25600
26378
  if (!result.success) return {
@@ -25654,7 +26432,7 @@ var AugmentcodeSkill = class AugmentcodeSkill extends ToolSkill {
25654
26432
  const result = AugmentcodeSkillFrontmatterSchema.safeParse(loaded.frontmatter);
25655
26433
  if (!result.success) {
25656
26434
  const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
25657
- throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
26435
+ throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
25658
26436
  }
25659
26437
  return new AugmentcodeSkill({
25660
26438
  outputRoot: loaded.outputRoot,
@@ -25795,7 +26573,7 @@ var ClaudecodeSkill = class ClaudecodeSkill extends ToolSkill {
25795
26573
  relativeDirPath,
25796
26574
  dirName,
25797
26575
  mainFile: {
25798
- name: SKILL_FILE_NAME,
26576
+ name: SKILL_FILE_NAME$1,
25799
26577
  body,
25800
26578
  frontmatter: { ...frontmatter }
25801
26579
  },
@@ -25822,7 +26600,7 @@ var ClaudecodeSkill = class ClaudecodeSkill extends ToolSkill {
25822
26600
  validate() {
25823
26601
  if (this.mainFile === void 0) return {
25824
26602
  success: false,
25825
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
26603
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
25826
26604
  };
25827
26605
  const result = ClaudecodeSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
25828
26606
  if (!result.success) return {
@@ -25910,7 +26688,7 @@ var ClaudecodeSkill = class ClaudecodeSkill extends ToolSkill {
25910
26688
  const result = ClaudecodeSkillFrontmatterSchema.safeParse(loaded.frontmatter);
25911
26689
  if (!result.success) {
25912
26690
  const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
25913
- throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
26691
+ throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
25914
26692
  }
25915
26693
  return new ClaudecodeSkill({
25916
26694
  outputRoot: loaded.outputRoot,
@@ -25956,7 +26734,7 @@ var ClineSkill = class ClineSkill extends ToolSkill {
25956
26734
  relativeDirPath,
25957
26735
  dirName,
25958
26736
  mainFile: {
25959
- name: SKILL_FILE_NAME,
26737
+ name: SKILL_FILE_NAME$1,
25960
26738
  body,
25961
26739
  frontmatter: { ...frontmatter }
25962
26740
  },
@@ -25980,7 +26758,7 @@ var ClineSkill = class ClineSkill extends ToolSkill {
25980
26758
  validate() {
25981
26759
  if (!this.mainFile) return {
25982
26760
  success: false,
25983
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
26761
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
25984
26762
  };
25985
26763
  const result = ClineSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
25986
26764
  if (!result.success) return {
@@ -26044,10 +26822,10 @@ var ClineSkill = class ClineSkill extends ToolSkill {
26044
26822
  const result = ClineSkillFrontmatterSchema.safeParse(loaded.frontmatter);
26045
26823
  if (!result.success) {
26046
26824
  const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
26047
- throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
26825
+ throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
26048
26826
  }
26049
26827
  if (result.data.name !== loaded.dirName) {
26050
- const skillFilePath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME);
26828
+ const skillFilePath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME$1);
26051
26829
  throw new Error(`Frontmatter name (${result.data.name}) must match directory name (${loaded.dirName}) in ${skillFilePath}`);
26052
26830
  }
26053
26831
  return new ClineSkill({
@@ -26161,7 +26939,7 @@ var CodexCliSkill = class CodexCliSkill extends ToolSkill {
26161
26939
  relativeDirPath,
26162
26940
  dirName,
26163
26941
  mainFile: {
26164
- name: SKILL_FILE_NAME,
26942
+ name: SKILL_FILE_NAME$1,
26165
26943
  body,
26166
26944
  frontmatter: { ...frontmatter }
26167
26945
  },
@@ -26185,7 +26963,7 @@ var CodexCliSkill = class CodexCliSkill extends ToolSkill {
26185
26963
  validate() {
26186
26964
  if (!this.mainFile) return {
26187
26965
  success: false,
26188
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
26966
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
26189
26967
  };
26190
26968
  const result = CodexCliSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
26191
26969
  if (!result.success) return {
@@ -26263,7 +27041,7 @@ var CodexCliSkill = class CodexCliSkill extends ToolSkill {
26263
27041
  const result = CodexCliSkillFrontmatterSchema.safeParse(loaded.frontmatter);
26264
27042
  if (!result.success) {
26265
27043
  const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
26266
- throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
27044
+ throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
26267
27045
  }
26268
27046
  return new CodexCliSkill({
26269
27047
  outputRoot: loaded.outputRoot,
@@ -26315,7 +27093,7 @@ var CopilotSkill = class CopilotSkill extends ToolSkill {
26315
27093
  relativeDirPath,
26316
27094
  dirName,
26317
27095
  mainFile: {
26318
- name: SKILL_FILE_NAME,
27096
+ name: SKILL_FILE_NAME$1,
26319
27097
  body,
26320
27098
  frontmatter: { ...frontmatter }
26321
27099
  },
@@ -26340,7 +27118,7 @@ var CopilotSkill = class CopilotSkill extends ToolSkill {
26340
27118
  validate() {
26341
27119
  if (!this.mainFile) return {
26342
27120
  success: false,
26343
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
27121
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
26344
27122
  };
26345
27123
  const result = CopilotSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
26346
27124
  if (!result.success) return {
@@ -26407,7 +27185,7 @@ var CopilotSkill = class CopilotSkill extends ToolSkill {
26407
27185
  const result = CopilotSkillFrontmatterSchema.safeParse(loaded.frontmatter);
26408
27186
  if (!result.success) {
26409
27187
  const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
26410
- throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
27188
+ throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
26411
27189
  }
26412
27190
  return new CopilotSkill({
26413
27191
  outputRoot: loaded.outputRoot,
@@ -26461,7 +27239,7 @@ var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
26461
27239
  relativeDirPath,
26462
27240
  dirName,
26463
27241
  mainFile: {
26464
- name: SKILL_FILE_NAME,
27242
+ name: SKILL_FILE_NAME$1,
26465
27243
  body,
26466
27244
  frontmatter: { ...frontmatter }
26467
27245
  },
@@ -26486,7 +27264,7 @@ var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
26486
27264
  validate() {
26487
27265
  if (!this.mainFile) return {
26488
27266
  success: false,
26489
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
27267
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
26490
27268
  };
26491
27269
  const result = CopilotcliSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
26492
27270
  if (!result.success) return {
@@ -26555,7 +27333,7 @@ var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
26555
27333
  const result = CopilotcliSkillFrontmatterSchema.safeParse(loaded.frontmatter);
26556
27334
  if (!result.success) {
26557
27335
  const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
26558
- throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
27336
+ throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
26559
27337
  }
26560
27338
  return new CopilotcliSkill({
26561
27339
  outputRoot: loaded.outputRoot,
@@ -26605,7 +27383,7 @@ var CursorSkill = class CursorSkill extends ToolSkill {
26605
27383
  relativeDirPath,
26606
27384
  dirName,
26607
27385
  mainFile: {
26608
- name: SKILL_FILE_NAME,
27386
+ name: SKILL_FILE_NAME$1,
26609
27387
  body,
26610
27388
  frontmatter: { ...frontmatter }
26611
27389
  },
@@ -26629,7 +27407,7 @@ var CursorSkill = class CursorSkill extends ToolSkill {
26629
27407
  validate() {
26630
27408
  if (!this.mainFile) return {
26631
27409
  success: false,
26632
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
27410
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
26633
27411
  };
26634
27412
  const result = CursorSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
26635
27413
  if (!result.success) return {
@@ -26703,7 +27481,7 @@ var CursorSkill = class CursorSkill extends ToolSkill {
26703
27481
  const result = CursorSkillFrontmatterSchema.safeParse(loaded.frontmatter);
26704
27482
  if (!result.success) {
26705
27483
  const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
26706
- throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
27484
+ throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
26707
27485
  }
26708
27486
  return new CursorSkill({
26709
27487
  outputRoot: loaded.outputRoot,
@@ -26750,7 +27528,7 @@ var DeepagentsSkill = class DeepagentsSkill extends ToolSkill {
26750
27528
  relativeDirPath,
26751
27529
  dirName,
26752
27530
  mainFile: {
26753
- name: SKILL_FILE_NAME,
27531
+ name: SKILL_FILE_NAME$1,
26754
27532
  body,
26755
27533
  frontmatter: { ...frontmatter }
26756
27534
  },
@@ -26774,7 +27552,7 @@ var DeepagentsSkill = class DeepagentsSkill extends ToolSkill {
26774
27552
  validate() {
26775
27553
  if (!this.mainFile) return {
26776
27554
  success: false,
26777
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
27555
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
26778
27556
  };
26779
27557
  const result = DeepagentsSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
26780
27558
  if (!result.success) return {
@@ -26850,7 +27628,7 @@ var DeepagentsSkill = class DeepagentsSkill extends ToolSkill {
26850
27628
  const result = DeepagentsSkillFrontmatterSchema.safeParse(loaded.frontmatter);
26851
27629
  if (!result.success) {
26852
27630
  const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
26853
- throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
27631
+ throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
26854
27632
  }
26855
27633
  return new DeepagentsSkill({
26856
27634
  outputRoot: loaded.outputRoot,
@@ -26901,7 +27679,7 @@ var DevinSkill = class DevinSkill extends ToolSkill {
26901
27679
  relativeDirPath,
26902
27680
  dirName,
26903
27681
  mainFile: {
26904
- name: SKILL_FILE_NAME,
27682
+ name: SKILL_FILE_NAME$1,
26905
27683
  body,
26906
27684
  frontmatter: { ...frontmatter }
26907
27685
  },
@@ -26926,7 +27704,7 @@ var DevinSkill = class DevinSkill extends ToolSkill {
26926
27704
  validate() {
26927
27705
  if (!this.mainFile) return {
26928
27706
  success: false,
26929
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
27707
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
26930
27708
  };
26931
27709
  const result = DevinSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
26932
27710
  if (!result.success) return {
@@ -26978,6 +27756,18 @@ var DevinSkill = class DevinSkill extends ToolSkill {
26978
27756
  const targets = rulesyncSkill.getFrontmatter().targets;
26979
27757
  return targets.includes("*") || targets.includes("devin");
26980
27758
  }
27759
+ /**
27760
+ * Commands are emitted into this same skills tree as `<slug>/SKILL.md`
27761
+ * (see `DevinCommand`), so a directory matching a current rulesync command
27762
+ * slug is owned by the commands feature: it must not be imported as a
27763
+ * skill nor deleted as an orphan skill.
27764
+ */
27765
+ static async isDirOwned({ dirName, inputRoot }) {
27766
+ return !await rulesyncCommandSlugExists({
27767
+ inputRoot,
27768
+ dirName
27769
+ });
27770
+ }
26981
27771
  static async fromDir(params) {
26982
27772
  const loaded = await this.loadSkillDirContent({
26983
27773
  ...params,
@@ -26986,7 +27776,7 @@ var DevinSkill = class DevinSkill extends ToolSkill {
26986
27776
  const result = DevinSkillFrontmatterSchema.safeParse(loaded.frontmatter);
26987
27777
  if (!result.success) {
26988
27778
  const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
26989
- throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
27779
+ throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
26990
27780
  }
26991
27781
  return new DevinSkill({
26992
27782
  outputRoot: loaded.outputRoot,
@@ -27038,7 +27828,7 @@ var FactorydroidSkill = class FactorydroidSkill extends ToolSkill {
27038
27828
  relativeDirPath,
27039
27829
  dirName,
27040
27830
  mainFile: {
27041
- name: SKILL_FILE_NAME,
27831
+ name: SKILL_FILE_NAME$1,
27042
27832
  body,
27043
27833
  frontmatter: { ...frontmatter }
27044
27834
  },
@@ -27062,7 +27852,7 @@ var FactorydroidSkill = class FactorydroidSkill extends ToolSkill {
27062
27852
  validate() {
27063
27853
  if (this.mainFile === void 0) return {
27064
27854
  success: false,
27065
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
27855
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
27066
27856
  };
27067
27857
  const result = FactorydroidSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
27068
27858
  if (!result.success) return {
@@ -27137,7 +27927,7 @@ var FactorydroidSkill = class FactorydroidSkill extends ToolSkill {
27137
27927
  const result = FactorydroidSkillFrontmatterSchema.safeParse(loaded.frontmatter);
27138
27928
  if (!result.success) {
27139
27929
  const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
27140
- throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
27930
+ throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
27141
27931
  }
27142
27932
  return new FactorydroidSkill({
27143
27933
  outputRoot: loaded.outputRoot,
@@ -27184,7 +27974,7 @@ var GooseSkill = class GooseSkill extends ToolSkill {
27184
27974
  relativeDirPath,
27185
27975
  dirName,
27186
27976
  mainFile: {
27187
- name: SKILL_FILE_NAME,
27977
+ name: SKILL_FILE_NAME$1,
27188
27978
  body,
27189
27979
  frontmatter: { ...frontmatter }
27190
27980
  },
@@ -27209,7 +27999,7 @@ var GooseSkill = class GooseSkill extends ToolSkill {
27209
27999
  validate() {
27210
28000
  if (!this.mainFile) return {
27211
28001
  success: false,
27212
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
28002
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
27213
28003
  };
27214
28004
  const result = GooseSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
27215
28005
  if (!result.success) return {
@@ -27269,7 +28059,7 @@ var GooseSkill = class GooseSkill extends ToolSkill {
27269
28059
  const result = GooseSkillFrontmatterSchema.safeParse(loaded.frontmatter);
27270
28060
  if (!result.success) {
27271
28061
  const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
27272
- throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
28062
+ throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
27273
28063
  }
27274
28064
  return new GooseSkill({
27275
28065
  outputRoot: loaded.outputRoot,
@@ -27322,7 +28112,7 @@ var GrokcliSkill = class GrokcliSkill extends ToolSkill {
27322
28112
  relativeDirPath,
27323
28113
  dirName,
27324
28114
  mainFile: {
27325
- name: SKILL_FILE_NAME,
28115
+ name: SKILL_FILE_NAME$1,
27326
28116
  body,
27327
28117
  frontmatter: { ...frontmatter }
27328
28118
  },
@@ -27346,7 +28136,7 @@ var GrokcliSkill = class GrokcliSkill extends ToolSkill {
27346
28136
  validate() {
27347
28137
  if (this.mainFile === void 0) return {
27348
28138
  success: false,
27349
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
28139
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
27350
28140
  };
27351
28141
  const result = GrokcliSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
27352
28142
  if (!result.success) return {
@@ -27406,7 +28196,7 @@ var GrokcliSkill = class GrokcliSkill extends ToolSkill {
27406
28196
  const result = GrokcliSkillFrontmatterSchema.safeParse(loaded.frontmatter);
27407
28197
  if (!result.success) {
27408
28198
  const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
27409
- throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
28199
+ throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
27410
28200
  }
27411
28201
  return new GrokcliSkill({
27412
28202
  outputRoot: loaded.outputRoot,
@@ -27441,6 +28231,18 @@ var HermesagentSkill = class extends AgentsSkillsSkill {
27441
28231
  static getSettablePaths() {
27442
28232
  return { relativeDirPath: HERMESAGENT_SKILLS_DIR_PATH };
27443
28233
  }
28234
+ /**
28235
+ * Commands are emitted into this same skills tree as `<slug>/SKILL.md`
28236
+ * (see `HermesagentCommand`), so a directory matching a current rulesync
28237
+ * command slug is owned by the commands feature: it must not be imported
28238
+ * as a skill nor deleted as an orphan skill.
28239
+ */
28240
+ static async isDirOwned({ dirName, inputRoot }) {
28241
+ return !await rulesyncCommandSlugExists({
28242
+ inputRoot,
28243
+ dirName
28244
+ });
28245
+ }
27444
28246
  constructor(params) {
27445
28247
  super({
27446
28248
  ...params,
@@ -27465,7 +28267,7 @@ var JunieSkill = class JunieSkill extends ToolSkill {
27465
28267
  relativeDirPath,
27466
28268
  dirName,
27467
28269
  mainFile: {
27468
- name: SKILL_FILE_NAME,
28270
+ name: SKILL_FILE_NAME$1,
27469
28271
  body,
27470
28272
  frontmatter: { ...frontmatter }
27471
28273
  },
@@ -27489,7 +28291,7 @@ var JunieSkill = class JunieSkill extends ToolSkill {
27489
28291
  validate() {
27490
28292
  if (!this.mainFile) return {
27491
28293
  success: false,
27492
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
28294
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
27493
28295
  };
27494
28296
  const result = JunieSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
27495
28297
  if (!result.success) return {
@@ -27553,10 +28355,10 @@ var JunieSkill = class JunieSkill extends ToolSkill {
27553
28355
  const result = JunieSkillFrontmatterSchema.safeParse(loaded.frontmatter);
27554
28356
  if (!result.success) {
27555
28357
  const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
27556
- throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
28358
+ throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
27557
28359
  }
27558
28360
  if (result.data.name !== loaded.dirName) {
27559
- const skillFilePath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME);
28361
+ const skillFilePath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME$1);
27560
28362
  throw new Error(`Frontmatter name (${result.data.name}) must match directory name (${loaded.dirName}) in ${skillFilePath}`);
27561
28363
  }
27562
28364
  return new JunieSkill({
@@ -27604,7 +28406,7 @@ var KiloSkill = class KiloSkill extends ToolSkill {
27604
28406
  relativeDirPath,
27605
28407
  dirName,
27606
28408
  mainFile: {
27607
- name: SKILL_FILE_NAME,
28409
+ name: SKILL_FILE_NAME$1,
27608
28410
  body,
27609
28411
  frontmatter: { ...frontmatter }
27610
28412
  },
@@ -27628,7 +28430,7 @@ var KiloSkill = class KiloSkill extends ToolSkill {
27628
28430
  validate() {
27629
28431
  if (this.mainFile === void 0) return {
27630
28432
  success: false,
27631
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
28433
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
27632
28434
  };
27633
28435
  const result = KiloSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
27634
28436
  if (!result.success) return {
@@ -27700,7 +28502,7 @@ var KiloSkill = class KiloSkill extends ToolSkill {
27700
28502
  const result = KiloSkillFrontmatterSchema.safeParse(loaded.frontmatter);
27701
28503
  if (!result.success) {
27702
28504
  const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
27703
- throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
28505
+ throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
27704
28506
  }
27705
28507
  return new KiloSkill({
27706
28508
  outputRoot: loaded.outputRoot,
@@ -27746,7 +28548,7 @@ var KiroSkill = class KiroSkill extends ToolSkill {
27746
28548
  relativeDirPath,
27747
28549
  dirName,
27748
28550
  mainFile: {
27749
- name: SKILL_FILE_NAME,
28551
+ name: SKILL_FILE_NAME$1,
27750
28552
  body,
27751
28553
  frontmatter: { ...frontmatter }
27752
28554
  },
@@ -27770,7 +28572,7 @@ var KiroSkill = class KiroSkill extends ToolSkill {
27770
28572
  validate() {
27771
28573
  if (!this.mainFile) return {
27772
28574
  success: false,
27773
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
28575
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
27774
28576
  };
27775
28577
  const result = KiroSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
27776
28578
  if (!result.success) return {
@@ -27834,10 +28636,10 @@ var KiroSkill = class KiroSkill extends ToolSkill {
27834
28636
  const result = KiroSkillFrontmatterSchema.safeParse(loaded.frontmatter);
27835
28637
  if (!result.success) {
27836
28638
  const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
27837
- throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
28639
+ throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
27838
28640
  }
27839
28641
  if (result.data.name !== loaded.dirName) {
27840
- const skillFilePath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME);
28642
+ const skillFilePath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME$1);
27841
28643
  throw new Error(`Frontmatter name (${result.data.name}) must match directory name (${loaded.dirName}) in ${skillFilePath}`);
27842
28644
  }
27843
28645
  return new KiroSkill({
@@ -27920,7 +28722,7 @@ var OpenCodeSkill = class OpenCodeSkill extends ToolSkill {
27920
28722
  relativeDirPath,
27921
28723
  dirName,
27922
28724
  mainFile: {
27923
- name: SKILL_FILE_NAME,
28725
+ name: SKILL_FILE_NAME$1,
27924
28726
  body,
27925
28727
  frontmatter: { ...frontmatter }
27926
28728
  },
@@ -27947,7 +28749,7 @@ var OpenCodeSkill = class OpenCodeSkill extends ToolSkill {
27947
28749
  validate() {
27948
28750
  if (this.mainFile === void 0) return {
27949
28751
  success: false,
27950
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
28752
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
27951
28753
  };
27952
28754
  const result = OpenCodeSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
27953
28755
  if (!result.success) return {
@@ -28026,7 +28828,7 @@ var OpenCodeSkill = class OpenCodeSkill extends ToolSkill {
28026
28828
  const result = OpenCodeSkillFrontmatterSchema.safeParse(loaded.frontmatter);
28027
28829
  if (!result.success) {
28028
28830
  const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
28029
- throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
28831
+ throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
28030
28832
  }
28031
28833
  return new OpenCodeSkill({
28032
28834
  outputRoot: loaded.outputRoot,
@@ -28087,7 +28889,7 @@ var PiSkill = class PiSkill extends ToolSkill {
28087
28889
  relativeDirPath: resolvedDirPath,
28088
28890
  dirName,
28089
28891
  mainFile: {
28090
- name: SKILL_FILE_NAME,
28892
+ name: SKILL_FILE_NAME$1,
28091
28893
  body,
28092
28894
  frontmatter: { ...frontmatter }
28093
28895
  },
@@ -28112,7 +28914,7 @@ var PiSkill = class PiSkill extends ToolSkill {
28112
28914
  validate() {
28113
28915
  if (!this.mainFile) return {
28114
28916
  success: false,
28115
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
28917
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
28116
28918
  };
28117
28919
  const result = PiSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
28118
28920
  if (!result.success) return {
@@ -28187,7 +28989,7 @@ var PiSkill = class PiSkill extends ToolSkill {
28187
28989
  const result = PiSkillFrontmatterSchema.safeParse(loaded.frontmatter);
28188
28990
  if (!result.success) {
28189
28991
  const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
28190
- throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
28992
+ throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
28191
28993
  }
28192
28994
  return new PiSkill({
28193
28995
  outputRoot: loaded.outputRoot,
@@ -28240,7 +29042,7 @@ var QwencodeSkill = class QwencodeSkill extends ToolSkill {
28240
29042
  relativeDirPath,
28241
29043
  dirName,
28242
29044
  mainFile: {
28243
- name: SKILL_FILE_NAME,
29045
+ name: SKILL_FILE_NAME$1,
28244
29046
  body,
28245
29047
  frontmatter: { ...frontmatter }
28246
29048
  },
@@ -28264,7 +29066,7 @@ var QwencodeSkill = class QwencodeSkill extends ToolSkill {
28264
29066
  validate() {
28265
29067
  if (this.mainFile === void 0) return {
28266
29068
  success: false,
28267
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
29069
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
28268
29070
  };
28269
29071
  const result = QwencodeSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
28270
29072
  if (!result.success) return {
@@ -28344,7 +29146,7 @@ var QwencodeSkill = class QwencodeSkill extends ToolSkill {
28344
29146
  const result = QwencodeSkillFrontmatterSchema.safeParse(loaded.frontmatter);
28345
29147
  if (!result.success) {
28346
29148
  const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
28347
- throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
29149
+ throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
28348
29150
  }
28349
29151
  return new QwencodeSkill({
28350
29152
  outputRoot: loaded.outputRoot,
@@ -28394,7 +29196,7 @@ var ReasonixSkill = class ReasonixSkill extends ToolSkill {
28394
29196
  relativeDirPath,
28395
29197
  dirName,
28396
29198
  mainFile: {
28397
- name: SKILL_FILE_NAME,
29199
+ name: SKILL_FILE_NAME$1,
28398
29200
  body,
28399
29201
  frontmatter: { ...frontmatter }
28400
29202
  },
@@ -28418,7 +29220,7 @@ var ReasonixSkill = class ReasonixSkill extends ToolSkill {
28418
29220
  validate() {
28419
29221
  if (this.mainFile === void 0) return {
28420
29222
  success: false,
28421
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
29223
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
28422
29224
  };
28423
29225
  const result = ReasonixSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
28424
29226
  if (!result.success) return {
@@ -28478,7 +29280,7 @@ var ReasonixSkill = class ReasonixSkill extends ToolSkill {
28478
29280
  const result = ReasonixSkillFrontmatterSchema.safeParse(loaded.frontmatter);
28479
29281
  if (!result.success) {
28480
29282
  const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
28481
- throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
29283
+ throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
28482
29284
  }
28483
29285
  return new ReasonixSkill({
28484
29286
  outputRoot: loaded.outputRoot,
@@ -28491,6 +29293,24 @@ var ReasonixSkill = class ReasonixSkill extends ToolSkill {
28491
29293
  global: loaded.global
28492
29294
  });
28493
29295
  }
29296
+ /**
29297
+ * Whether the skill directory belongs to the skills feature.
29298
+ *
29299
+ * `.reasonix/skills/` is shared with the subagents feature: a directory whose
29300
+ * SKILL.md declares `runAs: subagent` is a subagent profile, not a regular
29301
+ * skill, so it must be neither imported as a skill nor deleted as an orphan
29302
+ * skill. Directories without a readable/parsable SKILL.md keep the default
29303
+ * skills-feature ownership, matching the previous behavior for such dirs.
29304
+ */
29305
+ static async isDirOwned({ outputRoot, relativeDirPath, dirName }) {
29306
+ const skillFilePath = (0, node_path.join)(outputRoot, relativeDirPath, dirName, SKILL_FILE_NAME$1);
29307
+ try {
29308
+ const { frontmatter } = parseFrontmatter(await readFileContent(skillFilePath), skillFilePath);
29309
+ return frontmatter["runAs"] !== REASONIX_SUBAGENT_RUN_AS;
29310
+ } catch {
29311
+ return true;
29312
+ }
29313
+ }
28494
29314
  static forDeletion({ outputRoot = process.cwd(), relativeDirPath, dirName, global = false }) {
28495
29315
  return new ReasonixSkill({
28496
29316
  outputRoot,
@@ -28533,7 +29353,7 @@ var ReplitSkill = class ReplitSkill extends ToolSkill {
28533
29353
  relativeDirPath,
28534
29354
  dirName,
28535
29355
  mainFile: {
28536
- name: SKILL_FILE_NAME,
29356
+ name: SKILL_FILE_NAME$1,
28537
29357
  body,
28538
29358
  frontmatter: { ...frontmatter }
28539
29359
  },
@@ -28557,7 +29377,7 @@ var ReplitSkill = class ReplitSkill extends ToolSkill {
28557
29377
  validate() {
28558
29378
  if (!this.mainFile) return {
28559
29379
  success: false,
28560
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
29380
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
28561
29381
  };
28562
29382
  const result = ReplitSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
28563
29383
  if (!result.success) return {
@@ -28625,7 +29445,7 @@ var ReplitSkill = class ReplitSkill extends ToolSkill {
28625
29445
  const result = ReplitSkillFrontmatterSchema.safeParse(loaded.frontmatter);
28626
29446
  if (!result.success) {
28627
29447
  const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
28628
- throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
29448
+ throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
28629
29449
  }
28630
29450
  return new ReplitSkill({
28631
29451
  outputRoot: loaded.outputRoot,
@@ -28672,7 +29492,7 @@ var RooSkill = class RooSkill extends ToolSkill {
28672
29492
  relativeDirPath,
28673
29493
  dirName,
28674
29494
  mainFile: {
28675
- name: SKILL_FILE_NAME,
29495
+ name: SKILL_FILE_NAME$1,
28676
29496
  body,
28677
29497
  frontmatter: { ...frontmatter }
28678
29498
  },
@@ -28696,7 +29516,7 @@ var RooSkill = class RooSkill extends ToolSkill {
28696
29516
  validate() {
28697
29517
  if (!this.mainFile) return {
28698
29518
  success: false,
28699
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
29519
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
28700
29520
  };
28701
29521
  const result = RooSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
28702
29522
  if (!result.success) return {
@@ -28760,10 +29580,10 @@ var RooSkill = class RooSkill extends ToolSkill {
28760
29580
  const result = RooSkillFrontmatterSchema.safeParse(loaded.frontmatter);
28761
29581
  if (!result.success) {
28762
29582
  const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
28763
- throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
29583
+ throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
28764
29584
  }
28765
29585
  if (result.data.name !== loaded.dirName) {
28766
- const skillFilePath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME);
29586
+ const skillFilePath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME$1);
28767
29587
  throw new Error(`Frontmatter name (${result.data.name}) must match directory name (${loaded.dirName}) in ${skillFilePath}`);
28768
29588
  }
28769
29589
  return new RooSkill({
@@ -28980,7 +29800,7 @@ var VibeSkill = class VibeSkill extends ToolSkill {
28980
29800
  relativeDirPath,
28981
29801
  dirName,
28982
29802
  mainFile: {
28983
- name: SKILL_FILE_NAME,
29803
+ name: SKILL_FILE_NAME$1,
28984
29804
  body,
28985
29805
  frontmatter: { ...frontmatter }
28986
29806
  },
@@ -29007,7 +29827,7 @@ var VibeSkill = class VibeSkill extends ToolSkill {
29007
29827
  validate() {
29008
29828
  if (!this.mainFile) return {
29009
29829
  success: false,
29010
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
29830
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
29011
29831
  };
29012
29832
  const result = VibeSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
29013
29833
  if (!result.success) return {
@@ -29071,7 +29891,7 @@ var VibeSkill = class VibeSkill extends ToolSkill {
29071
29891
  const result = VibeSkillFrontmatterSchema.safeParse(loaded.frontmatter);
29072
29892
  if (!result.success) {
29073
29893
  const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
29074
- throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
29894
+ throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
29075
29895
  }
29076
29896
  return new VibeSkill({
29077
29897
  outputRoot: loaded.outputRoot,
@@ -29127,7 +29947,7 @@ var WarpSkill = class WarpSkill extends ToolSkill {
29127
29947
  relativeDirPath,
29128
29948
  dirName,
29129
29949
  mainFile: {
29130
- name: SKILL_FILE_NAME,
29950
+ name: SKILL_FILE_NAME$1,
29131
29951
  body,
29132
29952
  frontmatter: { ...frontmatter }
29133
29953
  },
@@ -29151,7 +29971,7 @@ var WarpSkill = class WarpSkill extends ToolSkill {
29151
29971
  validate() {
29152
29972
  if (this.mainFile === void 0) return {
29153
29973
  success: false,
29154
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
29974
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
29155
29975
  };
29156
29976
  const result = WarpSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
29157
29977
  if (!result.success) return {
@@ -29211,7 +30031,7 @@ var WarpSkill = class WarpSkill extends ToolSkill {
29211
30031
  const result = WarpSkillFrontmatterSchema.safeParse(loaded.frontmatter);
29212
30032
  if (!result.success) {
29213
30033
  const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
29214
- throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
30034
+ throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
29215
30035
  }
29216
30036
  return new WarpSkill({
29217
30037
  outputRoot: loaded.outputRoot,
@@ -29259,7 +30079,7 @@ var ZedSkill = class ZedSkill extends ToolSkill {
29259
30079
  relativeDirPath,
29260
30080
  dirName,
29261
30081
  mainFile: {
29262
- name: SKILL_FILE_NAME,
30082
+ name: SKILL_FILE_NAME$1,
29263
30083
  body,
29264
30084
  frontmatter: { ...frontmatter }
29265
30085
  },
@@ -29283,7 +30103,7 @@ var ZedSkill = class ZedSkill extends ToolSkill {
29283
30103
  validate() {
29284
30104
  if (!this.mainFile) return {
29285
30105
  success: false,
29286
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
30106
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
29287
30107
  };
29288
30108
  const result = ZedSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
29289
30109
  if (!result.success) return {
@@ -29352,7 +30172,7 @@ var ZedSkill = class ZedSkill extends ToolSkill {
29352
30172
  const result = ZedSkillFrontmatterSchema.safeParse(loaded.frontmatter);
29353
30173
  if (!result.success) {
29354
30174
  const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
29355
- throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
30175
+ throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
29356
30176
  }
29357
30177
  return new ZedSkill({
29358
30178
  outputRoot: loaded.outputRoot,
@@ -29789,6 +30609,12 @@ var SkillsProcessor = class extends DirFeatureProcessor {
29789
30609
  for (const dirPath of dirPaths) {
29790
30610
  const dirName = (0, node_path.basename)(dirPath);
29791
30611
  if (seenDirNames.has(dirName)) continue;
30612
+ if (factory.class.isDirOwned && !await factory.class.isDirOwned({
30613
+ outputRoot: this.outputRoot,
30614
+ relativeDirPath: root,
30615
+ dirName,
30616
+ inputRoot: this.inputRoot
30617
+ })) continue;
29792
30618
  seenDirNames.add(dirName);
29793
30619
  loadEntries.push({
29794
30620
  root,
@@ -29815,6 +30641,12 @@ var SkillsProcessor = class extends DirFeatureProcessor {
29815
30641
  const dirPaths = await findFilesByGlobs((0, node_path.join)(skillsDirPath, "*"), { type: "dir" });
29816
30642
  for (const dirPath of dirPaths) {
29817
30643
  const dirName = (0, node_path.basename)(dirPath);
30644
+ if (factory.class.isDirOwned && !await factory.class.isDirOwned({
30645
+ outputRoot: this.outputRoot,
30646
+ relativeDirPath: root,
30647
+ dirName,
30648
+ inputRoot: this.inputRoot
30649
+ })) continue;
29818
30650
  const toolSkill = factory.class.forDeletion({
29819
30651
  outputRoot: this.outputRoot,
29820
30652
  relativeDirPath: root,
@@ -33114,6 +33946,188 @@ var OpenCodeSubagent = class OpenCodeSubagent extends OpenCodeStyleSubagent {
33114
33946
  }
33115
33947
  };
33116
33948
  //#endregion
33949
+ //#region src/features/subagents/reasonix-subagent.ts
33950
+ const ReasonixSubagentFrontmatterSchema = zod_mini.z.looseObject({
33951
+ name: zod_mini.z.string(),
33952
+ description: zod_mini.z.optional(zod_mini.z.string()),
33953
+ invocation: zod_mini.z.optional(zod_mini.z.string()),
33954
+ runAs: zod_mini.z.optional(zod_mini.z.string()),
33955
+ model: zod_mini.z.optional(zod_mini.z.string()),
33956
+ effort: zod_mini.z.optional(zod_mini.z.string()),
33957
+ "allowed-tools": zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
33958
+ color: zod_mini.z.optional(zod_mini.z.string())
33959
+ });
33960
+ /**
33961
+ * Represents a DeepSeek-Reasonix subagent profile.
33962
+ *
33963
+ * Reasonix discovers subagents as directory-layout Skills (`<name>/SKILL.md`)
33964
+ * under `.reasonix/skills/` (project) and `~/.reasonix/skills/` (global); the
33965
+ * global scope is served by the processor supplying the home directory as
33966
+ * outputRoot, so the relative directory path is identical for both scopes. A
33967
+ * subagent is a Skill whose frontmatter declares `invocation: manual` and
33968
+ * `runAs: subagent`.
33969
+ *
33970
+ * @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/SUBAGENT_PROFILES.md
33971
+ */
33972
+ var ReasonixSubagent = class ReasonixSubagent extends ToolSubagent {
33973
+ frontmatter;
33974
+ body;
33975
+ constructor({ frontmatter, body, fileContent, ...rest }) {
33976
+ if (rest.validate !== false) {
33977
+ const result = ReasonixSubagentFrontmatterSchema.safeParse(frontmatter);
33978
+ if (!result.success) throw new Error(`Invalid frontmatter in ${(0, node_path.join)(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
33979
+ }
33980
+ super({
33981
+ ...rest,
33982
+ fileContent: fileContent ?? stringifyFrontmatter(body, frontmatter)
33983
+ });
33984
+ this.frontmatter = frontmatter;
33985
+ this.body = body;
33986
+ }
33987
+ static getSettablePaths({ global: _global = false } = {}) {
33988
+ return { relativeDirPath: REASONIX_SUBAGENTS_DIR_PATH };
33989
+ }
33990
+ getFrontmatter() {
33991
+ return this.frontmatter;
33992
+ }
33993
+ getBody() {
33994
+ return this.body;
33995
+ }
33996
+ toRulesyncSubagent() {
33997
+ const { name, description, invocation: _invocation, runAs: _runAs, ...restFields } = this.frontmatter;
33998
+ const reasonixSection = { ...restFields };
33999
+ const rulesyncFrontmatter = {
34000
+ targets: ["*"],
34001
+ name,
34002
+ description,
34003
+ ...Object.keys(reasonixSection).length > 0 && { reasonix: reasonixSection }
34004
+ };
34005
+ return new RulesyncSubagent({
34006
+ outputRoot: this.getOutputRoot(),
34007
+ frontmatter: rulesyncFrontmatter,
34008
+ body: this.body,
34009
+ relativeDirPath: RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH,
34010
+ relativeFilePath: `${this.getSubagentName()}.md`,
34011
+ validate: true
34012
+ });
34013
+ }
34014
+ /**
34015
+ * Derive the subagent name from this instance's relative file path.
34016
+ *
34017
+ * The tool-side layout is `<name>/SKILL.md`, so the name is the parent
34018
+ * directory of the file. If the path is unexpectedly flat (e.g. a legacy
34019
+ * `<name>.md`), fall back to the basename without extension.
34020
+ */
34021
+ getSubagentName() {
34022
+ const relativeFilePath = this.getRelativeFilePath();
34023
+ const dir = (0, node_path.dirname)(relativeFilePath);
34024
+ if (dir && dir !== ".") return (0, node_path.basename)(dir);
34025
+ return (0, node_path.basename)(relativeFilePath, (0, node_path.extname)(relativeFilePath));
34026
+ }
34027
+ static fromRulesyncSubagent({ outputRoot = process.cwd(), rulesyncSubagent, validate = true, global = false }) {
34028
+ const rulesyncFrontmatter = rulesyncSubagent.getFrontmatter();
34029
+ const reasonixSection = this.filterToolSpecificSection(rulesyncFrontmatter.reasonix ?? {}, ["name", "description"]);
34030
+ const rawReasonixFrontmatter = {
34031
+ name: rulesyncFrontmatter.name,
34032
+ description: rulesyncFrontmatter.description,
34033
+ ...reasonixSection,
34034
+ invocation: REASONIX_SUBAGENT_INVOCATION,
34035
+ runAs: REASONIX_SUBAGENT_RUN_AS
34036
+ };
34037
+ const result = ReasonixSubagentFrontmatterSchema.safeParse(rawReasonixFrontmatter);
34038
+ if (!result.success) throw new Error(`Invalid reasonix subagent frontmatter in ${rulesyncSubagent.getRelativeFilePath()}: ${formatError(result.error)}`);
34039
+ const reasonixFrontmatter = result.data;
34040
+ const body = rulesyncSubagent.getBody();
34041
+ const fileContent = stringifyFrontmatter(body, reasonixFrontmatter);
34042
+ const paths = this.getSettablePaths({ global });
34043
+ const subagentName = (0, node_path.basename)(rulesyncSubagent.getRelativeFilePath(), (0, node_path.extname)(rulesyncSubagent.getRelativeFilePath()));
34044
+ return new ReasonixSubagent({
34045
+ outputRoot,
34046
+ frontmatter: reasonixFrontmatter,
34047
+ body,
34048
+ relativeDirPath: paths.relativeDirPath,
34049
+ relativeFilePath: (0, node_path.join)(subagentName, SKILL_FILE_NAME$1),
34050
+ fileContent,
34051
+ validate,
34052
+ global
34053
+ });
34054
+ }
34055
+ validate() {
34056
+ if (!this.frontmatter) return {
34057
+ success: true,
34058
+ error: null
34059
+ };
34060
+ const result = ReasonixSubagentFrontmatterSchema.safeParse(this.frontmatter);
34061
+ if (result.success) return {
34062
+ success: true,
34063
+ error: null
34064
+ };
34065
+ else return {
34066
+ success: false,
34067
+ error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${(0, node_path.join)(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
34068
+ };
34069
+ }
34070
+ static isTargetedByRulesyncSubagent(rulesyncSubagent) {
34071
+ return this.isTargetedByRulesyncSubagentDefault({
34072
+ rulesyncSubagent,
34073
+ toolTarget: "reasonix"
34074
+ });
34075
+ }
34076
+ static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
34077
+ const paths = this.getSettablePaths({ global });
34078
+ const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, relativeFilePath);
34079
+ const fileContent = await readFileContent(filePath);
34080
+ const { frontmatter, body: content } = parseFrontmatter(fileContent, filePath);
34081
+ const result = ReasonixSubagentFrontmatterSchema.safeParse(frontmatter);
34082
+ if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
34083
+ return new ReasonixSubagent({
34084
+ outputRoot,
34085
+ relativeDirPath: paths.relativeDirPath,
34086
+ relativeFilePath,
34087
+ frontmatter: result.data,
34088
+ body: content.trim(),
34089
+ fileContent,
34090
+ validate,
34091
+ global
34092
+ });
34093
+ }
34094
+ /**
34095
+ * Whether the SKILL.md at the given path is a subagent profile.
34096
+ *
34097
+ * `.reasonix/skills/` is shared with the skills feature: a regular skill and
34098
+ * a subagent profile differ only by their frontmatter markers. Only files
34099
+ * carrying `runAs: subagent` belong to this feature, so regular skills are
34100
+ * neither imported as subagents nor deleted as orphans by the subagents
34101
+ * feature. `runAs` alone is checked (not `invocation`) because it is the
34102
+ * marker that switches the execution mode; generation always emits both.
34103
+ * Unreadable or unparsable files are treated as not owned, erring on the
34104
+ * side of leaving foreign files untouched.
34105
+ */
34106
+ static async isFileOwned({ outputRoot, relativeDirPath, relativeFilePath }) {
34107
+ const filePath = (0, node_path.join)(outputRoot, relativeDirPath, relativeFilePath);
34108
+ try {
34109
+ const { frontmatter } = parseFrontmatter(await readFileContent(filePath), filePath);
34110
+ return frontmatter["runAs"] === REASONIX_SUBAGENT_RUN_AS;
34111
+ } catch {
34112
+ return false;
34113
+ }
34114
+ }
34115
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
34116
+ return new ReasonixSubagent({
34117
+ outputRoot,
34118
+ relativeDirPath,
34119
+ relativeFilePath,
34120
+ frontmatter: {
34121
+ name: "",
34122
+ description: ""
34123
+ },
34124
+ body: "",
34125
+ fileContent: "",
34126
+ validate: false
34127
+ });
34128
+ }
34129
+ };
34130
+ //#endregion
33117
34131
  //#region src/features/subagents/roo-subagent.ts
33118
34132
  /**
33119
34133
  * Default tool groups assigned to a generated mode when the rulesync source
@@ -33748,6 +34762,14 @@ const toolSubagentFactories = /* @__PURE__ */ new Map([
33748
34762
  filePattern: "*.md"
33749
34763
  }
33750
34764
  }],
34765
+ ["reasonix", {
34766
+ class: ReasonixSubagent,
34767
+ meta: {
34768
+ supportsSimulated: false,
34769
+ supportsGlobal: true,
34770
+ filePattern: (0, node_path.join)("*", "SKILL.md")
34771
+ }
34772
+ }],
33751
34773
  ["roo", {
33752
34774
  class: RooSubagent,
33753
34775
  meta: {
@@ -33900,8 +34922,17 @@ var SubagentsProcessor = class extends FeatureProcessor {
33900
34922
  const baseDir = (0, node_path.join)(this.outputRoot, dirPath);
33901
34923
  const subagentFilePaths = await findFilesByGlobs((0, node_path.join)(baseDir, factory.meta.filePattern));
33902
34924
  const toRelativeFilePath = (path) => (0, node_path.relative)(baseDir, path);
34925
+ let ownedFilePaths = subagentFilePaths;
34926
+ if (factory.class.isFileOwned) {
34927
+ const ownership = await Promise.all(subagentFilePaths.map((path) => factory.class.isFileOwned({
34928
+ outputRoot: this.outputRoot,
34929
+ relativeDirPath: dirPath,
34930
+ relativeFilePath: toRelativeFilePath(path)
34931
+ })));
34932
+ ownedFilePaths = subagentFilePaths.filter((_, index) => ownership[index]);
34933
+ }
33903
34934
  if (forDeletion) {
33904
- toolSubagents.push(...subagentFilePaths.map((path) => factory.class.forDeletion({
34935
+ toolSubagents.push(...ownedFilePaths.map((path) => factory.class.forDeletion({
33905
34936
  outputRoot: this.outputRoot,
33906
34937
  relativeDirPath: dirPath,
33907
34938
  relativeFilePath: toRelativeFilePath(path),
@@ -33909,7 +34940,7 @@ var SubagentsProcessor = class extends FeatureProcessor {
33909
34940
  })).filter((subagent) => subagent.isDeletable()));
33910
34941
  continue;
33911
34942
  }
33912
- const loaded = await Promise.all(subagentFilePaths.map((path) => factory.class.fromFile({
34943
+ const loaded = await Promise.all(ownedFilePaths.map((path) => factory.class.fromFile({
33913
34944
  outputRoot: this.outputRoot,
33914
34945
  relativeDirPath: dirPath,
33915
34946
  relativeFilePath: toRelativeFilePath(path),
@@ -34021,6 +35052,7 @@ const RulesyncRuleFrontmatterSchema = zod_mini.z.object({
34021
35052
  name: zod_mini.z.optional(zod_mini.z.string()),
34022
35053
  description: zod_mini.z.optional(zod_mini.z.string())
34023
35054
  })),
35055
+ pi: zod_mini.z.optional(zod_mini.z.looseObject({ systemPrompt: zod_mini.z.optional(zod_mini.z.enum(["append"])) })),
34024
35056
  takt: zod_mini.z.optional(zod_mini.z.looseObject({
34025
35057
  name: zod_mini.z.optional(zod_mini.z.string()),
34026
35058
  extends: zod_mini.z.optional(zod_mini.z.string()),
@@ -34184,6 +35216,16 @@ var ToolRule = class extends ToolFile {
34184
35216
  isRoot() {
34185
35217
  return this.root;
34186
35218
  }
35219
+ /**
35220
+ * Whether this rule must be left out of the root rule's reference/MCP
35221
+ * instruction listings even though it is a non-root survivor. Used by files
35222
+ * the tool loads through its own mechanism (e.g. Pi's `APPEND_SYSTEM.md`,
35223
+ * which Pi appends to the system prompt itself — referencing it from
35224
+ * `AGENTS.md` would double-load the content).
35225
+ */
35226
+ isExcludedFromRootReferences() {
35227
+ return false;
35228
+ }
34187
35229
  getDescription() {
34188
35230
  return this.description;
34189
35231
  }
@@ -36530,6 +37572,15 @@ var FactorydroidRule = class FactorydroidRule extends ToolRule {
36530
37572
  * is `undefined`). This mirrors the grokcli, warp, and deepagents targets.
36531
37573
  *
36532
37574
  * Goose uses plain markdown files (.goosehints) without frontmatter.
37575
+ *
37576
+ * Global scope emits only `~/.config/goose/.goosehints`. Goose v1.41.0 (PR
37577
+ * block/goose#9736) additionally loads the vendor-neutral
37578
+ * `~/.agents/AGENTS.md` alongside the config-dir hints, but rulesync
37579
+ * deliberately does not emit that shared path from the goose target: the
37580
+ * config-dir hints remain fully loaded (no capability loss), and the
37581
+ * cross-tool `~/.agents/AGENTS.md` is already written by targets that own it
37582
+ * (e.g. cline in global mode) — a second writer would need cross-target
37583
+ * shared-file coordination for zero gain (decision recorded in issue #2207).
36533
37584
  */
36534
37585
  var GooseRule = class GooseRule extends ToolRule {
36535
37586
  static getSettablePaths({ global } = {}) {
@@ -36731,14 +37782,21 @@ var HermesagentRule = class HermesagentRule extends ToolRule {
36731
37782
  /**
36732
37783
  * Rule generator for JetBrains Junie AI coding agent
36733
37784
  *
36734
- * Generates `.junie/AGENTS.md` files based on rulesync rule content. `.junie/AGENTS.md`
36735
- * is the preferred guideline file in current Junie; the legacy `.junie/guidelines.md`
36736
- * is still read by Junie and is accepted as an import fallback, but generation always
36737
- * targets `.junie/AGENTS.md`. Junie uses plain markdown without frontmatter requirements.
36738
- *
36739
- * Global (user) scope writes a single `~/.junie/AGENTS.md` file. Junie merges these
36740
- * user-scope guidelines with the project `.junie/AGENTS.md` (project takes priority on
36741
- * conflicts); memory files (`.junie/memories/`) remain project-scoped only.
37785
+ * Generates `.junie/AGENTS.md` files based on rulesync rule content. Junie CLI
37786
+ * resolves project guidelines **first-match-wins**: `.junie/AGENTS.md` → root
37787
+ * `AGENTS.md` legacy `.junie/guidelines.md` / `.junie/guidelines/`. Only the
37788
+ * first match is loaded, it documents no `@`-reference or file-inclusion
37789
+ * mechanism, and no `.junie/memories/` read path exists — so non-root rules
37790
+ * are folded into the single root `.junie/AGENTS.md` by the RulesProcessor
37791
+ * (`nonRoot` is `undefined`, mirroring the grokcli / warp / deepagents
37792
+ * targets; decision recorded in issue #2211). The legacy
37793
+ * `.junie/guidelines.md` is still accepted as an import fallback, but
37794
+ * generation always targets `.junie/AGENTS.md`. Junie uses plain markdown
37795
+ * without frontmatter requirements.
37796
+ *
37797
+ * Global (user) scope writes a single `~/.junie/AGENTS.md` file. Junie merges
37798
+ * these user-scope guidelines with the project guidelines (both are included
37799
+ * and marked clearly).
36742
37800
  *
36743
37801
  * @see https://junie.jetbrains.com/docs/junie-ide-plugin.html
36744
37802
  * @see https://junie.jetbrains.com/docs/guidelines-and-memory.html
@@ -36757,15 +37815,12 @@ var JunieRule = class JunieRule extends ToolRule {
36757
37815
  alternativeRoots: [{
36758
37816
  relativeDirPath: buildToolPath(JUNIE_DIR, ".", excludeToolDir),
36759
37817
  relativeFilePath: JUNIE_LEGACY_RULE_FILE_NAME
36760
- }],
36761
- nonRoot: { relativeDirPath: buildToolPath(JUNIE_DIR, "memories", excludeToolDir) }
37818
+ }]
36762
37819
  };
36763
37820
  }
36764
37821
  /**
36765
37822
  * Determines whether a given relative file path refers to a root guideline file.
36766
37823
  * The preferred file is `AGENTS.md`; the legacy `guidelines.md` is still accepted.
36767
- * Memory files live under `.junie/memories/` and are passed in as bare filenames
36768
- * (e.g. `memo.md`), so a top-level `AGENTS.md`/`guidelines.md` is the root entry.
36769
37824
  */
36770
37825
  static isRootRelativeFilePath(relativeFilePath) {
36771
37826
  return relativeFilePath === "AGENTS.md" || relativeFilePath === "guidelines.md";
@@ -36784,10 +37839,7 @@ var JunieRule = class JunieRule extends ToolRule {
36784
37839
  root: true
36785
37840
  });
36786
37841
  }
36787
- const isRoot = JunieRule.isRootRelativeFilePath(relativeFilePath);
36788
- const settablePaths = this.getSettablePaths();
36789
- if (!settablePaths.nonRoot) throw new Error("JunieRule project settable paths must include a nonRoot path");
36790
- const relativeDirPath = isRoot ? settablePaths.root.relativeDirPath : settablePaths.nonRoot.relativeDirPath;
37842
+ const relativeDirPath = this.getSettablePaths().root.relativeDirPath;
36791
37843
  const fileContent = await readFileContent((0, node_path.join)(outputRoot, (0, node_path.join)(relativeDirPath, relativeFilePath)));
36792
37844
  return new JunieRule({
36793
37845
  outputRoot,
@@ -36795,7 +37847,7 @@ var JunieRule = class JunieRule extends ToolRule {
36795
37847
  relativeFilePath,
36796
37848
  fileContent,
36797
37849
  validate,
36798
- root: isRoot
37850
+ root: JunieRule.isRootRelativeFilePath(relativeFilePath)
36799
37851
  });
36800
37852
  }
36801
37853
  static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
@@ -36810,13 +37862,15 @@ var JunieRule = class JunieRule extends ToolRule {
36810
37862
  rootPath: paths.root
36811
37863
  }));
36812
37864
  }
36813
- return new JunieRule(this.buildToolRuleParamsDefault({
37865
+ const { root } = this.getSettablePaths();
37866
+ return new JunieRule({
36814
37867
  outputRoot,
36815
- rulesyncRule,
37868
+ relativeDirPath: root.relativeDirPath,
37869
+ relativeFilePath: root.relativeFilePath,
37870
+ fileContent: rulesyncRule.getBody(),
36816
37871
  validate,
36817
- rootPath: this.getSettablePaths().root,
36818
- nonRootPath: this.getSettablePaths().nonRoot
36819
- }));
37872
+ root: rulesyncRule.getFrontmatter().root ?? false
37873
+ });
36820
37874
  }
36821
37875
  toRulesyncRule() {
36822
37876
  return this.toRulesyncRuleDefault();
@@ -37219,31 +38273,68 @@ var OpenCodeRule = class OpenCodeRule extends ToolRule {
37219
38273
  * RulesProcessor (there is no separate non-root output location — `nonRoot` is
37220
38274
  * `undefined`). This mirrors the codexcli, grokcli, warp, and deepagents targets.
37221
38275
  *
37222
- * Pi also loads two system-prompt instruction files that rulesync does NOT emit:
38276
+ * Pi also loads two system-prompt instruction files. `.pi/APPEND_SYSTEM.md`
38277
+ * (global `~/.pi/agent/APPEND_SYSTEM.md`) *appends* to the default system prompt,
38278
+ * and rulesync emits it from any rule that opts in via the `pi.systemPrompt:
38279
+ * append` frontmatter block: those bodies are routed here instead of being folded
38280
+ * into `AGENTS.md`, and multiple opted-in rules concatenate in source order.
37223
38281
  * `.pi/SYSTEM.md` (global `~/.pi/agent/SYSTEM.md`) *replaces* the default system
37224
- * prompt entirely, and `.pi/APPEND_SYSTEM.md` (global
37225
- * `~/.pi/agent/APPEND_SYSTEM.md`) *appends* to it. rulesync's rules model only
37226
- * routes a designated `root` rule to a single context file and has no convention
37227
- * for marking a rule as "replace" vs "append" the system prompt, so these
37228
- * surfaces are documented in docs/reference/file-formats.md and left to be
37229
- * authored by hand rather than mapped to a speculative new frontmatter flag.
38282
+ * prompt entirely which silently disables Pi's built-in tool instructions — so
38283
+ * rulesync deliberately never emits it and leaves it to be authored by hand.
38284
+ * See docs/reference/file-formats.md.
37230
38285
  */
37231
38286
  var PiRule = class PiRule extends ToolRule {
37232
- constructor({ fileContent, root, ...rest }) {
38287
+ appendSystemPrompt;
38288
+ constructor({ fileContent, root, appendSystemPrompt = false, ...rest }) {
37233
38289
  super({
37234
38290
  ...rest,
37235
38291
  fileContent,
37236
38292
  root: root ?? false
37237
38293
  });
38294
+ this.appendSystemPrompt = appendSystemPrompt;
37238
38295
  }
37239
38296
  static getSettablePaths({ global = false, excludeToolDir } = {}) {
37240
- return { root: {
37241
- relativeDirPath: global ? buildToolPath(".pi", "agent", excludeToolDir) : ".",
37242
- relativeFilePath: PI_RULE_FILE_NAME
37243
- } };
38297
+ return {
38298
+ root: {
38299
+ relativeDirPath: global ? buildToolPath(".pi", "agent", excludeToolDir) : ".",
38300
+ relativeFilePath: PI_RULE_FILE_NAME
38301
+ },
38302
+ appendSystemPrompt: {
38303
+ relativeDirPath: global ? buildToolPath(".pi", "agent", excludeToolDir) : ".pi",
38304
+ relativeFilePath: PI_APPEND_SYSTEM_FILE_NAME
38305
+ }
38306
+ };
37244
38307
  }
37245
- static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, validate = true, global = false }) {
37246
- const { root } = this.getSettablePaths({ global });
38308
+ /**
38309
+ * Extra fixed files this tool manages beyond the root rule. The
38310
+ * RulesProcessor enumerates these for import and deletion so a stale
38311
+ * `APPEND_SYSTEM.md` is cleaned up once no rule opts in anymore.
38312
+ */
38313
+ static getExtraFixedFiles({ global = false } = {}) {
38314
+ return [this.getSettablePaths({ global }).appendSystemPrompt];
38315
+ }
38316
+ /**
38317
+ * Pi appends `APPEND_SYSTEM.md` to the system prompt itself, so listing it in
38318
+ * the root rule's reference section (toon/explicit discovery modes) would
38319
+ * double-load the content.
38320
+ */
38321
+ isExcludedFromRootReferences() {
38322
+ return this.appendSystemPrompt;
38323
+ }
38324
+ static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
38325
+ const { root, appendSystemPrompt } = this.getSettablePaths({ global });
38326
+ if (relativeFilePath === "APPEND_SYSTEM.md") {
38327
+ const fileContent = await readFileContent((0, node_path.join)(outputRoot, (0, node_path.join)(appendSystemPrompt.relativeDirPath, appendSystemPrompt.relativeFilePath)));
38328
+ return new PiRule({
38329
+ outputRoot,
38330
+ relativeDirPath: appendSystemPrompt.relativeDirPath,
38331
+ relativeFilePath: appendSystemPrompt.relativeFilePath,
38332
+ fileContent,
38333
+ validate,
38334
+ root: false,
38335
+ appendSystemPrompt: true
38336
+ });
38337
+ }
37247
38338
  const fileContent = await readFileContent((0, node_path.join)(outputRoot, (0, node_path.join)(root.relativeDirPath, root.relativeFilePath)));
37248
38339
  return new PiRule({
37249
38340
  outputRoot,
@@ -37255,8 +38346,18 @@ var PiRule = class PiRule extends ToolRule {
37255
38346
  });
37256
38347
  }
37257
38348
  static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
37258
- const { root } = this.getSettablePaths({ global });
37259
- const isRoot = rulesyncRule.getFrontmatter().root ?? false;
38349
+ const { root, appendSystemPrompt } = this.getSettablePaths({ global });
38350
+ const frontmatter = rulesyncRule.getFrontmatter();
38351
+ if (!frontmatter.root && frontmatter.pi?.systemPrompt === "append") return new PiRule({
38352
+ outputRoot,
38353
+ relativeDirPath: appendSystemPrompt.relativeDirPath,
38354
+ relativeFilePath: appendSystemPrompt.relativeFilePath,
38355
+ fileContent: rulesyncRule.getBody(),
38356
+ validate,
38357
+ root: false,
38358
+ appendSystemPrompt: true
38359
+ });
38360
+ const isRoot = frontmatter.root ?? false;
37260
38361
  return new PiRule({
37261
38362
  outputRoot,
37262
38363
  relativeDirPath: root.relativeDirPath,
@@ -37267,6 +38368,17 @@ var PiRule = class PiRule extends ToolRule {
37267
38368
  });
37268
38369
  }
37269
38370
  toRulesyncRule() {
38371
+ if (this.appendSystemPrompt) return new RulesyncRule({
38372
+ outputRoot: process.cwd(),
38373
+ relativeDirPath: RULESYNC_RULES_RELATIVE_DIR_PATH,
38374
+ relativeFilePath: PI_APPEND_SYSTEM_FILE_NAME,
38375
+ frontmatter: {
38376
+ root: false,
38377
+ targets: ["pi"],
38378
+ pi: { systemPrompt: "append" }
38379
+ },
38380
+ body: this.getFileContent()
38381
+ });
37270
38382
  return this.toRulesyncRuleDefault();
37271
38383
  }
37272
38384
  validate() {
@@ -37277,6 +38389,15 @@ var PiRule = class PiRule extends ToolRule {
37277
38389
  }
37278
38390
  static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
37279
38391
  const { root } = this.getSettablePaths({ global });
38392
+ if (relativeFilePath === "APPEND_SYSTEM.md") return new PiRule({
38393
+ outputRoot,
38394
+ relativeDirPath,
38395
+ relativeFilePath,
38396
+ fileContent: "",
38397
+ validate: false,
38398
+ root: false,
38399
+ appendSystemPrompt: true
38400
+ });
37280
38401
  const isRoot = relativeFilePath === "AGENTS.md" && (relativeDirPath === "." || relativeDirPath === root.relativeDirPath);
37281
38402
  return new PiRule({
37282
38403
  outputRoot,
@@ -38436,7 +39557,8 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
38436
39557
  meta: {
38437
39558
  extension: "md",
38438
39559
  supportsGlobal: true,
38439
- ruleDiscoveryMode: "toon"
39560
+ ruleDiscoveryMode: "auto",
39561
+ foldsNonRootIntoRoot: true
38440
39562
  }
38441
39563
  }],
38442
39564
  ["kilo", {
@@ -38702,7 +39824,7 @@ var RulesProcessor = class extends FeatureProcessor {
38702
39824
  */
38703
39825
  async buildMcpInstructionFiles({ toolRules, meta }) {
38704
39826
  if (!meta.mcpInstructionsRegistrar || this.global) return [];
38705
- const instructionPaths = toolRules.filter((rule) => !rule.isRoot()).map((rule) => toPosixPath((0, node_path.join)(rule.getRelativeDirPath(), rule.getRelativeFilePath())));
39827
+ const instructionPaths = toolRules.filter((rule) => !rule.isRoot() && !rule.isExcludedFromRootReferences()).map((rule) => toPosixPath((0, node_path.join)(rule.getRelativeDirPath(), rule.getRelativeFilePath())));
38706
39828
  if (instructionPaths.length === 0) return [];
38707
39829
  return [await meta.mcpInstructionsRegistrar.fromInstructions({
38708
39830
  outputRoot: this.outputRoot,
@@ -38734,7 +39856,7 @@ var RulesProcessor = class extends FeatureProcessor {
38734
39856
  const toolRelativeDirPath = skillClass.getSettablePaths({ global: this.global }).relativeDirPath;
38735
39857
  return this.skills.filter((skill) => skillClass.isTargetedByRulesyncSkill(skill)).map((skill) => {
38736
39858
  const frontmatter = skill.getFrontmatter();
38737
- const relativePath = (0, node_path.join)(toolRelativeDirPath, skill.getDirName(), SKILL_FILE_NAME);
39859
+ const relativePath = (0, node_path.join)(toolRelativeDirPath, skill.getDirName(), SKILL_FILE_NAME$1);
38738
39860
  return {
38739
39861
  name: frontmatter.name,
38740
39862
  description: frontmatter.description,
@@ -38758,11 +39880,25 @@ var RulesProcessor = class extends FeatureProcessor {
38758
39880
  */
38759
39881
  foldNonRootRulesIntoRootRule(toolRules) {
38760
39882
  if (toolRules.length <= 1) return;
38761
- const target = toolRules.find((rule) => rule.isRoot()) ?? toolRules[0];
38762
- if (!target) return;
38763
- const mergedContent = [target, ...toolRules.filter((rule) => rule !== target)].map((rule) => rule.getFileContent().trim()).filter((content) => content.length > 0).join("\n\n");
38764
- target.setFileContent(mergedContent);
38765
- for (let i = toolRules.length - 1; i >= 0; i--) if (toolRules[i] !== target) toolRules.splice(i, 1);
39883
+ const groups = /* @__PURE__ */ new Map();
39884
+ for (const rule of toolRules) {
39885
+ const path = (0, node_path.join)(rule.getRelativeDirPath(), rule.getRelativeFilePath());
39886
+ const group = groups.get(path);
39887
+ if (group) group.push(rule);
39888
+ else groups.set(path, [rule]);
39889
+ }
39890
+ const survivors = /* @__PURE__ */ new Set();
39891
+ for (const group of groups.values()) {
39892
+ const target = group.find((rule) => rule.isRoot()) ?? group[0];
39893
+ if (!target) continue;
39894
+ const mergedContent = [target, ...group.filter((rule) => rule !== target)].map((rule) => rule.getFileContent().trim()).filter((content) => content.length > 0).join("\n\n");
39895
+ target.setFileContent(mergedContent);
39896
+ survivors.add(target);
39897
+ }
39898
+ for (let i = toolRules.length - 1; i >= 0; i--) {
39899
+ const rule = toolRules[i];
39900
+ if (rule && !survivors.has(rule)) toolRules.splice(i, 1);
39901
+ }
38766
39902
  }
38767
39903
  /**
38768
39904
  * Handle localRoot rule generation based on tool target.
@@ -38972,6 +40108,27 @@ As this project's AI coding tool, you must follow the additional conventions bel
38972
40108
  const mirrorPaths = await findFilesByGlobs(mirrorGlob);
38973
40109
  return buildDeletionRulesFromPaths(mirrorPaths);
38974
40110
  })();
40111
+ const extraFixedToolRules = await (async () => {
40112
+ const extraFiles = factory.class.getExtraFixedFiles?.({ global: this.global });
40113
+ if (!extraFiles || extraFiles.length === 0) return [];
40114
+ const filePaths = await findFilesByGlobs(extraFiles.map((file) => (0, node_path.join)(this.outputRoot, file.relativeDirPath, file.relativeFilePath)));
40115
+ if (filePaths.length === 0) return [];
40116
+ if (forDeletion) return buildDeletionRulesFromPaths(filePaths);
40117
+ return await Promise.all(filePaths.map((filePath) => {
40118
+ const relativeDirPath = resolveRelativeDirPath(filePath);
40119
+ checkPathTraversal({
40120
+ relativePath: relativeDirPath,
40121
+ intendedRootDir: this.outputRoot
40122
+ });
40123
+ return factory.class.fromFile({
40124
+ outputRoot: this.outputRoot,
40125
+ relativeDirPath,
40126
+ relativeFilePath: (0, node_path.basename)(filePath),
40127
+ global: this.global
40128
+ });
40129
+ }));
40130
+ })();
40131
+ this.logger.debug(`Found ${extraFixedToolRules.length} extra fixed tool rule files`);
38975
40132
  const nonRootToolRules = await (async () => {
38976
40133
  if (!settablePaths.nonRoot) return [];
38977
40134
  const nonRootOutputRoot = (0, node_path.join)(this.outputRoot, settablePaths.nonRoot.relativeDirPath);
@@ -39007,6 +40164,7 @@ As this project's AI coding tool, you must follow the additional conventions bel
39007
40164
  ...rootToolRules,
39008
40165
  ...localRootToolRules,
39009
40166
  ...rootMirrorDeletionRules,
40167
+ ...extraFixedToolRules,
39010
40168
  ...nonRootToolRules
39011
40169
  ];
39012
40170
  } catch (error) {
@@ -39034,7 +40192,7 @@ As this project's AI coding tool, you must follow the additional conventions bel
39034
40192
  return toolRuleFactories.get(result.data);
39035
40193
  }
39036
40194
  generateToonReferencesSection(toolRules) {
39037
- const toolRulesWithoutRoot = toolRules.filter((rule) => !rule.isRoot());
40195
+ const toolRulesWithoutRoot = toolRules.filter((rule) => !rule.isRoot() && !rule.isExcludedFromRootReferences());
39038
40196
  if (toolRulesWithoutRoot.length === 0) return "";
39039
40197
  const lines = [];
39040
40198
  lines.push("Please also reference the following rules as needed. The list below is provided in TOON format, and `@` stands for the project root directory.");
@@ -39050,7 +40208,7 @@ As this project's AI coding tool, you must follow the additional conventions bel
39050
40208
  return lines.join("\n") + "\n\n";
39051
40209
  }
39052
40210
  generateReferencesSection(toolRules) {
39053
- const toolRulesWithoutRoot = toolRules.filter((rule) => !rule.isRoot());
40211
+ const toolRulesWithoutRoot = toolRules.filter((rule) => !rule.isRoot() && !rule.isExcludedFromRootReferences());
39054
40212
  if (toolRulesWithoutRoot.length === 0) return "";
39055
40213
  const lines = [];
39056
40214
  lines.push("Please also reference the following rules as needed:");
@@ -39071,7 +40229,7 @@ As this project's AI coding tool, you must follow the additional conventions bel
39071
40229
  */
39072
40230
  async function convertFromTool(params) {
39073
40231
  const ctx = params;
39074
- const [rulesCount, ignoreCount, mcpCount, commandsCount, subagentsCount, skillsCount, hooksCount, permissionsCount] = [
40232
+ const [rulesCount, ignoreCount, mcpCount, commandsCount, subagentsCount, skillsCount, hooksCount, permissionsCount, checksCount] = [
39075
40233
  await runFeatureConvert(ctx, buildRulesStrategy(ctx)),
39076
40234
  await runFeatureConvert(ctx, buildIgnoreStrategy(ctx)),
39077
40235
  await runFeatureConvert(ctx, buildMcpStrategy(ctx)),
@@ -39079,7 +40237,8 @@ async function convertFromTool(params) {
39079
40237
  await runFeatureConvert(ctx, buildSubagentsStrategy(ctx)),
39080
40238
  await runFeatureConvert(ctx, buildSkillsStrategy(ctx)),
39081
40239
  await runFeatureConvert(ctx, buildHooksStrategy(ctx)),
39082
- await runFeatureConvert(ctx, buildPermissionsStrategy(ctx))
40240
+ await runFeatureConvert(ctx, buildPermissionsStrategy(ctx)),
40241
+ await runFeatureConvert(ctx, buildChecksStrategy(ctx))
39083
40242
  ];
39084
40243
  return {
39085
40244
  rulesCount,
@@ -39089,7 +40248,8 @@ async function convertFromTool(params) {
39089
40248
  subagentsCount,
39090
40249
  skillsCount,
39091
40250
  hooksCount,
39092
- permissionsCount
40251
+ permissionsCount,
40252
+ checksCount
39093
40253
  };
39094
40254
  }
39095
40255
  async function runFeatureConvert(ctx, strategy) {
@@ -39322,6 +40482,27 @@ function buildPermissionsStrategy(ctx) {
39322
40482
  write: (p, files) => p.writeAiFiles(files)
39323
40483
  };
39324
40484
  }
40485
+ function buildChecksStrategy(ctx) {
40486
+ const { config, logger } = ctx;
40487
+ const global = config.getGlobal();
40488
+ const outputRoot = getOutputRoot(config);
40489
+ return {
40490
+ feature: "checks",
40491
+ itemLabel: "check file(s)",
40492
+ allTargets: ChecksProcessor.getToolTargets({ global }),
40493
+ createProcessor: ({ toolTarget, dryRun }) => new ChecksProcessor({
40494
+ outputRoot,
40495
+ toolTarget,
40496
+ global,
40497
+ dryRun,
40498
+ logger
40499
+ }),
40500
+ loadSource: (p) => p.loadToolFiles(),
40501
+ toRulesync: (p, files) => p.convertToolFilesToRulesyncFiles(files),
40502
+ fromRulesync: (p, files) => p.convertRulesyncFilesToToolFiles(files),
40503
+ write: (p, files) => p.writeAiFiles(files)
40504
+ };
40505
+ }
39325
40506
  //#endregion
39326
40507
  //#region src/types/processor-registry.ts
39327
40508
  const PROCESSOR_REGISTRY = [
@@ -39372,6 +40553,12 @@ const PROCESSOR_REGISTRY = [
39372
40553
  processor: PermissionsProcessor,
39373
40554
  schema: PermissionsProcessorToolTargetSchema,
39374
40555
  factory: toolPermissionsFactories
40556
+ },
40557
+ {
40558
+ feature: "checks",
40559
+ processor: ChecksProcessor,
40560
+ schema: ChecksProcessorToolTargetSchema,
40561
+ factory: toolCheckFactories
39375
40562
  }
39376
40563
  ];
39377
40564
  const getProcessorRegistryEntry = (feature) => {
@@ -39721,6 +40908,7 @@ const GENERATION_STEP_GRAPH = [
39721
40908
  id: "permissions",
39722
40909
  ...sharedWriteMeta("permissions")
39723
40910
  },
40911
+ { id: "checks" },
39724
40912
  {
39725
40913
  id: "rules",
39726
40914
  ...sharedWriteMeta("rules"),
@@ -39728,11 +40916,51 @@ const GENERATION_STEP_GRAPH = [
39728
40916
  }
39729
40917
  ];
39730
40918
  /**
40919
+ * Warn when a rulesync skill and a rulesync subagent share a name for a tool
40920
+ * that emits both features into the same directory (e.g. Reasonix, where both
40921
+ * write `<name>/SKILL.md` under `.reasonix/skills/`). The colliding outputs
40922
+ * target the same on-disk file, so whichever generation step runs later
40923
+ * silently overwrites the other's file.
40924
+ */
40925
+ async function warnSkillSubagentNameCollisions(params) {
40926
+ const { config, logger } = params;
40927
+ const global = config.getGlobal();
40928
+ for (const toolTarget of config.getTargets()) {
40929
+ const features = config.getFeatures(toolTarget);
40930
+ if (!features.includes("skills") || !features.includes("subagents")) continue;
40931
+ if (!SubagentsProcessor.getToolTargets({ global }).includes(toolTarget) || !SkillsProcessor.getToolTargets({ global }).includes(toolTarget)) continue;
40932
+ const subagentFactory = SubagentsProcessor.getFactory(toolTarget);
40933
+ const skillFactory = SkillsProcessor.getFactory(toolTarget);
40934
+ if (!subagentFactory || !skillFactory) continue;
40935
+ const subagentsDirPath = subagentFactory.class.getSettablePaths({ global }).relativeDirPath;
40936
+ if (subagentsDirPath !== skillFactory.class.getSettablePaths({ global }).relativeDirPath) continue;
40937
+ const subagentsProcessor = new SubagentsProcessor({
40938
+ inputRoot: config.getInputRoot(),
40939
+ toolTarget,
40940
+ global,
40941
+ logger
40942
+ });
40943
+ const subagentNames = new Set((await subagentsProcessor.loadRulesyncFiles()).filter((file) => file instanceof RulesyncSubagent).filter((file) => subagentFactory.class.isTargetedByRulesyncSubagent(file)).map((file) => (0, node_path.basename)(file.getRelativeFilePath(), (0, node_path.extname)(file.getRelativeFilePath()))));
40944
+ if (subagentNames.size === 0) continue;
40945
+ const skillNames = (await new SkillsProcessor({
40946
+ inputRoot: config.getInputRoot(),
40947
+ toolTarget,
40948
+ global,
40949
+ logger
40950
+ }).loadRulesyncDirs()).filter((dir) => dir instanceof RulesyncSkill).filter((skill) => skillFactory.class.isTargetedByRulesyncSkill(skill)).map((skill) => skill.getDirName());
40951
+ for (const name of skillNames) if (subagentNames.has(name)) logger.warn(`Skill "${name}" and subagent "${name}" both target '${toolTarget}' and write the same path '${(0, node_path.join)(subagentsDirPath, name)}'; the later generation step overwrites the other's output. Rename one of them or narrow their targets.`);
40952
+ }
40953
+ }
40954
+ /**
39731
40955
  * Generate configuration files for AI tools.
39732
40956
  * @throws Error if generation fails
39733
40957
  */
39734
40958
  async function generate(params) {
39735
40959
  const { config, logger } = params;
40960
+ await warnSkillSubagentNameCollisions({
40961
+ config,
40962
+ logger
40963
+ });
39736
40964
  let skillsResult;
39737
40965
  const runners = {
39738
40966
  ignore: () => generateIgnoreCore({
@@ -39766,6 +40994,10 @@ async function generate(params) {
39766
40994
  config,
39767
40995
  logger
39768
40996
  }),
40997
+ checks: () => generateChecksCore({
40998
+ config,
40999
+ logger
41000
+ }),
39769
41001
  rules: () => generateRulesCore({
39770
41002
  config,
39771
41003
  logger,
@@ -39802,6 +41034,8 @@ async function generate(params) {
39802
41034
  hooksPaths: get("hooks").paths,
39803
41035
  permissionsCount: get("permissions").count,
39804
41036
  permissionsPaths: get("permissions").paths,
41037
+ checksCount: get("checks").count,
41038
+ checksPaths: get("checks").paths,
39805
41039
  skills: skillsResult.skills,
39806
41040
  hasDiff
39807
41041
  };
@@ -40170,6 +41404,44 @@ async function generatePermissionsCore(params) {
40170
41404
  hasDiff
40171
41405
  };
40172
41406
  }
41407
+ async function generateChecksCore(params) {
41408
+ const { config, logger } = params;
41409
+ let totalCount = 0;
41410
+ const allPaths = [];
41411
+ let hasDiff = false;
41412
+ const supportedChecksTargets = ChecksProcessor.getToolTargets({ global: config.getGlobal() });
41413
+ const toolTargets = (0, es_toolkit.intersection)(config.getTargets(), supportedChecksTargets);
41414
+ warnUnsupportedTargets({
41415
+ config,
41416
+ supportedTargets: supportedChecksTargets,
41417
+ featureName: "checks",
41418
+ logger
41419
+ });
41420
+ for (const toolTarget of toolTargets) for (const outputRoot of config.getOutputRoots(toolTarget)) {
41421
+ if (!config.getFeatures(toolTarget).includes("checks")) continue;
41422
+ const processor = new ChecksProcessor({
41423
+ outputRoot,
41424
+ inputRoot: config.getInputRoot(),
41425
+ toolTarget,
41426
+ global: config.getGlobal(),
41427
+ dryRun: config.isPreviewMode(),
41428
+ logger
41429
+ });
41430
+ const result = await processFeatureWithRulesyncFiles({
41431
+ config,
41432
+ processor,
41433
+ rulesyncFiles: await processor.loadRulesyncFiles()
41434
+ });
41435
+ totalCount += result.count;
41436
+ allPaths.push(...result.paths);
41437
+ if (result.hasDiff) hasDiff = true;
41438
+ }
41439
+ return {
41440
+ count: totalCount,
41441
+ paths: allPaths,
41442
+ hasDiff
41443
+ };
41444
+ }
40173
41445
  //#endregion
40174
41446
  //#region src/lib/import.ts
40175
41447
  /**
@@ -40227,6 +41499,11 @@ async function importFromTool(params) {
40227
41499
  config,
40228
41500
  tool,
40229
41501
  logger
41502
+ }),
41503
+ checksCount: await importChecksCore({
41504
+ config,
41505
+ tool,
41506
+ logger
40230
41507
  })
40231
41508
  };
40232
41509
  }
@@ -40443,6 +41720,27 @@ async function importPermissionsCore(params) {
40443
41720
  if (config.getVerbose() && writtenCount > 0) logger.success(`Created ${writtenCount} permissions file(s)`);
40444
41721
  return writtenCount;
40445
41722
  }
41723
+ async function importChecksCore(params) {
41724
+ const { config, tool, logger } = params;
41725
+ if (!config.getFeatures(tool).includes("checks")) return 0;
41726
+ const global = config.getGlobal();
41727
+ if (!ChecksProcessor.getToolTargets({ global }).includes(tool)) return 0;
41728
+ const checksProcessor = new ChecksProcessor({
41729
+ outputRoot: config.getOutputRoots()[0] ?? ".",
41730
+ toolTarget: tool,
41731
+ global,
41732
+ logger
41733
+ });
41734
+ const toolFiles = await checksProcessor.loadToolFiles();
41735
+ if (toolFiles.length === 0) {
41736
+ logger.warn(`No check files found for ${tool}. Skipping import.`);
41737
+ return 0;
41738
+ }
41739
+ const rulesyncFiles = await checksProcessor.convertToolFilesToRulesyncFiles(toolFiles);
41740
+ const { count: writtenCount } = await checksProcessor.writeAiFiles(rulesyncFiles);
41741
+ if (config.getVerbose() && writtenCount > 0) logger.success(`Created ${writtenCount} check files`);
41742
+ return writtenCount;
41743
+ }
40446
41744
  //#endregion
40447
41745
  Object.defineProperty(exports, "ALL_FEATURES", {
40448
41746
  enumerable: true,
@@ -40504,12 +41802,24 @@ Object.defineProperty(exports, "CLIError", {
40504
41802
  return CLIError;
40505
41803
  }
40506
41804
  });
41805
+ Object.defineProperty(exports, "CODEXCLI_BASH_RULES_FILE_NAME", {
41806
+ enumerable: true,
41807
+ get: function() {
41808
+ return CODEXCLI_BASH_RULES_FILE_NAME;
41809
+ }
41810
+ });
40507
41811
  Object.defineProperty(exports, "CODEXCLI_DIR", {
40508
41812
  enumerable: true,
40509
41813
  get: function() {
40510
41814
  return CODEXCLI_DIR;
40511
41815
  }
40512
41816
  });
41817
+ Object.defineProperty(exports, "ChecksProcessor", {
41818
+ enumerable: true,
41819
+ get: function() {
41820
+ return ChecksProcessor;
41821
+ }
41822
+ });
40513
41823
  Object.defineProperty(exports, "CommandsProcessor", {
40514
41824
  enumerable: true,
40515
41825
  get: function() {
@@ -40576,6 +41886,12 @@ Object.defineProperty(exports, "RULESYNC_AIIGNORE_RELATIVE_FILE_PATH", {
40576
41886
  return RULESYNC_AIIGNORE_RELATIVE_FILE_PATH;
40577
41887
  }
40578
41888
  });
41889
+ Object.defineProperty(exports, "RULESYNC_CHECKS_RELATIVE_DIR_PATH", {
41890
+ enumerable: true,
41891
+ get: function() {
41892
+ return RULESYNC_CHECKS_RELATIVE_DIR_PATH;
41893
+ }
41894
+ });
40579
41895
  Object.defineProperty(exports, "RULESYNC_COMMANDS_RELATIVE_DIR_PATH", {
40580
41896
  enumerable: true,
40581
41897
  get: function() {
@@ -40720,6 +42036,18 @@ Object.defineProperty(exports, "RulesProcessor", {
40720
42036
  return RulesProcessor;
40721
42037
  }
40722
42038
  });
42039
+ Object.defineProperty(exports, "RulesyncCheck", {
42040
+ enumerable: true,
42041
+ get: function() {
42042
+ return RulesyncCheck;
42043
+ }
42044
+ });
42045
+ Object.defineProperty(exports, "RulesyncCheckFrontmatterSchema", {
42046
+ enumerable: true,
42047
+ get: function() {
42048
+ return RulesyncCheckFrontmatterSchema;
42049
+ }
42050
+ });
40723
42051
  Object.defineProperty(exports, "RulesyncCommand", {
40724
42052
  enumerable: true,
40725
42053
  get: function() {
@@ -40795,7 +42123,7 @@ Object.defineProperty(exports, "RulesyncSubagentFrontmatterSchema", {
40795
42123
  Object.defineProperty(exports, "SKILL_FILE_NAME", {
40796
42124
  enumerable: true,
40797
42125
  get: function() {
40798
- return SKILL_FILE_NAME;
42126
+ return SKILL_FILE_NAME$1;
40799
42127
  }
40800
42128
  });
40801
42129
  Object.defineProperty(exports, "SkillsProcessor", {