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.
@@ -12,6 +12,7 @@ import { parse as parse$1, stringify } from "smol-toml";
12
12
  import matter from "gray-matter";
13
13
  import { YAMLException, dump, load } from "js-yaml";
14
14
  import { omit } from "es-toolkit/object";
15
+ import { createHash } from "node:crypto";
15
16
  import { encode } from "@toon-format/toon";
16
17
  //#region src/types/features.ts
17
18
  const ALL_FEATURES = [
@@ -22,7 +23,8 @@ const ALL_FEATURES = [
22
23
  "commands",
23
24
  "skills",
24
25
  "hooks",
25
- "permissions"
26
+ "permissions",
27
+ "checks"
26
28
  ];
27
29
  const ALL_FEATURES_WITH_WILDCARD = [...ALL_FEATURES, "*"];
28
30
  const FeatureSchema = z.enum(ALL_FEATURES);
@@ -96,6 +98,7 @@ const RULESYNC_RELATIVE_DIR_PATH = ".rulesync";
96
98
  const RULESYNC_RULES_RELATIVE_DIR_PATH = join$1(RULESYNC_RELATIVE_DIR_PATH, "rules");
97
99
  const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$1(RULESYNC_RELATIVE_DIR_PATH, "commands");
98
100
  const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$1(RULESYNC_RELATIVE_DIR_PATH, "subagents");
101
+ const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$1(RULESYNC_RELATIVE_DIR_PATH, "checks");
99
102
  const RULESYNC_MCP_RELATIVE_FILE_PATH = join$1(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
100
103
  const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$1(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
101
104
  const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$1(RULESYNC_RELATIVE_DIR_PATH, "permissions.json");
@@ -264,6 +267,7 @@ const subagentsProcessorToolTargetTuple = [
264
267
  "kiro-ide",
265
268
  "opencode",
266
269
  "qwencode",
270
+ "reasonix",
267
271
  "roo",
268
272
  "rovodev",
269
273
  "takt",
@@ -360,6 +364,7 @@ const permissionsProcessorToolTargetTuple = [
360
364
  "warp",
361
365
  "zed"
362
366
  ];
367
+ const checksProcessorToolTargetTuple = ["amp"];
363
368
  //#endregion
364
369
  //#region src/types/tool-targets.ts
365
370
  const ALL_TOOL_TARGETS = [...new Set([
@@ -370,7 +375,8 @@ const ALL_TOOL_TARGETS = [...new Set([
370
375
  subagentsProcessorToolTargetTuple,
371
376
  skillsProcessorToolTargetTuple,
372
377
  hooksProcessorToolTargetTuple,
373
- permissionsProcessorToolTargetTuple
378
+ permissionsProcessorToolTargetTuple,
379
+ checksProcessorToolTargetTuple
374
380
  ].flat())];
375
381
  const ALL_TOOL_TARGETS_WITH_WILDCARD = [...ALL_TOOL_TARGETS, "*"];
376
382
  const ToolTargetSchema = z.enum(ALL_TOOL_TARGETS);
@@ -1444,7 +1450,7 @@ function isPlainObject$1(value) {
1444
1450
  /**
1445
1451
  * Type guard to check if a value is an array of strings.
1446
1452
  */
1447
- function isStringArray(value) {
1453
+ function isStringArray$1(value) {
1448
1454
  return Array.isArray(value) && value.every((item) => typeof item === "string");
1449
1455
  }
1450
1456
  //#endregion
@@ -1692,13 +1698,17 @@ var FeatureProcessor = class {
1692
1698
  }
1693
1699
  };
1694
1700
  //#endregion
1695
- //#region src/constants/agentsmd-paths.ts
1696
- const AGENTSMD_DIR = ".agents";
1697
- const AGENTSMD_MEMORIES_DIR_PATH = join(AGENTSMD_DIR, "memories");
1698
- const AGENTSMD_COMMANDS_DIR_PATH = join(AGENTSMD_DIR, "commands");
1699
- const AGENTSMD_SKILLS_DIR_PATH = join(AGENTSMD_DIR, "skills");
1700
- const AGENTSMD_SUBAGENTS_DIR_PATH = join(AGENTSMD_DIR, "subagents");
1701
- const AGENTSMD_RULE_FILE_NAME = "AGENTS.md";
1701
+ //#region src/constants/amp-paths.ts
1702
+ const AMP_DIR = ".amp";
1703
+ const AMP_GLOBAL_DIR = join(".config", "amp");
1704
+ const AMP_AGENTS_DIR = ".agents";
1705
+ const AMP_SKILLS_PROJECT_DIR = join(AMP_AGENTS_DIR, "skills");
1706
+ const AMP_SKILLS_GLOBAL_DIR = join(".config", "agents", "skills");
1707
+ const AMP_CHECKS_PROJECT_DIR = join(AMP_AGENTS_DIR, "checks");
1708
+ const AMP_CHECKS_GLOBAL_DIR = join(AMP_GLOBAL_DIR, "checks");
1709
+ const AMP_RULE_FILE_NAME = "AGENTS.md";
1710
+ const AMP_SETTINGS_FILE_NAME = "settings.json";
1711
+ const AMP_SETTINGS_JSONC_FILE_NAME = "settings.jsonc";
1702
1712
  //#endregion
1703
1713
  //#region src/types/ai-file.ts
1704
1714
  var AiFile = class {
@@ -1772,6 +1782,414 @@ var AiFile = class {
1772
1782
  }
1773
1783
  };
1774
1784
  //#endregion
1785
+ //#region src/types/rulesync-file.ts
1786
+ var RulesyncFile = class extends AiFile {
1787
+ static async fromFile(_params) {
1788
+ throw new Error("Please implement this method in the subclass.");
1789
+ }
1790
+ };
1791
+ //#endregion
1792
+ //#region src/features/checks/rulesync-check.ts
1793
+ const RulesyncCheckFrontmatterSchema = z.looseObject({
1794
+ targets: z._default(RulesyncTargetsSchema, ["*"]),
1795
+ description: z.optional(z.string()),
1796
+ severity: z.optional(z.enum([
1797
+ "low",
1798
+ "medium",
1799
+ "high",
1800
+ "critical"
1801
+ ])),
1802
+ tools: z.optional(z.array(z.string()))
1803
+ });
1804
+ var RulesyncCheck = class RulesyncCheck extends RulesyncFile {
1805
+ frontmatter;
1806
+ body;
1807
+ constructor({ frontmatter, body, ...rest }) {
1808
+ const parseResult = RulesyncCheckFrontmatterSchema.safeParse(frontmatter);
1809
+ if (!parseResult.success && rest.validate !== false) throw new Error(`Invalid frontmatter in ${join(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(parseResult.error)}`);
1810
+ const parsedFrontmatter = parseResult.success ? {
1811
+ ...frontmatter,
1812
+ ...parseResult.data
1813
+ } : {
1814
+ ...frontmatter,
1815
+ targets: frontmatter?.targets ?? ["*"]
1816
+ };
1817
+ super({
1818
+ ...rest,
1819
+ fileContent: stringifyFrontmatter(body, parsedFrontmatter)
1820
+ });
1821
+ this.frontmatter = parsedFrontmatter;
1822
+ this.body = body;
1823
+ }
1824
+ static getSettablePaths() {
1825
+ return { relativeDirPath: RULESYNC_CHECKS_RELATIVE_DIR_PATH };
1826
+ }
1827
+ getFrontmatter() {
1828
+ return this.frontmatter;
1829
+ }
1830
+ getBody() {
1831
+ return this.body;
1832
+ }
1833
+ validate() {
1834
+ if (!this.frontmatter) return {
1835
+ success: true,
1836
+ error: null
1837
+ };
1838
+ const result = RulesyncCheckFrontmatterSchema.safeParse(this.frontmatter);
1839
+ if (result.success) return {
1840
+ success: true,
1841
+ error: null
1842
+ };
1843
+ else return {
1844
+ success: false,
1845
+ error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${join(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
1846
+ };
1847
+ }
1848
+ static async fromFile({ outputRoot = process.cwd(), relativeFilePath }) {
1849
+ const filePath = join(outputRoot, RULESYNC_CHECKS_RELATIVE_DIR_PATH, relativeFilePath);
1850
+ const { frontmatter, body: content, hasFrontmatter } = parseFrontmatter(await readFileContent(filePath), filePath);
1851
+ if (!hasFrontmatter) throw new Error(`Missing frontmatter in ${filePath}. Rulesync files must begin with a YAML frontmatter block delimited by '---'.`);
1852
+ const result = RulesyncCheckFrontmatterSchema.safeParse(frontmatter);
1853
+ if (!result.success) throw new Error(`Invalid frontmatter in ${relativeFilePath}: ${formatError(result.error)}`);
1854
+ const filename = basename(relativeFilePath);
1855
+ return new RulesyncCheck({
1856
+ outputRoot,
1857
+ relativeDirPath: this.getSettablePaths().relativeDirPath,
1858
+ relativeFilePath: filename,
1859
+ frontmatter: result.data,
1860
+ body: content.trim()
1861
+ });
1862
+ }
1863
+ };
1864
+ //#endregion
1865
+ //#region src/types/tool-file.ts
1866
+ var ToolFile = class extends AiFile {};
1867
+ //#endregion
1868
+ //#region src/features/checks/tool-check.ts
1869
+ var ToolCheck = class extends ToolFile {
1870
+ static getSettablePaths(_options = {}) {
1871
+ throw new Error("Please implement this method in the subclass.");
1872
+ }
1873
+ static async fromFile(_params) {
1874
+ throw new Error("Please implement this method in the subclass.");
1875
+ }
1876
+ /**
1877
+ * Create a minimal instance for deletion purposes.
1878
+ * This method does not read or parse file content, making it safe to use
1879
+ * even when files have old/incompatible formats.
1880
+ */
1881
+ static forDeletion(_params) {
1882
+ throw new Error("Please implement this method in the subclass.");
1883
+ }
1884
+ static fromRulesyncCheck(_params) {
1885
+ throw new Error("Please implement this method in the subclass.");
1886
+ }
1887
+ static isTargetedByRulesyncCheck(_rulesyncCheck) {
1888
+ throw new Error("Please implement this method in the subclass.");
1889
+ }
1890
+ static isTargetedByRulesyncCheckDefault({ rulesyncCheck, toolTarget }) {
1891
+ const targets = rulesyncCheck.getFrontmatter().targets;
1892
+ if (!targets) return true;
1893
+ if (targets.includes("*")) return true;
1894
+ if (targets.includes(toolTarget)) return true;
1895
+ return false;
1896
+ }
1897
+ static filterToolSpecificSection(rawSection, excludeFields) {
1898
+ const filtered = {};
1899
+ for (const [key, value] of Object.entries(rawSection)) if (!excludeFields.includes(key)) filtered[key] = value;
1900
+ return filtered;
1901
+ }
1902
+ };
1903
+ //#endregion
1904
+ //#region src/features/checks/amp-check.ts
1905
+ const AmpCheckFrontmatterSchema = z.looseObject({
1906
+ name: z.string(),
1907
+ description: z.optional(z.string()),
1908
+ "severity-default": z.optional(z.enum([
1909
+ "low",
1910
+ "medium",
1911
+ "high",
1912
+ "critical"
1913
+ ])),
1914
+ tools: z.optional(z.array(z.string()))
1915
+ });
1916
+ /**
1917
+ * Represents an Amp code review check.
1918
+ *
1919
+ * Amp natively reads code review checks as Markdown files with YAML frontmatter,
1920
+ * scoped to the project (`.agents/checks/`) and user-wide (`~/.config/amp/checks/`).
1921
+ * Each check runs as a per-check subagent during code review.
1922
+ * @see https://ampcode.com/manual
1923
+ */
1924
+ var AmpCheck = class AmpCheck extends ToolCheck {
1925
+ frontmatter;
1926
+ body;
1927
+ constructor({ frontmatter, body, fileContent, ...rest }) {
1928
+ if (rest.validate !== false) {
1929
+ const result = AmpCheckFrontmatterSchema.safeParse(frontmatter);
1930
+ if (!result.success) throw new Error(`Invalid frontmatter in ${join(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
1931
+ }
1932
+ super({
1933
+ ...rest,
1934
+ fileContent: fileContent ?? stringifyFrontmatter(body, frontmatter)
1935
+ });
1936
+ this.frontmatter = frontmatter;
1937
+ this.body = body;
1938
+ }
1939
+ static getSettablePaths({ global = false } = {}) {
1940
+ return { relativeDirPath: global ? AMP_CHECKS_GLOBAL_DIR : AMP_CHECKS_PROJECT_DIR };
1941
+ }
1942
+ getFrontmatter() {
1943
+ return this.frontmatter;
1944
+ }
1945
+ getBody() {
1946
+ return this.body;
1947
+ }
1948
+ toRulesyncCheck() {
1949
+ const { name: _name, description, "severity-default": severityDefault, tools, ...restFields } = this.frontmatter;
1950
+ return new RulesyncCheck({
1951
+ outputRoot: ".",
1952
+ frontmatter: {
1953
+ targets: ["*"],
1954
+ ...description !== void 0 && { description },
1955
+ ...severityDefault !== void 0 && { severity: severityDefault },
1956
+ ...tools !== void 0 && { tools },
1957
+ ...restFields
1958
+ },
1959
+ body: this.body,
1960
+ relativeDirPath: RULESYNC_CHECKS_RELATIVE_DIR_PATH,
1961
+ relativeFilePath: this.getRelativeFilePath(),
1962
+ validate: true
1963
+ });
1964
+ }
1965
+ static fromRulesyncCheck({ outputRoot = process.cwd(), rulesyncCheck, validate = true, global = false }) {
1966
+ const rulesyncFrontmatter = rulesyncCheck.getFrontmatter();
1967
+ const relativeFilePath = rulesyncCheck.getRelativeFilePath();
1968
+ const name = basename(relativeFilePath, ".md");
1969
+ const { targets: _targets, description, severity, tools, ...restFields } = rulesyncFrontmatter;
1970
+ const toolTargetKeys = new Set(ALL_TOOL_TARGETS);
1971
+ const passthroughFields = Object.fromEntries(Object.entries(restFields).filter(([key]) => !toolTargetKeys.has(key) && key !== "name"));
1972
+ const ampSection = this.filterToolSpecificSection(rulesyncFrontmatter.amp ?? {}, ["name"]);
1973
+ const rawAmpFrontmatter = {
1974
+ name,
1975
+ ...description !== void 0 && { description },
1976
+ ...severity !== void 0 && { "severity-default": severity },
1977
+ ...tools !== void 0 && { tools },
1978
+ ...passthroughFields,
1979
+ ...ampSection
1980
+ };
1981
+ const result = AmpCheckFrontmatterSchema.safeParse(rawAmpFrontmatter);
1982
+ if (!result.success) throw new Error(`Invalid amp check frontmatter in ${relativeFilePath}: ${formatError(result.error)}`);
1983
+ const ampFrontmatter = result.data;
1984
+ const body = rulesyncCheck.getBody();
1985
+ const fileContent = stringifyFrontmatter(body, ampFrontmatter);
1986
+ const paths = this.getSettablePaths({ global });
1987
+ return new AmpCheck({
1988
+ outputRoot,
1989
+ frontmatter: ampFrontmatter,
1990
+ body,
1991
+ relativeDirPath: paths.relativeDirPath,
1992
+ relativeFilePath,
1993
+ fileContent,
1994
+ validate
1995
+ });
1996
+ }
1997
+ validate() {
1998
+ if (!this.frontmatter) return {
1999
+ success: true,
2000
+ error: null
2001
+ };
2002
+ const result = AmpCheckFrontmatterSchema.safeParse(this.frontmatter);
2003
+ if (result.success) return {
2004
+ success: true,
2005
+ error: null
2006
+ };
2007
+ else return {
2008
+ success: false,
2009
+ error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${join(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
2010
+ };
2011
+ }
2012
+ static isTargetedByRulesyncCheck(rulesyncCheck) {
2013
+ return this.isTargetedByRulesyncCheckDefault({
2014
+ rulesyncCheck,
2015
+ toolTarget: "amp"
2016
+ });
2017
+ }
2018
+ static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
2019
+ const paths = this.getSettablePaths({ global });
2020
+ const filePath = join(outputRoot, paths.relativeDirPath, relativeFilePath);
2021
+ const fileContent = await readFileContent(filePath);
2022
+ const { frontmatter, body: content } = parseFrontmatter(fileContent, filePath);
2023
+ const result = AmpCheckFrontmatterSchema.safeParse(frontmatter);
2024
+ if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
2025
+ return new AmpCheck({
2026
+ outputRoot,
2027
+ relativeDirPath: paths.relativeDirPath,
2028
+ relativeFilePath,
2029
+ frontmatter: result.data,
2030
+ body: content.trim(),
2031
+ fileContent,
2032
+ validate
2033
+ });
2034
+ }
2035
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
2036
+ return new AmpCheck({
2037
+ outputRoot,
2038
+ relativeDirPath,
2039
+ relativeFilePath,
2040
+ frontmatter: { name: "" },
2041
+ body: "",
2042
+ fileContent: "",
2043
+ validate: false
2044
+ });
2045
+ }
2046
+ };
2047
+ //#endregion
2048
+ //#region src/features/checks/checks-processor.ts
2049
+ const ChecksProcessorToolTargetSchema = z.enum(checksProcessorToolTargetTuple);
2050
+ /**
2051
+ * Factory Map mapping tool targets to their check factories.
2052
+ * Using Map to preserve insertion order for consistent iteration.
2053
+ */
2054
+ const toolCheckFactories = /* @__PURE__ */ new Map([["amp", {
2055
+ class: AmpCheck,
2056
+ meta: {
2057
+ supportsGlobal: true,
2058
+ filePattern: "*.md"
2059
+ }
2060
+ }]]);
2061
+ const defaultGetFactory$6 = (target) => {
2062
+ const factory = toolCheckFactories.get(target);
2063
+ if (!factory) throw new Error(`Unsupported tool target: ${target}`);
2064
+ return factory;
2065
+ };
2066
+ const allToolTargetKeys$5 = [...toolCheckFactories.keys()];
2067
+ const checksProcessorToolTargets = allToolTargetKeys$5;
2068
+ const checksProcessorToolTargetsGlobal = allToolTargetKeys$5.filter((target) => {
2069
+ return toolCheckFactories.get(target)?.meta.supportsGlobal ?? false;
2070
+ });
2071
+ var ChecksProcessor = class extends FeatureProcessor {
2072
+ toolTarget;
2073
+ global;
2074
+ getFactory;
2075
+ constructor({ outputRoot = process.cwd(), inputRoot = process.cwd(), toolTarget, global = false, getFactory = defaultGetFactory$6, dryRun = false, logger }) {
2076
+ super({
2077
+ outputRoot,
2078
+ inputRoot,
2079
+ dryRun,
2080
+ logger
2081
+ });
2082
+ const result = ChecksProcessorToolTargetSchema.safeParse(toolTarget);
2083
+ if (!result.success) throw new Error(`Invalid tool target for ChecksProcessor: ${toolTarget}. ${formatError(result.error)}`);
2084
+ this.toolTarget = result.data;
2085
+ this.global = global;
2086
+ this.getFactory = getFactory;
2087
+ }
2088
+ async convertRulesyncFilesToToolFiles(rulesyncFiles) {
2089
+ const rulesyncChecks = rulesyncFiles.filter((file) => file instanceof RulesyncCheck);
2090
+ const factory = this.getFactory(this.toolTarget);
2091
+ return rulesyncChecks.filter((rulesyncCheck) => factory.class.isTargetedByRulesyncCheck(rulesyncCheck)).map((rulesyncCheck) => factory.class.fromRulesyncCheck({
2092
+ outputRoot: this.outputRoot,
2093
+ relativeDirPath: RulesyncCheck.getSettablePaths().relativeDirPath,
2094
+ rulesyncCheck,
2095
+ global: this.global
2096
+ }));
2097
+ }
2098
+ async convertToolFilesToRulesyncFiles(toolFiles) {
2099
+ return toolFiles.filter((file) => file instanceof ToolCheck).map((toolCheck) => toolCheck.toRulesyncCheck());
2100
+ }
2101
+ /**
2102
+ * Implementation of abstract method from Processor
2103
+ * Load and parse rulesync check files from .rulesync/checks/ directory
2104
+ */
2105
+ async loadRulesyncFiles() {
2106
+ const checksDir = join(this.inputRoot, RulesyncCheck.getSettablePaths().relativeDirPath);
2107
+ if (!await directoryExists(checksDir)) {
2108
+ this.logger.debug(`Rulesync checks directory not found: ${checksDir}`);
2109
+ return [];
2110
+ }
2111
+ const mdFiles = (await listDirectoryFiles(checksDir)).filter((file) => file.endsWith(".md"));
2112
+ if (mdFiles.length === 0) {
2113
+ this.logger.debug(`No markdown files found in rulesync checks directory: ${checksDir}`);
2114
+ return [];
2115
+ }
2116
+ this.logger.debug(`Found ${mdFiles.length} check files in ${checksDir}`);
2117
+ const rulesyncChecks = [];
2118
+ for (const mdFile of mdFiles) {
2119
+ const filepath = join(checksDir, mdFile);
2120
+ try {
2121
+ const rulesyncCheck = await RulesyncCheck.fromFile({
2122
+ outputRoot: this.inputRoot,
2123
+ relativeFilePath: mdFile,
2124
+ validate: true
2125
+ });
2126
+ rulesyncChecks.push(rulesyncCheck);
2127
+ this.logger.debug(`Successfully loaded check: ${mdFile}`);
2128
+ } catch (error) {
2129
+ this.logger.warn(`Failed to load check file ${filepath}: ${formatError(error)}`);
2130
+ continue;
2131
+ }
2132
+ }
2133
+ this.logger.debug(`Successfully loaded ${rulesyncChecks.length} rulesync checks`);
2134
+ return rulesyncChecks;
2135
+ }
2136
+ /**
2137
+ * Implementation of abstract method from Processor
2138
+ * Load tool-specific check files and parse them into ToolCheck instances
2139
+ */
2140
+ async loadToolFiles({ forDeletion = false } = {}) {
2141
+ const factory = this.getFactory(this.toolTarget);
2142
+ const paths = factory.class.getSettablePaths({ global: this.global });
2143
+ const baseDir = join(this.outputRoot, paths.relativeDirPath);
2144
+ const checkFilePaths = await findFilesByGlobs(join(baseDir, factory.meta.filePattern));
2145
+ const toRelativeFilePath = (path) => relative(baseDir, path);
2146
+ if (forDeletion) return checkFilePaths.map((path) => factory.class.forDeletion({
2147
+ outputRoot: this.outputRoot,
2148
+ relativeDirPath: paths.relativeDirPath,
2149
+ relativeFilePath: toRelativeFilePath(path),
2150
+ global: this.global
2151
+ })).filter((check) => check.isDeletable());
2152
+ const loaded = await Promise.all(checkFilePaths.map((path) => factory.class.fromFile({
2153
+ outputRoot: this.outputRoot,
2154
+ relativeDirPath: paths.relativeDirPath,
2155
+ relativeFilePath: toRelativeFilePath(path),
2156
+ global: this.global
2157
+ })));
2158
+ this.logger.debug(`Successfully loaded ${loaded.length} ${this.toolTarget} checks from ${paths.relativeDirPath}`);
2159
+ return loaded;
2160
+ }
2161
+ /**
2162
+ * Implementation of abstract method from FeatureProcessor
2163
+ * Return the tool targets that this processor supports
2164
+ */
2165
+ static getToolTargets({ global = false } = {}) {
2166
+ if (global) return [...checksProcessorToolTargetsGlobal];
2167
+ return [...checksProcessorToolTargets];
2168
+ }
2169
+ static getToolTargetsSimulated() {
2170
+ return [];
2171
+ }
2172
+ /**
2173
+ * Get the factory for a specific tool target.
2174
+ * This is a static version of the internal getFactory for external use.
2175
+ * @param target - The tool target. Must be a valid ChecksProcessorToolTarget.
2176
+ * @returns The factory for the target, or undefined if not found.
2177
+ */
2178
+ static getFactory(target) {
2179
+ const result = ChecksProcessorToolTargetSchema.safeParse(target);
2180
+ if (!result.success) return;
2181
+ return toolCheckFactories.get(result.data);
2182
+ }
2183
+ };
2184
+ //#endregion
2185
+ //#region src/constants/agentsmd-paths.ts
2186
+ const AGENTSMD_DIR = ".agents";
2187
+ const AGENTSMD_MEMORIES_DIR_PATH = join(AGENTSMD_DIR, "memories");
2188
+ const AGENTSMD_COMMANDS_DIR_PATH = join(AGENTSMD_DIR, "commands");
2189
+ const AGENTSMD_SKILLS_DIR_PATH = join(AGENTSMD_DIR, "skills");
2190
+ const AGENTSMD_SUBAGENTS_DIR_PATH = join(AGENTSMD_DIR, "subagents");
2191
+ const AGENTSMD_RULE_FILE_NAME = "AGENTS.md";
2192
+ //#endregion
1775
2193
  //#region src/features/commands/tool-command.ts
1776
2194
  /**
1777
2195
  * Abstract base class for AI development tool-specific command formats.
@@ -2019,13 +2437,6 @@ const AntigravityCommandFrontmatterSchema = z.looseObject({
2019
2437
  ...AntigravityWorkflowFrontmatterSchema.shape
2020
2438
  });
2021
2439
  //#endregion
2022
- //#region src/types/rulesync-file.ts
2023
- var RulesyncFile = class extends AiFile {
2024
- static async fromFile(_params) {
2025
- throw new Error("Please implement this method in the subclass.");
2026
- }
2027
- };
2028
- //#endregion
2029
2440
  //#region src/features/commands/rulesync-command.ts
2030
2441
  const RulesyncCommandFrontmatterSchema = z.looseObject({
2031
2442
  targets: z._default(RulesyncTargetsSchema, ["*"]),
@@ -3086,135 +3497,118 @@ var CursorCommand = class CursorCommand extends ToolCommand {
3086
3497
  //#endregion
3087
3498
  //#region src/constants/devin-paths.ts
3088
3499
  const DEVIN_DIR = ".devin";
3089
- const CODEIUM_WINDSURF_DIR = join(".codeium", "windsurf");
3090
- const DEVIN_WORKFLOWS_DIR_PATH = join(DEVIN_DIR, "workflows");
3091
3500
  const DEVIN_SKILLS_DIR_PATH = join(DEVIN_DIR, "skills");
3092
3501
  const DEVIN_AGENTS_DIR_PATH = join(DEVIN_DIR, "agents");
3093
3502
  const DEVIN_GLOBAL_CONFIG_DIR_PATH = join(".config", "devin");
3094
3503
  const DEVIN_GLOBAL_AGENTS_DIR_PATH = join(DEVIN_GLOBAL_CONFIG_DIR_PATH, "agents");
3095
3504
  const DEVIN_GLOBAL_SKILLS_DIR_PATH = join(DEVIN_GLOBAL_CONFIG_DIR_PATH, "skills");
3096
- const CODEIUM_WINDSURF_GLOBAL_WORKFLOWS_DIR_PATH = join(CODEIUM_WINDSURF_DIR, "global_workflows");
3097
3505
  const DEVIN_CONFIG_FILE_NAME = "config.json";
3098
3506
  const DEVIN_HOOKS_V1_FILE_NAME = "hooks.v1.json";
3099
3507
  const DEVIN_GLOBAL_AGENTS_FILE_NAME = "AGENTS.md";
3100
3508
  const DEVIN_IGNORE_FILE_NAME = ".devinignore";
3101
3509
  const DEVIN_LEGACY_IGNORE_FILE_NAME = ".codeiumignore";
3102
3510
  //#endregion
3511
+ //#region src/constants/general.ts
3512
+ const SKILL_FILE_NAME$1 = "SKILL.md";
3513
+ //#endregion
3514
+ //#region src/features/commands/command-skill-ownership.ts
3515
+ /**
3516
+ * Slug used for the per-command `<slug>/SKILL.md` directory when a tool's
3517
+ * commands are emitted onto its skills surface (Devin, Hermes Agent).
3518
+ */
3519
+ function commandSlug(relativeFilePath) {
3520
+ return basename(relativeFilePath, ".md").replace(/[^a-zA-Z0-9_-]/g, "-");
3521
+ }
3522
+ /**
3523
+ * Whether a rulesync command exists whose slug matches `dirName`.
3524
+ *
3525
+ * Used by the skills-surface `isDirOwned` hooks of tools whose commands are
3526
+ * emitted as `<slug>/SKILL.md` into the skills tree: a directory matching a
3527
+ * current command slug is owned by the commands feature, so the skills
3528
+ * feature must neither import it as a skill nor delete it as an orphan
3529
+ * skill. Once the command is removed from `.rulesync/commands/`, the
3530
+ * directory stops matching and the skills feature cleans it up as a regular
3531
+ * orphan.
3532
+ */
3533
+ async function rulesyncCommandSlugExists({ inputRoot, dirName }) {
3534
+ return (await findFilesByGlobs(join(inputRoot, RULESYNC_COMMANDS_RELATIVE_DIR_PATH, "**", "*.md"))).some((filePath) => commandSlug(basename(filePath)) === dirName);
3535
+ }
3536
+ //#endregion
3103
3537
  //#region src/features/commands/devin-command.ts
3104
- const DevinCommandFrontmatterSchema = z.looseObject({ description: z.optional(z.string()) });
3538
+ function commandSkillContent$1(rulesyncCommand) {
3539
+ const slug = commandSlug(rulesyncCommand.getRelativeFilePath());
3540
+ const description = rulesyncCommand.getFrontmatter().description ?? `${slug} command`;
3541
+ return stringifyFrontmatter(rulesyncCommand.getBody().trim(), {
3542
+ name: slug,
3543
+ description
3544
+ });
3545
+ }
3105
3546
  /**
3106
- * Represents a Devin (Cascade, now Devin Desktop) workflow command.
3107
- * Devin supports workflows in both project mode under .devin/workflows/
3108
- * (preferred since the Devin Desktop rebrand; .devin/workflows/ is the
3109
- * legacy fallback the tool still reads) and global mode under
3110
- * ~/.codeium/windsurf/global_workflows/ (unchanged by the rebrand).
3547
+ * Represents a Devin slash command, emitted as a Devin Skill.
3548
+ *
3549
+ * Devin's extensibility docs no longer document a standalone
3550
+ * workflows/commands component reusable prompts invoked as slash commands
3551
+ * are Skills (`/name`). Commands are therefore emitted onto the native skills
3552
+ * surface, one `SKILL.md` per command: `.devin/skills/<slug>/SKILL.md`
3553
+ * (project) and `~/.config/devin/skills/<slug>/SKILL.md` (global). The legacy
3554
+ * Windsurf/Cascade-era `.devin/workflows/` and
3555
+ * `~/.codeium/windsurf/global_workflows/` locations are no longer emitted.
3556
+ *
3557
+ * Import and deletion are intentionally no-ops for this target: the skills
3558
+ * feature owns the `.devin/skills/` tree, so importing it as commands would
3559
+ * double-import every skill (mirrors the Hermes Agent commands target).
3560
+ *
3561
+ * @see https://docs.devin.ai/cli/extensibility
3562
+ * @see https://docs.devin.ai/cli/extensibility/skills/overview
3111
3563
  */
3112
3564
  var DevinCommand = class DevinCommand extends ToolCommand {
3113
- frontmatter;
3114
- body;
3115
- constructor({ frontmatter, body, ...rest }) {
3116
- if (rest.validate) {
3117
- const result = DevinCommandFrontmatterSchema.safeParse(frontmatter);
3118
- if (!result.success) throw new Error(`Invalid frontmatter in ${join(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
3119
- }
3120
- super({
3121
- ...rest,
3122
- fileContent: stringifyFrontmatter(body, frontmatter)
3565
+ static isTargetedByRulesyncCommand(rulesyncCommand) {
3566
+ return this.isTargetedByRulesyncCommandDefault({
3567
+ rulesyncCommand,
3568
+ toolTarget: "devin"
3123
3569
  });
3124
- this.frontmatter = frontmatter;
3125
- this.body = body;
3126
3570
  }
3127
3571
  static getSettablePaths({ global = false } = {}) {
3128
- if (global) return { relativeDirPath: CODEIUM_WINDSURF_GLOBAL_WORKFLOWS_DIR_PATH };
3129
- return { relativeDirPath: DEVIN_WORKFLOWS_DIR_PATH };
3130
- }
3131
- getBody() {
3132
- return this.body;
3133
- }
3134
- getFrontmatter() {
3135
- return this.frontmatter;
3572
+ return { relativeDirPath: global ? DEVIN_GLOBAL_SKILLS_DIR_PATH : DEVIN_SKILLS_DIR_PATH };
3136
3573
  }
3137
- toRulesyncCommand() {
3138
- const { description, ...restFields } = this.frontmatter;
3139
- const rulesyncFrontmatter = {
3140
- targets: ["*"],
3141
- description,
3142
- ...Object.keys(restFields).length > 0 && { devin: restFields }
3143
- };
3144
- const fileContent = stringifyFrontmatter(this.body, rulesyncFrontmatter);
3145
- return new RulesyncCommand({
3146
- outputRoot: process.cwd(),
3147
- frontmatter: rulesyncFrontmatter,
3148
- body: this.body,
3149
- relativeDirPath: RulesyncCommand.getSettablePaths().relativeDirPath,
3150
- relativeFilePath: this.relativeFilePath,
3151
- fileContent,
3152
- validate: true
3153
- });
3154
- }
3155
- static fromRulesyncCommand({ outputRoot = process.cwd(), rulesyncCommand, validate = true, global = false }) {
3156
- const rulesyncFrontmatter = rulesyncCommand.getFrontmatter();
3157
- const devinFields = rulesyncFrontmatter.devin ?? {};
3158
- const devinFrontmatter = {
3159
- description: rulesyncFrontmatter.description,
3160
- ...devinFields
3161
- };
3162
- const body = rulesyncCommand.getBody();
3163
- const paths = this.getSettablePaths({ global });
3164
- return new DevinCommand({
3165
- outputRoot,
3166
- frontmatter: devinFrontmatter,
3167
- body,
3168
- relativeDirPath: paths.relativeDirPath,
3169
- relativeFilePath: rulesyncCommand.getRelativeFilePath(),
3170
- validate
3574
+ constructor({ slug, ...params }) {
3575
+ super({
3576
+ ...params,
3577
+ ...slug !== void 0 && {
3578
+ relativeDirPath: join(params.relativeDirPath, slug),
3579
+ relativeFilePath: "SKILL.md"
3580
+ }
3171
3581
  });
3172
3582
  }
3173
3583
  validate() {
3174
- if (!this.frontmatter) return {
3175
- success: true,
3176
- error: null
3177
- };
3178
- const result = DevinCommandFrontmatterSchema.safeParse(this.frontmatter);
3179
- if (result.success) return {
3584
+ return {
3180
3585
  success: true,
3181
3586
  error: null
3182
3587
  };
3183
- else return {
3184
- success: false,
3185
- error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${join(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
3186
- };
3187
3588
  }
3188
- static isTargetedByRulesyncCommand(rulesyncCommand) {
3189
- return this.isTargetedByRulesyncCommandDefault({
3190
- rulesyncCommand,
3191
- toolTarget: "devin"
3589
+ toRulesyncCommand() {
3590
+ const slug = basename(dirname(this.getRelativePathFromCwd()));
3591
+ const { frontmatter, body } = parseFrontmatter(this.getFileContent(), this.getFilePath());
3592
+ const description = typeof frontmatter.description === "string" ? frontmatter.description : void 0;
3593
+ return new RulesyncCommand({
3594
+ relativeDirPath: RULESYNC_COMMANDS_RELATIVE_DIR_PATH,
3595
+ relativeFilePath: `${slug}.md`,
3596
+ frontmatter: { description },
3597
+ body: body.trimStart()
3192
3598
  });
3193
3599
  }
3194
- static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
3195
- const paths = this.getSettablePaths({ global });
3196
- const filePath = join(outputRoot, paths.relativeDirPath, relativeFilePath);
3197
- const { frontmatter, body: content } = parseFrontmatter(await readFileContent(filePath), filePath);
3198
- const result = DevinCommandFrontmatterSchema.safeParse(frontmatter);
3199
- if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
3600
+ static fromRulesyncCommand({ outputRoot, rulesyncCommand, global = false }) {
3601
+ const paths = DevinCommand.getSettablePaths({ global });
3200
3602
  return new DevinCommand({
3201
3603
  outputRoot,
3202
3604
  relativeDirPath: paths.relativeDirPath,
3203
- relativeFilePath,
3204
- frontmatter: result.data,
3205
- body: content.trim(),
3206
- validate
3605
+ relativeFilePath: SKILL_FILE_NAME$1,
3606
+ slug: commandSlug(rulesyncCommand.getRelativeFilePath()),
3607
+ fileContent: commandSkillContent$1(rulesyncCommand)
3207
3608
  });
3208
3609
  }
3209
- static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
3210
- return new DevinCommand({
3211
- outputRoot,
3212
- relativeDirPath,
3213
- relativeFilePath,
3214
- frontmatter: { description: "" },
3215
- body: "",
3216
- validate: false
3217
- });
3610
+ getFileContent() {
3611
+ return this.fileContent;
3218
3612
  }
3219
3613
  };
3220
3614
  //#endregion
@@ -3542,10 +3936,7 @@ const HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_MANIFEST_PATH = join(HERMESAGENT_RUL
3542
3936
  const HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_INIT_PATH = join(HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_DIR_PATH, "__init__.py");
3543
3937
  //#endregion
3544
3938
  //#region src/features/commands/hermesagent-command.ts
3545
- const SKILL_FILE_NAME$1 = "SKILL.md";
3546
- function commandSlug(relativeFilePath) {
3547
- return basename(relativeFilePath, ".md").replace(/[^a-zA-Z0-9_-]/g, "-");
3548
- }
3939
+ const SKILL_FILE_NAME = "SKILL.md";
3549
3940
  function commandSkillContent(rulesyncCommand) {
3550
3941
  const slug = commandSlug(rulesyncCommand.getRelativeFilePath());
3551
3942
  const description = rulesyncCommand.getFrontmatter().description ?? `${slug} command`;
@@ -3562,7 +3953,7 @@ var HermesagentCommand = class HermesagentCommand extends ToolCommand {
3562
3953
  static getSettablePaths({ slug = "command" } = {}) {
3563
3954
  return {
3564
3955
  relativeDirPath: join(HERMESAGENT_SKILLS_DIR_PATH, slug),
3565
- relativeFilePath: SKILL_FILE_NAME$1
3956
+ relativeFilePath: SKILL_FILE_NAME
3566
3957
  };
3567
3958
  }
3568
3959
  constructor({ slug, ...params }) {
@@ -3593,7 +3984,7 @@ var HermesagentCommand = class HermesagentCommand extends ToolCommand {
3593
3984
  return new HermesagentCommand({
3594
3985
  outputRoot,
3595
3986
  relativeDirPath: "",
3596
- relativeFilePath: SKILL_FILE_NAME$1,
3987
+ relativeFilePath: SKILL_FILE_NAME,
3597
3988
  slug: commandSlug(rulesyncCommand.getRelativeFilePath()),
3598
3989
  fileContent: commandSkillContent(rulesyncCommand)
3599
3990
  });
@@ -4828,6 +5219,7 @@ const PI_AGENT_SKILLS_DIR_PATH = join(PI_AGENT_DIR, "skills");
4828
5219
  const PI_PROMPTS_DIR_PATH = join(".pi", "prompts");
4829
5220
  const PI_SKILLS_DIR_PATH = join(".pi", "skills");
4830
5221
  const PI_RULE_FILE_NAME = "AGENTS.md";
5222
+ const PI_APPEND_SYSTEM_FILE_NAME = "APPEND_SYSTEM.md";
4831
5223
  //#endregion
4832
5224
  //#region src/features/commands/pi-command.ts
4833
5225
  /**
@@ -5092,6 +5484,9 @@ const REASONIX_SETTINGS_FILE_NAME = "settings.json";
5092
5484
  const REASONIX_COMMANDS_DIR_PATH = join(REASONIX_DIR, "commands");
5093
5485
  const REASONIX_RULE_FILE_NAME = "REASONIX.md";
5094
5486
  const REASONIX_SKILLS_DIR_PATH = join(REASONIX_DIR, "skills");
5487
+ const REASONIX_SUBAGENTS_DIR_PATH = REASONIX_SKILLS_DIR_PATH;
5488
+ const REASONIX_SUBAGENT_INVOCATION = "manual";
5489
+ const REASONIX_SUBAGENT_RUN_AS = "subagent";
5095
5490
  //#endregion
5096
5491
  //#region src/features/commands/reasonix-command.ts
5097
5492
  /**
@@ -5359,9 +5754,6 @@ const ROVODEV_AGENTS_SKILLS_DIR_PATH = join(".agents", "skills");
5359
5754
  const ROVODEV_PROMPTS_FILE_NAME = "prompts.yml";
5360
5755
  const ROVODEV_PROMPTS_DIR_PATH = join(ROVODEV_DIR, "prompts");
5361
5756
  //#endregion
5362
- //#region src/types/tool-file.ts
5363
- var ToolFile = class extends AiFile {};
5364
- //#endregion
5365
5757
  //#region src/features/commands/rovodev-command.ts
5366
5758
  /**
5367
5759
  * Rovo Dev CLI "saved prompts": a file-based custom-command surface made of a
@@ -5850,7 +6242,8 @@ const toolCommandFactories = /* @__PURE__ */ new Map([
5850
6242
  supportsProject: false,
5851
6243
  supportsGlobal: true,
5852
6244
  isSimulated: false,
5853
- supportsSubdirectory: true
6245
+ supportsSubdirectory: true,
6246
+ skipToolFileScan: true
5854
6247
  }
5855
6248
  }],
5856
6249
  ["junie", {
@@ -5980,7 +6373,8 @@ const toolCommandFactories = /* @__PURE__ */ new Map([
5980
6373
  supportsProject: true,
5981
6374
  supportsGlobal: true,
5982
6375
  isSimulated: false,
5983
- supportsSubdirectory: false
6376
+ supportsSubdirectory: false,
6377
+ skipToolFileScan: true
5984
6378
  }
5985
6379
  }]
5986
6380
  ]);
@@ -6083,6 +6477,7 @@ var CommandsProcessor = class extends FeatureProcessor {
6083
6477
  */
6084
6478
  async loadToolFiles({ forDeletion = false } = {}) {
6085
6479
  const factory = this.getFactory(this.toolTarget);
6480
+ if (factory.meta.skipToolFileScan) return [];
6086
6481
  const paths = factory.class.getSettablePaths({ global: this.global });
6087
6482
  const outputRootFull = join(this.outputRoot, paths.relativeDirPath);
6088
6483
  const commandFilePaths = await findFilesByGlobs(factory.meta.supportsSubdirectory ? join(outputRootFull, "**", `*.${factory.meta.extension}`) : join(outputRootFull, `*.${factory.meta.extension}`));
@@ -6192,7 +6587,10 @@ const HookDefinitionSchema = z.looseObject({
6192
6587
  type: z.optional(z.enum([
6193
6588
  "command",
6194
6589
  "prompt",
6195
- "http"
6590
+ "http",
6591
+ "agent",
6592
+ "mcp_tool",
6593
+ "function"
6196
6594
  ])),
6197
6595
  url: z.optional(safeString),
6198
6596
  timeout: z.optional(z.number()),
@@ -6205,12 +6603,71 @@ const HookDefinitionSchema = z.looseObject({
6205
6603
  sequential: z.optional(z.boolean()),
6206
6604
  async: z.optional(z.boolean()),
6207
6605
  env: z.optional(z.record(z.string(), safeString)),
6208
- shell: z.optional(safeString),
6606
+ shell: z.optional(z.enum(["bash", "powershell"])),
6209
6607
  statusMessage: z.optional(safeString),
6210
6608
  headers: z.optional(z.record(z.string(), safeString)),
6211
6609
  allowedEnvVars: z.optional(z.array(z.string())),
6212
- once: z.optional(z.boolean())
6610
+ once: z.optional(z.boolean()),
6611
+ server: z.optional(safeString),
6612
+ tool: z.optional(safeString),
6613
+ input: z.optional(z.looseObject({})),
6614
+ model: z.optional(safeString)
6213
6615
  });
6616
+ /**
6617
+ * All canonical hook event names.
6618
+ * Each tool supports a subset of these events.
6619
+ */
6620
+ const HOOK_EVENTS = [
6621
+ "sessionStart",
6622
+ "sessionEnd",
6623
+ "preToolUse",
6624
+ "postToolUse",
6625
+ "preModelInvocation",
6626
+ "postModelInvocation",
6627
+ "beforeSubmitPrompt",
6628
+ "stop",
6629
+ "subagentStop",
6630
+ "preCompact",
6631
+ "postCompact",
6632
+ "contextOffload",
6633
+ "postToolUseFailure",
6634
+ "subagentStart",
6635
+ "beforeShellExecution",
6636
+ "afterShellExecution",
6637
+ "beforeMCPExecution",
6638
+ "afterMCPExecution",
6639
+ "beforeReadFile",
6640
+ "afterFileEdit",
6641
+ "beforeAgentResponse",
6642
+ "afterAgentResponse",
6643
+ "afterAgentThought",
6644
+ "beforeTabFileRead",
6645
+ "afterTabFileEdit",
6646
+ "permissionRequest",
6647
+ "notification",
6648
+ "setup",
6649
+ "afterError",
6650
+ "beforeToolSelection",
6651
+ "worktreeCreate",
6652
+ "worktreeRemove",
6653
+ "workspaceOpen",
6654
+ "messageDisplay",
6655
+ "todoCreated",
6656
+ "todoCompleted",
6657
+ "stopFailure",
6658
+ "instructionsLoaded",
6659
+ "userPromptExpansion",
6660
+ "postToolBatch",
6661
+ "permissionDenied",
6662
+ "taskCreated",
6663
+ "taskCompleted",
6664
+ "teammateIdle",
6665
+ "configChange",
6666
+ "cwdChanged",
6667
+ "fileChanged",
6668
+ "elicitation",
6669
+ "elicitationResult"
6670
+ ];
6214
6671
  /** Hook events supported by Cursor. */
6215
6672
  const CURSOR_HOOK_EVENTS = [
6216
6673
  "sessionStart",
@@ -6685,12 +7142,28 @@ const CANONICAL_TO_HERMESAGENT_EVENT_NAMES = {
6685
7142
  */
6686
7143
  const HERMESAGENT_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_HERMESAGENT_EVENT_NAMES).map(([k, v]) => [v, k]));
6687
7144
  const hooksRecordSchema = z.record(z.string(), z.array(HookDefinitionSchema));
7145
+ const HOOK_EVENT_SET = new Set(HOOK_EVENTS);
7146
+ /** Whether `value` is a canonical hook event name. */
7147
+ const isHookEvent = (value) => HOOK_EVENT_SET.has(value);
7148
+ /**
7149
+ * Top-level `hooks` record whose keys must be canonical event names, so typos
7150
+ * are rejected at parse time. Keys are validated with a refinement (instead of
7151
+ * an enum key schema) to keep the inferred type a plain string record.
7152
+ *
7153
+ * The per-tool override blocks below deliberately keep the lenient
7154
+ * `hooksRecordSchema`: some are documented to pass tool-native event keys
7155
+ * through verbatim (e.g. kiro-ide's IDE-only `PostFileSave`/`PreTaskExec`
7156
+ * triggers), which the canonical enum would reject.
7157
+ */
7158
+ const canonicalHooksRecordSchema = z.record(z.string(), z.array(HookDefinitionSchema)).check(z.refine((record) => Object.keys(record).every((key) => HOOK_EVENT_SET.has(key)), { error: (issue) => {
7159
+ return `unknown hook event name(s): ${Object.keys(issue.input ?? {}).filter((key) => !HOOK_EVENT_SET.has(key)).join(", ")}`;
7160
+ } }));
6688
7161
  /**
6689
7162
  * Canonical hooks config (canonical event names in camelCase).
6690
7163
  */
6691
7164
  const HooksConfigSchema = z.looseObject({
6692
7165
  version: z.optional(z.number()),
6693
- hooks: hooksRecordSchema,
7166
+ hooks: canonicalHooksRecordSchema,
6694
7167
  cursor: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
6695
7168
  claudecode: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
6696
7169
  copilot: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
@@ -7127,6 +7600,20 @@ const CANONICAL_TO_GROKCLI_EVENT_NAMES = {
7127
7600
  */
7128
7601
  const GROKCLI_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_GROKCLI_EVENT_NAMES).map(([k, v]) => [v, k]));
7129
7602
  //#endregion
7603
+ //#region src/utils/object.ts
7604
+ /**
7605
+ * Return a shallow copy of `obj` keeping only the entries whose value is
7606
+ * neither `undefined` nor `null`.
7607
+ *
7608
+ * Used to assemble generated config objects without one conditional spread per
7609
+ * optional field (which would otherwise exceed the lint complexity budget).
7610
+ */
7611
+ function compact(obj) {
7612
+ const result = {};
7613
+ for (const [key, value] of Object.entries(obj)) if (value !== void 0 && value !== null) result[key] = value;
7614
+ return result;
7615
+ }
7616
+ //#endregion
7130
7617
  //#region src/features/hooks/tool-hooks-converter.ts
7131
7618
  function isToolMatcherEntry(x) {
7132
7619
  if (x === null || typeof x !== "object") return false;
@@ -7184,6 +7671,25 @@ function importBooleanPassthroughFields({ h, converterConfig }) {
7184
7671
  return Object.fromEntries((converterConfig.booleanPassthroughFields ?? []).filter(({ tool }) => typeof h[tool] === "boolean").map(({ canonical, tool }) => [canonical, h[tool]]));
7185
7672
  }
7186
7673
  /**
7674
+ * Emit the payload fields specific to a hook type — `url`/`headers`/
7675
+ * `allowedEnvVars` for http, `server`/`tool`/`input` for mcp_tool, `model`
7676
+ * for prompt/agent. https://code.claude.com/docs/en/hooks
7677
+ */
7678
+ function emitTypePayloadFields({ def, hookType, converterConfig }) {
7679
+ if (hookType === "http") return compact({
7680
+ url: def.url,
7681
+ headers: def.headers,
7682
+ allowedEnvVars: def.allowedEnvVars
7683
+ });
7684
+ if (hookType === "mcp_tool") return compact({
7685
+ server: def.server,
7686
+ tool: def.tool,
7687
+ input: def.input
7688
+ });
7689
+ if ((hookType === "prompt" || hookType === "agent") && converterConfig.emitsPromptModel) return compact({ model: def.model });
7690
+ return {};
7691
+ }
7692
+ /**
7187
7693
  * Convert the definitions of a single matcher group into tool hook entries,
7188
7694
  * honoring supported hook types and passthrough fields.
7189
7695
  */
@@ -7205,6 +7711,11 @@ function buildToolHooks({ defs, converterConfig }) {
7205
7711
  ...command !== void 0 && command !== null && { command },
7206
7712
  ...def.timeout !== void 0 && def.timeout !== null && { timeout: def.timeout },
7207
7713
  ...def.prompt !== void 0 && def.prompt !== null && { prompt: def.prompt },
7714
+ ...emitTypePayloadFields({
7715
+ def,
7716
+ hookType,
7717
+ converterConfig
7718
+ }),
7208
7719
  ...converterConfig.passthroughFields?.includes("name") && def.name !== void 0 && def.name !== null && { name: def.name },
7209
7720
  ...converterConfig.passthroughFields?.includes("description") && def.description !== void 0 && def.description !== null && { description: def.description }
7210
7721
  });
@@ -7271,6 +7782,46 @@ function stripCommandPrefix({ command, converterConfig }) {
7271
7782
  return cmd;
7272
7783
  }
7273
7784
  /**
7785
+ * Hook types preserved verbatim on import — Claude Code's five documented
7786
+ * handler types. Anything else is coerced to `command` as before.
7787
+ * https://code.claude.com/docs/en/hooks
7788
+ */
7789
+ const IMPORTED_HOOK_TYPES = /* @__PURE__ */ new Set([
7790
+ "command",
7791
+ "prompt",
7792
+ "http",
7793
+ "mcp_tool",
7794
+ "agent"
7795
+ ]);
7796
+ function isImportedHookType(value) {
7797
+ return typeof value === "string" && IMPORTED_HOOK_TYPES.has(value);
7798
+ }
7799
+ function isStringRecord(value) {
7800
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
7801
+ return Object.values(value).every((v) => typeof v === "string");
7802
+ }
7803
+ function isStringArray(value) {
7804
+ return Array.isArray(value) && value.every((v) => typeof v === "string");
7805
+ }
7806
+ /**
7807
+ * Import the payload fields specific to a hook type, type-checking each raw
7808
+ * value before it enters the canonical definition.
7809
+ */
7810
+ function importTypePayloadFields({ h, hookType }) {
7811
+ if (hookType === "http") return {
7812
+ ...typeof h.url === "string" && { url: h.url },
7813
+ ...isStringRecord(h.headers) && { headers: h.headers },
7814
+ ...isStringArray(h.allowedEnvVars) && { allowedEnvVars: h.allowedEnvVars }
7815
+ };
7816
+ if (hookType === "mcp_tool") return {
7817
+ ...typeof h.server === "string" && { server: h.server },
7818
+ ...typeof h.tool === "string" && { tool: h.tool },
7819
+ ...h.input !== null && typeof h.input === "object" && !Array.isArray(h.input) && { input: h.input }
7820
+ };
7821
+ if (hookType === "prompt" || hookType === "agent") return typeof h.model === "string" ? { model: h.model } : {};
7822
+ return {};
7823
+ }
7824
+ /**
7274
7825
  * Convert a single tool hook record into a canonical hook definition.
7275
7826
  */
7276
7827
  function toolHookToCanonical({ h, rawEntry, converterConfig }) {
@@ -7278,7 +7829,7 @@ function toolHookToCanonical({ h, rawEntry, converterConfig }) {
7278
7829
  command: h.command,
7279
7830
  converterConfig
7280
7831
  });
7281
- const hookType = h.type === "command" || h.type === "prompt" ? h.type : "command";
7832
+ const hookType = isImportedHookType(h.type) ? h.type : "command";
7282
7833
  const timeout = typeof h.timeout === "number" ? h.timeout : void 0;
7283
7834
  const prompt = typeof h.prompt === "string" ? h.prompt : void 0;
7284
7835
  return {
@@ -7286,6 +7837,10 @@ function toolHookToCanonical({ h, rawEntry, converterConfig }) {
7286
7837
  ...command !== void 0 && command !== null && { command },
7287
7838
  ...timeout !== void 0 && timeout !== null && { timeout },
7288
7839
  ...prompt !== void 0 && prompt !== null && { prompt },
7840
+ ...importTypePayloadFields({
7841
+ h,
7842
+ hookType
7843
+ }),
7289
7844
  ...converterConfig.passthroughFields?.includes("name") && typeof h.name === "string" && { name: h.name },
7290
7845
  ...converterConfig.passthroughFields?.includes("description") && typeof h.description === "string" && { description: h.description },
7291
7846
  ...importBooleanPassthroughFields({
@@ -7305,6 +7860,32 @@ function toolMatcherEntryToCanonical({ rawEntry, converterConfig }) {
7305
7860
  converterConfig
7306
7861
  }));
7307
7862
  }
7863
+ /**
7864
+ * Assemble the canonical hooks config a tool importer writes to
7865
+ * `.rulesync/hooks.json`.
7866
+ *
7867
+ * The top-level `hooks` record only accepts canonical event names, so any
7868
+ * imported native event key without a canonical mapping is moved under the
7869
+ * importing tool's own override block (`<overrideKey>.hooks`), whose keys stay
7870
+ * lenient. This mirrors the generate direction — override blocks pass
7871
+ * tool-native keys through verbatim — so documented native triggers (e.g.
7872
+ * kiro-ide's `PostFileSave`) survive an import → generate round-trip instead
7873
+ * of failing canonical validation.
7874
+ */
7875
+ function buildImportedHooksConfig({ hooks, overrideKey, version = 1, extraOverride }) {
7876
+ const canonical = {};
7877
+ const native = {};
7878
+ for (const [event, defs] of Object.entries(hooks)) if (isHookEvent(event)) canonical[event] = defs;
7879
+ else native[event] = defs;
7880
+ const override = { ...extraOverride };
7881
+ if (Object.keys(native).length > 0) override.hooks = native;
7882
+ const config = {
7883
+ version,
7884
+ hooks: canonical
7885
+ };
7886
+ if (Object.keys(override).length > 0) config[overrideKey] = override;
7887
+ return config;
7888
+ }
7308
7889
  function toolHooksToCanonical({ hooks, converterConfig }) {
7309
7890
  if (hooks === null || hooks === void 0 || typeof hooks !== "object") return {};
7310
7891
  const canonical = {};
@@ -7475,7 +8056,8 @@ const ANTIGRAVITY_CONVERTER_CONFIG = {
7475
8056
  "preModelInvocation",
7476
8057
  "postModelInvocation",
7477
8058
  "stop"
7478
- ])
8059
+ ]),
8060
+ supportedHookTypes: /* @__PURE__ */ new Set(["command"])
7479
8061
  };
7480
8062
  /**
7481
8063
  * Antigravity's `hooks.json` is keyed by a named hook whose value holds the
@@ -7582,10 +8164,11 @@ var AntigravityHooks = class extends ToolHooks {
7582
8164
  hooks: flattenAntigravityHooks(parsed),
7583
8165
  converterConfig: ANTIGRAVITY_CONVERTER_CONFIG
7584
8166
  });
7585
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
7586
- version: 1,
7587
- hooks
7588
- }, null, 2) });
8167
+ const overrideKey = this.constructor.getOverrideKey();
8168
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
8169
+ hooks,
8170
+ overrideKey
8171
+ }), null, 2) });
7589
8172
  }
7590
8173
  validate() {
7591
8174
  return {
@@ -7706,7 +8289,8 @@ const AUGMENTCODE_CONVERTER_CONFIG = {
7706
8289
  "sessionEnd",
7707
8290
  "stop",
7708
8291
  "notification"
7709
- ])
8292
+ ]),
8293
+ supportedHookTypes: /* @__PURE__ */ new Set(["command"])
7710
8294
  };
7711
8295
  /**
7712
8296
  * AugmentCode (Auggie CLI) lifecycle hooks.
@@ -7788,10 +8372,10 @@ var AugmentcodeHooks = class AugmentcodeHooks extends ToolHooks {
7788
8372
  hooks: settings.hooks,
7789
8373
  converterConfig: AUGMENTCODE_CONVERTER_CONFIG
7790
8374
  });
7791
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
7792
- version: 1,
7793
- hooks
7794
- }, null, 2) });
8375
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
8376
+ hooks,
8377
+ overrideKey: "augmentcode"
8378
+ }), null, 2) });
7795
8379
  }
7796
8380
  validate() {
7797
8381
  return {
@@ -7826,7 +8410,15 @@ const CLAUDE_CONVERTER_CONFIG = {
7826
8410
  "taskCompleted",
7827
8411
  "teammateIdle",
7828
8412
  "cwdChanged"
7829
- ])
8413
+ ]),
8414
+ supportedHookTypes: /* @__PURE__ */ new Set([
8415
+ "command",
8416
+ "prompt",
8417
+ "http",
8418
+ "mcp_tool",
8419
+ "agent"
8420
+ ]),
8421
+ emitsPromptModel: true
7830
8422
  };
7831
8423
  var ClaudecodeHooks = class ClaudecodeHooks extends ToolHooks {
7832
8424
  constructor(params) {
@@ -7891,10 +8483,10 @@ var ClaudecodeHooks = class ClaudecodeHooks extends ToolHooks {
7891
8483
  hooks: settings.hooks,
7892
8484
  converterConfig: CLAUDE_CONVERTER_CONFIG
7893
8485
  });
7894
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
7895
- version: 1,
7896
- hooks
7897
- }, null, 2) });
8486
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
8487
+ hooks,
8488
+ overrideKey: "claudecode"
8489
+ }), null, 2) });
7898
8490
  }
7899
8491
  validate() {
7900
8492
  return {
@@ -8037,10 +8629,10 @@ var CodexcliHooks = class CodexcliHooks extends ToolHooks {
8037
8629
  hooks: parsed.hooks,
8038
8630
  converterConfig: CODEXCLI_CONVERTER_CONFIG
8039
8631
  });
8040
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
8041
- version: 1,
8042
- hooks
8043
- }, null, 2) });
8632
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
8633
+ hooks,
8634
+ overrideKey: "codexcli"
8635
+ }), null, 2) });
8044
8636
  }
8045
8637
  validate() {
8046
8638
  return {
@@ -8208,10 +8800,10 @@ var CopilotHooks = class CopilotHooks extends ToolHooks {
8208
8800
  throw new Error(`Failed to parse Copilot hooks content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
8209
8801
  }
8210
8802
  const hooks = copilotHooksToCanonical(parsed.hooks, options?.logger);
8211
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
8212
- version: 1,
8213
- hooks
8214
- }, null, 2) });
8803
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
8804
+ hooks,
8805
+ overrideKey: "copilot"
8806
+ }), null, 2) });
8215
8807
  }
8216
8808
  validate() {
8217
8809
  return {
@@ -8230,20 +8822,6 @@ var CopilotHooks = class CopilotHooks extends ToolHooks {
8230
8822
  }
8231
8823
  };
8232
8824
  //#endregion
8233
- //#region src/utils/object.ts
8234
- /**
8235
- * Return a shallow copy of `obj` keeping only the entries whose value is
8236
- * neither `undefined` nor `null`.
8237
- *
8238
- * Used to assemble generated config objects without one conditional spread per
8239
- * optional field (which would otherwise exceed the lint complexity budget).
8240
- */
8241
- function compact(obj) {
8242
- const result = {};
8243
- for (const [key, value] of Object.entries(obj)) if (value !== void 0 && value !== null) result[key] = value;
8244
- return result;
8245
- }
8246
- //#endregion
8247
8825
  //#region src/features/hooks/copilotcli-hooks.ts
8248
8826
  /**
8249
8827
  * GitHub Copilot CLI hooks.
@@ -8376,7 +8954,7 @@ function buildCopilotCliEntriesForEvent({ eventName, definitions, canonicalSchem
8376
8954
  ...timeoutPart,
8377
8955
  ...rest
8378
8956
  });
8379
- else entries.push({
8957
+ else if (hookType === "command") entries.push({
8380
8958
  type: "command",
8381
8959
  ...matcherPart,
8382
8960
  ...compact({
@@ -8528,10 +9106,10 @@ var CopilotcliHooks = class CopilotcliHooks extends ToolHooks {
8528
9106
  throw new Error(`Failed to parse Copilot CLI hooks content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
8529
9107
  }
8530
9108
  const hooks = copilotCliHooksToCanonical(parsed.hooks, options?.logger);
8531
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
8532
- version: 1,
8533
- hooks
8534
- }, null, 2) });
9109
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
9110
+ hooks,
9111
+ overrideKey: "copilotcli"
9112
+ }), null, 2) });
8535
9113
  }
8536
9114
  validate() {
8537
9115
  return {
@@ -8586,9 +9164,10 @@ var CursorHooks = class CursorHooks extends ToolHooks {
8586
9164
  ...config.cursor?.hooks
8587
9165
  };
8588
9166
  const mappedHooks = {};
9167
+ const cursorSupportedTypes = /* @__PURE__ */ new Set(["command", "prompt"]);
8589
9168
  for (const [eventName, defs] of Object.entries(mergedHooks)) {
8590
9169
  const cursorEventName = CANONICAL_TO_CURSOR_EVENT_NAMES[eventName] ?? eventName;
8591
- mappedHooks[cursorEventName] = defs.map((def) => ({
9170
+ const mappedDefs = defs.filter((def) => cursorSupportedTypes.has(def.type ?? "command")).map((def) => ({
8592
9171
  ...def.type !== void 0 && def.type !== null && { type: def.type },
8593
9172
  ...def.command !== void 0 && def.command !== null && { command: def.command },
8594
9173
  ...def.timeout !== void 0 && def.timeout !== null && { timeout: def.timeout },
@@ -8597,6 +9176,7 @@ var CursorHooks = class CursorHooks extends ToolHooks {
8597
9176
  ...def.prompt !== void 0 && def.prompt !== null && { prompt: def.prompt },
8598
9177
  ...def.failClosed !== void 0 && def.failClosed !== null && { failClosed: def.failClosed }
8599
9178
  }));
9179
+ if (mappedDefs.length > 0) mappedHooks[cursorEventName] = mappedDefs;
8600
9180
  }
8601
9181
  const cursorConfig = {
8602
9182
  version: config.version ?? 1,
@@ -8623,10 +9203,11 @@ var CursorHooks = class CursorHooks extends ToolHooks {
8623
9203
  canonicalHooks[eventName] = defs;
8624
9204
  }
8625
9205
  const version = parsed.version ?? 1;
8626
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
8627
- version,
8628
- hooks: canonicalHooks
8629
- }, null, 2) });
9206
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
9207
+ hooks: canonicalHooks,
9208
+ overrideKey: "cursor",
9209
+ version
9210
+ }), null, 2) });
8630
9211
  }
8631
9212
  validate() {
8632
9213
  return {
@@ -8681,7 +9262,7 @@ function canonicalToDeepagentsHooks(config) {
8681
9262
  const deepagentsEvent = CANONICAL_TO_DEEPAGENTS_EVENT_NAMES[canonicalEvent];
8682
9263
  if (!deepagentsEvent) continue;
8683
9264
  for (const def of definitions) {
8684
- if (def.type === "prompt") continue;
9265
+ if ((def.type ?? "command") !== "command") continue;
8685
9266
  if (!def.command) continue;
8686
9267
  if (def.matcher) continue;
8687
9268
  entries.push({
@@ -8771,10 +9352,10 @@ var DeepagentsHooks = class DeepagentsHooks extends ToolHooks {
8771
9352
  throw new Error(`Failed to parse deepagents hooks content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
8772
9353
  }
8773
9354
  const hooks = deepagentsToCanonicalHooks(isDeepagentsHooksFile(parsed) ? parsed.hooks : []);
8774
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
8775
- version: 1,
8776
- hooks
8777
- }, null, 2) });
9355
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
9356
+ hooks,
9357
+ overrideKey: "deepagents"
9358
+ }), null, 2) });
8778
9359
  }
8779
9360
  validate() {
8780
9361
  return {
@@ -8903,10 +9484,10 @@ var DevinHooks = class DevinHooks extends ToolHooks {
8903
9484
  hooks: this.getRelativeFilePath() === "config.json" ? isRecord(parsed) && isRecord(parsed.hooks) ? parsed.hooks : {} : parsed,
8904
9485
  converterConfig: DEVIN_CONVERTER_CONFIG
8905
9486
  });
8906
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
8907
- version: 1,
8908
- hooks
8909
- }, null, 2) });
9487
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
9488
+ hooks,
9489
+ overrideKey: "devin"
9490
+ }), null, 2) });
8910
9491
  }
8911
9492
  validate() {
8912
9493
  return {
@@ -8930,7 +9511,8 @@ const FACTORYDROID_CONVERTER_CONFIG = {
8930
9511
  supportedEvents: FACTORYDROID_HOOK_EVENTS,
8931
9512
  canonicalToToolEventNames: CANONICAL_TO_FACTORYDROID_EVENT_NAMES,
8932
9513
  toolToCanonicalEventNames: FACTORYDROID_TO_CANONICAL_EVENT_NAMES,
8933
- projectDirVar: "$FACTORY_PROJECT_DIR"
9514
+ projectDirVar: "$FACTORY_PROJECT_DIR",
9515
+ supportedHookTypes: /* @__PURE__ */ new Set(["command", "prompt"])
8934
9516
  };
8935
9517
  var FactorydroidHooks = class FactorydroidHooks extends ToolHooks {
8936
9518
  constructor(params) {
@@ -8998,10 +9580,10 @@ var FactorydroidHooks = class FactorydroidHooks extends ToolHooks {
8998
9580
  hooks: settings.hooks,
8999
9581
  converterConfig: FACTORYDROID_CONVERTER_CONFIG
9000
9582
  });
9001
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
9002
- version: 1,
9003
- hooks
9004
- }, null, 2) });
9583
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
9584
+ hooks,
9585
+ overrideKey: "factorydroid"
9586
+ }), null, 2) });
9005
9587
  }
9006
9588
  validate() {
9007
9589
  return {
@@ -9092,10 +9674,10 @@ var GooseHooks = class GooseHooks extends ToolHooks {
9092
9674
  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,
9093
9675
  converterConfig: GOOSE_CONVERTER_CONFIG
9094
9676
  });
9095
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
9096
- version: 1,
9097
- hooks
9098
- }, null, 2) });
9677
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
9678
+ hooks,
9679
+ overrideKey: "goose"
9680
+ }), null, 2) });
9099
9681
  }
9100
9682
  validate() {
9101
9683
  return {
@@ -9253,10 +9835,10 @@ var GrokcliHooks = class GrokcliHooks extends ToolHooks {
9253
9835
  hooks: isRecord(parsed) && isRecord(parsed.hooks) ? parsed.hooks : {},
9254
9836
  converterConfig: GROKCLI_CONVERTER_CONFIG
9255
9837
  });
9256
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
9257
- version: 1,
9258
- hooks
9259
- }, null, 2) });
9838
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
9839
+ hooks,
9840
+ overrideKey: "grokcli"
9841
+ }), null, 2) });
9260
9842
  }
9261
9843
  validate() {
9262
9844
  return {
@@ -9417,10 +9999,10 @@ var HermesagentHooks = class HermesagentHooks extends ToolHooks {
9417
9999
  format: "yaml",
9418
10000
  fileContent: this.getFileContent()
9419
10001
  }).hooks);
9420
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
9421
- version: 1,
9422
- hooks
9423
- }, null, 2) });
10002
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
10003
+ hooks,
10004
+ overrideKey: "hermesagent"
10005
+ }), null, 2) });
9424
10006
  }
9425
10007
  static fromRulesyncHooks({ outputRoot, rulesyncHooks, logger }) {
9426
10008
  const config = rulesyncHooks.getJson();
@@ -9523,10 +10105,10 @@ var JunieHooks = class JunieHooks extends ToolHooks {
9523
10105
  hooks: settings.hooks,
9524
10106
  converterConfig: JUNIE_CONVERTER_CONFIG
9525
10107
  });
9526
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
9527
- version: 1,
9528
- hooks
9529
- }, null, 2) });
10108
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
10109
+ hooks,
10110
+ overrideKey: "junie"
10111
+ }), null, 2) });
9530
10112
  }
9531
10113
  validate() {
9532
10114
  return {
@@ -9571,7 +10153,7 @@ function collectOpencodeStyleHandlers({ effectiveHooks, eventMap, namedEventHand
9571
10153
  if (!toolEvent) continue;
9572
10154
  const handlers = [];
9573
10155
  for (const def of definitions) {
9574
- if (def.type === "prompt") continue;
10156
+ if ((def.type ?? "command") !== "command") continue;
9575
10157
  if (!def.command) continue;
9576
10158
  handlers.push({
9577
10159
  command: def.command,
@@ -9876,10 +10458,11 @@ var KiroHooks = class KiroHooks extends ToolHooks {
9876
10458
  throw new Error(`Failed to parse Kiro hooks content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
9877
10459
  }
9878
10460
  const hooks = kiroHooksToCanonical(agentConfig.hooks);
9879
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
9880
- version: 1,
9881
- hooks
9882
- }, null, 2) });
10461
+ const overrideKey = this.constructor.getOverrideKey();
10462
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
10463
+ hooks,
10464
+ overrideKey
10465
+ }), null, 2) });
9883
10466
  }
9884
10467
  validate() {
9885
10468
  return {
@@ -10088,10 +10671,10 @@ var KiroIdeHooks = class KiroIdeHooks extends ToolHooks {
10088
10671
  throw new Error(`Failed to parse Kiro IDE hooks content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
10089
10672
  }
10090
10673
  const hooks = kiroIdeHooksToCanonical(parsed.hooks ?? []);
10091
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
10092
- version: 1,
10093
- hooks
10094
- }, null, 2) });
10674
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
10675
+ hooks,
10676
+ overrideKey: "kiro-ide"
10677
+ }), null, 2) });
10095
10678
  }
10096
10679
  validate() {
10097
10680
  return {
@@ -10215,11 +10798,18 @@ function canonicalToQwencodeHooks(config) {
10215
10798
  ...sharedHooks,
10216
10799
  ...config.qwencode?.hooks
10217
10800
  };
10801
+ const qwencodeSupportedTypes = /* @__PURE__ */ new Set([
10802
+ "command",
10803
+ "prompt",
10804
+ "http",
10805
+ "function"
10806
+ ]);
10218
10807
  const qwencode = {};
10219
10808
  for (const [eventName, definitions] of Object.entries(effectiveHooks)) {
10220
10809
  const qwencodeEventName = CANONICAL_TO_QWENCODE_EVENT_NAMES[eventName] ?? eventName;
10221
10810
  const byMatcher = /* @__PURE__ */ new Map();
10222
10811
  for (const def of definitions) {
10812
+ if (!qwencodeSupportedTypes.has(def.type ?? "command")) continue;
10223
10813
  const key = def.matcher ?? "";
10224
10814
  const list = byMatcher.get(key);
10225
10815
  if (list) list.push(def);
@@ -10279,9 +10869,10 @@ function qwencodeMatcherEntryToCanonical(entry) {
10279
10869
  const sequential = entry.sequential === true;
10280
10870
  const matcher = entry.matcher !== void 0 && entry.matcher !== null && entry.matcher !== "" ? entry.matcher : void 0;
10281
10871
  for (const h of hooks) {
10282
- const hookType = h.type === "command" || h.type === "prompt" || h.type === "http" ? h.type : "command";
10872
+ const hookType = h.type === "command" || h.type === "prompt" || h.type === "http" || h.type === "function" ? h.type : "command";
10283
10873
  const isHttp = hookType === "http";
10284
10874
  const isCommand = hookType === "command";
10875
+ const shell = h.shell === "bash" || h.shell === "powershell" ? h.shell : void 0;
10285
10876
  defs.push({
10286
10877
  type: hookType,
10287
10878
  ...compact({
@@ -10293,7 +10884,7 @@ function qwencodeMatcherEntryToCanonical(entry) {
10293
10884
  statusMessage: h.statusMessage,
10294
10885
  async: isCommand ? h.async : void 0,
10295
10886
  env: isCommand ? h.env : void 0,
10296
- shell: isCommand ? h.shell : void 0,
10887
+ shell: isCommand ? shell : void 0,
10297
10888
  headers: isHttp ? h.headers : void 0,
10298
10889
  allowedEnvVars: isHttp ? h.allowedEnvVars : void 0,
10299
10890
  once: isHttp ? h.once : void 0,
@@ -10380,15 +10971,11 @@ var QwencodeHooks = class QwencodeHooks extends ToolHooks {
10380
10971
  } catch (error) {
10381
10972
  throw new Error(`Failed to parse Qwen Code hooks content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
10382
10973
  }
10383
- const hooks = qwencodeHooksToCanonical(settings.hooks);
10384
- const canonical = typeof settings.disableAllHooks === "boolean" ? {
10385
- version: 1,
10386
- hooks,
10387
- qwencode: { disableAllHooks: settings.disableAllHooks }
10388
- } : {
10389
- version: 1,
10390
- hooks
10391
- };
10974
+ const canonical = buildImportedHooksConfig({
10975
+ hooks: qwencodeHooksToCanonical(settings.hooks),
10976
+ overrideKey: "qwencode",
10977
+ ...typeof settings.disableAllHooks === "boolean" && { extraOverride: { disableAllHooks: settings.disableAllHooks } }
10978
+ });
10392
10979
  return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(canonical, null, 2) });
10393
10980
  }
10394
10981
  validate() {
@@ -10550,10 +11137,10 @@ var ReasonixHooks = class ReasonixHooks extends ToolHooks {
10550
11137
  throw new Error(`Failed to parse Reasonix hooks content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
10551
11138
  }
10552
11139
  const hooks = reasonixHooksToCanonical(settings.hooks);
10553
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
10554
- version: 1,
10555
- hooks
10556
- }, null, 2) });
11140
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
11141
+ hooks,
11142
+ overrideKey: "reasonix"
11143
+ }), null, 2) });
10557
11144
  }
10558
11145
  validate() {
10559
11146
  return {
@@ -10776,10 +11363,10 @@ var VibeHooks = class VibeHooks extends ToolHooks {
10776
11363
  throw new Error(`Failed to parse Vibe hooks content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
10777
11364
  }
10778
11365
  const hooks = vibeHooksToCanonical(parsed);
10779
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
10780
- version: 1,
10781
- hooks
10782
- }, null, 2) });
11366
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
11367
+ hooks,
11368
+ overrideKey: "vibe"
11369
+ }), null, 2) });
10783
11370
  }
10784
11371
  validate() {
10785
11372
  try {
@@ -10866,7 +11453,13 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
10866
11453
  supportsImport: true
10867
11454
  },
10868
11455
  supportedEvents: CLAUDE_HOOK_EVENTS,
10869
- supportedHookTypes: ["command", "prompt"],
11456
+ supportedHookTypes: [
11457
+ "command",
11458
+ "prompt",
11459
+ "http",
11460
+ "mcp_tool",
11461
+ "agent"
11462
+ ],
10870
11463
  supportsMatcher: true
10871
11464
  }],
10872
11465
  ["codexcli", {
@@ -11058,7 +11651,12 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
11058
11651
  supportsImport: true
11059
11652
  },
11060
11653
  supportedEvents: QWENCODE_HOOK_EVENTS,
11061
- supportedHookTypes: ["command"],
11654
+ supportedHookTypes: [
11655
+ "command",
11656
+ "prompt",
11657
+ "http",
11658
+ "function"
11659
+ ],
11062
11660
  supportsMatcher: true
11063
11661
  }],
11064
11662
  ["reasonix", {
@@ -12360,16 +12958,6 @@ var IgnoreProcessor = class extends FeatureProcessor {
12360
12958
  }
12361
12959
  };
12362
12960
  //#endregion
12363
- //#region src/constants/amp-paths.ts
12364
- const AMP_DIR = ".amp";
12365
- const AMP_GLOBAL_DIR = join(".config", "amp");
12366
- const AMP_AGENTS_DIR = ".agents";
12367
- const AMP_SKILLS_PROJECT_DIR = join(AMP_AGENTS_DIR, "skills");
12368
- const AMP_SKILLS_GLOBAL_DIR = join(".config", "agents", "skills");
12369
- const AMP_RULE_FILE_NAME = "AGENTS.md";
12370
- const AMP_SETTINGS_FILE_NAME = "settings.json";
12371
- const AMP_SETTINGS_JSONC_FILE_NAME = "settings.jsonc";
12372
- //#endregion
12373
12961
  //#region src/types/mcp.ts
12374
12962
  const EnvVarNameSchema = z.string().check(refine((value) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(value), "envVars entries must be valid environment variable names"));
12375
12963
  const McpServerSchema = z.looseObject({
@@ -13461,10 +14049,19 @@ function mapOauthFromCodex(oauth) {
13461
14049
  }
13462
14050
  const CODEX_MCP_SERVER_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
13463
14051
  function normalizeCodexMcpServerName(name) {
13464
- if (PROTOTYPE_POLLUTION_KEYS.has(name)) return null;
13465
- if (CODEX_MCP_SERVER_NAME_PATTERN.test(name)) return name;
14052
+ if (!PROTOTYPE_POLLUTION_KEYS.has(name) && CODEX_MCP_SERVER_NAME_PATTERN.test(name)) return {
14053
+ codexName: name,
14054
+ usedFallback: false
14055
+ };
13466
14056
  const normalizedName = name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
13467
- return normalizedName && !PROTOTYPE_POLLUTION_KEYS.has(normalizedName) ? normalizedName : null;
14057
+ if (normalizedName && !PROTOTYPE_POLLUTION_KEYS.has(normalizedName)) return {
14058
+ codexName: normalizedName,
14059
+ usedFallback: false
14060
+ };
14061
+ return {
14062
+ codexName: `mcp_${createHash("sha256").update(name).digest("hex").slice(0, 8)}`,
14063
+ usedFallback: true
14064
+ };
13468
14065
  }
13469
14066
  function convertFromCodexFormat(codexMcp) {
13470
14067
  const result = {};
@@ -13478,7 +14075,7 @@ function convertFromCodexFormat(codexMcp) {
13478
14075
  } else if (key === "oauth" && isRecord(value)) converted[key] = mapOauthFromCodex(value);
13479
14076
  else if (Object.hasOwn(CODEX_TO_RULESYNC_FIELD_MAP, key)) {
13480
14077
  const mappedKey = CODEX_TO_RULESYNC_FIELD_MAP[key];
13481
- if (mappedKey) if (isStringArray(value)) converted[mappedKey] = value;
14078
+ if (mappedKey) if (isStringArray$1(value)) converted[mappedKey] = value;
13482
14079
  else warnWithFallback(void 0, `Ignored malformed array for ${key} in MCP server ${name}`);
13483
14080
  } else converted[key] = value;
13484
14081
  }
@@ -13490,12 +14087,9 @@ function convertToCodexFormat(mcpServers) {
13490
14087
  const result = {};
13491
14088
  const originalNames = /* @__PURE__ */ new Map();
13492
14089
  for (const [name, config] of Object.entries(mcpServers)) {
13493
- const codexName = normalizeCodexMcpServerName(name);
13494
- if (codexName === null) {
13495
- warnWithFallback(void 0, `MCP server "${name}" could not be normalized to a valid Codex MCP server name and was skipped.`);
13496
- continue;
13497
- }
13498
14090
  if (!isRecord(config)) continue;
14091
+ const { codexName, usedFallback } = normalizeCodexMcpServerName(name);
14092
+ 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.`);
13499
14093
  const converted = {};
13500
14094
  for (const [key, value] of Object.entries(config)) {
13501
14095
  if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
@@ -13504,12 +14098,12 @@ function convertToCodexFormat(mcpServers) {
13504
14098
  } else if (key === "oauth" && isRecord(value)) converted[key] = mapOauthToCodex(value);
13505
14099
  else if (Object.hasOwn(RULESYNC_TO_CODEX_FIELD_MAP, key)) {
13506
14100
  const mappedKey = RULESYNC_TO_CODEX_FIELD_MAP[key];
13507
- if (mappedKey) if (isStringArray(value)) converted[mappedKey] = value;
14101
+ if (mappedKey) if (isStringArray$1(value)) converted[mappedKey] = value;
13508
14102
  else warnWithFallback(void 0, `[CodexCliMcp] Skipping invalid value type for mapped key '${key}': expected string array, got ${typeof value}`);
13509
14103
  } else converted[key] = value;
13510
14104
  }
13511
14105
  const previousName = originalNames.get(codexName);
13512
- 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.`);
14106
+ if (previousName !== void 0) warnWithFallback(void 0, `Codex MCP server name collision: "${previousName}" and "${name}" both normalize to "${codexName}"; "${name}" (processed last) overwrites "${previousName}".`);
13513
14107
  originalNames.set(codexName, name);
13514
14108
  result[codexName] = converted;
13515
14109
  }
@@ -13576,7 +14170,7 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
13576
14170
  const rawServer = isRecord(rawMcpServers) ? rawMcpServers[serverName] : void 0;
13577
14171
  return [serverName, {
13578
14172
  ...serverConfig,
13579
- ...isRecord(rawServer) && isStringArray(rawServer.envVars) ? { envVars: rawServer.envVars } : {}
14173
+ ...isRecord(rawServer) && isStringArray$1(rawServer.envVars) ? { envVars: rawServer.envVars } : {}
13580
14174
  }];
13581
14175
  })));
13582
14176
  const filteredMcpServers = this.removeEmptyEntries(converted);
@@ -14313,11 +14907,11 @@ function applyGooseStdioFields(ext, config) {
14313
14907
  if (Array.isArray(command)) {
14314
14908
  if (typeof command[0] === "string") ext.cmd = command[0];
14315
14909
  const rest = command.slice(1).filter((c) => typeof c === "string");
14316
- const args = isStringArray(config.args) ? config.args : [];
14910
+ const args = isStringArray$1(config.args) ? config.args : [];
14317
14911
  if (rest.length > 0 || args.length > 0) ext.args = [...rest, ...args];
14318
14912
  } else if (typeof command === "string") {
14319
14913
  ext.cmd = command;
14320
- if (isStringArray(config.args)) ext.args = config.args;
14914
+ if (isStringArray$1(config.args)) ext.args = config.args;
14321
14915
  }
14322
14916
  if (isPlainObject$1(config.env)) ext.envs = omitPrototypePollutionKeys(config.env);
14323
14917
  }
@@ -14375,7 +14969,7 @@ function convertFromGooseFormat(extensions) {
14375
14969
  else if (type === "streamable_http") server.type = "http";
14376
14970
  else if (type === "stdio") server.type = "stdio";
14377
14971
  if (typeof ext.cmd === "string") server.command = ext.cmd;
14378
- if (isStringArray(ext.args)) server.args = ext.args;
14972
+ if (isStringArray$1(ext.args)) server.args = ext.args;
14379
14973
  if (isPlainObject$1(ext.envs)) server.env = omitPrototypePollutionKeys(ext.envs);
14380
14974
  if (typeof ext.uri === "string") server.url = ext.uri;
14381
14975
  if (isPlainObject$1(ext.headers)) server.headers = omitPrototypePollutionKeys(ext.headers);
@@ -14395,11 +14989,11 @@ function buildGoosePluginStdioServer(config) {
14395
14989
  if (Array.isArray(command)) {
14396
14990
  if (typeof command[0] === "string") server.command = command[0];
14397
14991
  const rest = command.slice(1).filter((c) => typeof c === "string");
14398
- const args = isStringArray(config.args) ? config.args : [];
14992
+ const args = isStringArray$1(config.args) ? config.args : [];
14399
14993
  if (rest.length > 0 || args.length > 0) server.args = [...rest, ...args];
14400
14994
  } else if (typeof command === "string") {
14401
14995
  server.command = command;
14402
- if (isStringArray(config.args)) server.args = config.args;
14996
+ if (isStringArray$1(config.args)) server.args = config.args;
14403
14997
  }
14404
14998
  if (isPlainObject$1(config.env)) server.env = omitPrototypePollutionKeys(config.env);
14405
14999
  if (typeof config.cwd === "string") server.cwd = config.cwd;
@@ -14712,7 +15306,7 @@ function resolveHermesTimeout(config) {
14712
15306
  */
14713
15307
  function copyHermesAdvancedFields(source, target) {
14714
15308
  if (typeof source.auth === "string") target.auth = source.auth;
14715
- if (typeof source.client_cert === "string" || isStringArray(source.client_cert)) target.client_cert = source.client_cert;
15309
+ if (typeof source.client_cert === "string" || isStringArray$1(source.client_cert)) target.client_cert = source.client_cert;
14716
15310
  if (typeof source.client_key === "string") target.client_key = source.client_key;
14717
15311
  if (typeof source.connect_timeout === "number") target.connect_timeout = source.connect_timeout;
14718
15312
  if (typeof source.supports_parallel_tool_calls === "boolean") target.supports_parallel_tool_calls = source.supports_parallel_tool_calls;
@@ -14731,8 +15325,8 @@ function copyHermesAdvancedFields(source, target) {
14731
15325
  */
14732
15326
  function buildHermesToolsBlock(config) {
14733
15327
  const tools = {};
14734
- if (isStringArray(config.enabledTools)) tools.include = config.enabledTools;
14735
- if (isStringArray(config.disabledTools)) tools.exclude = config.disabledTools;
15328
+ if (isStringArray$1(config.enabledTools)) tools.include = config.enabledTools;
15329
+ if (isStringArray$1(config.disabledTools)) tools.exclude = config.disabledTools;
14736
15330
  if (typeof config.promptsEnabled === "boolean") tools.prompts = config.promptsEnabled;
14737
15331
  if (typeof config.resourcesEnabled === "boolean") tools.resources = config.resourcesEnabled;
14738
15332
  return tools;
@@ -14744,8 +15338,8 @@ function buildHermesToolsBlock(config) {
14744
15338
  * `promptsEnabled`/`resourcesEnabled` top-level toggles.
14745
15339
  */
14746
15340
  function applyHermesToolsBlock(hermesTools, server) {
14747
- if (isStringArray(hermesTools.include)) server.enabledTools = hermesTools.include;
14748
- if (isStringArray(hermesTools.exclude)) server.disabledTools = hermesTools.exclude;
15341
+ if (isStringArray$1(hermesTools.include)) server.enabledTools = hermesTools.include;
15342
+ if (isStringArray$1(hermesTools.exclude)) server.disabledTools = hermesTools.exclude;
14749
15343
  if (typeof hermesTools.prompts === "boolean") server.promptsEnabled = hermesTools.prompts;
14750
15344
  if (typeof hermesTools.resources === "boolean") server.resourcesEnabled = hermesTools.resources;
14751
15345
  }
@@ -14769,11 +15363,11 @@ function convertServerToHermes(config) {
14769
15363
  if (Array.isArray(command)) {
14770
15364
  if (typeof command[0] === "string") out.command = command[0];
14771
15365
  const rest = command.slice(1).filter((c) => typeof c === "string");
14772
- const args = isStringArray(config.args) ? config.args : [];
15366
+ const args = isStringArray$1(config.args) ? config.args : [];
14773
15367
  if (rest.length > 0 || args.length > 0) out.args = [...rest, ...args];
14774
15368
  } else if (typeof command === "string") {
14775
15369
  out.command = command;
14776
- if (isStringArray(config.args)) out.args = config.args;
15370
+ if (isStringArray$1(config.args)) out.args = config.args;
14777
15371
  }
14778
15372
  if (isPlainObject$1(config.env)) out.env = omitPrototypePollutionKeys(config.env);
14779
15373
  } else if (url !== void 0) {
@@ -14821,7 +15415,7 @@ function convertFromHermesFormat(mcpServers) {
14821
15415
  if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord(config)) continue;
14822
15416
  const server = {};
14823
15417
  if (typeof config.command === "string") server.command = config.command;
14824
- if (isStringArray(config.args)) server.args = config.args;
15418
+ if (isStringArray$1(config.args)) server.args = config.args;
14825
15419
  if (isPlainObject$1(config.env)) server.env = omitPrototypePollutionKeys(config.env);
14826
15420
  if (typeof config.url === "string") server.url = config.url;
14827
15421
  if (isPlainObject$1(config.headers)) server.headers = omitPrototypePollutionKeys(config.headers);
@@ -17146,7 +17740,11 @@ const VibePermissionsOverrideSchema = z.looseObject({ permission: z.optional(z.r
17146
17740
  */
17147
17741
  const CursorPermissionsOverrideSchema = z.looseObject({
17148
17742
  permission: z.optional(ToolScopedPermissionSchema),
17149
- approvalMode: z.optional(z.string()),
17743
+ approvalMode: z.optional(z.enum([
17744
+ "allowlist",
17745
+ "auto-review",
17746
+ "unrestricted"
17747
+ ])),
17150
17748
  sandbox: z.optional(z.looseObject({}))
17151
17749
  });
17152
17750
  /**
@@ -17234,7 +17832,11 @@ const FactorydroidPermissionsOverrideSchema = z.looseObject({
17234
17832
  */
17235
17833
  const WarpPermissionsOverrideSchema = z.looseObject({
17236
17834
  permission: z.optional(ToolScopedPermissionSchema),
17237
- agent_mode_coding_permissions: z.optional(z.string()),
17835
+ agent_mode_coding_permissions: z.optional(z.enum([
17836
+ "always_ask_before_reading",
17837
+ "always_allow_reading",
17838
+ "allow_reading_specific_files"
17839
+ ])),
17238
17840
  agent_mode_coding_file_read_allowlist: z.optional(z.array(z.string())),
17239
17841
  agent_mode_execute_readonly_commands: z.optional(z.boolean())
17240
17842
  });
@@ -17280,7 +17882,11 @@ const JuniePermissionsOverrideSchema = z.looseObject({
17280
17882
  */
17281
17883
  const TaktPermissionsOverrideSchema = z.looseObject({
17282
17884
  permission: z.optional(ToolScopedPermissionSchema),
17283
- step_permission_overrides: z.optional(z.record(z.string(), z.string())),
17885
+ step_permission_overrides: z.optional(z.record(z.string(), z.enum([
17886
+ "readonly",
17887
+ "edit",
17888
+ "full"
17889
+ ]))),
17284
17890
  provider_options: z.optional(z.looseObject({}))
17285
17891
  });
17286
17892
  /**
@@ -17310,9 +17916,15 @@ const AmpPermissionsOverrideSchema = z.looseObject({
17310
17916
  permission: z.optional(ToolScopedPermissionSchema),
17311
17917
  permissions: z.optional(z.array(z.looseObject({
17312
17918
  tool: z.string(),
17313
- action: z.string()
17919
+ action: z.enum([
17920
+ "allow",
17921
+ "ask",
17922
+ "reject",
17923
+ "delegate"
17924
+ ]),
17925
+ context: z.optional(z.enum(["thread", "subagent"]))
17314
17926
  }))),
17315
- mcpPermissions: z.optional(z.array(z.looseObject({}))),
17927
+ mcpPermissions: z.optional(z.array(z.looseObject({ action: z.enum(["allow", "reject"]) }))),
17316
17928
  guardedFiles: z.optional(z.looseObject({ allowlist: z.optional(z.array(z.string())) })),
17317
17929
  dangerouslyAllowAll: z.optional(z.boolean())
17318
17930
  });
@@ -17337,7 +17949,12 @@ const AmpPermissionsOverrideSchema = z.looseObject({
17337
17949
  */
17338
17950
  const AntigravityCliPermissionsOverrideSchema = z.looseObject({
17339
17951
  permission: z.optional(ToolScopedPermissionSchema),
17340
- toolPermission: z.optional(z.string()),
17952
+ toolPermission: z.optional(z.enum([
17953
+ "request-review",
17954
+ "proceed-in-sandbox",
17955
+ "always-proceed",
17956
+ "strict"
17957
+ ])),
17341
17958
  enableTerminalSandbox: z.optional(z.boolean())
17342
17959
  });
17343
17960
  /**
@@ -17366,7 +17983,14 @@ const AugmentcodePermissionsOverrideSchema = z.looseObject({
17366
17983
  permission: z.optional(ToolScopedPermissionSchema),
17367
17984
  toolPermissions: z.optional(z.array(z.looseObject({
17368
17985
  toolName: z.string(),
17369
- permission: z.looseObject({ type: z.string() })
17986
+ eventType: z.optional(z.enum(["tool-call", "tool-response"])),
17987
+ permission: z.looseObject({ type: z.enum([
17988
+ "allow",
17989
+ "deny",
17990
+ "ask-user",
17991
+ "webhook-policy",
17992
+ "script-policy"
17993
+ ]) })
17370
17994
  })))
17371
17995
  });
17372
17996
  /**
@@ -17456,12 +18080,20 @@ const CodexApprovalsReviewerSchema = z.enum([
17456
18080
  * `[permissions.rulesync]` profile may extend. Codex ships three built-in
17457
18081
  * profiles (`:read-only`, `:workspace`, `:danger-full-access`; the leading
17458
18082
  * colon is reserved for built-ins), but `extends` rejects
17459
- * `:danger-full-access` at config load time, so only the two extendable
17460
- * baselines are accepted here. The value list is exported so the Codex CLI
17461
- * translator derives its import-side baseline check from the same source.
18083
+ * `:danger-full-access` at config load time, so only these two are valid
18084
+ * `extends` parents. The value list is exported so the Codex CLI translator
18085
+ * derives its import-side baseline check from the same source.
17462
18086
  * @see https://learn.chatgpt.com/docs/permissions
17463
18087
  */
17464
- const CODEX_BASE_PERMISSION_PROFILES = [":read-only", ":workspace"];
18088
+ const CODEX_EXTENDABLE_BASELINE_PROFILES = [":read-only", ":workspace"];
18089
+ /**
18090
+ * All accepted `codexcli.base_permission_profile` values. `:danger-full-access`
18091
+ * cannot be an `extends` parent, so selecting it makes rulesync emit
18092
+ * `default_permissions = ":danger-full-access"` directly and skip the managed
18093
+ * `[permissions.rulesync]` profile entirely (there is no sandbox for
18094
+ * filesystem/network rules to refine in that mode).
18095
+ */
18096
+ const CODEX_BASE_PERMISSION_PROFILES = [...CODEX_EXTENDABLE_BASELINE_PROFILES, ":danger-full-access"];
17465
18097
  const CodexBasePermissionProfileSchema = z.enum(CODEX_BASE_PERMISSION_PROFILES);
17466
18098
  /**
17467
18099
  * Codex CLI-scoped permission override.
@@ -17472,10 +18104,14 @@ const CodexBasePermissionProfileSchema = z.enum(CODEX_BASE_PERMISSION_PROFILES);
17472
18104
  * override whose fields are written verbatim as top-level `.codex/config.toml`
17473
18105
  * keys (the override wins per key; existing sibling keys the user set directly
17474
18106
  * are preserved):
17475
- * - `base_permission_profile` — the built-in profile the managed
17476
- * `[permissions.rulesync]` profile extends (`:read-only` | `:workspace`).
17477
- * Unlike the other keys it is not a top-level config key: it is emitted as
17478
- * the profile's `extends` value. Defaults to `:workspace` when unspecified.
18107
+ * - `base_permission_profile` — the built-in baseline profile (`:read-only` |
18108
+ * `:workspace` | `:danger-full-access`). Unlike the other keys it is not a
18109
+ * top-level config key: the extendable baselines are emitted as the managed
18110
+ * `[permissions.rulesync]` profile's `extends` value, while
18111
+ * `:danger-full-access` (which Codex rejects as an `extends` parent) is
18112
+ * selected directly via `default_permissions` and skips the managed profile
18113
+ * entirely — canonical filesystem/network rules are ignored in that mode.
18114
+ * Defaults to `:workspace` when unspecified.
17479
18115
  * - `approval_policy` — `untrusted` | `on-request` (legacy alias `on-failure`) |
17480
18116
  * `never`, or a `{ granular = { … } }` table (kept verbatim; the granular
17481
18117
  * schema has required fields that are brittle to model as typed keys).
@@ -17495,6 +18131,15 @@ const CodexBasePermissionProfileSchema = z.enum(CODEX_BASE_PERMISSION_PROFILES);
17495
18131
  * | `guardian_subagent`), or a table for the richer reviewer config.
17496
18132
  * Defaults to `auto_review` when neither the override nor the existing
17497
18133
  * config sets it.
18134
+ * - `git_write_rules` — whether the managed profile's `:workspace_roots` table
18135
+ * emits the default `.git` carve-out (`".git/**" = "write"`). Codex's
18136
+ * `:workspace` baseline makes `.git` read-only, which denies basic git
18137
+ * workflows (commit/stage writes to `.git/index`, `.git/objects`, refs,
18138
+ * logs; everyday commands like `git remote add` or `git push -u` write to
18139
+ * `.git/config`), so the carve-out is emitted by default. Defaults to
18140
+ * `true`; only an explicit `false` suppresses it. Like
18141
+ * `base_permission_profile` it is consumed by the profile builder, not
18142
+ * written as a top-level config key.
17498
18143
  *
17499
18144
  * Two surfaces are deliberately NOT authorable here so the override can never
17500
18145
  * clobber a feature-owned key: `mcp_servers.*` per-MCP gating is owned by the
@@ -17520,7 +18165,8 @@ const CodexcliPermissionsOverrideSchema = z.looseObject({
17520
18165
  /** @deprecated Superseded by `base_permission_profile` (permission profiles). */
17521
18166
  sandbox_workspace_write: z.optional(z.looseObject({})),
17522
18167
  apps: z.optional(z.looseObject({})),
17523
- approvals_reviewer: z.optional(z.union([CodexApprovalsReviewerSchema, z.looseObject({})]))
18168
+ approvals_reviewer: z.optional(z.union([CodexApprovalsReviewerSchema, z.looseObject({})])),
18169
+ git_write_rules: z.optional(z.boolean())
17524
18170
  });
17525
18171
  /**
17526
18172
  * Permissions configuration.
@@ -19489,11 +20135,14 @@ var ClinePermissions = class ClinePermissions extends ToolPermissions {
19489
20135
  const RULESYNC_PROFILE_NAME = "rulesync";
19490
20136
  const CODEX_WORKSPACE_ROOTS_KEY = ":workspace_roots";
19491
20137
  const CODEX_WORKSPACE_BASELINE = ":workspace";
19492
- const CODEX_EXTENDABLE_BASELINES = new Set(CODEX_BASE_PERMISSION_PROFILES);
20138
+ const CODEX_READ_ONLY_BASELINE = ":read-only";
20139
+ const CODEX_DANGER_FULL_ACCESS_BASELINE = ":danger-full-access";
20140
+ const CODEX_EXTENDABLE_BASELINES = new Set(CODEX_EXTENDABLE_BASELINE_PROFILES);
19493
20141
  const CODEX_DEFAULT_APPROVAL_POLICY = "on-request";
19494
20142
  const CODEX_DEFAULT_APPROVALS_REVIEWER = "auto_review";
19495
20143
  const CODEX_GLOB_SCAN_MAX_DEPTH = 8;
19496
20144
  const CODEX_MINIMAL_KEY = ":minimal";
20145
+ const CODEX_GIT_WRITE_RULES = { ".git/**": "write" };
19497
20146
  const GLOBAL_WILDCARD_DOMAIN = "*";
19498
20147
  var CodexcliPermissions = class CodexcliPermissions extends ToolPermissions {
19499
20148
  static getSettablePaths(_options = {}) {
@@ -19521,8 +20170,40 @@ var CodexcliPermissions = class CodexcliPermissions extends ToolPermissions {
19521
20170
  const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
19522
20171
  const existingContent = await readFileContentOrNull(filePath) ?? "";
19523
20172
  const existing = toMutableTable(smolToml.parse(existingContent || smolToml.stringify({})));
20173
+ const canonicalConfig = rulesyncPermissions.getJson();
20174
+ if (canonicalConfig.codexcli?.base_permission_profile === CODEX_DANGER_FULL_ACCESS_BASELINE) {
20175
+ const ignoredCategories = Object.entries(canonicalConfig.permission).filter(([category, rules]) => category !== "bash" && Object.keys(rules).length > 0).map(([category]) => category);
20176
+ 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.`);
20177
+ const permissionsTable = toMutableTable(existing.permissions);
20178
+ if (permissionsTable[RULESYNC_PROFILE_NAME] !== void 0) {
20179
+ 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.`);
20180
+ delete permissionsTable[RULESYNC_PROFILE_NAME];
20181
+ }
20182
+ const overridePatch = computeCodexcliOverridePatch({
20183
+ existing,
20184
+ override: canonicalConfig.codexcli,
20185
+ logger
20186
+ });
20187
+ return new CodexcliPermissions({
20188
+ outputRoot,
20189
+ relativeDirPath: paths.relativeDirPath,
20190
+ relativeFilePath: paths.relativeFilePath,
20191
+ fileContent: applySharedConfigPatch({
20192
+ fileKey: sharedConfigFileKey(paths),
20193
+ feature: "permissions",
20194
+ existingContent,
20195
+ patch: {
20196
+ permissions: Object.keys(permissionsTable).length > 0 ? permissionsTable : void 0,
20197
+ default_permissions: CODEX_DANGER_FULL_ACCESS_BASELINE,
20198
+ ...overridePatch
20199
+ },
20200
+ filePath
20201
+ }),
20202
+ validate
20203
+ });
20204
+ }
19524
20205
  const newProfile = convertRulesyncToCodexProfile({
19525
- config: rulesyncPermissions.getJson(),
20206
+ config: canonicalConfig,
19526
20207
  logger
19527
20208
  });
19528
20209
  const permissionsTable = toMutableTable(existing.permissions);
@@ -19583,6 +20264,7 @@ var CodexcliPermissions = class CodexcliPermissions extends ToolPermissions {
19583
20264
  });
19584
20265
  const override = extractCodexcliOverride(table);
19585
20266
  if (typeof profile?.extends === "string" && CODEX_EXTENDABLE_BASELINES.has(profile.extends)) override.base_permission_profile = profile.extends;
20267
+ if (defaultProfile === CODEX_DANGER_FULL_ACCESS_BASELINE) override.base_permission_profile = CODEX_DANGER_FULL_ACCESS_BASELINE;
19586
20268
  const result = Object.keys(override).length > 0 ? {
19587
20269
  ...config,
19588
20270
  codexcli: override
@@ -19638,16 +20320,10 @@ function convertRulesyncToCodexProfile({ config, logger }) {
19638
20320
  const filesystem = { [CODEX_MINIMAL_KEY]: "read" };
19639
20321
  const workspaceRootFilesystem = {};
19640
20322
  const domains = {};
20323
+ const filesystemCategoryRules = {};
19641
20324
  for (const [toolName, rules] of Object.entries(config.permission)) {
19642
20325
  if (toolName === "read" || toolName === "edit" || toolName === "write") {
19643
- const mapAction = toolName === "read" ? mapReadAction : mapWriteAction;
19644
- for (const [pattern, action] of Object.entries(rules)) addFilesystemRule({
19645
- filesystem,
19646
- workspaceRootFilesystem,
19647
- pattern,
19648
- access: mapAction(action),
19649
- logger
19650
- });
20326
+ filesystemCategoryRules[toolName] = rules;
19651
20327
  continue;
19652
20328
  }
19653
20329
  if (toolName === "webfetch") {
@@ -19660,6 +20336,21 @@ function convertRulesyncToCodexProfile({ config, logger }) {
19660
20336
  }
19661
20337
  logger?.warn(`Codex CLI permissions support only read/edit/write/webfetch categories. Skipping: ${toolName}`);
19662
20338
  }
20339
+ for (const [pattern, access] of mergeFilesystemCategoryRules({
20340
+ categoryRules: filesystemCategoryRules,
20341
+ logger
20342
+ })) addFilesystemRule({
20343
+ filesystem,
20344
+ workspaceRootFilesystem,
20345
+ pattern,
20346
+ access,
20347
+ logger
20348
+ });
20349
+ applyDefaultGitWriteRules({
20350
+ config,
20351
+ filesystem,
20352
+ workspaceRootFilesystem
20353
+ });
19663
20354
  if (Object.keys(workspaceRootFilesystem).length > 0) {
19664
20355
  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.`);
19665
20356
  if (Object.keys(workspaceRootFilesystem).some((pattern) => pattern.includes("**"))) filesystem.glob_scan_max_depth = CODEX_GLOB_SCAN_MAX_DEPTH;
@@ -19677,6 +20368,12 @@ function convertRulesyncToCodexProfile({ config, logger }) {
19677
20368
  ...network ? { network } : {}
19678
20369
  };
19679
20370
  }
20371
+ function applyDefaultGitWriteRules({ config, filesystem, workspaceRootFilesystem }) {
20372
+ if (config.codexcli?.git_write_rules === false) return;
20373
+ if (config.codexcli?.base_permission_profile === CODEX_READ_ONLY_BASELINE) return;
20374
+ if (typeof filesystem[CODEX_WORKSPACE_ROOTS_KEY] === "string") return;
20375
+ for (const [pattern, access] of Object.entries(CODEX_GIT_WRITE_RULES)) workspaceRootFilesystem[pattern] ??= access;
20376
+ }
19680
20377
  function convertCodexProfileToRulesync({ profile, domainsHadUnknown }) {
19681
20378
  const permission = {};
19682
20379
  if (profile?.filesystem) {
@@ -19688,7 +20385,10 @@ function convertCodexProfileToRulesync({ profile, domainsHadUnknown }) {
19688
20385
  addRulesyncFilesystemRule(permission, pattern, access);
19689
20386
  continue;
19690
20387
  }
19691
- if (isCodexFilesystemRuleTable(access)) for (const [nestedPattern, nestedAccess] of Object.entries(access)) addRulesyncFilesystemRule(permission, nestedPattern, nestedAccess);
20388
+ if (isCodexFilesystemRuleTable(access)) for (const [nestedPattern, nestedAccess] of Object.entries(access)) {
20389
+ if (pattern === CODEX_WORKSPACE_ROOTS_KEY && CODEX_GIT_WRITE_RULES[nestedPattern] === nestedAccess) continue;
20390
+ addRulesyncFilesystemRule(permission, nestedPattern, nestedAccess);
20391
+ }
19692
20392
  }
19693
20393
  }
19694
20394
  if (profile?.network && profile.network.enabled !== false) {
@@ -19730,9 +20430,28 @@ function toCodexProfile(value) {
19730
20430
  }
19731
20431
  function warnAboutPreservedProfileState({ existingProfile, newProfile, existingDomainsHadUnknown, logger }) {
19732
20432
  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)"}".`);
20433
+ warnAboutPreservedNetworkState({
20434
+ existingProfile,
20435
+ newProfile,
20436
+ logger
20437
+ });
20438
+ if (existingDomainsHadUnknown) logger?.warn(`Existing "network.domains" contained unrecognized values. These entries were skipped and will not be imported.`);
20439
+ }
20440
+ function networkHasAllowDomain(network) {
20441
+ return Object.values(network?.domains ?? {}).some((action) => action === "allow");
20442
+ }
20443
+ function warnAboutNetworkEnabledState({ existingProfile, newProfile, logger }) {
20444
+ 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.`);
20445
+ 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.`);
20446
+ }
20447
+ function warnAboutPreservedNetworkState({ existingProfile, newProfile, logger }) {
19733
20448
  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.`);
20449
+ warnAboutNetworkEnabledState({
20450
+ existingProfile,
20451
+ newProfile,
20452
+ logger
20453
+ });
19734
20454
  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.`);
19735
- if (existingDomainsHadUnknown) logger?.warn(`Existing "network.domains" contained unrecognized values. These entries were skipped and will not be imported.`);
19736
20455
  }
19737
20456
  const MANAGED_PROFILE_KEYS = /* @__PURE__ */ new Set([
19738
20457
  "description",
@@ -19774,6 +20493,7 @@ function preserveUnmanagedProfileKeys({ rawExistingProfile, profile, logger }) {
19774
20493
  function mergeWithExistingProfile({ newProfile, existingProfile }) {
19775
20494
  if (!existingProfile) return newProfile;
19776
20495
  const mergedNetwork = { ...newProfile.network };
20496
+ if (existingProfile.network?.enabled !== void 0 && mergedNetwork.enabled === void 0 && !networkHasAllowDomain(existingProfile.network)) mergedNetwork.enabled = existingProfile.network.enabled;
19777
20497
  if (existingProfile.network?.mode !== void 0 && mergedNetwork.mode === void 0) mergedNetwork.mode = existingProfile.network.mode;
19778
20498
  if (existingProfile.network?.unix_sockets !== void 0 && mergedNetwork.unix_sockets === void 0) mergedNetwork.unix_sockets = existingProfile.network.unix_sockets;
19779
20499
  const hasNetwork = Object.keys(mergedNetwork).length > 0;
@@ -19825,7 +20545,7 @@ function computeCodexcliOverridePatch({ existing, override, logger }) {
19825
20545
  const patch = {};
19826
20546
  const allowed = new Set(CODEXCLI_OVERRIDE_KEYS);
19827
20547
  for (const [key, value] of Object.entries(override ?? {})) {
19828
- if (key === "base_permission_profile") continue;
20548
+ if (key === "base_permission_profile" || key === "git_write_rules") continue;
19829
20549
  if (!allowed.has(key)) {
19830
20550
  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.`);
19831
20551
  continue;
@@ -19906,6 +20626,67 @@ function mapReadAction(action) {
19906
20626
  function mapWriteAction(action) {
19907
20627
  return action === "allow" ? "write" : "deny";
19908
20628
  }
20629
+ /**
20630
+ * Merge the canonical read/edit/write category rules into one Codex access
20631
+ * level per path pattern (Codex models a single `deny` < `read` < `write`
20632
+ * level, with no `ask`).
20633
+ *
20634
+ * - `edit` and `write` collapse onto Codex's write side; when both carry the
20635
+ * same pattern, the more restrictive action wins (`deny` > `ask` > `allow`).
20636
+ * - `read: allow` + write-side `allow` → `"write"`.
20637
+ * - `read: allow` + write-side non-allow → `"read"` (readable but not
20638
+ * writable — exactly what Codex's `"read"` level expresses).
20639
+ * - `read` non-allow → `"deny"` regardless of the write side; a contradictory
20640
+ * write-side `allow` (unreadable but writable is not expressible in Codex)
20641
+ * is warned about.
20642
+ * - Single-category patterns keep the existing mapReadAction/mapWriteAction
20643
+ * mappings.
20644
+ *
20645
+ * Iteration order is read → edit → write with first-seen pattern order, so
20646
+ * the emitted table is stable regardless of the authored category order.
20647
+ * Note the merge is one-way: `"{path}" = "read"` imports back as
20648
+ * `read: allow` only (the explicit write-side deny is implied by Codex's
20649
+ * access level and not re-materialized).
20650
+ */
20651
+ function mergeFilesystemCategoryRules({ categoryRules, logger }) {
20652
+ const readRules = categoryRules.read ?? {};
20653
+ const writeSideRestrictiveness = {
20654
+ deny: 2,
20655
+ ask: 1,
20656
+ allow: 0
20657
+ };
20658
+ const writeSideRules = {};
20659
+ for (const category of ["edit", "write"]) for (const [pattern, action] of Object.entries(categoryRules[category] ?? {})) {
20660
+ const existing = writeSideRules[pattern];
20661
+ if (existing === void 0 || writeSideRestrictiveness[action] > writeSideRestrictiveness[existing]) writeSideRules[pattern] = action;
20662
+ }
20663
+ const patterns = [];
20664
+ const seen = /* @__PURE__ */ new Set();
20665
+ for (const pattern of [...Object.keys(readRules), ...Object.keys(writeSideRules)]) if (!seen.has(pattern)) {
20666
+ seen.add(pattern);
20667
+ patterns.push(pattern);
20668
+ }
20669
+ const merged = [];
20670
+ for (const pattern of patterns) {
20671
+ const readAction = readRules[pattern];
20672
+ const writeAction = writeSideRules[pattern];
20673
+ if (readAction === void 0) {
20674
+ merged.push([pattern, mapWriteAction(writeAction)]);
20675
+ continue;
20676
+ }
20677
+ if (writeAction === void 0) {
20678
+ merged.push([pattern, mapReadAction(readAction)]);
20679
+ continue;
20680
+ }
20681
+ if (readAction === "allow") {
20682
+ merged.push([pattern, writeAction === "allow" ? "write" : "read"]);
20683
+ continue;
20684
+ }
20685
+ 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".`);
20686
+ merged.push([pattern, "deny"]);
20687
+ }
20688
+ return merged;
20689
+ }
19909
20690
  function buildCodexBashRulesContent(config) {
19910
20691
  const bashRules = config.permission.bash ?? {};
19911
20692
  const entries = Object.entries(bashRules);
@@ -20857,7 +21638,7 @@ function convertRulesyncToGoosePermissionConfig({ config, logger }) {
20857
21638
  function convertGoosePermissionConfigToRulesync(userPermission) {
20858
21639
  const permission = {};
20859
21640
  for (const key of GOOSE_PERMISSION_LIST_KEYS) {
20860
- const toolNames = isStringArray(userPermission[key]) ? userPermission[key] : [];
21641
+ const toolNames = isStringArray$1(userPermission[key]) ? userPermission[key] : [];
20861
21642
  const action = GOOSE_LIST_TO_ACTION[key];
20862
21643
  for (const toolName of toolNames) {
20863
21644
  const category = GOOSE_TO_RULESYNC_TOOL_NAME[toolName] ?? toolName;
@@ -21097,7 +21878,7 @@ const ACTION_RANK = {
21097
21878
  * (mirrors the Cursor adapter's preservation of unmanaged types).
21098
21879
  */
21099
21880
  function unmanagedEntries(existingPermission, key) {
21100
- return (isStringArray(existingPermission[key]) ? existingPermission[key] : []).filter((entry) => parseGrokEntry(entry) === null);
21881
+ return (isStringArray$1(existingPermission[key]) ? existingPermission[key] : []).filter((entry) => parseGrokEntry(entry) === null);
21101
21882
  }
21102
21883
  /**
21103
21884
  * Bucket canonical rules into Grok's `[permission]` allow/deny/ask arrays of
@@ -21138,9 +21919,9 @@ function buildGrokPermissionArrays(config, existingPermission, logger) {
21138
21919
  * arrays resolves to the strictest action.
21139
21920
  */
21140
21921
  function parseGrokPermissionArrays(permission) {
21141
- const allow = isStringArray(permission.allow) ? permission.allow : void 0;
21142
- const deny = isStringArray(permission.deny) ? permission.deny : void 0;
21143
- const ask = isStringArray(permission.ask) ? permission.ask : void 0;
21922
+ const allow = isStringArray$1(permission.allow) ? permission.allow : void 0;
21923
+ const deny = isStringArray$1(permission.deny) ? permission.deny : void 0;
21924
+ const ask = isStringArray$1(permission.ask) ? permission.ask : void 0;
21144
21925
  if ((allow?.length ?? 0) + (deny?.length ?? 0) + (ask?.length ?? 0) === 0) return null;
21145
21926
  const result = {};
21146
21927
  const apply = (entries, action) => {
@@ -22921,7 +23702,7 @@ function convertRovodevToolPermissionsToRulesync(toolPermissions) {
22921
23702
  permission[category][CATCH_ALL_PATTERN$1] = value;
22922
23703
  }
22923
23704
  }
22924
- if (isStringArray(toolPermissions.allowedExternalPaths)) for (const path of toolPermissions.allowedExternalPaths) {
23705
+ if (isStringArray$1(toolPermissions.allowedExternalPaths)) for (const path of toolPermissions.allowedExternalPaths) {
22925
23706
  permission.read ??= {};
22926
23707
  permission.read[path] = "allow";
22927
23708
  }
@@ -23481,8 +24262,8 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
23481
24262
  const agents = isRecord(settings.agents) ? settings.agents : {};
23482
24263
  const profiles = isRecord(agents.profiles) ? agents.profiles : {};
23483
24264
  const config = convertWarpToRulesyncPermissions({
23484
- allow: isStringArray(profiles[ALLOWLIST_KEY]) ? profiles[ALLOWLIST_KEY] : [],
23485
- deny: isStringArray(profiles[DENYLIST_KEY]) ? profiles[DENYLIST_KEY] : []
24265
+ allow: isStringArray$1(profiles[ALLOWLIST_KEY]) ? profiles[ALLOWLIST_KEY] : [],
24266
+ deny: isStringArray$1(profiles[DENYLIST_KEY]) ? profiles[DENYLIST_KEY] : []
23486
24267
  });
23487
24268
  const warpOverride = {};
23488
24269
  for (const key of WARP_OVERRIDE_KEYS) if (profiles[key] !== void 0) warpOverride[key] = profiles[key];
@@ -24062,9 +24843,6 @@ var PermissionsProcessor = class extends FeatureProcessor {
24062
24843
  }
24063
24844
  };
24064
24845
  //#endregion
24065
- //#region src/constants/general.ts
24066
- const SKILL_FILE_NAME = "SKILL.md";
24067
- //#endregion
24068
24846
  //#region src/types/ai-dir.ts
24069
24847
  var AiDir = class {
24070
24848
  /**
@@ -24265,10 +25043,10 @@ var ToolSkill = class extends AiDir {
24265
25043
  const settablePaths = getSettablePaths({ global });
24266
25044
  const actualRelativeDirPath = relativeDirPath ?? settablePaths.relativeDirPath;
24267
25045
  const skillDirPath = join(outputRoot, actualRelativeDirPath, dirName);
24268
- const skillFilePath = join(skillDirPath, SKILL_FILE_NAME);
24269
- if (!await fileExists(skillFilePath)) throw new Error(`${SKILL_FILE_NAME} not found in ${skillDirPath}`);
25046
+ const skillFilePath = join(skillDirPath, SKILL_FILE_NAME$1);
25047
+ if (!await fileExists(skillFilePath)) throw new Error(`${SKILL_FILE_NAME$1} not found in ${skillDirPath}`);
24270
25048
  const { frontmatter, body: content } = parseFrontmatter(await readFileContent(skillFilePath), skillFilePath);
24271
- const otherFiles = await this.collectOtherFiles(outputRoot, actualRelativeDirPath, dirName, SKILL_FILE_NAME);
25049
+ const otherFiles = await this.collectOtherFiles(outputRoot, actualRelativeDirPath, dirName, SKILL_FILE_NAME$1);
24272
25050
  return {
24273
25051
  outputRoot,
24274
25052
  relativeDirPath: actualRelativeDirPath,
@@ -24310,7 +25088,7 @@ var SimulatedSkill = class extends ToolSkill {
24310
25088
  relativeDirPath,
24311
25089
  dirName,
24312
25090
  mainFile: {
24313
- name: SKILL_FILE_NAME,
25091
+ name: SKILL_FILE_NAME$1,
24314
25092
  body,
24315
25093
  frontmatter: { ...frontmatter }
24316
25094
  },
@@ -24368,12 +25146,12 @@ var SimulatedSkill = class extends ToolSkill {
24368
25146
  const settablePaths = this.getSettablePaths();
24369
25147
  const actualRelativeDirPath = relativeDirPath ?? settablePaths.relativeDirPath;
24370
25148
  const skillDirPath = join(outputRoot, actualRelativeDirPath, dirName);
24371
- const skillFilePath = join(skillDirPath, SKILL_FILE_NAME);
24372
- if (!await fileExists(skillFilePath)) throw new Error(`${SKILL_FILE_NAME} not found in ${skillDirPath}`);
25149
+ const skillFilePath = join(skillDirPath, SKILL_FILE_NAME$1);
25150
+ if (!await fileExists(skillFilePath)) throw new Error(`${SKILL_FILE_NAME$1} not found in ${skillDirPath}`);
24373
25151
  const { frontmatter, body: content } = parseFrontmatter(await readFileContent(skillFilePath), skillFilePath);
24374
25152
  const result = SimulatedSkillFrontmatterSchema.safeParse(frontmatter);
24375
25153
  if (!result.success) throw new Error(`Invalid frontmatter in ${skillFilePath}: ${formatError(result.error)}`);
24376
- const otherFiles = await this.collectOtherFiles(outputRoot, actualRelativeDirPath, dirName, SKILL_FILE_NAME);
25154
+ const otherFiles = await this.collectOtherFiles(outputRoot, actualRelativeDirPath, dirName, SKILL_FILE_NAME$1);
24377
25155
  return {
24378
25156
  outputRoot,
24379
25157
  relativeDirPath: actualRelativeDirPath,
@@ -24583,7 +25361,7 @@ var RulesyncSkill = class RulesyncSkill extends AiDir {
24583
25361
  relativeDirPath,
24584
25362
  dirName,
24585
25363
  mainFile: {
24586
- name: SKILL_FILE_NAME,
25364
+ name: SKILL_FILE_NAME$1,
24587
25365
  body,
24588
25366
  frontmatter: { ...frontmatter }
24589
25367
  },
@@ -24618,13 +25396,13 @@ var RulesyncSkill = class RulesyncSkill extends AiDir {
24618
25396
  }
24619
25397
  static async fromDir({ outputRoot = process.cwd(), relativeDirPath = RULESYNC_SKILLS_RELATIVE_DIR_PATH, dirName, global = false }) {
24620
25398
  const skillDirPath = join(outputRoot, relativeDirPath, dirName);
24621
- const skillFilePath = join(skillDirPath, SKILL_FILE_NAME);
24622
- if (!await fileExists(skillFilePath)) throw new Error(`${SKILL_FILE_NAME} not found in ${skillDirPath}`);
25399
+ const skillFilePath = join(skillDirPath, SKILL_FILE_NAME$1);
25400
+ if (!await fileExists(skillFilePath)) throw new Error(`${SKILL_FILE_NAME$1} not found in ${skillDirPath}`);
24623
25401
  const { frontmatter, body: content, hasFrontmatter } = parseFrontmatter(await readFileContent(skillFilePath), skillFilePath);
24624
25402
  if (!hasFrontmatter) throw new Error(`Missing frontmatter in ${skillFilePath}. Rulesync files must begin with a YAML frontmatter block delimited by '---'.`);
24625
25403
  const result = RulesyncSkillFrontmatterSchema.safeParse(frontmatter);
24626
25404
  if (!result.success) throw new Error(`Invalid frontmatter in ${skillFilePath}: ${formatError(result.error)}`);
24627
- const otherFiles = await this.collectOtherFiles(outputRoot, relativeDirPath, dirName, SKILL_FILE_NAME);
25405
+ const otherFiles = await this.collectOtherFiles(outputRoot, relativeDirPath, dirName, SKILL_FILE_NAME$1);
24628
25406
  return new RulesyncSkill({
24629
25407
  outputRoot,
24630
25408
  relativeDirPath,
@@ -24664,7 +25442,7 @@ var RovodevSkill = class RovodevSkill extends ToolSkill {
24664
25442
  relativeDirPath,
24665
25443
  dirName,
24666
25444
  mainFile: {
24667
- name: SKILL_FILE_NAME,
25445
+ name: SKILL_FILE_NAME$1,
24668
25446
  body,
24669
25447
  frontmatter: { ...frontmatter }
24670
25448
  },
@@ -24691,7 +25469,7 @@ var RovodevSkill = class RovodevSkill extends ToolSkill {
24691
25469
  validate() {
24692
25470
  if (!this.mainFile) return {
24693
25471
  success: false,
24694
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
25472
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
24695
25473
  };
24696
25474
  const result = RovodevSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
24697
25475
  if (!result.success) return {
@@ -24767,10 +25545,10 @@ var RovodevSkill = class RovodevSkill extends ToolSkill {
24767
25545
  const result = RovodevSkillFrontmatterSchema.safeParse(loaded.frontmatter);
24768
25546
  if (!result.success) {
24769
25547
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
24770
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
25548
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
24771
25549
  }
24772
25550
  if (result.data.name !== loaded.dirName) {
24773
- const skillFilePath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME);
25551
+ const skillFilePath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME$1);
24774
25552
  throw new Error(`Frontmatter name (${result.data.name}) must match directory name (${loaded.dirName}) in ${skillFilePath}`);
24775
25553
  }
24776
25554
  return new RovodevSkill({
@@ -24935,7 +25713,7 @@ var AgentsSkillsSkill = class AgentsSkillsSkill extends ToolSkill {
24935
25713
  relativeDirPath,
24936
25714
  dirName,
24937
25715
  mainFile: {
24938
- name: SKILL_FILE_NAME,
25716
+ name: SKILL_FILE_NAME$1,
24939
25717
  body,
24940
25718
  frontmatter: { ...frontmatter }
24941
25719
  },
@@ -24959,7 +25737,7 @@ var AgentsSkillsSkill = class AgentsSkillsSkill extends ToolSkill {
24959
25737
  validate() {
24960
25738
  if (!this.mainFile) return {
24961
25739
  success: false,
24962
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
25740
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
24963
25741
  };
24964
25742
  const result = AgentsSkillsSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
24965
25743
  if (!result.success) return {
@@ -25031,7 +25809,7 @@ var AgentsSkillsSkill = class AgentsSkillsSkill extends ToolSkill {
25031
25809
  const result = AgentsSkillsSkillFrontmatterSchema.safeParse(loaded.frontmatter);
25032
25810
  if (!result.success) {
25033
25811
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
25034
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
25812
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
25035
25813
  }
25036
25814
  return new this({
25037
25815
  outputRoot: loaded.outputRoot,
@@ -25088,7 +25866,7 @@ var AiassistantSkill = class AiassistantSkill extends ToolSkill {
25088
25866
  relativeDirPath,
25089
25867
  dirName,
25090
25868
  mainFile: {
25091
- name: SKILL_FILE_NAME,
25869
+ name: SKILL_FILE_NAME$1,
25092
25870
  body,
25093
25871
  frontmatter: { ...frontmatter }
25094
25872
  },
@@ -25113,7 +25891,7 @@ var AiassistantSkill = class AiassistantSkill extends ToolSkill {
25113
25891
  validate() {
25114
25892
  if (!this.mainFile) return {
25115
25893
  success: false,
25116
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
25894
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
25117
25895
  };
25118
25896
  const result = AiassistantSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
25119
25897
  if (!result.success) return {
@@ -25173,7 +25951,7 @@ var AiassistantSkill = class AiassistantSkill extends ToolSkill {
25173
25951
  const result = AiassistantSkillFrontmatterSchema.safeParse(loaded.frontmatter);
25174
25952
  if (!result.success) {
25175
25953
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
25176
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
25954
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
25177
25955
  }
25178
25956
  return new AiassistantSkill({
25179
25957
  outputRoot: loaded.outputRoot,
@@ -25224,7 +26002,7 @@ var AmpSkill = class AmpSkill extends ToolSkill {
25224
26002
  relativeDirPath,
25225
26003
  dirName,
25226
26004
  mainFile: {
25227
- name: SKILL_FILE_NAME,
26005
+ name: SKILL_FILE_NAME$1,
25228
26006
  body,
25229
26007
  frontmatter: { ...frontmatter }
25230
26008
  },
@@ -25248,7 +26026,7 @@ var AmpSkill = class AmpSkill extends ToolSkill {
25248
26026
  validate() {
25249
26027
  if (!this.mainFile) return {
25250
26028
  success: false,
25251
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
26029
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
25252
26030
  };
25253
26031
  const result = AmpSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
25254
26032
  if (!result.success) return {
@@ -25308,7 +26086,7 @@ var AmpSkill = class AmpSkill extends ToolSkill {
25308
26086
  const result = AmpSkillFrontmatterSchema.safeParse(loaded.frontmatter);
25309
26087
  if (!result.success) {
25310
26088
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
25311
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
26089
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
25312
26090
  }
25313
26091
  return new AmpSkill({
25314
26092
  outputRoot: loaded.outputRoot,
@@ -25364,7 +26142,7 @@ var AntigravitySharedSkill = class extends ToolSkill {
25364
26142
  relativeDirPath,
25365
26143
  dirName,
25366
26144
  mainFile: {
25367
- name: SKILL_FILE_NAME,
26145
+ name: SKILL_FILE_NAME$1,
25368
26146
  body,
25369
26147
  frontmatter: { ...frontmatter }
25370
26148
  },
@@ -25397,7 +26175,7 @@ var AntigravitySharedSkill = class extends ToolSkill {
25397
26175
  validate() {
25398
26176
  if (this.mainFile === void 0) return {
25399
26177
  success: false,
25400
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
26178
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
25401
26179
  };
25402
26180
  const result = AntigravitySkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
25403
26181
  if (!result.success) return {
@@ -25457,7 +26235,7 @@ var AntigravitySharedSkill = class extends ToolSkill {
25457
26235
  const result = AntigravitySkillFrontmatterSchema.safeParse(loaded.frontmatter);
25458
26236
  if (!result.success) {
25459
26237
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
25460
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
26238
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
25461
26239
  }
25462
26240
  return new this({
25463
26241
  outputRoot: loaded.outputRoot,
@@ -25541,7 +26319,7 @@ var AugmentcodeSkill = class AugmentcodeSkill extends ToolSkill {
25541
26319
  relativeDirPath,
25542
26320
  dirName,
25543
26321
  mainFile: {
25544
- name: SKILL_FILE_NAME,
26322
+ name: SKILL_FILE_NAME$1,
25545
26323
  body,
25546
26324
  frontmatter: { ...frontmatter }
25547
26325
  },
@@ -25568,7 +26346,7 @@ var AugmentcodeSkill = class AugmentcodeSkill extends ToolSkill {
25568
26346
  validate() {
25569
26347
  if (this.mainFile === void 0) return {
25570
26348
  success: false,
25571
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
26349
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
25572
26350
  };
25573
26351
  const result = AugmentcodeSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
25574
26352
  if (!result.success) return {
@@ -25628,7 +26406,7 @@ var AugmentcodeSkill = class AugmentcodeSkill extends ToolSkill {
25628
26406
  const result = AugmentcodeSkillFrontmatterSchema.safeParse(loaded.frontmatter);
25629
26407
  if (!result.success) {
25630
26408
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
25631
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
26409
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
25632
26410
  }
25633
26411
  return new AugmentcodeSkill({
25634
26412
  outputRoot: loaded.outputRoot,
@@ -25769,7 +26547,7 @@ var ClaudecodeSkill = class ClaudecodeSkill extends ToolSkill {
25769
26547
  relativeDirPath,
25770
26548
  dirName,
25771
26549
  mainFile: {
25772
- name: SKILL_FILE_NAME,
26550
+ name: SKILL_FILE_NAME$1,
25773
26551
  body,
25774
26552
  frontmatter: { ...frontmatter }
25775
26553
  },
@@ -25796,7 +26574,7 @@ var ClaudecodeSkill = class ClaudecodeSkill extends ToolSkill {
25796
26574
  validate() {
25797
26575
  if (this.mainFile === void 0) return {
25798
26576
  success: false,
25799
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
26577
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
25800
26578
  };
25801
26579
  const result = ClaudecodeSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
25802
26580
  if (!result.success) return {
@@ -25884,7 +26662,7 @@ var ClaudecodeSkill = class ClaudecodeSkill extends ToolSkill {
25884
26662
  const result = ClaudecodeSkillFrontmatterSchema.safeParse(loaded.frontmatter);
25885
26663
  if (!result.success) {
25886
26664
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
25887
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
26665
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
25888
26666
  }
25889
26667
  return new ClaudecodeSkill({
25890
26668
  outputRoot: loaded.outputRoot,
@@ -25930,7 +26708,7 @@ var ClineSkill = class ClineSkill extends ToolSkill {
25930
26708
  relativeDirPath,
25931
26709
  dirName,
25932
26710
  mainFile: {
25933
- name: SKILL_FILE_NAME,
26711
+ name: SKILL_FILE_NAME$1,
25934
26712
  body,
25935
26713
  frontmatter: { ...frontmatter }
25936
26714
  },
@@ -25954,7 +26732,7 @@ var ClineSkill = class ClineSkill extends ToolSkill {
25954
26732
  validate() {
25955
26733
  if (!this.mainFile) return {
25956
26734
  success: false,
25957
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
26735
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
25958
26736
  };
25959
26737
  const result = ClineSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
25960
26738
  if (!result.success) return {
@@ -26018,10 +26796,10 @@ var ClineSkill = class ClineSkill extends ToolSkill {
26018
26796
  const result = ClineSkillFrontmatterSchema.safeParse(loaded.frontmatter);
26019
26797
  if (!result.success) {
26020
26798
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
26021
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
26799
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
26022
26800
  }
26023
26801
  if (result.data.name !== loaded.dirName) {
26024
- const skillFilePath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME);
26802
+ const skillFilePath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME$1);
26025
26803
  throw new Error(`Frontmatter name (${result.data.name}) must match directory name (${loaded.dirName}) in ${skillFilePath}`);
26026
26804
  }
26027
26805
  return new ClineSkill({
@@ -26135,7 +26913,7 @@ var CodexCliSkill = class CodexCliSkill extends ToolSkill {
26135
26913
  relativeDirPath,
26136
26914
  dirName,
26137
26915
  mainFile: {
26138
- name: SKILL_FILE_NAME,
26916
+ name: SKILL_FILE_NAME$1,
26139
26917
  body,
26140
26918
  frontmatter: { ...frontmatter }
26141
26919
  },
@@ -26159,7 +26937,7 @@ var CodexCliSkill = class CodexCliSkill extends ToolSkill {
26159
26937
  validate() {
26160
26938
  if (!this.mainFile) return {
26161
26939
  success: false,
26162
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
26940
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
26163
26941
  };
26164
26942
  const result = CodexCliSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
26165
26943
  if (!result.success) return {
@@ -26237,7 +27015,7 @@ var CodexCliSkill = class CodexCliSkill extends ToolSkill {
26237
27015
  const result = CodexCliSkillFrontmatterSchema.safeParse(loaded.frontmatter);
26238
27016
  if (!result.success) {
26239
27017
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
26240
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
27018
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
26241
27019
  }
26242
27020
  return new CodexCliSkill({
26243
27021
  outputRoot: loaded.outputRoot,
@@ -26289,7 +27067,7 @@ var CopilotSkill = class CopilotSkill extends ToolSkill {
26289
27067
  relativeDirPath,
26290
27068
  dirName,
26291
27069
  mainFile: {
26292
- name: SKILL_FILE_NAME,
27070
+ name: SKILL_FILE_NAME$1,
26293
27071
  body,
26294
27072
  frontmatter: { ...frontmatter }
26295
27073
  },
@@ -26314,7 +27092,7 @@ var CopilotSkill = class CopilotSkill extends ToolSkill {
26314
27092
  validate() {
26315
27093
  if (!this.mainFile) return {
26316
27094
  success: false,
26317
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
27095
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
26318
27096
  };
26319
27097
  const result = CopilotSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
26320
27098
  if (!result.success) return {
@@ -26381,7 +27159,7 @@ var CopilotSkill = class CopilotSkill extends ToolSkill {
26381
27159
  const result = CopilotSkillFrontmatterSchema.safeParse(loaded.frontmatter);
26382
27160
  if (!result.success) {
26383
27161
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
26384
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
27162
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
26385
27163
  }
26386
27164
  return new CopilotSkill({
26387
27165
  outputRoot: loaded.outputRoot,
@@ -26435,7 +27213,7 @@ var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
26435
27213
  relativeDirPath,
26436
27214
  dirName,
26437
27215
  mainFile: {
26438
- name: SKILL_FILE_NAME,
27216
+ name: SKILL_FILE_NAME$1,
26439
27217
  body,
26440
27218
  frontmatter: { ...frontmatter }
26441
27219
  },
@@ -26460,7 +27238,7 @@ var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
26460
27238
  validate() {
26461
27239
  if (!this.mainFile) return {
26462
27240
  success: false,
26463
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
27241
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
26464
27242
  };
26465
27243
  const result = CopilotcliSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
26466
27244
  if (!result.success) return {
@@ -26529,7 +27307,7 @@ var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
26529
27307
  const result = CopilotcliSkillFrontmatterSchema.safeParse(loaded.frontmatter);
26530
27308
  if (!result.success) {
26531
27309
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
26532
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
27310
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
26533
27311
  }
26534
27312
  return new CopilotcliSkill({
26535
27313
  outputRoot: loaded.outputRoot,
@@ -26579,7 +27357,7 @@ var CursorSkill = class CursorSkill extends ToolSkill {
26579
27357
  relativeDirPath,
26580
27358
  dirName,
26581
27359
  mainFile: {
26582
- name: SKILL_FILE_NAME,
27360
+ name: SKILL_FILE_NAME$1,
26583
27361
  body,
26584
27362
  frontmatter: { ...frontmatter }
26585
27363
  },
@@ -26603,7 +27381,7 @@ var CursorSkill = class CursorSkill extends ToolSkill {
26603
27381
  validate() {
26604
27382
  if (!this.mainFile) return {
26605
27383
  success: false,
26606
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
27384
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
26607
27385
  };
26608
27386
  const result = CursorSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
26609
27387
  if (!result.success) return {
@@ -26677,7 +27455,7 @@ var CursorSkill = class CursorSkill extends ToolSkill {
26677
27455
  const result = CursorSkillFrontmatterSchema.safeParse(loaded.frontmatter);
26678
27456
  if (!result.success) {
26679
27457
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
26680
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
27458
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
26681
27459
  }
26682
27460
  return new CursorSkill({
26683
27461
  outputRoot: loaded.outputRoot,
@@ -26724,7 +27502,7 @@ var DeepagentsSkill = class DeepagentsSkill extends ToolSkill {
26724
27502
  relativeDirPath,
26725
27503
  dirName,
26726
27504
  mainFile: {
26727
- name: SKILL_FILE_NAME,
27505
+ name: SKILL_FILE_NAME$1,
26728
27506
  body,
26729
27507
  frontmatter: { ...frontmatter }
26730
27508
  },
@@ -26748,7 +27526,7 @@ var DeepagentsSkill = class DeepagentsSkill extends ToolSkill {
26748
27526
  validate() {
26749
27527
  if (!this.mainFile) return {
26750
27528
  success: false,
26751
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
27529
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
26752
27530
  };
26753
27531
  const result = DeepagentsSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
26754
27532
  if (!result.success) return {
@@ -26824,7 +27602,7 @@ var DeepagentsSkill = class DeepagentsSkill extends ToolSkill {
26824
27602
  const result = DeepagentsSkillFrontmatterSchema.safeParse(loaded.frontmatter);
26825
27603
  if (!result.success) {
26826
27604
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
26827
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
27605
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
26828
27606
  }
26829
27607
  return new DeepagentsSkill({
26830
27608
  outputRoot: loaded.outputRoot,
@@ -26875,7 +27653,7 @@ var DevinSkill = class DevinSkill extends ToolSkill {
26875
27653
  relativeDirPath,
26876
27654
  dirName,
26877
27655
  mainFile: {
26878
- name: SKILL_FILE_NAME,
27656
+ name: SKILL_FILE_NAME$1,
26879
27657
  body,
26880
27658
  frontmatter: { ...frontmatter }
26881
27659
  },
@@ -26900,7 +27678,7 @@ var DevinSkill = class DevinSkill extends ToolSkill {
26900
27678
  validate() {
26901
27679
  if (!this.mainFile) return {
26902
27680
  success: false,
26903
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
27681
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
26904
27682
  };
26905
27683
  const result = DevinSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
26906
27684
  if (!result.success) return {
@@ -26952,6 +27730,18 @@ var DevinSkill = class DevinSkill extends ToolSkill {
26952
27730
  const targets = rulesyncSkill.getFrontmatter().targets;
26953
27731
  return targets.includes("*") || targets.includes("devin");
26954
27732
  }
27733
+ /**
27734
+ * Commands are emitted into this same skills tree as `<slug>/SKILL.md`
27735
+ * (see `DevinCommand`), so a directory matching a current rulesync command
27736
+ * slug is owned by the commands feature: it must not be imported as a
27737
+ * skill nor deleted as an orphan skill.
27738
+ */
27739
+ static async isDirOwned({ dirName, inputRoot }) {
27740
+ return !await rulesyncCommandSlugExists({
27741
+ inputRoot,
27742
+ dirName
27743
+ });
27744
+ }
26955
27745
  static async fromDir(params) {
26956
27746
  const loaded = await this.loadSkillDirContent({
26957
27747
  ...params,
@@ -26960,7 +27750,7 @@ var DevinSkill = class DevinSkill extends ToolSkill {
26960
27750
  const result = DevinSkillFrontmatterSchema.safeParse(loaded.frontmatter);
26961
27751
  if (!result.success) {
26962
27752
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
26963
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
27753
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
26964
27754
  }
26965
27755
  return new DevinSkill({
26966
27756
  outputRoot: loaded.outputRoot,
@@ -27012,7 +27802,7 @@ var FactorydroidSkill = class FactorydroidSkill extends ToolSkill {
27012
27802
  relativeDirPath,
27013
27803
  dirName,
27014
27804
  mainFile: {
27015
- name: SKILL_FILE_NAME,
27805
+ name: SKILL_FILE_NAME$1,
27016
27806
  body,
27017
27807
  frontmatter: { ...frontmatter }
27018
27808
  },
@@ -27036,7 +27826,7 @@ var FactorydroidSkill = class FactorydroidSkill extends ToolSkill {
27036
27826
  validate() {
27037
27827
  if (this.mainFile === void 0) return {
27038
27828
  success: false,
27039
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
27829
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
27040
27830
  };
27041
27831
  const result = FactorydroidSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
27042
27832
  if (!result.success) return {
@@ -27111,7 +27901,7 @@ var FactorydroidSkill = class FactorydroidSkill extends ToolSkill {
27111
27901
  const result = FactorydroidSkillFrontmatterSchema.safeParse(loaded.frontmatter);
27112
27902
  if (!result.success) {
27113
27903
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
27114
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
27904
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
27115
27905
  }
27116
27906
  return new FactorydroidSkill({
27117
27907
  outputRoot: loaded.outputRoot,
@@ -27158,7 +27948,7 @@ var GooseSkill = class GooseSkill extends ToolSkill {
27158
27948
  relativeDirPath,
27159
27949
  dirName,
27160
27950
  mainFile: {
27161
- name: SKILL_FILE_NAME,
27951
+ name: SKILL_FILE_NAME$1,
27162
27952
  body,
27163
27953
  frontmatter: { ...frontmatter }
27164
27954
  },
@@ -27183,7 +27973,7 @@ var GooseSkill = class GooseSkill extends ToolSkill {
27183
27973
  validate() {
27184
27974
  if (!this.mainFile) return {
27185
27975
  success: false,
27186
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
27976
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
27187
27977
  };
27188
27978
  const result = GooseSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
27189
27979
  if (!result.success) return {
@@ -27243,7 +28033,7 @@ var GooseSkill = class GooseSkill extends ToolSkill {
27243
28033
  const result = GooseSkillFrontmatterSchema.safeParse(loaded.frontmatter);
27244
28034
  if (!result.success) {
27245
28035
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
27246
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
28036
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
27247
28037
  }
27248
28038
  return new GooseSkill({
27249
28039
  outputRoot: loaded.outputRoot,
@@ -27296,7 +28086,7 @@ var GrokcliSkill = class GrokcliSkill extends ToolSkill {
27296
28086
  relativeDirPath,
27297
28087
  dirName,
27298
28088
  mainFile: {
27299
- name: SKILL_FILE_NAME,
28089
+ name: SKILL_FILE_NAME$1,
27300
28090
  body,
27301
28091
  frontmatter: { ...frontmatter }
27302
28092
  },
@@ -27320,7 +28110,7 @@ var GrokcliSkill = class GrokcliSkill extends ToolSkill {
27320
28110
  validate() {
27321
28111
  if (this.mainFile === void 0) return {
27322
28112
  success: false,
27323
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
28113
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
27324
28114
  };
27325
28115
  const result = GrokcliSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
27326
28116
  if (!result.success) return {
@@ -27380,7 +28170,7 @@ var GrokcliSkill = class GrokcliSkill extends ToolSkill {
27380
28170
  const result = GrokcliSkillFrontmatterSchema.safeParse(loaded.frontmatter);
27381
28171
  if (!result.success) {
27382
28172
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
27383
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
28173
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
27384
28174
  }
27385
28175
  return new GrokcliSkill({
27386
28176
  outputRoot: loaded.outputRoot,
@@ -27415,6 +28205,18 @@ var HermesagentSkill = class extends AgentsSkillsSkill {
27415
28205
  static getSettablePaths() {
27416
28206
  return { relativeDirPath: HERMESAGENT_SKILLS_DIR_PATH };
27417
28207
  }
28208
+ /**
28209
+ * Commands are emitted into this same skills tree as `<slug>/SKILL.md`
28210
+ * (see `HermesagentCommand`), so a directory matching a current rulesync
28211
+ * command slug is owned by the commands feature: it must not be imported
28212
+ * as a skill nor deleted as an orphan skill.
28213
+ */
28214
+ static async isDirOwned({ dirName, inputRoot }) {
28215
+ return !await rulesyncCommandSlugExists({
28216
+ inputRoot,
28217
+ dirName
28218
+ });
28219
+ }
27418
28220
  constructor(params) {
27419
28221
  super({
27420
28222
  ...params,
@@ -27439,7 +28241,7 @@ var JunieSkill = class JunieSkill extends ToolSkill {
27439
28241
  relativeDirPath,
27440
28242
  dirName,
27441
28243
  mainFile: {
27442
- name: SKILL_FILE_NAME,
28244
+ name: SKILL_FILE_NAME$1,
27443
28245
  body,
27444
28246
  frontmatter: { ...frontmatter }
27445
28247
  },
@@ -27463,7 +28265,7 @@ var JunieSkill = class JunieSkill extends ToolSkill {
27463
28265
  validate() {
27464
28266
  if (!this.mainFile) return {
27465
28267
  success: false,
27466
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
28268
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
27467
28269
  };
27468
28270
  const result = JunieSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
27469
28271
  if (!result.success) return {
@@ -27527,10 +28329,10 @@ var JunieSkill = class JunieSkill extends ToolSkill {
27527
28329
  const result = JunieSkillFrontmatterSchema.safeParse(loaded.frontmatter);
27528
28330
  if (!result.success) {
27529
28331
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
27530
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
28332
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
27531
28333
  }
27532
28334
  if (result.data.name !== loaded.dirName) {
27533
- const skillFilePath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME);
28335
+ const skillFilePath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME$1);
27534
28336
  throw new Error(`Frontmatter name (${result.data.name}) must match directory name (${loaded.dirName}) in ${skillFilePath}`);
27535
28337
  }
27536
28338
  return new JunieSkill({
@@ -27578,7 +28380,7 @@ var KiloSkill = class KiloSkill extends ToolSkill {
27578
28380
  relativeDirPath,
27579
28381
  dirName,
27580
28382
  mainFile: {
27581
- name: SKILL_FILE_NAME,
28383
+ name: SKILL_FILE_NAME$1,
27582
28384
  body,
27583
28385
  frontmatter: { ...frontmatter }
27584
28386
  },
@@ -27602,7 +28404,7 @@ var KiloSkill = class KiloSkill extends ToolSkill {
27602
28404
  validate() {
27603
28405
  if (this.mainFile === void 0) return {
27604
28406
  success: false,
27605
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
28407
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
27606
28408
  };
27607
28409
  const result = KiloSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
27608
28410
  if (!result.success) return {
@@ -27674,7 +28476,7 @@ var KiloSkill = class KiloSkill extends ToolSkill {
27674
28476
  const result = KiloSkillFrontmatterSchema.safeParse(loaded.frontmatter);
27675
28477
  if (!result.success) {
27676
28478
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
27677
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
28479
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
27678
28480
  }
27679
28481
  return new KiloSkill({
27680
28482
  outputRoot: loaded.outputRoot,
@@ -27720,7 +28522,7 @@ var KiroSkill = class KiroSkill extends ToolSkill {
27720
28522
  relativeDirPath,
27721
28523
  dirName,
27722
28524
  mainFile: {
27723
- name: SKILL_FILE_NAME,
28525
+ name: SKILL_FILE_NAME$1,
27724
28526
  body,
27725
28527
  frontmatter: { ...frontmatter }
27726
28528
  },
@@ -27744,7 +28546,7 @@ var KiroSkill = class KiroSkill extends ToolSkill {
27744
28546
  validate() {
27745
28547
  if (!this.mainFile) return {
27746
28548
  success: false,
27747
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
28549
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
27748
28550
  };
27749
28551
  const result = KiroSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
27750
28552
  if (!result.success) return {
@@ -27808,10 +28610,10 @@ var KiroSkill = class KiroSkill extends ToolSkill {
27808
28610
  const result = KiroSkillFrontmatterSchema.safeParse(loaded.frontmatter);
27809
28611
  if (!result.success) {
27810
28612
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
27811
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
28613
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
27812
28614
  }
27813
28615
  if (result.data.name !== loaded.dirName) {
27814
- const skillFilePath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME);
28616
+ const skillFilePath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME$1);
27815
28617
  throw new Error(`Frontmatter name (${result.data.name}) must match directory name (${loaded.dirName}) in ${skillFilePath}`);
27816
28618
  }
27817
28619
  return new KiroSkill({
@@ -27894,7 +28696,7 @@ var OpenCodeSkill = class OpenCodeSkill extends ToolSkill {
27894
28696
  relativeDirPath,
27895
28697
  dirName,
27896
28698
  mainFile: {
27897
- name: SKILL_FILE_NAME,
28699
+ name: SKILL_FILE_NAME$1,
27898
28700
  body,
27899
28701
  frontmatter: { ...frontmatter }
27900
28702
  },
@@ -27921,7 +28723,7 @@ var OpenCodeSkill = class OpenCodeSkill extends ToolSkill {
27921
28723
  validate() {
27922
28724
  if (this.mainFile === void 0) return {
27923
28725
  success: false,
27924
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
28726
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
27925
28727
  };
27926
28728
  const result = OpenCodeSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
27927
28729
  if (!result.success) return {
@@ -28000,7 +28802,7 @@ var OpenCodeSkill = class OpenCodeSkill extends ToolSkill {
28000
28802
  const result = OpenCodeSkillFrontmatterSchema.safeParse(loaded.frontmatter);
28001
28803
  if (!result.success) {
28002
28804
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
28003
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
28805
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
28004
28806
  }
28005
28807
  return new OpenCodeSkill({
28006
28808
  outputRoot: loaded.outputRoot,
@@ -28061,7 +28863,7 @@ var PiSkill = class PiSkill extends ToolSkill {
28061
28863
  relativeDirPath: resolvedDirPath,
28062
28864
  dirName,
28063
28865
  mainFile: {
28064
- name: SKILL_FILE_NAME,
28866
+ name: SKILL_FILE_NAME$1,
28065
28867
  body,
28066
28868
  frontmatter: { ...frontmatter }
28067
28869
  },
@@ -28086,7 +28888,7 @@ var PiSkill = class PiSkill extends ToolSkill {
28086
28888
  validate() {
28087
28889
  if (!this.mainFile) return {
28088
28890
  success: false,
28089
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
28891
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
28090
28892
  };
28091
28893
  const result = PiSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
28092
28894
  if (!result.success) return {
@@ -28161,7 +28963,7 @@ var PiSkill = class PiSkill extends ToolSkill {
28161
28963
  const result = PiSkillFrontmatterSchema.safeParse(loaded.frontmatter);
28162
28964
  if (!result.success) {
28163
28965
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
28164
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
28966
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
28165
28967
  }
28166
28968
  return new PiSkill({
28167
28969
  outputRoot: loaded.outputRoot,
@@ -28214,7 +29016,7 @@ var QwencodeSkill = class QwencodeSkill extends ToolSkill {
28214
29016
  relativeDirPath,
28215
29017
  dirName,
28216
29018
  mainFile: {
28217
- name: SKILL_FILE_NAME,
29019
+ name: SKILL_FILE_NAME$1,
28218
29020
  body,
28219
29021
  frontmatter: { ...frontmatter }
28220
29022
  },
@@ -28238,7 +29040,7 @@ var QwencodeSkill = class QwencodeSkill extends ToolSkill {
28238
29040
  validate() {
28239
29041
  if (this.mainFile === void 0) return {
28240
29042
  success: false,
28241
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
29043
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
28242
29044
  };
28243
29045
  const result = QwencodeSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
28244
29046
  if (!result.success) return {
@@ -28318,7 +29120,7 @@ var QwencodeSkill = class QwencodeSkill extends ToolSkill {
28318
29120
  const result = QwencodeSkillFrontmatterSchema.safeParse(loaded.frontmatter);
28319
29121
  if (!result.success) {
28320
29122
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
28321
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
29123
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
28322
29124
  }
28323
29125
  return new QwencodeSkill({
28324
29126
  outputRoot: loaded.outputRoot,
@@ -28368,7 +29170,7 @@ var ReasonixSkill = class ReasonixSkill extends ToolSkill {
28368
29170
  relativeDirPath,
28369
29171
  dirName,
28370
29172
  mainFile: {
28371
- name: SKILL_FILE_NAME,
29173
+ name: SKILL_FILE_NAME$1,
28372
29174
  body,
28373
29175
  frontmatter: { ...frontmatter }
28374
29176
  },
@@ -28392,7 +29194,7 @@ var ReasonixSkill = class ReasonixSkill extends ToolSkill {
28392
29194
  validate() {
28393
29195
  if (this.mainFile === void 0) return {
28394
29196
  success: false,
28395
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
29197
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
28396
29198
  };
28397
29199
  const result = ReasonixSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
28398
29200
  if (!result.success) return {
@@ -28452,7 +29254,7 @@ var ReasonixSkill = class ReasonixSkill extends ToolSkill {
28452
29254
  const result = ReasonixSkillFrontmatterSchema.safeParse(loaded.frontmatter);
28453
29255
  if (!result.success) {
28454
29256
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
28455
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
29257
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
28456
29258
  }
28457
29259
  return new ReasonixSkill({
28458
29260
  outputRoot: loaded.outputRoot,
@@ -28465,6 +29267,24 @@ var ReasonixSkill = class ReasonixSkill extends ToolSkill {
28465
29267
  global: loaded.global
28466
29268
  });
28467
29269
  }
29270
+ /**
29271
+ * Whether the skill directory belongs to the skills feature.
29272
+ *
29273
+ * `.reasonix/skills/` is shared with the subagents feature: a directory whose
29274
+ * SKILL.md declares `runAs: subagent` is a subagent profile, not a regular
29275
+ * skill, so it must be neither imported as a skill nor deleted as an orphan
29276
+ * skill. Directories without a readable/parsable SKILL.md keep the default
29277
+ * skills-feature ownership, matching the previous behavior for such dirs.
29278
+ */
29279
+ static async isDirOwned({ outputRoot, relativeDirPath, dirName }) {
29280
+ const skillFilePath = join(outputRoot, relativeDirPath, dirName, SKILL_FILE_NAME$1);
29281
+ try {
29282
+ const { frontmatter } = parseFrontmatter(await readFileContent(skillFilePath), skillFilePath);
29283
+ return frontmatter["runAs"] !== REASONIX_SUBAGENT_RUN_AS;
29284
+ } catch {
29285
+ return true;
29286
+ }
29287
+ }
28468
29288
  static forDeletion({ outputRoot = process.cwd(), relativeDirPath, dirName, global = false }) {
28469
29289
  return new ReasonixSkill({
28470
29290
  outputRoot,
@@ -28507,7 +29327,7 @@ var ReplitSkill = class ReplitSkill extends ToolSkill {
28507
29327
  relativeDirPath,
28508
29328
  dirName,
28509
29329
  mainFile: {
28510
- name: SKILL_FILE_NAME,
29330
+ name: SKILL_FILE_NAME$1,
28511
29331
  body,
28512
29332
  frontmatter: { ...frontmatter }
28513
29333
  },
@@ -28531,7 +29351,7 @@ var ReplitSkill = class ReplitSkill extends ToolSkill {
28531
29351
  validate() {
28532
29352
  if (!this.mainFile) return {
28533
29353
  success: false,
28534
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
29354
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
28535
29355
  };
28536
29356
  const result = ReplitSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
28537
29357
  if (!result.success) return {
@@ -28599,7 +29419,7 @@ var ReplitSkill = class ReplitSkill extends ToolSkill {
28599
29419
  const result = ReplitSkillFrontmatterSchema.safeParse(loaded.frontmatter);
28600
29420
  if (!result.success) {
28601
29421
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
28602
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
29422
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
28603
29423
  }
28604
29424
  return new ReplitSkill({
28605
29425
  outputRoot: loaded.outputRoot,
@@ -28646,7 +29466,7 @@ var RooSkill = class RooSkill extends ToolSkill {
28646
29466
  relativeDirPath,
28647
29467
  dirName,
28648
29468
  mainFile: {
28649
- name: SKILL_FILE_NAME,
29469
+ name: SKILL_FILE_NAME$1,
28650
29470
  body,
28651
29471
  frontmatter: { ...frontmatter }
28652
29472
  },
@@ -28670,7 +29490,7 @@ var RooSkill = class RooSkill extends ToolSkill {
28670
29490
  validate() {
28671
29491
  if (!this.mainFile) return {
28672
29492
  success: false,
28673
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
29493
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
28674
29494
  };
28675
29495
  const result = RooSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
28676
29496
  if (!result.success) return {
@@ -28734,10 +29554,10 @@ var RooSkill = class RooSkill extends ToolSkill {
28734
29554
  const result = RooSkillFrontmatterSchema.safeParse(loaded.frontmatter);
28735
29555
  if (!result.success) {
28736
29556
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
28737
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
29557
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
28738
29558
  }
28739
29559
  if (result.data.name !== loaded.dirName) {
28740
- const skillFilePath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME);
29560
+ const skillFilePath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME$1);
28741
29561
  throw new Error(`Frontmatter name (${result.data.name}) must match directory name (${loaded.dirName}) in ${skillFilePath}`);
28742
29562
  }
28743
29563
  return new RooSkill({
@@ -28954,7 +29774,7 @@ var VibeSkill = class VibeSkill extends ToolSkill {
28954
29774
  relativeDirPath,
28955
29775
  dirName,
28956
29776
  mainFile: {
28957
- name: SKILL_FILE_NAME,
29777
+ name: SKILL_FILE_NAME$1,
28958
29778
  body,
28959
29779
  frontmatter: { ...frontmatter }
28960
29780
  },
@@ -28981,7 +29801,7 @@ var VibeSkill = class VibeSkill extends ToolSkill {
28981
29801
  validate() {
28982
29802
  if (!this.mainFile) return {
28983
29803
  success: false,
28984
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
29804
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
28985
29805
  };
28986
29806
  const result = VibeSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
28987
29807
  if (!result.success) return {
@@ -29045,7 +29865,7 @@ var VibeSkill = class VibeSkill extends ToolSkill {
29045
29865
  const result = VibeSkillFrontmatterSchema.safeParse(loaded.frontmatter);
29046
29866
  if (!result.success) {
29047
29867
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
29048
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
29868
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
29049
29869
  }
29050
29870
  return new VibeSkill({
29051
29871
  outputRoot: loaded.outputRoot,
@@ -29101,7 +29921,7 @@ var WarpSkill = class WarpSkill extends ToolSkill {
29101
29921
  relativeDirPath,
29102
29922
  dirName,
29103
29923
  mainFile: {
29104
- name: SKILL_FILE_NAME,
29924
+ name: SKILL_FILE_NAME$1,
29105
29925
  body,
29106
29926
  frontmatter: { ...frontmatter }
29107
29927
  },
@@ -29125,7 +29945,7 @@ var WarpSkill = class WarpSkill extends ToolSkill {
29125
29945
  validate() {
29126
29946
  if (this.mainFile === void 0) return {
29127
29947
  success: false,
29128
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
29948
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
29129
29949
  };
29130
29950
  const result = WarpSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
29131
29951
  if (!result.success) return {
@@ -29185,7 +30005,7 @@ var WarpSkill = class WarpSkill extends ToolSkill {
29185
30005
  const result = WarpSkillFrontmatterSchema.safeParse(loaded.frontmatter);
29186
30006
  if (!result.success) {
29187
30007
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
29188
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
30008
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
29189
30009
  }
29190
30010
  return new WarpSkill({
29191
30011
  outputRoot: loaded.outputRoot,
@@ -29233,7 +30053,7 @@ var ZedSkill = class ZedSkill extends ToolSkill {
29233
30053
  relativeDirPath,
29234
30054
  dirName,
29235
30055
  mainFile: {
29236
- name: SKILL_FILE_NAME,
30056
+ name: SKILL_FILE_NAME$1,
29237
30057
  body,
29238
30058
  frontmatter: { ...frontmatter }
29239
30059
  },
@@ -29257,7 +30077,7 @@ var ZedSkill = class ZedSkill extends ToolSkill {
29257
30077
  validate() {
29258
30078
  if (!this.mainFile) return {
29259
30079
  success: false,
29260
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
30080
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
29261
30081
  };
29262
30082
  const result = ZedSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
29263
30083
  if (!result.success) return {
@@ -29326,7 +30146,7 @@ var ZedSkill = class ZedSkill extends ToolSkill {
29326
30146
  const result = ZedSkillFrontmatterSchema.safeParse(loaded.frontmatter);
29327
30147
  if (!result.success) {
29328
30148
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
29329
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
30149
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
29330
30150
  }
29331
30151
  return new ZedSkill({
29332
30152
  outputRoot: loaded.outputRoot,
@@ -29763,6 +30583,12 @@ var SkillsProcessor = class extends DirFeatureProcessor {
29763
30583
  for (const dirPath of dirPaths) {
29764
30584
  const dirName = basename(dirPath);
29765
30585
  if (seenDirNames.has(dirName)) continue;
30586
+ if (factory.class.isDirOwned && !await factory.class.isDirOwned({
30587
+ outputRoot: this.outputRoot,
30588
+ relativeDirPath: root,
30589
+ dirName,
30590
+ inputRoot: this.inputRoot
30591
+ })) continue;
29766
30592
  seenDirNames.add(dirName);
29767
30593
  loadEntries.push({
29768
30594
  root,
@@ -29789,6 +30615,12 @@ var SkillsProcessor = class extends DirFeatureProcessor {
29789
30615
  const dirPaths = await findFilesByGlobs(join(skillsDirPath, "*"), { type: "dir" });
29790
30616
  for (const dirPath of dirPaths) {
29791
30617
  const dirName = basename(dirPath);
30618
+ if (factory.class.isDirOwned && !await factory.class.isDirOwned({
30619
+ outputRoot: this.outputRoot,
30620
+ relativeDirPath: root,
30621
+ dirName,
30622
+ inputRoot: this.inputRoot
30623
+ })) continue;
29792
30624
  const toolSkill = factory.class.forDeletion({
29793
30625
  outputRoot: this.outputRoot,
29794
30626
  relativeDirPath: root,
@@ -33088,6 +33920,188 @@ var OpenCodeSubagent = class OpenCodeSubagent extends OpenCodeStyleSubagent {
33088
33920
  }
33089
33921
  };
33090
33922
  //#endregion
33923
+ //#region src/features/subagents/reasonix-subagent.ts
33924
+ const ReasonixSubagentFrontmatterSchema = z.looseObject({
33925
+ name: z.string(),
33926
+ description: z.optional(z.string()),
33927
+ invocation: z.optional(z.string()),
33928
+ runAs: z.optional(z.string()),
33929
+ model: z.optional(z.string()),
33930
+ effort: z.optional(z.string()),
33931
+ "allowed-tools": z.optional(z.array(z.string())),
33932
+ color: z.optional(z.string())
33933
+ });
33934
+ /**
33935
+ * Represents a DeepSeek-Reasonix subagent profile.
33936
+ *
33937
+ * Reasonix discovers subagents as directory-layout Skills (`<name>/SKILL.md`)
33938
+ * under `.reasonix/skills/` (project) and `~/.reasonix/skills/` (global); the
33939
+ * global scope is served by the processor supplying the home directory as
33940
+ * outputRoot, so the relative directory path is identical for both scopes. A
33941
+ * subagent is a Skill whose frontmatter declares `invocation: manual` and
33942
+ * `runAs: subagent`.
33943
+ *
33944
+ * @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/SUBAGENT_PROFILES.md
33945
+ */
33946
+ var ReasonixSubagent = class ReasonixSubagent extends ToolSubagent {
33947
+ frontmatter;
33948
+ body;
33949
+ constructor({ frontmatter, body, fileContent, ...rest }) {
33950
+ if (rest.validate !== false) {
33951
+ const result = ReasonixSubagentFrontmatterSchema.safeParse(frontmatter);
33952
+ if (!result.success) throw new Error(`Invalid frontmatter in ${join(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
33953
+ }
33954
+ super({
33955
+ ...rest,
33956
+ fileContent: fileContent ?? stringifyFrontmatter(body, frontmatter)
33957
+ });
33958
+ this.frontmatter = frontmatter;
33959
+ this.body = body;
33960
+ }
33961
+ static getSettablePaths({ global: _global = false } = {}) {
33962
+ return { relativeDirPath: REASONIX_SUBAGENTS_DIR_PATH };
33963
+ }
33964
+ getFrontmatter() {
33965
+ return this.frontmatter;
33966
+ }
33967
+ getBody() {
33968
+ return this.body;
33969
+ }
33970
+ toRulesyncSubagent() {
33971
+ const { name, description, invocation: _invocation, runAs: _runAs, ...restFields } = this.frontmatter;
33972
+ const reasonixSection = { ...restFields };
33973
+ const rulesyncFrontmatter = {
33974
+ targets: ["*"],
33975
+ name,
33976
+ description,
33977
+ ...Object.keys(reasonixSection).length > 0 && { reasonix: reasonixSection }
33978
+ };
33979
+ return new RulesyncSubagent({
33980
+ outputRoot: this.getOutputRoot(),
33981
+ frontmatter: rulesyncFrontmatter,
33982
+ body: this.body,
33983
+ relativeDirPath: RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH,
33984
+ relativeFilePath: `${this.getSubagentName()}.md`,
33985
+ validate: true
33986
+ });
33987
+ }
33988
+ /**
33989
+ * Derive the subagent name from this instance's relative file path.
33990
+ *
33991
+ * The tool-side layout is `<name>/SKILL.md`, so the name is the parent
33992
+ * directory of the file. If the path is unexpectedly flat (e.g. a legacy
33993
+ * `<name>.md`), fall back to the basename without extension.
33994
+ */
33995
+ getSubagentName() {
33996
+ const relativeFilePath = this.getRelativeFilePath();
33997
+ const dir = dirname(relativeFilePath);
33998
+ if (dir && dir !== ".") return basename(dir);
33999
+ return basename(relativeFilePath, extname(relativeFilePath));
34000
+ }
34001
+ static fromRulesyncSubagent({ outputRoot = process.cwd(), rulesyncSubagent, validate = true, global = false }) {
34002
+ const rulesyncFrontmatter = rulesyncSubagent.getFrontmatter();
34003
+ const reasonixSection = this.filterToolSpecificSection(rulesyncFrontmatter.reasonix ?? {}, ["name", "description"]);
34004
+ const rawReasonixFrontmatter = {
34005
+ name: rulesyncFrontmatter.name,
34006
+ description: rulesyncFrontmatter.description,
34007
+ ...reasonixSection,
34008
+ invocation: REASONIX_SUBAGENT_INVOCATION,
34009
+ runAs: REASONIX_SUBAGENT_RUN_AS
34010
+ };
34011
+ const result = ReasonixSubagentFrontmatterSchema.safeParse(rawReasonixFrontmatter);
34012
+ if (!result.success) throw new Error(`Invalid reasonix subagent frontmatter in ${rulesyncSubagent.getRelativeFilePath()}: ${formatError(result.error)}`);
34013
+ const reasonixFrontmatter = result.data;
34014
+ const body = rulesyncSubagent.getBody();
34015
+ const fileContent = stringifyFrontmatter(body, reasonixFrontmatter);
34016
+ const paths = this.getSettablePaths({ global });
34017
+ const subagentName = basename(rulesyncSubagent.getRelativeFilePath(), extname(rulesyncSubagent.getRelativeFilePath()));
34018
+ return new ReasonixSubagent({
34019
+ outputRoot,
34020
+ frontmatter: reasonixFrontmatter,
34021
+ body,
34022
+ relativeDirPath: paths.relativeDirPath,
34023
+ relativeFilePath: join(subagentName, SKILL_FILE_NAME$1),
34024
+ fileContent,
34025
+ validate,
34026
+ global
34027
+ });
34028
+ }
34029
+ validate() {
34030
+ if (!this.frontmatter) return {
34031
+ success: true,
34032
+ error: null
34033
+ };
34034
+ const result = ReasonixSubagentFrontmatterSchema.safeParse(this.frontmatter);
34035
+ if (result.success) return {
34036
+ success: true,
34037
+ error: null
34038
+ };
34039
+ else return {
34040
+ success: false,
34041
+ error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${join(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
34042
+ };
34043
+ }
34044
+ static isTargetedByRulesyncSubagent(rulesyncSubagent) {
34045
+ return this.isTargetedByRulesyncSubagentDefault({
34046
+ rulesyncSubagent,
34047
+ toolTarget: "reasonix"
34048
+ });
34049
+ }
34050
+ static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
34051
+ const paths = this.getSettablePaths({ global });
34052
+ const filePath = join(outputRoot, paths.relativeDirPath, relativeFilePath);
34053
+ const fileContent = await readFileContent(filePath);
34054
+ const { frontmatter, body: content } = parseFrontmatter(fileContent, filePath);
34055
+ const result = ReasonixSubagentFrontmatterSchema.safeParse(frontmatter);
34056
+ if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
34057
+ return new ReasonixSubagent({
34058
+ outputRoot,
34059
+ relativeDirPath: paths.relativeDirPath,
34060
+ relativeFilePath,
34061
+ frontmatter: result.data,
34062
+ body: content.trim(),
34063
+ fileContent,
34064
+ validate,
34065
+ global
34066
+ });
34067
+ }
34068
+ /**
34069
+ * Whether the SKILL.md at the given path is a subagent profile.
34070
+ *
34071
+ * `.reasonix/skills/` is shared with the skills feature: a regular skill and
34072
+ * a subagent profile differ only by their frontmatter markers. Only files
34073
+ * carrying `runAs: subagent` belong to this feature, so regular skills are
34074
+ * neither imported as subagents nor deleted as orphans by the subagents
34075
+ * feature. `runAs` alone is checked (not `invocation`) because it is the
34076
+ * marker that switches the execution mode; generation always emits both.
34077
+ * Unreadable or unparsable files are treated as not owned, erring on the
34078
+ * side of leaving foreign files untouched.
34079
+ */
34080
+ static async isFileOwned({ outputRoot, relativeDirPath, relativeFilePath }) {
34081
+ const filePath = join(outputRoot, relativeDirPath, relativeFilePath);
34082
+ try {
34083
+ const { frontmatter } = parseFrontmatter(await readFileContent(filePath), filePath);
34084
+ return frontmatter["runAs"] === REASONIX_SUBAGENT_RUN_AS;
34085
+ } catch {
34086
+ return false;
34087
+ }
34088
+ }
34089
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
34090
+ return new ReasonixSubagent({
34091
+ outputRoot,
34092
+ relativeDirPath,
34093
+ relativeFilePath,
34094
+ frontmatter: {
34095
+ name: "",
34096
+ description: ""
34097
+ },
34098
+ body: "",
34099
+ fileContent: "",
34100
+ validate: false
34101
+ });
34102
+ }
34103
+ };
34104
+ //#endregion
33091
34105
  //#region src/features/subagents/roo-subagent.ts
33092
34106
  /**
33093
34107
  * Default tool groups assigned to a generated mode when the rulesync source
@@ -33722,6 +34736,14 @@ const toolSubagentFactories = /* @__PURE__ */ new Map([
33722
34736
  filePattern: "*.md"
33723
34737
  }
33724
34738
  }],
34739
+ ["reasonix", {
34740
+ class: ReasonixSubagent,
34741
+ meta: {
34742
+ supportsSimulated: false,
34743
+ supportsGlobal: true,
34744
+ filePattern: join("*", "SKILL.md")
34745
+ }
34746
+ }],
33725
34747
  ["roo", {
33726
34748
  class: RooSubagent,
33727
34749
  meta: {
@@ -33874,8 +34896,17 @@ var SubagentsProcessor = class extends FeatureProcessor {
33874
34896
  const baseDir = join(this.outputRoot, dirPath);
33875
34897
  const subagentFilePaths = await findFilesByGlobs(join(baseDir, factory.meta.filePattern));
33876
34898
  const toRelativeFilePath = (path) => relative(baseDir, path);
34899
+ let ownedFilePaths = subagentFilePaths;
34900
+ if (factory.class.isFileOwned) {
34901
+ const ownership = await Promise.all(subagentFilePaths.map((path) => factory.class.isFileOwned({
34902
+ outputRoot: this.outputRoot,
34903
+ relativeDirPath: dirPath,
34904
+ relativeFilePath: toRelativeFilePath(path)
34905
+ })));
34906
+ ownedFilePaths = subagentFilePaths.filter((_, index) => ownership[index]);
34907
+ }
33877
34908
  if (forDeletion) {
33878
- toolSubagents.push(...subagentFilePaths.map((path) => factory.class.forDeletion({
34909
+ toolSubagents.push(...ownedFilePaths.map((path) => factory.class.forDeletion({
33879
34910
  outputRoot: this.outputRoot,
33880
34911
  relativeDirPath: dirPath,
33881
34912
  relativeFilePath: toRelativeFilePath(path),
@@ -33883,7 +34914,7 @@ var SubagentsProcessor = class extends FeatureProcessor {
33883
34914
  })).filter((subagent) => subagent.isDeletable()));
33884
34915
  continue;
33885
34916
  }
33886
- const loaded = await Promise.all(subagentFilePaths.map((path) => factory.class.fromFile({
34917
+ const loaded = await Promise.all(ownedFilePaths.map((path) => factory.class.fromFile({
33887
34918
  outputRoot: this.outputRoot,
33888
34919
  relativeDirPath: dirPath,
33889
34920
  relativeFilePath: toRelativeFilePath(path),
@@ -33995,6 +35026,7 @@ const RulesyncRuleFrontmatterSchema = z.object({
33995
35026
  name: z.optional(z.string()),
33996
35027
  description: z.optional(z.string())
33997
35028
  })),
35029
+ pi: z.optional(z.looseObject({ systemPrompt: z.optional(z.enum(["append"])) })),
33998
35030
  takt: z.optional(z.looseObject({
33999
35031
  name: z.optional(z.string()),
34000
35032
  extends: z.optional(z.string()),
@@ -34158,6 +35190,16 @@ var ToolRule = class extends ToolFile {
34158
35190
  isRoot() {
34159
35191
  return this.root;
34160
35192
  }
35193
+ /**
35194
+ * Whether this rule must be left out of the root rule's reference/MCP
35195
+ * instruction listings even though it is a non-root survivor. Used by files
35196
+ * the tool loads through its own mechanism (e.g. Pi's `APPEND_SYSTEM.md`,
35197
+ * which Pi appends to the system prompt itself — referencing it from
35198
+ * `AGENTS.md` would double-load the content).
35199
+ */
35200
+ isExcludedFromRootReferences() {
35201
+ return false;
35202
+ }
34161
35203
  getDescription() {
34162
35204
  return this.description;
34163
35205
  }
@@ -36504,6 +37546,15 @@ var FactorydroidRule = class FactorydroidRule extends ToolRule {
36504
37546
  * is `undefined`). This mirrors the grokcli, warp, and deepagents targets.
36505
37547
  *
36506
37548
  * Goose uses plain markdown files (.goosehints) without frontmatter.
37549
+ *
37550
+ * Global scope emits only `~/.config/goose/.goosehints`. Goose v1.41.0 (PR
37551
+ * block/goose#9736) additionally loads the vendor-neutral
37552
+ * `~/.agents/AGENTS.md` alongside the config-dir hints, but rulesync
37553
+ * deliberately does not emit that shared path from the goose target: the
37554
+ * config-dir hints remain fully loaded (no capability loss), and the
37555
+ * cross-tool `~/.agents/AGENTS.md` is already written by targets that own it
37556
+ * (e.g. cline in global mode) — a second writer would need cross-target
37557
+ * shared-file coordination for zero gain (decision recorded in issue #2207).
36507
37558
  */
36508
37559
  var GooseRule = class GooseRule extends ToolRule {
36509
37560
  static getSettablePaths({ global } = {}) {
@@ -36705,14 +37756,21 @@ var HermesagentRule = class HermesagentRule extends ToolRule {
36705
37756
  /**
36706
37757
  * Rule generator for JetBrains Junie AI coding agent
36707
37758
  *
36708
- * Generates `.junie/AGENTS.md` files based on rulesync rule content. `.junie/AGENTS.md`
36709
- * is the preferred guideline file in current Junie; the legacy `.junie/guidelines.md`
36710
- * is still read by Junie and is accepted as an import fallback, but generation always
36711
- * targets `.junie/AGENTS.md`. Junie uses plain markdown without frontmatter requirements.
36712
- *
36713
- * Global (user) scope writes a single `~/.junie/AGENTS.md` file. Junie merges these
36714
- * user-scope guidelines with the project `.junie/AGENTS.md` (project takes priority on
36715
- * conflicts); memory files (`.junie/memories/`) remain project-scoped only.
37759
+ * Generates `.junie/AGENTS.md` files based on rulesync rule content. Junie CLI
37760
+ * resolves project guidelines **first-match-wins**: `.junie/AGENTS.md` → root
37761
+ * `AGENTS.md` legacy `.junie/guidelines.md` / `.junie/guidelines/`. Only the
37762
+ * first match is loaded, it documents no `@`-reference or file-inclusion
37763
+ * mechanism, and no `.junie/memories/` read path exists — so non-root rules
37764
+ * are folded into the single root `.junie/AGENTS.md` by the RulesProcessor
37765
+ * (`nonRoot` is `undefined`, mirroring the grokcli / warp / deepagents
37766
+ * targets; decision recorded in issue #2211). The legacy
37767
+ * `.junie/guidelines.md` is still accepted as an import fallback, but
37768
+ * generation always targets `.junie/AGENTS.md`. Junie uses plain markdown
37769
+ * without frontmatter requirements.
37770
+ *
37771
+ * Global (user) scope writes a single `~/.junie/AGENTS.md` file. Junie merges
37772
+ * these user-scope guidelines with the project guidelines (both are included
37773
+ * and marked clearly).
36716
37774
  *
36717
37775
  * @see https://junie.jetbrains.com/docs/junie-ide-plugin.html
36718
37776
  * @see https://junie.jetbrains.com/docs/guidelines-and-memory.html
@@ -36731,15 +37789,12 @@ var JunieRule = class JunieRule extends ToolRule {
36731
37789
  alternativeRoots: [{
36732
37790
  relativeDirPath: buildToolPath(JUNIE_DIR, ".", excludeToolDir),
36733
37791
  relativeFilePath: JUNIE_LEGACY_RULE_FILE_NAME
36734
- }],
36735
- nonRoot: { relativeDirPath: buildToolPath(JUNIE_DIR, "memories", excludeToolDir) }
37792
+ }]
36736
37793
  };
36737
37794
  }
36738
37795
  /**
36739
37796
  * Determines whether a given relative file path refers to a root guideline file.
36740
37797
  * The preferred file is `AGENTS.md`; the legacy `guidelines.md` is still accepted.
36741
- * Memory files live under `.junie/memories/` and are passed in as bare filenames
36742
- * (e.g. `memo.md`), so a top-level `AGENTS.md`/`guidelines.md` is the root entry.
36743
37798
  */
36744
37799
  static isRootRelativeFilePath(relativeFilePath) {
36745
37800
  return relativeFilePath === "AGENTS.md" || relativeFilePath === "guidelines.md";
@@ -36758,10 +37813,7 @@ var JunieRule = class JunieRule extends ToolRule {
36758
37813
  root: true
36759
37814
  });
36760
37815
  }
36761
- const isRoot = JunieRule.isRootRelativeFilePath(relativeFilePath);
36762
- const settablePaths = this.getSettablePaths();
36763
- if (!settablePaths.nonRoot) throw new Error("JunieRule project settable paths must include a nonRoot path");
36764
- const relativeDirPath = isRoot ? settablePaths.root.relativeDirPath : settablePaths.nonRoot.relativeDirPath;
37816
+ const relativeDirPath = this.getSettablePaths().root.relativeDirPath;
36765
37817
  const fileContent = await readFileContent(join(outputRoot, join(relativeDirPath, relativeFilePath)));
36766
37818
  return new JunieRule({
36767
37819
  outputRoot,
@@ -36769,7 +37821,7 @@ var JunieRule = class JunieRule extends ToolRule {
36769
37821
  relativeFilePath,
36770
37822
  fileContent,
36771
37823
  validate,
36772
- root: isRoot
37824
+ root: JunieRule.isRootRelativeFilePath(relativeFilePath)
36773
37825
  });
36774
37826
  }
36775
37827
  static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
@@ -36784,13 +37836,15 @@ var JunieRule = class JunieRule extends ToolRule {
36784
37836
  rootPath: paths.root
36785
37837
  }));
36786
37838
  }
36787
- return new JunieRule(this.buildToolRuleParamsDefault({
37839
+ const { root } = this.getSettablePaths();
37840
+ return new JunieRule({
36788
37841
  outputRoot,
36789
- rulesyncRule,
37842
+ relativeDirPath: root.relativeDirPath,
37843
+ relativeFilePath: root.relativeFilePath,
37844
+ fileContent: rulesyncRule.getBody(),
36790
37845
  validate,
36791
- rootPath: this.getSettablePaths().root,
36792
- nonRootPath: this.getSettablePaths().nonRoot
36793
- }));
37846
+ root: rulesyncRule.getFrontmatter().root ?? false
37847
+ });
36794
37848
  }
36795
37849
  toRulesyncRule() {
36796
37850
  return this.toRulesyncRuleDefault();
@@ -37193,31 +38247,68 @@ var OpenCodeRule = class OpenCodeRule extends ToolRule {
37193
38247
  * RulesProcessor (there is no separate non-root output location — `nonRoot` is
37194
38248
  * `undefined`). This mirrors the codexcli, grokcli, warp, and deepagents targets.
37195
38249
  *
37196
- * Pi also loads two system-prompt instruction files that rulesync does NOT emit:
38250
+ * Pi also loads two system-prompt instruction files. `.pi/APPEND_SYSTEM.md`
38251
+ * (global `~/.pi/agent/APPEND_SYSTEM.md`) *appends* to the default system prompt,
38252
+ * and rulesync emits it from any rule that opts in via the `pi.systemPrompt:
38253
+ * append` frontmatter block: those bodies are routed here instead of being folded
38254
+ * into `AGENTS.md`, and multiple opted-in rules concatenate in source order.
37197
38255
  * `.pi/SYSTEM.md` (global `~/.pi/agent/SYSTEM.md`) *replaces* the default system
37198
- * prompt entirely, and `.pi/APPEND_SYSTEM.md` (global
37199
- * `~/.pi/agent/APPEND_SYSTEM.md`) *appends* to it. rulesync's rules model only
37200
- * routes a designated `root` rule to a single context file and has no convention
37201
- * for marking a rule as "replace" vs "append" the system prompt, so these
37202
- * surfaces are documented in docs/reference/file-formats.md and left to be
37203
- * authored by hand rather than mapped to a speculative new frontmatter flag.
38256
+ * prompt entirely which silently disables Pi's built-in tool instructions — so
38257
+ * rulesync deliberately never emits it and leaves it to be authored by hand.
38258
+ * See docs/reference/file-formats.md.
37204
38259
  */
37205
38260
  var PiRule = class PiRule extends ToolRule {
37206
- constructor({ fileContent, root, ...rest }) {
38261
+ appendSystemPrompt;
38262
+ constructor({ fileContent, root, appendSystemPrompt = false, ...rest }) {
37207
38263
  super({
37208
38264
  ...rest,
37209
38265
  fileContent,
37210
38266
  root: root ?? false
37211
38267
  });
38268
+ this.appendSystemPrompt = appendSystemPrompt;
37212
38269
  }
37213
38270
  static getSettablePaths({ global = false, excludeToolDir } = {}) {
37214
- return { root: {
37215
- relativeDirPath: global ? buildToolPath(".pi", "agent", excludeToolDir) : ".",
37216
- relativeFilePath: PI_RULE_FILE_NAME
37217
- } };
38271
+ return {
38272
+ root: {
38273
+ relativeDirPath: global ? buildToolPath(".pi", "agent", excludeToolDir) : ".",
38274
+ relativeFilePath: PI_RULE_FILE_NAME
38275
+ },
38276
+ appendSystemPrompt: {
38277
+ relativeDirPath: global ? buildToolPath(".pi", "agent", excludeToolDir) : ".pi",
38278
+ relativeFilePath: PI_APPEND_SYSTEM_FILE_NAME
38279
+ }
38280
+ };
37218
38281
  }
37219
- static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, validate = true, global = false }) {
37220
- const { root } = this.getSettablePaths({ global });
38282
+ /**
38283
+ * Extra fixed files this tool manages beyond the root rule. The
38284
+ * RulesProcessor enumerates these for import and deletion so a stale
38285
+ * `APPEND_SYSTEM.md` is cleaned up once no rule opts in anymore.
38286
+ */
38287
+ static getExtraFixedFiles({ global = false } = {}) {
38288
+ return [this.getSettablePaths({ global }).appendSystemPrompt];
38289
+ }
38290
+ /**
38291
+ * Pi appends `APPEND_SYSTEM.md` to the system prompt itself, so listing it in
38292
+ * the root rule's reference section (toon/explicit discovery modes) would
38293
+ * double-load the content.
38294
+ */
38295
+ isExcludedFromRootReferences() {
38296
+ return this.appendSystemPrompt;
38297
+ }
38298
+ static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
38299
+ const { root, appendSystemPrompt } = this.getSettablePaths({ global });
38300
+ if (relativeFilePath === "APPEND_SYSTEM.md") {
38301
+ const fileContent = await readFileContent(join(outputRoot, join(appendSystemPrompt.relativeDirPath, appendSystemPrompt.relativeFilePath)));
38302
+ return new PiRule({
38303
+ outputRoot,
38304
+ relativeDirPath: appendSystemPrompt.relativeDirPath,
38305
+ relativeFilePath: appendSystemPrompt.relativeFilePath,
38306
+ fileContent,
38307
+ validate,
38308
+ root: false,
38309
+ appendSystemPrompt: true
38310
+ });
38311
+ }
37221
38312
  const fileContent = await readFileContent(join(outputRoot, join(root.relativeDirPath, root.relativeFilePath)));
37222
38313
  return new PiRule({
37223
38314
  outputRoot,
@@ -37229,8 +38320,18 @@ var PiRule = class PiRule extends ToolRule {
37229
38320
  });
37230
38321
  }
37231
38322
  static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
37232
- const { root } = this.getSettablePaths({ global });
37233
- const isRoot = rulesyncRule.getFrontmatter().root ?? false;
38323
+ const { root, appendSystemPrompt } = this.getSettablePaths({ global });
38324
+ const frontmatter = rulesyncRule.getFrontmatter();
38325
+ if (!frontmatter.root && frontmatter.pi?.systemPrompt === "append") return new PiRule({
38326
+ outputRoot,
38327
+ relativeDirPath: appendSystemPrompt.relativeDirPath,
38328
+ relativeFilePath: appendSystemPrompt.relativeFilePath,
38329
+ fileContent: rulesyncRule.getBody(),
38330
+ validate,
38331
+ root: false,
38332
+ appendSystemPrompt: true
38333
+ });
38334
+ const isRoot = frontmatter.root ?? false;
37234
38335
  return new PiRule({
37235
38336
  outputRoot,
37236
38337
  relativeDirPath: root.relativeDirPath,
@@ -37241,6 +38342,17 @@ var PiRule = class PiRule extends ToolRule {
37241
38342
  });
37242
38343
  }
37243
38344
  toRulesyncRule() {
38345
+ if (this.appendSystemPrompt) return new RulesyncRule({
38346
+ outputRoot: process.cwd(),
38347
+ relativeDirPath: RULESYNC_RULES_RELATIVE_DIR_PATH,
38348
+ relativeFilePath: PI_APPEND_SYSTEM_FILE_NAME,
38349
+ frontmatter: {
38350
+ root: false,
38351
+ targets: ["pi"],
38352
+ pi: { systemPrompt: "append" }
38353
+ },
38354
+ body: this.getFileContent()
38355
+ });
37244
38356
  return this.toRulesyncRuleDefault();
37245
38357
  }
37246
38358
  validate() {
@@ -37251,6 +38363,15 @@ var PiRule = class PiRule extends ToolRule {
37251
38363
  }
37252
38364
  static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
37253
38365
  const { root } = this.getSettablePaths({ global });
38366
+ if (relativeFilePath === "APPEND_SYSTEM.md") return new PiRule({
38367
+ outputRoot,
38368
+ relativeDirPath,
38369
+ relativeFilePath,
38370
+ fileContent: "",
38371
+ validate: false,
38372
+ root: false,
38373
+ appendSystemPrompt: true
38374
+ });
37254
38375
  const isRoot = relativeFilePath === "AGENTS.md" && (relativeDirPath === "." || relativeDirPath === root.relativeDirPath);
37255
38376
  return new PiRule({
37256
38377
  outputRoot,
@@ -38410,7 +39531,8 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
38410
39531
  meta: {
38411
39532
  extension: "md",
38412
39533
  supportsGlobal: true,
38413
- ruleDiscoveryMode: "toon"
39534
+ ruleDiscoveryMode: "auto",
39535
+ foldsNonRootIntoRoot: true
38414
39536
  }
38415
39537
  }],
38416
39538
  ["kilo", {
@@ -38676,7 +39798,7 @@ var RulesProcessor = class extends FeatureProcessor {
38676
39798
  */
38677
39799
  async buildMcpInstructionFiles({ toolRules, meta }) {
38678
39800
  if (!meta.mcpInstructionsRegistrar || this.global) return [];
38679
- const instructionPaths = toolRules.filter((rule) => !rule.isRoot()).map((rule) => toPosixPath(join(rule.getRelativeDirPath(), rule.getRelativeFilePath())));
39801
+ const instructionPaths = toolRules.filter((rule) => !rule.isRoot() && !rule.isExcludedFromRootReferences()).map((rule) => toPosixPath(join(rule.getRelativeDirPath(), rule.getRelativeFilePath())));
38680
39802
  if (instructionPaths.length === 0) return [];
38681
39803
  return [await meta.mcpInstructionsRegistrar.fromInstructions({
38682
39804
  outputRoot: this.outputRoot,
@@ -38708,7 +39830,7 @@ var RulesProcessor = class extends FeatureProcessor {
38708
39830
  const toolRelativeDirPath = skillClass.getSettablePaths({ global: this.global }).relativeDirPath;
38709
39831
  return this.skills.filter((skill) => skillClass.isTargetedByRulesyncSkill(skill)).map((skill) => {
38710
39832
  const frontmatter = skill.getFrontmatter();
38711
- const relativePath = join(toolRelativeDirPath, skill.getDirName(), SKILL_FILE_NAME);
39833
+ const relativePath = join(toolRelativeDirPath, skill.getDirName(), SKILL_FILE_NAME$1);
38712
39834
  return {
38713
39835
  name: frontmatter.name,
38714
39836
  description: frontmatter.description,
@@ -38732,11 +39854,25 @@ var RulesProcessor = class extends FeatureProcessor {
38732
39854
  */
38733
39855
  foldNonRootRulesIntoRootRule(toolRules) {
38734
39856
  if (toolRules.length <= 1) return;
38735
- const target = toolRules.find((rule) => rule.isRoot()) ?? toolRules[0];
38736
- if (!target) return;
38737
- const mergedContent = [target, ...toolRules.filter((rule) => rule !== target)].map((rule) => rule.getFileContent().trim()).filter((content) => content.length > 0).join("\n\n");
38738
- target.setFileContent(mergedContent);
38739
- for (let i = toolRules.length - 1; i >= 0; i--) if (toolRules[i] !== target) toolRules.splice(i, 1);
39857
+ const groups = /* @__PURE__ */ new Map();
39858
+ for (const rule of toolRules) {
39859
+ const path = join(rule.getRelativeDirPath(), rule.getRelativeFilePath());
39860
+ const group = groups.get(path);
39861
+ if (group) group.push(rule);
39862
+ else groups.set(path, [rule]);
39863
+ }
39864
+ const survivors = /* @__PURE__ */ new Set();
39865
+ for (const group of groups.values()) {
39866
+ const target = group.find((rule) => rule.isRoot()) ?? group[0];
39867
+ if (!target) continue;
39868
+ const mergedContent = [target, ...group.filter((rule) => rule !== target)].map((rule) => rule.getFileContent().trim()).filter((content) => content.length > 0).join("\n\n");
39869
+ target.setFileContent(mergedContent);
39870
+ survivors.add(target);
39871
+ }
39872
+ for (let i = toolRules.length - 1; i >= 0; i--) {
39873
+ const rule = toolRules[i];
39874
+ if (rule && !survivors.has(rule)) toolRules.splice(i, 1);
39875
+ }
38740
39876
  }
38741
39877
  /**
38742
39878
  * Handle localRoot rule generation based on tool target.
@@ -38946,6 +40082,27 @@ As this project's AI coding tool, you must follow the additional conventions bel
38946
40082
  const mirrorPaths = await findFilesByGlobs(mirrorGlob);
38947
40083
  return buildDeletionRulesFromPaths(mirrorPaths);
38948
40084
  })();
40085
+ const extraFixedToolRules = await (async () => {
40086
+ const extraFiles = factory.class.getExtraFixedFiles?.({ global: this.global });
40087
+ if (!extraFiles || extraFiles.length === 0) return [];
40088
+ const filePaths = await findFilesByGlobs(extraFiles.map((file) => join(this.outputRoot, file.relativeDirPath, file.relativeFilePath)));
40089
+ if (filePaths.length === 0) return [];
40090
+ if (forDeletion) return buildDeletionRulesFromPaths(filePaths);
40091
+ return await Promise.all(filePaths.map((filePath) => {
40092
+ const relativeDirPath = resolveRelativeDirPath(filePath);
40093
+ checkPathTraversal({
40094
+ relativePath: relativeDirPath,
40095
+ intendedRootDir: this.outputRoot
40096
+ });
40097
+ return factory.class.fromFile({
40098
+ outputRoot: this.outputRoot,
40099
+ relativeDirPath,
40100
+ relativeFilePath: basename(filePath),
40101
+ global: this.global
40102
+ });
40103
+ }));
40104
+ })();
40105
+ this.logger.debug(`Found ${extraFixedToolRules.length} extra fixed tool rule files`);
38949
40106
  const nonRootToolRules = await (async () => {
38950
40107
  if (!settablePaths.nonRoot) return [];
38951
40108
  const nonRootOutputRoot = join(this.outputRoot, settablePaths.nonRoot.relativeDirPath);
@@ -38981,6 +40138,7 @@ As this project's AI coding tool, you must follow the additional conventions bel
38981
40138
  ...rootToolRules,
38982
40139
  ...localRootToolRules,
38983
40140
  ...rootMirrorDeletionRules,
40141
+ ...extraFixedToolRules,
38984
40142
  ...nonRootToolRules
38985
40143
  ];
38986
40144
  } catch (error) {
@@ -39008,7 +40166,7 @@ As this project's AI coding tool, you must follow the additional conventions bel
39008
40166
  return toolRuleFactories.get(result.data);
39009
40167
  }
39010
40168
  generateToonReferencesSection(toolRules) {
39011
- const toolRulesWithoutRoot = toolRules.filter((rule) => !rule.isRoot());
40169
+ const toolRulesWithoutRoot = toolRules.filter((rule) => !rule.isRoot() && !rule.isExcludedFromRootReferences());
39012
40170
  if (toolRulesWithoutRoot.length === 0) return "";
39013
40171
  const lines = [];
39014
40172
  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.");
@@ -39024,7 +40182,7 @@ As this project's AI coding tool, you must follow the additional conventions bel
39024
40182
  return lines.join("\n") + "\n\n";
39025
40183
  }
39026
40184
  generateReferencesSection(toolRules) {
39027
- const toolRulesWithoutRoot = toolRules.filter((rule) => !rule.isRoot());
40185
+ const toolRulesWithoutRoot = toolRules.filter((rule) => !rule.isRoot() && !rule.isExcludedFromRootReferences());
39028
40186
  if (toolRulesWithoutRoot.length === 0) return "";
39029
40187
  const lines = [];
39030
40188
  lines.push("Please also reference the following rules as needed:");
@@ -39045,7 +40203,7 @@ As this project's AI coding tool, you must follow the additional conventions bel
39045
40203
  */
39046
40204
  async function convertFromTool(params) {
39047
40205
  const ctx = params;
39048
- const [rulesCount, ignoreCount, mcpCount, commandsCount, subagentsCount, skillsCount, hooksCount, permissionsCount] = [
40206
+ const [rulesCount, ignoreCount, mcpCount, commandsCount, subagentsCount, skillsCount, hooksCount, permissionsCount, checksCount] = [
39049
40207
  await runFeatureConvert(ctx, buildRulesStrategy(ctx)),
39050
40208
  await runFeatureConvert(ctx, buildIgnoreStrategy(ctx)),
39051
40209
  await runFeatureConvert(ctx, buildMcpStrategy(ctx)),
@@ -39053,7 +40211,8 @@ async function convertFromTool(params) {
39053
40211
  await runFeatureConvert(ctx, buildSubagentsStrategy(ctx)),
39054
40212
  await runFeatureConvert(ctx, buildSkillsStrategy(ctx)),
39055
40213
  await runFeatureConvert(ctx, buildHooksStrategy(ctx)),
39056
- await runFeatureConvert(ctx, buildPermissionsStrategy(ctx))
40214
+ await runFeatureConvert(ctx, buildPermissionsStrategy(ctx)),
40215
+ await runFeatureConvert(ctx, buildChecksStrategy(ctx))
39057
40216
  ];
39058
40217
  return {
39059
40218
  rulesCount,
@@ -39063,7 +40222,8 @@ async function convertFromTool(params) {
39063
40222
  subagentsCount,
39064
40223
  skillsCount,
39065
40224
  hooksCount,
39066
- permissionsCount
40225
+ permissionsCount,
40226
+ checksCount
39067
40227
  };
39068
40228
  }
39069
40229
  async function runFeatureConvert(ctx, strategy) {
@@ -39296,6 +40456,27 @@ function buildPermissionsStrategy(ctx) {
39296
40456
  write: (p, files) => p.writeAiFiles(files)
39297
40457
  };
39298
40458
  }
40459
+ function buildChecksStrategy(ctx) {
40460
+ const { config, logger } = ctx;
40461
+ const global = config.getGlobal();
40462
+ const outputRoot = getOutputRoot(config);
40463
+ return {
40464
+ feature: "checks",
40465
+ itemLabel: "check file(s)",
40466
+ allTargets: ChecksProcessor.getToolTargets({ global }),
40467
+ createProcessor: ({ toolTarget, dryRun }) => new ChecksProcessor({
40468
+ outputRoot,
40469
+ toolTarget,
40470
+ global,
40471
+ dryRun,
40472
+ logger
40473
+ }),
40474
+ loadSource: (p) => p.loadToolFiles(),
40475
+ toRulesync: (p, files) => p.convertToolFilesToRulesyncFiles(files),
40476
+ fromRulesync: (p, files) => p.convertRulesyncFilesToToolFiles(files),
40477
+ write: (p, files) => p.writeAiFiles(files)
40478
+ };
40479
+ }
39299
40480
  //#endregion
39300
40481
  //#region src/types/processor-registry.ts
39301
40482
  const PROCESSOR_REGISTRY = [
@@ -39346,6 +40527,12 @@ const PROCESSOR_REGISTRY = [
39346
40527
  processor: PermissionsProcessor,
39347
40528
  schema: PermissionsProcessorToolTargetSchema,
39348
40529
  factory: toolPermissionsFactories
40530
+ },
40531
+ {
40532
+ feature: "checks",
40533
+ processor: ChecksProcessor,
40534
+ schema: ChecksProcessorToolTargetSchema,
40535
+ factory: toolCheckFactories
39349
40536
  }
39350
40537
  ];
39351
40538
  const getProcessorRegistryEntry = (feature) => {
@@ -39695,6 +40882,7 @@ const GENERATION_STEP_GRAPH = [
39695
40882
  id: "permissions",
39696
40883
  ...sharedWriteMeta("permissions")
39697
40884
  },
40885
+ { id: "checks" },
39698
40886
  {
39699
40887
  id: "rules",
39700
40888
  ...sharedWriteMeta("rules"),
@@ -39702,11 +40890,51 @@ const GENERATION_STEP_GRAPH = [
39702
40890
  }
39703
40891
  ];
39704
40892
  /**
40893
+ * Warn when a rulesync skill and a rulesync subagent share a name for a tool
40894
+ * that emits both features into the same directory (e.g. Reasonix, where both
40895
+ * write `<name>/SKILL.md` under `.reasonix/skills/`). The colliding outputs
40896
+ * target the same on-disk file, so whichever generation step runs later
40897
+ * silently overwrites the other's file.
40898
+ */
40899
+ async function warnSkillSubagentNameCollisions(params) {
40900
+ const { config, logger } = params;
40901
+ const global = config.getGlobal();
40902
+ for (const toolTarget of config.getTargets()) {
40903
+ const features = config.getFeatures(toolTarget);
40904
+ if (!features.includes("skills") || !features.includes("subagents")) continue;
40905
+ if (!SubagentsProcessor.getToolTargets({ global }).includes(toolTarget) || !SkillsProcessor.getToolTargets({ global }).includes(toolTarget)) continue;
40906
+ const subagentFactory = SubagentsProcessor.getFactory(toolTarget);
40907
+ const skillFactory = SkillsProcessor.getFactory(toolTarget);
40908
+ if (!subagentFactory || !skillFactory) continue;
40909
+ const subagentsDirPath = subagentFactory.class.getSettablePaths({ global }).relativeDirPath;
40910
+ if (subagentsDirPath !== skillFactory.class.getSettablePaths({ global }).relativeDirPath) continue;
40911
+ const subagentsProcessor = new SubagentsProcessor({
40912
+ inputRoot: config.getInputRoot(),
40913
+ toolTarget,
40914
+ global,
40915
+ logger
40916
+ });
40917
+ const subagentNames = new Set((await subagentsProcessor.loadRulesyncFiles()).filter((file) => file instanceof RulesyncSubagent).filter((file) => subagentFactory.class.isTargetedByRulesyncSubagent(file)).map((file) => basename(file.getRelativeFilePath(), extname(file.getRelativeFilePath()))));
40918
+ if (subagentNames.size === 0) continue;
40919
+ const skillNames = (await new SkillsProcessor({
40920
+ inputRoot: config.getInputRoot(),
40921
+ toolTarget,
40922
+ global,
40923
+ logger
40924
+ }).loadRulesyncDirs()).filter((dir) => dir instanceof RulesyncSkill).filter((skill) => skillFactory.class.isTargetedByRulesyncSkill(skill)).map((skill) => skill.getDirName());
40925
+ for (const name of skillNames) if (subagentNames.has(name)) logger.warn(`Skill "${name}" and subagent "${name}" both target '${toolTarget}' and write the same path '${join(subagentsDirPath, name)}'; the later generation step overwrites the other's output. Rename one of them or narrow their targets.`);
40926
+ }
40927
+ }
40928
+ /**
39705
40929
  * Generate configuration files for AI tools.
39706
40930
  * @throws Error if generation fails
39707
40931
  */
39708
40932
  async function generate(params) {
39709
40933
  const { config, logger } = params;
40934
+ await warnSkillSubagentNameCollisions({
40935
+ config,
40936
+ logger
40937
+ });
39710
40938
  let skillsResult;
39711
40939
  const runners = {
39712
40940
  ignore: () => generateIgnoreCore({
@@ -39740,6 +40968,10 @@ async function generate(params) {
39740
40968
  config,
39741
40969
  logger
39742
40970
  }),
40971
+ checks: () => generateChecksCore({
40972
+ config,
40973
+ logger
40974
+ }),
39743
40975
  rules: () => generateRulesCore({
39744
40976
  config,
39745
40977
  logger,
@@ -39776,6 +41008,8 @@ async function generate(params) {
39776
41008
  hooksPaths: get("hooks").paths,
39777
41009
  permissionsCount: get("permissions").count,
39778
41010
  permissionsPaths: get("permissions").paths,
41011
+ checksCount: get("checks").count,
41012
+ checksPaths: get("checks").paths,
39779
41013
  skills: skillsResult.skills,
39780
41014
  hasDiff
39781
41015
  };
@@ -40144,6 +41378,44 @@ async function generatePermissionsCore(params) {
40144
41378
  hasDiff
40145
41379
  };
40146
41380
  }
41381
+ async function generateChecksCore(params) {
41382
+ const { config, logger } = params;
41383
+ let totalCount = 0;
41384
+ const allPaths = [];
41385
+ let hasDiff = false;
41386
+ const supportedChecksTargets = ChecksProcessor.getToolTargets({ global: config.getGlobal() });
41387
+ const toolTargets = intersection(config.getTargets(), supportedChecksTargets);
41388
+ warnUnsupportedTargets({
41389
+ config,
41390
+ supportedTargets: supportedChecksTargets,
41391
+ featureName: "checks",
41392
+ logger
41393
+ });
41394
+ for (const toolTarget of toolTargets) for (const outputRoot of config.getOutputRoots(toolTarget)) {
41395
+ if (!config.getFeatures(toolTarget).includes("checks")) continue;
41396
+ const processor = new ChecksProcessor({
41397
+ outputRoot,
41398
+ inputRoot: config.getInputRoot(),
41399
+ toolTarget,
41400
+ global: config.getGlobal(),
41401
+ dryRun: config.isPreviewMode(),
41402
+ logger
41403
+ });
41404
+ const result = await processFeatureWithRulesyncFiles({
41405
+ config,
41406
+ processor,
41407
+ rulesyncFiles: await processor.loadRulesyncFiles()
41408
+ });
41409
+ totalCount += result.count;
41410
+ allPaths.push(...result.paths);
41411
+ if (result.hasDiff) hasDiff = true;
41412
+ }
41413
+ return {
41414
+ count: totalCount,
41415
+ paths: allPaths,
41416
+ hasDiff
41417
+ };
41418
+ }
40147
41419
  //#endregion
40148
41420
  //#region src/lib/import.ts
40149
41421
  /**
@@ -40201,6 +41473,11 @@ async function importFromTool(params) {
40201
41473
  config,
40202
41474
  tool,
40203
41475
  logger
41476
+ }),
41477
+ checksCount: await importChecksCore({
41478
+ config,
41479
+ tool,
41480
+ logger
40204
41481
  })
40205
41482
  };
40206
41483
  }
@@ -40417,7 +41694,28 @@ async function importPermissionsCore(params) {
40417
41694
  if (config.getVerbose() && writtenCount > 0) logger.success(`Created ${writtenCount} permissions file(s)`);
40418
41695
  return writtenCount;
40419
41696
  }
41697
+ async function importChecksCore(params) {
41698
+ const { config, tool, logger } = params;
41699
+ if (!config.getFeatures(tool).includes("checks")) return 0;
41700
+ const global = config.getGlobal();
41701
+ if (!ChecksProcessor.getToolTargets({ global }).includes(tool)) return 0;
41702
+ const checksProcessor = new ChecksProcessor({
41703
+ outputRoot: config.getOutputRoots()[0] ?? ".",
41704
+ toolTarget: tool,
41705
+ global,
41706
+ logger
41707
+ });
41708
+ const toolFiles = await checksProcessor.loadToolFiles();
41709
+ if (toolFiles.length === 0) {
41710
+ logger.warn(`No check files found for ${tool}. Skipping import.`);
41711
+ return 0;
41712
+ }
41713
+ const rulesyncFiles = await checksProcessor.convertToolFilesToRulesyncFiles(toolFiles);
41714
+ const { count: writtenCount } = await checksProcessor.writeAiFiles(rulesyncFiles);
41715
+ if (config.getVerbose() && writtenCount > 0) logger.success(`Created ${writtenCount} check files`);
41716
+ return writtenCount;
41717
+ }
40420
41718
  //#endregion
40421
- export { removeFile as $, CLAUDECODE_SKILLS_DIR_PATH as A, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as At, ErrorCodes as B, RulesyncHooks as C, RULESYNC_PERMISSIONS_FILE_NAME as Ct, CLAUDECODE_LOCAL_RULE_FILE_NAME as D, RULESYNC_RELATIVE_DIR_PATH as Dt, CLAUDECODE_DIR as E, RULESYNC_PERMISSIONS_SCHEMA_URL as Et, ConfigResolver as F, fileExists as G, createTempDirectory as H, findControlCharacter as I, getHomeDirectory as J, findFilesByGlobs as K, ConsoleLogger as L, RulesyncCommandFrontmatterSchema as M, formatError as Mt, stringifyFrontmatter as N, ALL_FEATURES as Nt, CLAUDECODE_MEMORIES_DIR_NAME as O, RULESYNC_RULES_RELATIVE_DIR_PATH as Ot, loadYaml as P, ALL_FEATURES_WITH_WILDCARD as Pt, removeDirectory as Q, JsonLogger as R, HooksProcessor as S, RULESYNC_OVERVIEW_FILE_NAME as St, CODEXCLI_DIR as T, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as Tt, directoryExists as U, checkPathTraversal as V, ensureDir as W, listDirectoryFiles as X, isSymlink as Y, readFileContent as Z, RulesyncPermissions as _, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as _t, convertFromTool as a, ToolTargetSchema as at, IgnoreProcessor as b, RULESYNC_MCP_RELATIVE_FILE_PATH as bt, RulesyncRuleFrontmatterSchema as c, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as ct, RulesyncSubagentFrontmatterSchema as d, RULESYNC_CONFIG_SCHEMA_URL as dt, removeTempDirectory as et, SkillsProcessor as f, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as ft, SKILL_FILE_NAME as g, RULESYNC_IGNORE_RELATIVE_FILE_PATH as gt, RulesyncSkillFrontmatterSchema as h, RULESYNC_HOOKS_RELATIVE_FILE_PATH as ht, getProcessorRegistryEntry as i, ALL_TOOL_TARGETS_WITH_WILDCARD as it, RulesyncCommand as j, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as jt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as k, RULESYNC_SKILLS_RELATIVE_DIR_PATH as kt, SubagentsProcessor as l, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as lt, RulesyncSkill as m, RULESYNC_HOOKS_JSONC_FILE_NAME as mt, checkRulesyncDirExists as n, writeFileContent as nt, RulesProcessor as o, MAX_FILE_SIZE as ot, getLocalSkillDirNames as p, RULESYNC_HOOKS_FILE_NAME as pt, getFileSize as q, generate as r, ALL_TOOL_TARGETS as rt, RulesyncRule as s, RULESYNC_AIIGNORE_FILE_NAME as st, importFromTool as t, toPosixPath as tt, RulesyncSubagent as u, RULESYNC_CONFIG_RELATIVE_FILE_PATH as ut, McpProcessor as v, RULESYNC_MCP_FILE_NAME as vt, CommandsProcessor as w, RULESYNC_PERMISSIONS_JSONC_FILE_NAME as wt, RulesyncIgnore as x, RULESYNC_MCP_SCHEMA_URL as xt, RulesyncMcp as y, RULESYNC_MCP_JSONC_FILE_NAME as yt, CLIError as z };
41719
+ export { isSymlink as $, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as A, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as At, findControlCharacter as B, CommandsProcessor as C, RULESYNC_MCP_FILE_NAME as Ct, CLAUDECODE_DIR as D, RULESYNC_OVERVIEW_FILE_NAME as Dt, CODEXCLI_DIR as E, RULESYNC_MCP_SCHEMA_URL as Et, RulesyncCheck as F, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as Ft, checkPathTraversal as G, JsonLogger as H, RulesyncCheckFrontmatterSchema as I, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as It, ensureDir as J, createTempDirectory as K, stringifyFrontmatter as L, formatError as Lt, RulesyncCommand as M, RULESYNC_RELATIVE_DIR_PATH as Mt, RulesyncCommandFrontmatterSchema as N, RULESYNC_RULES_RELATIVE_DIR_PATH as Nt, CLAUDECODE_LOCAL_RULE_FILE_NAME as O, RULESYNC_PERMISSIONS_FILE_NAME as Ot, ChecksProcessor as P, RULESYNC_SKILLS_RELATIVE_DIR_PATH as Pt, getHomeDirectory as Q, loadYaml as R, ALL_FEATURES as Rt, RulesyncHooks as S, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as St, CODEXCLI_BASH_RULES_FILE_NAME as T, RULESYNC_MCP_RELATIVE_FILE_PATH as Tt, CLIError as U, ConsoleLogger as V, ErrorCodes as W, findFilesByGlobs as X, fileExists as Y, getFileSize as Z, McpProcessor as _, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as _t, convertFromTool as a, toPosixPath as at, RulesyncIgnore as b, RULESYNC_HOOKS_RELATIVE_FILE_PATH as bt, RulesyncRuleFrontmatterSchema as c, ALL_TOOL_TARGETS_WITH_WILDCARD as ct, RulesyncSubagentFrontmatterSchema as d, RULESYNC_AIIGNORE_FILE_NAME as dt, listDirectoryFiles as et, SkillsProcessor as f, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as ft, RulesyncPermissions as g, RULESYNC_CONFIG_SCHEMA_URL as gt, RulesyncSkillFrontmatterSchema as h, RULESYNC_CONFIG_RELATIVE_FILE_PATH as ht, getProcessorRegistryEntry as i, removeTempDirectory as it, CLAUDECODE_SKILLS_DIR_PATH as j, RULESYNC_PERMISSIONS_SCHEMA_URL as jt, CLAUDECODE_MEMORIES_DIR_NAME as k, RULESYNC_PERMISSIONS_JSONC_FILE_NAME as kt, SubagentsProcessor as l, ToolTargetSchema as lt, RulesyncSkill as m, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as mt, checkRulesyncDirExists as n, removeDirectory as nt, RulesProcessor as o, writeFileContent as ot, getLocalSkillDirNames as p, RULESYNC_CHECKS_RELATIVE_DIR_PATH as pt, directoryExists as q, generate as r, removeFile as rt, RulesyncRule as s, ALL_TOOL_TARGETS as st, importFromTool as t, readFileContent as tt, RulesyncSubagent as u, MAX_FILE_SIZE as ut, RulesyncMcp as v, RULESYNC_HOOKS_FILE_NAME as vt, SKILL_FILE_NAME$1 as w, RULESYNC_MCP_JSONC_FILE_NAME as wt, HooksProcessor as x, RULESYNC_IGNORE_RELATIVE_FILE_PATH as xt, IgnoreProcessor as y, RULESYNC_HOOKS_JSONC_FILE_NAME as yt, ConfigResolver as z, ALL_FEATURES_WITH_WILDCARD as zt };
40422
41720
 
40423
- //# sourceMappingURL=import-AKytnIKl.js.map
41721
+ //# sourceMappingURL=import-CMSHVMqU.js.map