rulesync 12.0.0 → 14.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");
@@ -109,6 +112,7 @@ const RULESYNC_OVERVIEW_FILE_NAME = "overview.md";
109
112
  const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$1(RULESYNC_RELATIVE_DIR_PATH, "skills");
110
113
  const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$1(RULESYNC_SKILLS_RELATIVE_DIR_PATH, ".curated");
111
114
  const RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync.lock";
115
+ const RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync-npm.lock.json";
112
116
  const RULESYNC_MCP_FILE_NAME = "mcp.json";
113
117
  const RULESYNC_HOOKS_FILE_NAME = "hooks.json";
114
118
  const RULESYNC_PERMISSIONS_FILE_NAME = "permissions.json";
@@ -341,6 +345,7 @@ const permissionsProcessorToolTargetTuple = [
341
345
  "claudecode",
342
346
  "cline",
343
347
  "codexcli",
348
+ "copilot",
344
349
  "cursor",
345
350
  "devin",
346
351
  "factorydroid",
@@ -361,6 +366,7 @@ const permissionsProcessorToolTargetTuple = [
361
366
  "warp",
362
367
  "zed"
363
368
  ];
369
+ const checksProcessorToolTargetTuple = ["amp"];
364
370
  //#endregion
365
371
  //#region src/types/tool-targets.ts
366
372
  const ALL_TOOL_TARGETS = [...new Set([
@@ -371,7 +377,8 @@ const ALL_TOOL_TARGETS = [...new Set([
371
377
  subagentsProcessorToolTargetTuple,
372
378
  skillsProcessorToolTargetTuple,
373
379
  hooksProcessorToolTargetTuple,
374
- permissionsProcessorToolTargetTuple
380
+ permissionsProcessorToolTargetTuple,
381
+ checksProcessorToolTargetTuple
375
382
  ].flat())];
376
383
  const ALL_TOOL_TARGETS_WITH_WILDCARD = [...ALL_TOOL_TARGETS, "*"];
377
384
  const ToolTargetSchema = z.enum(ALL_TOOL_TARGETS);
@@ -850,9 +857,15 @@ const GITIGNORE_DESTINATION_KEY = "gitignoreDestination";
850
857
  const SourceEntrySchema = z.object({
851
858
  source: z.string().check(minLength(1, "source must be a non-empty string")),
852
859
  skills: optional(z.array(z.string())),
853
- transport: optional(z.enum(["github", "git"])),
860
+ transport: optional(z.enum([
861
+ "github",
862
+ "git",
863
+ "npm"
864
+ ])),
854
865
  ref: optional(z.string().check(refine((v) => !v.startsWith("-"), "ref must not start with \"-\""), refine((v) => !hasControlCharacters(v), "ref must not contain control characters"))),
855
866
  path: optional(z.string().check(refine((v) => !v.includes(".."), "path must not contain \"..\""), refine((v) => !isAbsolute(v), "path must not be absolute"), refine((v) => !hasControlCharacters(v), "path must not contain control characters"))),
867
+ registry: optional(z.string().check(refine((v) => v.startsWith("https://") || v.startsWith("http://"), "registry must be an http(s) URL"), refine((v) => !hasControlCharacters(v), "registry must not contain control characters"))),
868
+ tokenEnv: optional(z.string().check(refine((v) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(v), "tokenEnv must be a valid environment variable name"))),
856
869
  agent: optional(z.enum([
857
870
  "github-copilot",
858
871
  "claude-code",
@@ -862,7 +875,7 @@ const SourceEntrySchema = z.object({
862
875
  "antigravity"
863
876
  ])),
864
877
  scope: optional(z.enum(["project", "user"]))
865
- });
878
+ }).check(refine((entry) => entry.registry === void 0 && entry.tokenEnv === void 0 || entry.transport === "npm", "\"registry\" and \"tokenEnv\" are only valid with transport \"npm\""));
866
879
  const ConfigParamsSchema = z.object({
867
880
  outputRoots: z.union([z.array(z.string()), z.record(z.string(), z.union([z.string(), z.array(z.string())]))]),
868
881
  targets: RulesyncConfigTargetsSchema,
@@ -1445,7 +1458,7 @@ function isPlainObject$1(value) {
1445
1458
  /**
1446
1459
  * Type guard to check if a value is an array of strings.
1447
1460
  */
1448
- function isStringArray(value) {
1461
+ function isStringArray$1(value) {
1449
1462
  return Array.isArray(value) && value.every((item) => typeof item === "string");
1450
1463
  }
1451
1464
  //#endregion
@@ -1693,13 +1706,17 @@ var FeatureProcessor = class {
1693
1706
  }
1694
1707
  };
1695
1708
  //#endregion
1696
- //#region src/constants/agentsmd-paths.ts
1697
- const AGENTSMD_DIR = ".agents";
1698
- const AGENTSMD_MEMORIES_DIR_PATH = join(AGENTSMD_DIR, "memories");
1699
- const AGENTSMD_COMMANDS_DIR_PATH = join(AGENTSMD_DIR, "commands");
1700
- const AGENTSMD_SKILLS_DIR_PATH = join(AGENTSMD_DIR, "skills");
1701
- const AGENTSMD_SUBAGENTS_DIR_PATH = join(AGENTSMD_DIR, "subagents");
1702
- const AGENTSMD_RULE_FILE_NAME = "AGENTS.md";
1709
+ //#region src/constants/amp-paths.ts
1710
+ const AMP_DIR = ".amp";
1711
+ const AMP_GLOBAL_DIR = join(".config", "amp");
1712
+ const AMP_AGENTS_DIR = ".agents";
1713
+ const AMP_SKILLS_PROJECT_DIR = join(AMP_AGENTS_DIR, "skills");
1714
+ const AMP_SKILLS_GLOBAL_DIR = join(".config", "agents", "skills");
1715
+ const AMP_CHECKS_PROJECT_DIR = join(AMP_AGENTS_DIR, "checks");
1716
+ const AMP_CHECKS_GLOBAL_DIR = join(AMP_GLOBAL_DIR, "checks");
1717
+ const AMP_RULE_FILE_NAME = "AGENTS.md";
1718
+ const AMP_SETTINGS_FILE_NAME = "settings.json";
1719
+ const AMP_SETTINGS_JSONC_FILE_NAME = "settings.jsonc";
1703
1720
  //#endregion
1704
1721
  //#region src/types/ai-file.ts
1705
1722
  var AiFile = class {
@@ -1773,6 +1790,414 @@ var AiFile = class {
1773
1790
  }
1774
1791
  };
1775
1792
  //#endregion
1793
+ //#region src/types/rulesync-file.ts
1794
+ var RulesyncFile = class extends AiFile {
1795
+ static async fromFile(_params) {
1796
+ throw new Error("Please implement this method in the subclass.");
1797
+ }
1798
+ };
1799
+ //#endregion
1800
+ //#region src/features/checks/rulesync-check.ts
1801
+ const RulesyncCheckFrontmatterSchema = z.looseObject({
1802
+ targets: z._default(RulesyncTargetsSchema, ["*"]),
1803
+ description: z.optional(z.string()),
1804
+ severity: z.optional(z.enum([
1805
+ "low",
1806
+ "medium",
1807
+ "high",
1808
+ "critical"
1809
+ ])),
1810
+ tools: z.optional(z.array(z.string()))
1811
+ });
1812
+ var RulesyncCheck = class RulesyncCheck extends RulesyncFile {
1813
+ frontmatter;
1814
+ body;
1815
+ constructor({ frontmatter, body, ...rest }) {
1816
+ const parseResult = RulesyncCheckFrontmatterSchema.safeParse(frontmatter);
1817
+ if (!parseResult.success && rest.validate !== false) throw new Error(`Invalid frontmatter in ${join(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(parseResult.error)}`);
1818
+ const parsedFrontmatter = parseResult.success ? {
1819
+ ...frontmatter,
1820
+ ...parseResult.data
1821
+ } : {
1822
+ ...frontmatter,
1823
+ targets: frontmatter?.targets ?? ["*"]
1824
+ };
1825
+ super({
1826
+ ...rest,
1827
+ fileContent: stringifyFrontmatter(body, parsedFrontmatter)
1828
+ });
1829
+ this.frontmatter = parsedFrontmatter;
1830
+ this.body = body;
1831
+ }
1832
+ static getSettablePaths() {
1833
+ return { relativeDirPath: RULESYNC_CHECKS_RELATIVE_DIR_PATH };
1834
+ }
1835
+ getFrontmatter() {
1836
+ return this.frontmatter;
1837
+ }
1838
+ getBody() {
1839
+ return this.body;
1840
+ }
1841
+ validate() {
1842
+ if (!this.frontmatter) return {
1843
+ success: true,
1844
+ error: null
1845
+ };
1846
+ const result = RulesyncCheckFrontmatterSchema.safeParse(this.frontmatter);
1847
+ if (result.success) return {
1848
+ success: true,
1849
+ error: null
1850
+ };
1851
+ else return {
1852
+ success: false,
1853
+ error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${join(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
1854
+ };
1855
+ }
1856
+ static async fromFile({ outputRoot = process.cwd(), relativeFilePath }) {
1857
+ const filePath = join(outputRoot, RULESYNC_CHECKS_RELATIVE_DIR_PATH, relativeFilePath);
1858
+ const { frontmatter, body: content, hasFrontmatter } = parseFrontmatter(await readFileContent(filePath), filePath);
1859
+ if (!hasFrontmatter) throw new Error(`Missing frontmatter in ${filePath}. Rulesync files must begin with a YAML frontmatter block delimited by '---'.`);
1860
+ const result = RulesyncCheckFrontmatterSchema.safeParse(frontmatter);
1861
+ if (!result.success) throw new Error(`Invalid frontmatter in ${relativeFilePath}: ${formatError(result.error)}`);
1862
+ const filename = basename(relativeFilePath);
1863
+ return new RulesyncCheck({
1864
+ outputRoot,
1865
+ relativeDirPath: this.getSettablePaths().relativeDirPath,
1866
+ relativeFilePath: filename,
1867
+ frontmatter: result.data,
1868
+ body: content.trim()
1869
+ });
1870
+ }
1871
+ };
1872
+ //#endregion
1873
+ //#region src/types/tool-file.ts
1874
+ var ToolFile = class extends AiFile {};
1875
+ //#endregion
1876
+ //#region src/features/checks/tool-check.ts
1877
+ var ToolCheck = class extends ToolFile {
1878
+ static getSettablePaths(_options = {}) {
1879
+ throw new Error("Please implement this method in the subclass.");
1880
+ }
1881
+ static async fromFile(_params) {
1882
+ throw new Error("Please implement this method in the subclass.");
1883
+ }
1884
+ /**
1885
+ * Create a minimal instance for deletion purposes.
1886
+ * This method does not read or parse file content, making it safe to use
1887
+ * even when files have old/incompatible formats.
1888
+ */
1889
+ static forDeletion(_params) {
1890
+ throw new Error("Please implement this method in the subclass.");
1891
+ }
1892
+ static fromRulesyncCheck(_params) {
1893
+ throw new Error("Please implement this method in the subclass.");
1894
+ }
1895
+ static isTargetedByRulesyncCheck(_rulesyncCheck) {
1896
+ throw new Error("Please implement this method in the subclass.");
1897
+ }
1898
+ static isTargetedByRulesyncCheckDefault({ rulesyncCheck, toolTarget }) {
1899
+ const targets = rulesyncCheck.getFrontmatter().targets;
1900
+ if (!targets) return true;
1901
+ if (targets.includes("*")) return true;
1902
+ if (targets.includes(toolTarget)) return true;
1903
+ return false;
1904
+ }
1905
+ static filterToolSpecificSection(rawSection, excludeFields) {
1906
+ const filtered = {};
1907
+ for (const [key, value] of Object.entries(rawSection)) if (!excludeFields.includes(key)) filtered[key] = value;
1908
+ return filtered;
1909
+ }
1910
+ };
1911
+ //#endregion
1912
+ //#region src/features/checks/amp-check.ts
1913
+ const AmpCheckFrontmatterSchema = z.looseObject({
1914
+ name: z.string(),
1915
+ description: z.optional(z.string()),
1916
+ "severity-default": z.optional(z.enum([
1917
+ "low",
1918
+ "medium",
1919
+ "high",
1920
+ "critical"
1921
+ ])),
1922
+ tools: z.optional(z.array(z.string()))
1923
+ });
1924
+ /**
1925
+ * Represents an Amp code review check.
1926
+ *
1927
+ * Amp natively reads code review checks as Markdown files with YAML frontmatter,
1928
+ * scoped to the project (`.agents/checks/`) and user-wide (`~/.config/amp/checks/`).
1929
+ * Each check runs as a per-check subagent during code review.
1930
+ * @see https://ampcode.com/manual
1931
+ */
1932
+ var AmpCheck = class AmpCheck extends ToolCheck {
1933
+ frontmatter;
1934
+ body;
1935
+ constructor({ frontmatter, body, fileContent, ...rest }) {
1936
+ if (rest.validate !== false) {
1937
+ const result = AmpCheckFrontmatterSchema.safeParse(frontmatter);
1938
+ if (!result.success) throw new Error(`Invalid frontmatter in ${join(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
1939
+ }
1940
+ super({
1941
+ ...rest,
1942
+ fileContent: fileContent ?? stringifyFrontmatter(body, frontmatter)
1943
+ });
1944
+ this.frontmatter = frontmatter;
1945
+ this.body = body;
1946
+ }
1947
+ static getSettablePaths({ global = false } = {}) {
1948
+ return { relativeDirPath: global ? AMP_CHECKS_GLOBAL_DIR : AMP_CHECKS_PROJECT_DIR };
1949
+ }
1950
+ getFrontmatter() {
1951
+ return this.frontmatter;
1952
+ }
1953
+ getBody() {
1954
+ return this.body;
1955
+ }
1956
+ toRulesyncCheck() {
1957
+ const { name: _name, description, "severity-default": severityDefault, tools, ...restFields } = this.frontmatter;
1958
+ return new RulesyncCheck({
1959
+ outputRoot: ".",
1960
+ frontmatter: {
1961
+ targets: ["*"],
1962
+ ...description !== void 0 && { description },
1963
+ ...severityDefault !== void 0 && { severity: severityDefault },
1964
+ ...tools !== void 0 && { tools },
1965
+ ...restFields
1966
+ },
1967
+ body: this.body,
1968
+ relativeDirPath: RULESYNC_CHECKS_RELATIVE_DIR_PATH,
1969
+ relativeFilePath: this.getRelativeFilePath(),
1970
+ validate: true
1971
+ });
1972
+ }
1973
+ static fromRulesyncCheck({ outputRoot = process.cwd(), rulesyncCheck, validate = true, global = false }) {
1974
+ const rulesyncFrontmatter = rulesyncCheck.getFrontmatter();
1975
+ const relativeFilePath = rulesyncCheck.getRelativeFilePath();
1976
+ const name = basename(relativeFilePath, ".md");
1977
+ const { targets: _targets, description, severity, tools, ...restFields } = rulesyncFrontmatter;
1978
+ const toolTargetKeys = new Set(ALL_TOOL_TARGETS);
1979
+ const passthroughFields = Object.fromEntries(Object.entries(restFields).filter(([key]) => !toolTargetKeys.has(key) && key !== "name"));
1980
+ const ampSection = this.filterToolSpecificSection(rulesyncFrontmatter.amp ?? {}, ["name"]);
1981
+ const rawAmpFrontmatter = {
1982
+ name,
1983
+ ...description !== void 0 && { description },
1984
+ ...severity !== void 0 && { "severity-default": severity },
1985
+ ...tools !== void 0 && { tools },
1986
+ ...passthroughFields,
1987
+ ...ampSection
1988
+ };
1989
+ const result = AmpCheckFrontmatterSchema.safeParse(rawAmpFrontmatter);
1990
+ if (!result.success) throw new Error(`Invalid amp check frontmatter in ${relativeFilePath}: ${formatError(result.error)}`);
1991
+ const ampFrontmatter = result.data;
1992
+ const body = rulesyncCheck.getBody();
1993
+ const fileContent = stringifyFrontmatter(body, ampFrontmatter);
1994
+ const paths = this.getSettablePaths({ global });
1995
+ return new AmpCheck({
1996
+ outputRoot,
1997
+ frontmatter: ampFrontmatter,
1998
+ body,
1999
+ relativeDirPath: paths.relativeDirPath,
2000
+ relativeFilePath,
2001
+ fileContent,
2002
+ validate
2003
+ });
2004
+ }
2005
+ validate() {
2006
+ if (!this.frontmatter) return {
2007
+ success: true,
2008
+ error: null
2009
+ };
2010
+ const result = AmpCheckFrontmatterSchema.safeParse(this.frontmatter);
2011
+ if (result.success) return {
2012
+ success: true,
2013
+ error: null
2014
+ };
2015
+ else return {
2016
+ success: false,
2017
+ error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${join(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
2018
+ };
2019
+ }
2020
+ static isTargetedByRulesyncCheck(rulesyncCheck) {
2021
+ return this.isTargetedByRulesyncCheckDefault({
2022
+ rulesyncCheck,
2023
+ toolTarget: "amp"
2024
+ });
2025
+ }
2026
+ static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
2027
+ const paths = this.getSettablePaths({ global });
2028
+ const filePath = join(outputRoot, paths.relativeDirPath, relativeFilePath);
2029
+ const fileContent = await readFileContent(filePath);
2030
+ const { frontmatter, body: content } = parseFrontmatter(fileContent, filePath);
2031
+ const result = AmpCheckFrontmatterSchema.safeParse(frontmatter);
2032
+ if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
2033
+ return new AmpCheck({
2034
+ outputRoot,
2035
+ relativeDirPath: paths.relativeDirPath,
2036
+ relativeFilePath,
2037
+ frontmatter: result.data,
2038
+ body: content.trim(),
2039
+ fileContent,
2040
+ validate
2041
+ });
2042
+ }
2043
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
2044
+ return new AmpCheck({
2045
+ outputRoot,
2046
+ relativeDirPath,
2047
+ relativeFilePath,
2048
+ frontmatter: { name: "" },
2049
+ body: "",
2050
+ fileContent: "",
2051
+ validate: false
2052
+ });
2053
+ }
2054
+ };
2055
+ //#endregion
2056
+ //#region src/features/checks/checks-processor.ts
2057
+ const ChecksProcessorToolTargetSchema = z.enum(checksProcessorToolTargetTuple);
2058
+ /**
2059
+ * Factory Map mapping tool targets to their check factories.
2060
+ * Using Map to preserve insertion order for consistent iteration.
2061
+ */
2062
+ const toolCheckFactories = /* @__PURE__ */ new Map([["amp", {
2063
+ class: AmpCheck,
2064
+ meta: {
2065
+ supportsGlobal: true,
2066
+ filePattern: "*.md"
2067
+ }
2068
+ }]]);
2069
+ const defaultGetFactory$6 = (target) => {
2070
+ const factory = toolCheckFactories.get(target);
2071
+ if (!factory) throw new Error(`Unsupported tool target: ${target}`);
2072
+ return factory;
2073
+ };
2074
+ const allToolTargetKeys$5 = [...toolCheckFactories.keys()];
2075
+ const checksProcessorToolTargets = allToolTargetKeys$5;
2076
+ const checksProcessorToolTargetsGlobal = allToolTargetKeys$5.filter((target) => {
2077
+ return toolCheckFactories.get(target)?.meta.supportsGlobal ?? false;
2078
+ });
2079
+ var ChecksProcessor = class extends FeatureProcessor {
2080
+ toolTarget;
2081
+ global;
2082
+ getFactory;
2083
+ constructor({ outputRoot = process.cwd(), inputRoot = process.cwd(), toolTarget, global = false, getFactory = defaultGetFactory$6, dryRun = false, logger }) {
2084
+ super({
2085
+ outputRoot,
2086
+ inputRoot,
2087
+ dryRun,
2088
+ logger
2089
+ });
2090
+ const result = ChecksProcessorToolTargetSchema.safeParse(toolTarget);
2091
+ if (!result.success) throw new Error(`Invalid tool target for ChecksProcessor: ${toolTarget}. ${formatError(result.error)}`);
2092
+ this.toolTarget = result.data;
2093
+ this.global = global;
2094
+ this.getFactory = getFactory;
2095
+ }
2096
+ async convertRulesyncFilesToToolFiles(rulesyncFiles) {
2097
+ const rulesyncChecks = rulesyncFiles.filter((file) => file instanceof RulesyncCheck);
2098
+ const factory = this.getFactory(this.toolTarget);
2099
+ return rulesyncChecks.filter((rulesyncCheck) => factory.class.isTargetedByRulesyncCheck(rulesyncCheck)).map((rulesyncCheck) => factory.class.fromRulesyncCheck({
2100
+ outputRoot: this.outputRoot,
2101
+ relativeDirPath: RulesyncCheck.getSettablePaths().relativeDirPath,
2102
+ rulesyncCheck,
2103
+ global: this.global
2104
+ }));
2105
+ }
2106
+ async convertToolFilesToRulesyncFiles(toolFiles) {
2107
+ return toolFiles.filter((file) => file instanceof ToolCheck).map((toolCheck) => toolCheck.toRulesyncCheck());
2108
+ }
2109
+ /**
2110
+ * Implementation of abstract method from Processor
2111
+ * Load and parse rulesync check files from .rulesync/checks/ directory
2112
+ */
2113
+ async loadRulesyncFiles() {
2114
+ const checksDir = join(this.inputRoot, RulesyncCheck.getSettablePaths().relativeDirPath);
2115
+ if (!await directoryExists(checksDir)) {
2116
+ this.logger.debug(`Rulesync checks directory not found: ${checksDir}`);
2117
+ return [];
2118
+ }
2119
+ const mdFiles = (await listDirectoryFiles(checksDir)).filter((file) => file.endsWith(".md"));
2120
+ if (mdFiles.length === 0) {
2121
+ this.logger.debug(`No markdown files found in rulesync checks directory: ${checksDir}`);
2122
+ return [];
2123
+ }
2124
+ this.logger.debug(`Found ${mdFiles.length} check files in ${checksDir}`);
2125
+ const rulesyncChecks = [];
2126
+ for (const mdFile of mdFiles) {
2127
+ const filepath = join(checksDir, mdFile);
2128
+ try {
2129
+ const rulesyncCheck = await RulesyncCheck.fromFile({
2130
+ outputRoot: this.inputRoot,
2131
+ relativeFilePath: mdFile,
2132
+ validate: true
2133
+ });
2134
+ rulesyncChecks.push(rulesyncCheck);
2135
+ this.logger.debug(`Successfully loaded check: ${mdFile}`);
2136
+ } catch (error) {
2137
+ this.logger.warn(`Failed to load check file ${filepath}: ${formatError(error)}`);
2138
+ continue;
2139
+ }
2140
+ }
2141
+ this.logger.debug(`Successfully loaded ${rulesyncChecks.length} rulesync checks`);
2142
+ return rulesyncChecks;
2143
+ }
2144
+ /**
2145
+ * Implementation of abstract method from Processor
2146
+ * Load tool-specific check files and parse them into ToolCheck instances
2147
+ */
2148
+ async loadToolFiles({ forDeletion = false } = {}) {
2149
+ const factory = this.getFactory(this.toolTarget);
2150
+ const paths = factory.class.getSettablePaths({ global: this.global });
2151
+ const baseDir = join(this.outputRoot, paths.relativeDirPath);
2152
+ const checkFilePaths = await findFilesByGlobs(join(baseDir, factory.meta.filePattern));
2153
+ const toRelativeFilePath = (path) => relative(baseDir, path);
2154
+ if (forDeletion) return checkFilePaths.map((path) => factory.class.forDeletion({
2155
+ outputRoot: this.outputRoot,
2156
+ relativeDirPath: paths.relativeDirPath,
2157
+ relativeFilePath: toRelativeFilePath(path),
2158
+ global: this.global
2159
+ })).filter((check) => check.isDeletable());
2160
+ const loaded = await Promise.all(checkFilePaths.map((path) => factory.class.fromFile({
2161
+ outputRoot: this.outputRoot,
2162
+ relativeDirPath: paths.relativeDirPath,
2163
+ relativeFilePath: toRelativeFilePath(path),
2164
+ global: this.global
2165
+ })));
2166
+ this.logger.debug(`Successfully loaded ${loaded.length} ${this.toolTarget} checks from ${paths.relativeDirPath}`);
2167
+ return loaded;
2168
+ }
2169
+ /**
2170
+ * Implementation of abstract method from FeatureProcessor
2171
+ * Return the tool targets that this processor supports
2172
+ */
2173
+ static getToolTargets({ global = false } = {}) {
2174
+ if (global) return [...checksProcessorToolTargetsGlobal];
2175
+ return [...checksProcessorToolTargets];
2176
+ }
2177
+ static getToolTargetsSimulated() {
2178
+ return [];
2179
+ }
2180
+ /**
2181
+ * Get the factory for a specific tool target.
2182
+ * This is a static version of the internal getFactory for external use.
2183
+ * @param target - The tool target. Must be a valid ChecksProcessorToolTarget.
2184
+ * @returns The factory for the target, or undefined if not found.
2185
+ */
2186
+ static getFactory(target) {
2187
+ const result = ChecksProcessorToolTargetSchema.safeParse(target);
2188
+ if (!result.success) return;
2189
+ return toolCheckFactories.get(result.data);
2190
+ }
2191
+ };
2192
+ //#endregion
2193
+ //#region src/constants/agentsmd-paths.ts
2194
+ const AGENTSMD_DIR = ".agents";
2195
+ const AGENTSMD_MEMORIES_DIR_PATH = join(AGENTSMD_DIR, "memories");
2196
+ const AGENTSMD_COMMANDS_DIR_PATH = join(AGENTSMD_DIR, "commands");
2197
+ const AGENTSMD_SKILLS_DIR_PATH = join(AGENTSMD_DIR, "skills");
2198
+ const AGENTSMD_SUBAGENTS_DIR_PATH = join(AGENTSMD_DIR, "subagents");
2199
+ const AGENTSMD_RULE_FILE_NAME = "AGENTS.md";
2200
+ //#endregion
1776
2201
  //#region src/features/commands/tool-command.ts
1777
2202
  /**
1778
2203
  * Abstract base class for AI development tool-specific command formats.
@@ -2020,13 +2445,6 @@ const AntigravityCommandFrontmatterSchema = z.looseObject({
2020
2445
  ...AntigravityWorkflowFrontmatterSchema.shape
2021
2446
  });
2022
2447
  //#endregion
2023
- //#region src/types/rulesync-file.ts
2024
- var RulesyncFile = class extends AiFile {
2025
- static async fromFile(_params) {
2026
- throw new Error("Please implement this method in the subclass.");
2027
- }
2028
- };
2029
- //#endregion
2030
2448
  //#region src/features/commands/rulesync-command.ts
2031
2449
  const RulesyncCommandFrontmatterSchema = z.looseObject({
2032
2450
  targets: z._default(RulesyncTargetsSchema, ["*"]),
@@ -2829,6 +3247,7 @@ const COPILOT_HOOKS_DIR_PATH = join(GITHUB_DIR, "hooks");
2829
3247
  const COPILOT_HOOKS_FILE_NAME = "copilot-hooks.json";
2830
3248
  const COPILOT_MCP_DIR = ".vscode";
2831
3249
  const COPILOT_MCP_FILE_NAME = "mcp.json";
3250
+ const COPILOT_VSCODE_SETTINGS_FILE_NAME = "settings.json";
2832
3251
  const COPILOTCLI_MCP_FILE_NAME = "mcp-config.json";
2833
3252
  const COPILOTCLI_PROJECT_MCP_FILE_NAME = "mcp.json";
2834
3253
  const COPILOTCLI_AGENTS_DIR_PATH = join(COPILOT_DIR, "agents");
@@ -3087,135 +3506,118 @@ var CursorCommand = class CursorCommand extends ToolCommand {
3087
3506
  //#endregion
3088
3507
  //#region src/constants/devin-paths.ts
3089
3508
  const DEVIN_DIR = ".devin";
3090
- const CODEIUM_WINDSURF_DIR = join(".codeium", "windsurf");
3091
- const DEVIN_WORKFLOWS_DIR_PATH = join(DEVIN_DIR, "workflows");
3092
3509
  const DEVIN_SKILLS_DIR_PATH = join(DEVIN_DIR, "skills");
3093
3510
  const DEVIN_AGENTS_DIR_PATH = join(DEVIN_DIR, "agents");
3094
3511
  const DEVIN_GLOBAL_CONFIG_DIR_PATH = join(".config", "devin");
3095
3512
  const DEVIN_GLOBAL_AGENTS_DIR_PATH = join(DEVIN_GLOBAL_CONFIG_DIR_PATH, "agents");
3096
3513
  const DEVIN_GLOBAL_SKILLS_DIR_PATH = join(DEVIN_GLOBAL_CONFIG_DIR_PATH, "skills");
3097
- const CODEIUM_WINDSURF_GLOBAL_WORKFLOWS_DIR_PATH = join(CODEIUM_WINDSURF_DIR, "global_workflows");
3098
3514
  const DEVIN_CONFIG_FILE_NAME = "config.json";
3099
3515
  const DEVIN_HOOKS_V1_FILE_NAME = "hooks.v1.json";
3100
3516
  const DEVIN_GLOBAL_AGENTS_FILE_NAME = "AGENTS.md";
3101
3517
  const DEVIN_IGNORE_FILE_NAME = ".devinignore";
3102
3518
  const DEVIN_LEGACY_IGNORE_FILE_NAME = ".codeiumignore";
3103
3519
  //#endregion
3520
+ //#region src/constants/general.ts
3521
+ const SKILL_FILE_NAME$1 = "SKILL.md";
3522
+ //#endregion
3523
+ //#region src/features/commands/command-skill-ownership.ts
3524
+ /**
3525
+ * Slug used for the per-command `<slug>/SKILL.md` directory when a tool's
3526
+ * commands are emitted onto its skills surface (Devin, Hermes Agent).
3527
+ */
3528
+ function commandSlug(relativeFilePath) {
3529
+ return basename(relativeFilePath, ".md").replace(/[^a-zA-Z0-9_-]/g, "-");
3530
+ }
3531
+ /**
3532
+ * Whether a rulesync command exists whose slug matches `dirName`.
3533
+ *
3534
+ * Used by the skills-surface `isDirOwned` hooks of tools whose commands are
3535
+ * emitted as `<slug>/SKILL.md` into the skills tree: a directory matching a
3536
+ * current command slug is owned by the commands feature, so the skills
3537
+ * feature must neither import it as a skill nor delete it as an orphan
3538
+ * skill. Once the command is removed from `.rulesync/commands/`, the
3539
+ * directory stops matching and the skills feature cleans it up as a regular
3540
+ * orphan.
3541
+ */
3542
+ async function rulesyncCommandSlugExists({ inputRoot, dirName }) {
3543
+ return (await findFilesByGlobs(join(inputRoot, RULESYNC_COMMANDS_RELATIVE_DIR_PATH, "**", "*.md"))).some((filePath) => commandSlug(basename(filePath)) === dirName);
3544
+ }
3545
+ //#endregion
3104
3546
  //#region src/features/commands/devin-command.ts
3105
- const DevinCommandFrontmatterSchema = z.looseObject({ description: z.optional(z.string()) });
3547
+ function commandSkillContent$1(rulesyncCommand) {
3548
+ const slug = commandSlug(rulesyncCommand.getRelativeFilePath());
3549
+ const description = rulesyncCommand.getFrontmatter().description ?? `${slug} command`;
3550
+ return stringifyFrontmatter(rulesyncCommand.getBody().trim(), {
3551
+ name: slug,
3552
+ description
3553
+ });
3554
+ }
3106
3555
  /**
3107
- * Represents a Devin (Cascade, now Devin Desktop) workflow command.
3108
- * Devin supports workflows in both project mode under .devin/workflows/
3109
- * (preferred since the Devin Desktop rebrand; .devin/workflows/ is the
3110
- * legacy fallback the tool still reads) and global mode under
3111
- * ~/.codeium/windsurf/global_workflows/ (unchanged by the rebrand).
3556
+ * Represents a Devin slash command, emitted as a Devin Skill.
3557
+ *
3558
+ * Devin's extensibility docs no longer document a standalone
3559
+ * workflows/commands component reusable prompts invoked as slash commands
3560
+ * are Skills (`/name`). Commands are therefore emitted onto the native skills
3561
+ * surface, one `SKILL.md` per command: `.devin/skills/<slug>/SKILL.md`
3562
+ * (project) and `~/.config/devin/skills/<slug>/SKILL.md` (global). The legacy
3563
+ * Windsurf/Cascade-era `.devin/workflows/` and
3564
+ * `~/.codeium/windsurf/global_workflows/` locations are no longer emitted.
3565
+ *
3566
+ * Import and deletion are intentionally no-ops for this target: the skills
3567
+ * feature owns the `.devin/skills/` tree, so importing it as commands would
3568
+ * double-import every skill (mirrors the Hermes Agent commands target).
3569
+ *
3570
+ * @see https://docs.devin.ai/cli/extensibility
3571
+ * @see https://docs.devin.ai/cli/extensibility/skills/overview
3112
3572
  */
3113
3573
  var DevinCommand = class DevinCommand extends ToolCommand {
3114
- frontmatter;
3115
- body;
3116
- constructor({ frontmatter, body, ...rest }) {
3117
- if (rest.validate) {
3118
- const result = DevinCommandFrontmatterSchema.safeParse(frontmatter);
3119
- if (!result.success) throw new Error(`Invalid frontmatter in ${join(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
3120
- }
3121
- super({
3122
- ...rest,
3123
- fileContent: stringifyFrontmatter(body, frontmatter)
3574
+ static isTargetedByRulesyncCommand(rulesyncCommand) {
3575
+ return this.isTargetedByRulesyncCommandDefault({
3576
+ rulesyncCommand,
3577
+ toolTarget: "devin"
3124
3578
  });
3125
- this.frontmatter = frontmatter;
3126
- this.body = body;
3127
3579
  }
3128
3580
  static getSettablePaths({ global = false } = {}) {
3129
- if (global) return { relativeDirPath: CODEIUM_WINDSURF_GLOBAL_WORKFLOWS_DIR_PATH };
3130
- return { relativeDirPath: DEVIN_WORKFLOWS_DIR_PATH };
3131
- }
3132
- getBody() {
3133
- return this.body;
3581
+ return { relativeDirPath: global ? DEVIN_GLOBAL_SKILLS_DIR_PATH : DEVIN_SKILLS_DIR_PATH };
3134
3582
  }
3135
- getFrontmatter() {
3136
- return this.frontmatter;
3137
- }
3138
- toRulesyncCommand() {
3139
- const { description, ...restFields } = this.frontmatter;
3140
- const rulesyncFrontmatter = {
3141
- targets: ["*"],
3142
- description,
3143
- ...Object.keys(restFields).length > 0 && { devin: restFields }
3144
- };
3145
- const fileContent = stringifyFrontmatter(this.body, rulesyncFrontmatter);
3146
- return new RulesyncCommand({
3147
- outputRoot: process.cwd(),
3148
- frontmatter: rulesyncFrontmatter,
3149
- body: this.body,
3150
- relativeDirPath: RulesyncCommand.getSettablePaths().relativeDirPath,
3151
- relativeFilePath: this.relativeFilePath,
3152
- fileContent,
3153
- validate: true
3154
- });
3155
- }
3156
- static fromRulesyncCommand({ outputRoot = process.cwd(), rulesyncCommand, validate = true, global = false }) {
3157
- const rulesyncFrontmatter = rulesyncCommand.getFrontmatter();
3158
- const devinFields = rulesyncFrontmatter.devin ?? {};
3159
- const devinFrontmatter = {
3160
- description: rulesyncFrontmatter.description,
3161
- ...devinFields
3162
- };
3163
- const body = rulesyncCommand.getBody();
3164
- const paths = this.getSettablePaths({ global });
3165
- return new DevinCommand({
3166
- outputRoot,
3167
- frontmatter: devinFrontmatter,
3168
- body,
3169
- relativeDirPath: paths.relativeDirPath,
3170
- relativeFilePath: rulesyncCommand.getRelativeFilePath(),
3171
- validate
3583
+ constructor({ slug, ...params }) {
3584
+ super({
3585
+ ...params,
3586
+ ...slug !== void 0 && {
3587
+ relativeDirPath: join(params.relativeDirPath, slug),
3588
+ relativeFilePath: "SKILL.md"
3589
+ }
3172
3590
  });
3173
3591
  }
3174
3592
  validate() {
3175
- if (!this.frontmatter) return {
3176
- success: true,
3177
- error: null
3178
- };
3179
- const result = DevinCommandFrontmatterSchema.safeParse(this.frontmatter);
3180
- if (result.success) return {
3593
+ return {
3181
3594
  success: true,
3182
3595
  error: null
3183
3596
  };
3184
- else return {
3185
- success: false,
3186
- error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${join(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
3187
- };
3188
3597
  }
3189
- static isTargetedByRulesyncCommand(rulesyncCommand) {
3190
- return this.isTargetedByRulesyncCommandDefault({
3191
- rulesyncCommand,
3192
- toolTarget: "devin"
3598
+ toRulesyncCommand() {
3599
+ const slug = basename(dirname(this.getRelativePathFromCwd()));
3600
+ const { frontmatter, body } = parseFrontmatter(this.getFileContent(), this.getFilePath());
3601
+ const description = typeof frontmatter.description === "string" ? frontmatter.description : void 0;
3602
+ return new RulesyncCommand({
3603
+ relativeDirPath: RULESYNC_COMMANDS_RELATIVE_DIR_PATH,
3604
+ relativeFilePath: `${slug}.md`,
3605
+ frontmatter: { description },
3606
+ body: body.trimStart()
3193
3607
  });
3194
3608
  }
3195
- static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
3196
- const paths = this.getSettablePaths({ global });
3197
- const filePath = join(outputRoot, paths.relativeDirPath, relativeFilePath);
3198
- const { frontmatter, body: content } = parseFrontmatter(await readFileContent(filePath), filePath);
3199
- const result = DevinCommandFrontmatterSchema.safeParse(frontmatter);
3200
- if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
3609
+ static fromRulesyncCommand({ outputRoot, rulesyncCommand, global = false }) {
3610
+ const paths = DevinCommand.getSettablePaths({ global });
3201
3611
  return new DevinCommand({
3202
3612
  outputRoot,
3203
3613
  relativeDirPath: paths.relativeDirPath,
3204
- relativeFilePath,
3205
- frontmatter: result.data,
3206
- body: content.trim(),
3207
- validate
3614
+ relativeFilePath: SKILL_FILE_NAME$1,
3615
+ slug: commandSlug(rulesyncCommand.getRelativeFilePath()),
3616
+ fileContent: commandSkillContent$1(rulesyncCommand)
3208
3617
  });
3209
3618
  }
3210
- static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
3211
- return new DevinCommand({
3212
- outputRoot,
3213
- relativeDirPath,
3214
- relativeFilePath,
3215
- frontmatter: { description: "" },
3216
- body: "",
3217
- validate: false
3218
- });
3619
+ getFileContent() {
3620
+ return this.fileContent;
3219
3621
  }
3220
3622
  };
3221
3623
  //#endregion
@@ -3543,10 +3945,7 @@ const HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_MANIFEST_PATH = join(HERMESAGENT_RUL
3543
3945
  const HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_INIT_PATH = join(HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_DIR_PATH, "__init__.py");
3544
3946
  //#endregion
3545
3947
  //#region src/features/commands/hermesagent-command.ts
3546
- const SKILL_FILE_NAME$1 = "SKILL.md";
3547
- function commandSlug(relativeFilePath) {
3548
- return basename(relativeFilePath, ".md").replace(/[^a-zA-Z0-9_-]/g, "-");
3549
- }
3948
+ const SKILL_FILE_NAME = "SKILL.md";
3550
3949
  function commandSkillContent(rulesyncCommand) {
3551
3950
  const slug = commandSlug(rulesyncCommand.getRelativeFilePath());
3552
3951
  const description = rulesyncCommand.getFrontmatter().description ?? `${slug} command`;
@@ -3563,7 +3962,7 @@ var HermesagentCommand = class HermesagentCommand extends ToolCommand {
3563
3962
  static getSettablePaths({ slug = "command" } = {}) {
3564
3963
  return {
3565
3964
  relativeDirPath: join(HERMESAGENT_SKILLS_DIR_PATH, slug),
3566
- relativeFilePath: SKILL_FILE_NAME$1
3965
+ relativeFilePath: SKILL_FILE_NAME
3567
3966
  };
3568
3967
  }
3569
3968
  constructor({ slug, ...params }) {
@@ -3594,7 +3993,7 @@ var HermesagentCommand = class HermesagentCommand extends ToolCommand {
3594
3993
  return new HermesagentCommand({
3595
3994
  outputRoot,
3596
3995
  relativeDirPath: "",
3597
- relativeFilePath: SKILL_FILE_NAME$1,
3996
+ relativeFilePath: SKILL_FILE_NAME,
3598
3997
  slug: commandSlug(rulesyncCommand.getRelativeFilePath()),
3599
3998
  fileContent: commandSkillContent(rulesyncCommand)
3600
3999
  });
@@ -4254,6 +4653,15 @@ const SHARED_CONFIG_OWNERSHIP = {
4254
4653
  }
4255
4654
  }
4256
4655
  },
4656
+ ".vscode/settings.json": {
4657
+ format: "jsonc",
4658
+ invalidRootPolicy: "error",
4659
+ jsoncParseErrors: "error",
4660
+ features: { permissions: {
4661
+ kind: "replace-owned-keys",
4662
+ ownedKeys: ["chat.tools.terminal.autoApprove"]
4663
+ } }
4664
+ },
4257
4665
  ".qwen/settings.json": {
4258
4666
  format: "json",
4259
4667
  features: {
@@ -4829,6 +5237,7 @@ const PI_AGENT_SKILLS_DIR_PATH = join(PI_AGENT_DIR, "skills");
4829
5237
  const PI_PROMPTS_DIR_PATH = join(".pi", "prompts");
4830
5238
  const PI_SKILLS_DIR_PATH = join(".pi", "skills");
4831
5239
  const PI_RULE_FILE_NAME = "AGENTS.md";
5240
+ const PI_APPEND_SYSTEM_FILE_NAME = "APPEND_SYSTEM.md";
4832
5241
  //#endregion
4833
5242
  //#region src/features/commands/pi-command.ts
4834
5243
  /**
@@ -5363,9 +5772,6 @@ const ROVODEV_AGENTS_SKILLS_DIR_PATH = join(".agents", "skills");
5363
5772
  const ROVODEV_PROMPTS_FILE_NAME = "prompts.yml";
5364
5773
  const ROVODEV_PROMPTS_DIR_PATH = join(ROVODEV_DIR, "prompts");
5365
5774
  //#endregion
5366
- //#region src/types/tool-file.ts
5367
- var ToolFile = class extends AiFile {};
5368
- //#endregion
5369
5775
  //#region src/features/commands/rovodev-command.ts
5370
5776
  /**
5371
5777
  * Rovo Dev CLI "saved prompts": a file-based custom-command surface made of a
@@ -5854,7 +6260,8 @@ const toolCommandFactories = /* @__PURE__ */ new Map([
5854
6260
  supportsProject: false,
5855
6261
  supportsGlobal: true,
5856
6262
  isSimulated: false,
5857
- supportsSubdirectory: true
6263
+ supportsSubdirectory: true,
6264
+ skipToolFileScan: true
5858
6265
  }
5859
6266
  }],
5860
6267
  ["junie", {
@@ -5984,7 +6391,8 @@ const toolCommandFactories = /* @__PURE__ */ new Map([
5984
6391
  supportsProject: true,
5985
6392
  supportsGlobal: true,
5986
6393
  isSimulated: false,
5987
- supportsSubdirectory: false
6394
+ supportsSubdirectory: false,
6395
+ skipToolFileScan: true
5988
6396
  }
5989
6397
  }]
5990
6398
  ]);
@@ -6087,6 +6495,7 @@ var CommandsProcessor = class extends FeatureProcessor {
6087
6495
  */
6088
6496
  async loadToolFiles({ forDeletion = false } = {}) {
6089
6497
  const factory = this.getFactory(this.toolTarget);
6498
+ if (factory.meta.skipToolFileScan) return [];
6090
6499
  const paths = factory.class.getSettablePaths({ global: this.global });
6091
6500
  const outputRootFull = join(this.outputRoot, paths.relativeDirPath);
6092
6501
  const commandFilePaths = await findFilesByGlobs(factory.meta.supportsSubdirectory ? join(outputRootFull, "**", `*.${factory.meta.extension}`) : join(outputRootFull, `*.${factory.meta.extension}`));
@@ -6196,7 +6605,10 @@ const HookDefinitionSchema = z.looseObject({
6196
6605
  type: z.optional(z.enum([
6197
6606
  "command",
6198
6607
  "prompt",
6199
- "http"
6608
+ "http",
6609
+ "agent",
6610
+ "mcp_tool",
6611
+ "function"
6200
6612
  ])),
6201
6613
  url: z.optional(safeString),
6202
6614
  timeout: z.optional(z.number()),
@@ -6209,12 +6621,72 @@ const HookDefinitionSchema = z.looseObject({
6209
6621
  sequential: z.optional(z.boolean()),
6210
6622
  async: z.optional(z.boolean()),
6211
6623
  env: z.optional(z.record(z.string(), safeString)),
6212
- shell: z.optional(safeString),
6624
+ shell: z.optional(z.enum(["bash", "powershell"])),
6213
6625
  statusMessage: z.optional(safeString),
6214
6626
  headers: z.optional(z.record(z.string(), safeString)),
6215
6627
  allowedEnvVars: z.optional(z.array(z.string())),
6216
- once: z.optional(z.boolean())
6628
+ once: z.optional(z.boolean()),
6629
+ server: z.optional(safeString),
6630
+ tool: z.optional(safeString),
6631
+ input: z.optional(z.looseObject({})),
6632
+ model: z.optional(safeString),
6633
+ if: z.optional(safeString)
6217
6634
  });
6635
+ /**
6636
+ * All canonical hook event names.
6637
+ * Each tool supports a subset of these events.
6638
+ */
6639
+ const HOOK_EVENTS = [
6640
+ "sessionStart",
6641
+ "sessionEnd",
6642
+ "preToolUse",
6643
+ "postToolUse",
6644
+ "preModelInvocation",
6645
+ "postModelInvocation",
6646
+ "beforeSubmitPrompt",
6647
+ "stop",
6648
+ "subagentStop",
6649
+ "preCompact",
6650
+ "postCompact",
6651
+ "contextOffload",
6652
+ "postToolUseFailure",
6653
+ "subagentStart",
6654
+ "beforeShellExecution",
6655
+ "afterShellExecution",
6656
+ "beforeMCPExecution",
6657
+ "afterMCPExecution",
6658
+ "beforeReadFile",
6659
+ "afterFileEdit",
6660
+ "beforeAgentResponse",
6661
+ "afterAgentResponse",
6662
+ "afterAgentThought",
6663
+ "beforeTabFileRead",
6664
+ "afterTabFileEdit",
6665
+ "permissionRequest",
6666
+ "notification",
6667
+ "setup",
6668
+ "afterError",
6669
+ "beforeToolSelection",
6670
+ "worktreeCreate",
6671
+ "worktreeRemove",
6672
+ "workspaceOpen",
6673
+ "messageDisplay",
6674
+ "todoCreated",
6675
+ "todoCompleted",
6676
+ "stopFailure",
6677
+ "instructionsLoaded",
6678
+ "userPromptExpansion",
6679
+ "postToolBatch",
6680
+ "permissionDenied",
6681
+ "taskCreated",
6682
+ "taskCompleted",
6683
+ "teammateIdle",
6684
+ "configChange",
6685
+ "cwdChanged",
6686
+ "fileChanged",
6687
+ "elicitation",
6688
+ "elicitationResult"
6689
+ ];
6218
6690
  /** Hook events supported by Cursor. */
6219
6691
  const CURSOR_HOOK_EVENTS = [
6220
6692
  "sessionStart",
@@ -6689,12 +7161,28 @@ const CANONICAL_TO_HERMESAGENT_EVENT_NAMES = {
6689
7161
  */
6690
7162
  const HERMESAGENT_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_HERMESAGENT_EVENT_NAMES).map(([k, v]) => [v, k]));
6691
7163
  const hooksRecordSchema = z.record(z.string(), z.array(HookDefinitionSchema));
7164
+ const HOOK_EVENT_SET = new Set(HOOK_EVENTS);
7165
+ /** Whether `value` is a canonical hook event name. */
7166
+ const isHookEvent = (value) => HOOK_EVENT_SET.has(value);
7167
+ /**
7168
+ * Top-level `hooks` record whose keys must be canonical event names, so typos
7169
+ * are rejected at parse time. Keys are validated with a refinement (instead of
7170
+ * an enum key schema) to keep the inferred type a plain string record.
7171
+ *
7172
+ * The per-tool override blocks below deliberately keep the lenient
7173
+ * `hooksRecordSchema`: some are documented to pass tool-native event keys
7174
+ * through verbatim (e.g. kiro-ide's IDE-only `PostFileSave`/`PreTaskExec`
7175
+ * triggers), which the canonical enum would reject.
7176
+ */
7177
+ 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) => {
7178
+ return `unknown hook event name(s): ${Object.keys(issue.input ?? {}).filter((key) => !HOOK_EVENT_SET.has(key)).join(", ")}`;
7179
+ } }));
6692
7180
  /**
6693
7181
  * Canonical hooks config (canonical event names in camelCase).
6694
7182
  */
6695
7183
  const HooksConfigSchema = z.looseObject({
6696
7184
  version: z.optional(z.number()),
6697
- hooks: hooksRecordSchema,
7185
+ hooks: canonicalHooksRecordSchema,
6698
7186
  cursor: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
6699
7187
  claudecode: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
6700
7188
  copilot: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
@@ -7131,6 +7619,20 @@ const CANONICAL_TO_GROKCLI_EVENT_NAMES = {
7131
7619
  */
7132
7620
  const GROKCLI_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_GROKCLI_EVENT_NAMES).map(([k, v]) => [v, k]));
7133
7621
  //#endregion
7622
+ //#region src/utils/object.ts
7623
+ /**
7624
+ * Return a shallow copy of `obj` keeping only the entries whose value is
7625
+ * neither `undefined` nor `null`.
7626
+ *
7627
+ * Used to assemble generated config objects without one conditional spread per
7628
+ * optional field (which would otherwise exceed the lint complexity budget).
7629
+ */
7630
+ function compact(obj) {
7631
+ const result = {};
7632
+ for (const [key, value] of Object.entries(obj)) if (value !== void 0 && value !== null) result[key] = value;
7633
+ return result;
7634
+ }
7635
+ //#endregion
7134
7636
  //#region src/features/hooks/tool-hooks-converter.ts
7135
7637
  function isToolMatcherEntry(x) {
7136
7638
  if (x === null || typeof x !== "object") return false;
@@ -7188,6 +7690,41 @@ function importBooleanPassthroughFields({ h, converterConfig }) {
7188
7690
  return Object.fromEntries((converterConfig.booleanPassthroughFields ?? []).filter(({ tool }) => typeof h[tool] === "boolean").map(({ canonical, tool }) => [canonical, h[tool]]));
7189
7691
  }
7190
7692
  /**
7693
+ * Emit the configured string passthrough fields on the tool side, mapping each
7694
+ * canonical field name to its (possibly renamed) tool field name. Only non-empty
7695
+ * string values are carried through.
7696
+ */
7697
+ function emitStringPassthroughFields({ def, converterConfig }) {
7698
+ return Object.fromEntries((converterConfig.stringPassthroughFields ?? []).filter(({ canonical }) => typeof def[canonical] === "string" && def[canonical] !== "").map(({ canonical, tool }) => [tool, def[canonical]]));
7699
+ }
7700
+ /**
7701
+ * Import the configured string passthrough fields back into canonical fields,
7702
+ * reversing {@link emitStringPassthroughFields}. Only non-empty string values
7703
+ * are read.
7704
+ */
7705
+ function importStringPassthroughFields({ h, converterConfig }) {
7706
+ return Object.fromEntries((converterConfig.stringPassthroughFields ?? []).filter(({ tool }) => typeof h[tool] === "string" && h[tool] !== "").map(({ canonical, tool }) => [canonical, h[tool]]));
7707
+ }
7708
+ /**
7709
+ * Emit the payload fields specific to a hook type — `url`/`headers`/
7710
+ * `allowedEnvVars` for http, `server`/`tool`/`input` for mcp_tool, `model`
7711
+ * for prompt/agent. https://code.claude.com/docs/en/hooks
7712
+ */
7713
+ function emitTypePayloadFields({ def, hookType, converterConfig }) {
7714
+ if (hookType === "http") return compact({
7715
+ url: def.url,
7716
+ headers: def.headers,
7717
+ allowedEnvVars: def.allowedEnvVars
7718
+ });
7719
+ if (hookType === "mcp_tool") return compact({
7720
+ server: def.server,
7721
+ tool: def.tool,
7722
+ input: def.input
7723
+ });
7724
+ if ((hookType === "prompt" || hookType === "agent") && converterConfig.emitsPromptModel) return compact({ model: def.model });
7725
+ return {};
7726
+ }
7727
+ /**
7191
7728
  * Convert the definitions of a single matcher group into tool hook entries,
7192
7729
  * honoring supported hook types and passthrough fields.
7193
7730
  */
@@ -7205,10 +7742,19 @@ function buildToolHooks({ defs, converterConfig }) {
7205
7742
  def,
7206
7743
  converterConfig
7207
7744
  }),
7745
+ ...emitStringPassthroughFields({
7746
+ def,
7747
+ converterConfig
7748
+ }),
7208
7749
  type: hookType,
7209
7750
  ...command !== void 0 && command !== null && { command },
7210
7751
  ...def.timeout !== void 0 && def.timeout !== null && { timeout: def.timeout },
7211
7752
  ...def.prompt !== void 0 && def.prompt !== null && { prompt: def.prompt },
7753
+ ...emitTypePayloadFields({
7754
+ def,
7755
+ hookType,
7756
+ converterConfig
7757
+ }),
7212
7758
  ...converterConfig.passthroughFields?.includes("name") && def.name !== void 0 && def.name !== null && { name: def.name },
7213
7759
  ...converterConfig.passthroughFields?.includes("description") && def.description !== void 0 && def.description !== null && { description: def.description }
7214
7760
  });
@@ -7275,6 +7821,46 @@ function stripCommandPrefix({ command, converterConfig }) {
7275
7821
  return cmd;
7276
7822
  }
7277
7823
  /**
7824
+ * Hook types preserved verbatim on import — Claude Code's five documented
7825
+ * handler types. Anything else is coerced to `command` as before.
7826
+ * https://code.claude.com/docs/en/hooks
7827
+ */
7828
+ const IMPORTED_HOOK_TYPES = /* @__PURE__ */ new Set([
7829
+ "command",
7830
+ "prompt",
7831
+ "http",
7832
+ "mcp_tool",
7833
+ "agent"
7834
+ ]);
7835
+ function isImportedHookType(value) {
7836
+ return typeof value === "string" && IMPORTED_HOOK_TYPES.has(value);
7837
+ }
7838
+ function isStringRecord(value) {
7839
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
7840
+ return Object.values(value).every((v) => typeof v === "string");
7841
+ }
7842
+ function isStringArray(value) {
7843
+ return Array.isArray(value) && value.every((v) => typeof v === "string");
7844
+ }
7845
+ /**
7846
+ * Import the payload fields specific to a hook type, type-checking each raw
7847
+ * value before it enters the canonical definition.
7848
+ */
7849
+ function importTypePayloadFields({ h, hookType }) {
7850
+ if (hookType === "http") return {
7851
+ ...typeof h.url === "string" && { url: h.url },
7852
+ ...isStringRecord(h.headers) && { headers: h.headers },
7853
+ ...isStringArray(h.allowedEnvVars) && { allowedEnvVars: h.allowedEnvVars }
7854
+ };
7855
+ if (hookType === "mcp_tool") return {
7856
+ ...typeof h.server === "string" && { server: h.server },
7857
+ ...typeof h.tool === "string" && { tool: h.tool },
7858
+ ...h.input !== null && typeof h.input === "object" && !Array.isArray(h.input) && { input: h.input }
7859
+ };
7860
+ if (hookType === "prompt" || hookType === "agent") return typeof h.model === "string" ? { model: h.model } : {};
7861
+ return {};
7862
+ }
7863
+ /**
7278
7864
  * Convert a single tool hook record into a canonical hook definition.
7279
7865
  */
7280
7866
  function toolHookToCanonical({ h, rawEntry, converterConfig }) {
@@ -7282,7 +7868,7 @@ function toolHookToCanonical({ h, rawEntry, converterConfig }) {
7282
7868
  command: h.command,
7283
7869
  converterConfig
7284
7870
  });
7285
- const hookType = h.type === "command" || h.type === "prompt" ? h.type : "command";
7871
+ const hookType = isImportedHookType(h.type) ? h.type : "command";
7286
7872
  const timeout = typeof h.timeout === "number" ? h.timeout : void 0;
7287
7873
  const prompt = typeof h.prompt === "string" ? h.prompt : void 0;
7288
7874
  return {
@@ -7290,12 +7876,20 @@ function toolHookToCanonical({ h, rawEntry, converterConfig }) {
7290
7876
  ...command !== void 0 && command !== null && { command },
7291
7877
  ...timeout !== void 0 && timeout !== null && { timeout },
7292
7878
  ...prompt !== void 0 && prompt !== null && { prompt },
7879
+ ...importTypePayloadFields({
7880
+ h,
7881
+ hookType
7882
+ }),
7293
7883
  ...converterConfig.passthroughFields?.includes("name") && typeof h.name === "string" && { name: h.name },
7294
7884
  ...converterConfig.passthroughFields?.includes("description") && typeof h.description === "string" && { description: h.description },
7295
7885
  ...importBooleanPassthroughFields({
7296
7886
  h,
7297
7887
  converterConfig
7298
7888
  }),
7889
+ ...importStringPassthroughFields({
7890
+ h,
7891
+ converterConfig
7892
+ }),
7299
7893
  ...rawEntry.matcher !== void 0 && rawEntry.matcher !== null && rawEntry.matcher !== "" && { matcher: rawEntry.matcher }
7300
7894
  };
7301
7895
  }
@@ -7309,6 +7903,32 @@ function toolMatcherEntryToCanonical({ rawEntry, converterConfig }) {
7309
7903
  converterConfig
7310
7904
  }));
7311
7905
  }
7906
+ /**
7907
+ * Assemble the canonical hooks config a tool importer writes to
7908
+ * `.rulesync/hooks.json`.
7909
+ *
7910
+ * The top-level `hooks` record only accepts canonical event names, so any
7911
+ * imported native event key without a canonical mapping is moved under the
7912
+ * importing tool's own override block (`<overrideKey>.hooks`), whose keys stay
7913
+ * lenient. This mirrors the generate direction — override blocks pass
7914
+ * tool-native keys through verbatim — so documented native triggers (e.g.
7915
+ * kiro-ide's `PostFileSave`) survive an import → generate round-trip instead
7916
+ * of failing canonical validation.
7917
+ */
7918
+ function buildImportedHooksConfig({ hooks, overrideKey, version = 1, extraOverride }) {
7919
+ const canonical = {};
7920
+ const native = {};
7921
+ for (const [event, defs] of Object.entries(hooks)) if (isHookEvent(event)) canonical[event] = defs;
7922
+ else native[event] = defs;
7923
+ const override = { ...extraOverride };
7924
+ if (Object.keys(native).length > 0) override.hooks = native;
7925
+ const config = {
7926
+ version,
7927
+ hooks: canonical
7928
+ };
7929
+ if (Object.keys(override).length > 0) config[overrideKey] = override;
7930
+ return config;
7931
+ }
7312
7932
  function toolHooksToCanonical({ hooks, converterConfig }) {
7313
7933
  if (hooks === null || hooks === void 0 || typeof hooks !== "object") return {};
7314
7934
  const canonical = {};
@@ -7479,7 +8099,8 @@ const ANTIGRAVITY_CONVERTER_CONFIG = {
7479
8099
  "preModelInvocation",
7480
8100
  "postModelInvocation",
7481
8101
  "stop"
7482
- ])
8102
+ ]),
8103
+ supportedHookTypes: /* @__PURE__ */ new Set(["command"])
7483
8104
  };
7484
8105
  /**
7485
8106
  * Antigravity's `hooks.json` is keyed by a named hook whose value holds the
@@ -7586,10 +8207,11 @@ var AntigravityHooks = class extends ToolHooks {
7586
8207
  hooks: flattenAntigravityHooks(parsed),
7587
8208
  converterConfig: ANTIGRAVITY_CONVERTER_CONFIG
7588
8209
  });
7589
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
7590
- version: 1,
7591
- hooks
7592
- }, null, 2) });
8210
+ const overrideKey = this.constructor.getOverrideKey();
8211
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
8212
+ hooks,
8213
+ overrideKey
8214
+ }), null, 2) });
7593
8215
  }
7594
8216
  validate() {
7595
8217
  return {
@@ -7710,7 +8332,8 @@ const AUGMENTCODE_CONVERTER_CONFIG = {
7710
8332
  "sessionEnd",
7711
8333
  "stop",
7712
8334
  "notification"
7713
- ])
8335
+ ]),
8336
+ supportedHookTypes: /* @__PURE__ */ new Set(["command"])
7714
8337
  };
7715
8338
  /**
7716
8339
  * AugmentCode (Auggie CLI) lifecycle hooks.
@@ -7792,10 +8415,10 @@ var AugmentcodeHooks = class AugmentcodeHooks extends ToolHooks {
7792
8415
  hooks: settings.hooks,
7793
8416
  converterConfig: AUGMENTCODE_CONVERTER_CONFIG
7794
8417
  });
7795
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
7796
- version: 1,
7797
- hooks
7798
- }, null, 2) });
8418
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
8419
+ hooks,
8420
+ overrideKey: "augmentcode"
8421
+ }), null, 2) });
7799
8422
  }
7800
8423
  validate() {
7801
8424
  return {
@@ -7830,7 +8453,19 @@ const CLAUDE_CONVERTER_CONFIG = {
7830
8453
  "taskCompleted",
7831
8454
  "teammateIdle",
7832
8455
  "cwdChanged"
7833
- ])
8456
+ ]),
8457
+ supportedHookTypes: /* @__PURE__ */ new Set([
8458
+ "command",
8459
+ "prompt",
8460
+ "http",
8461
+ "mcp_tool",
8462
+ "agent"
8463
+ ]),
8464
+ emitsPromptModel: true,
8465
+ stringPassthroughFields: [{
8466
+ canonical: "if",
8467
+ tool: "if"
8468
+ }]
7834
8469
  };
7835
8470
  var ClaudecodeHooks = class ClaudecodeHooks extends ToolHooks {
7836
8471
  constructor(params) {
@@ -7895,10 +8530,10 @@ var ClaudecodeHooks = class ClaudecodeHooks extends ToolHooks {
7895
8530
  hooks: settings.hooks,
7896
8531
  converterConfig: CLAUDE_CONVERTER_CONFIG
7897
8532
  });
7898
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
7899
- version: 1,
7900
- hooks
7901
- }, null, 2) });
8533
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
8534
+ hooks,
8535
+ overrideKey: "claudecode"
8536
+ }), null, 2) });
7902
8537
  }
7903
8538
  validate() {
7904
8539
  return {
@@ -8041,10 +8676,10 @@ var CodexcliHooks = class CodexcliHooks extends ToolHooks {
8041
8676
  hooks: parsed.hooks,
8042
8677
  converterConfig: CODEXCLI_CONVERTER_CONFIG
8043
8678
  });
8044
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
8045
- version: 1,
8046
- hooks
8047
- }, null, 2) });
8679
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
8680
+ hooks,
8681
+ overrideKey: "codexcli"
8682
+ }), null, 2) });
8048
8683
  }
8049
8684
  validate() {
8050
8685
  return {
@@ -8212,10 +8847,10 @@ var CopilotHooks = class CopilotHooks extends ToolHooks {
8212
8847
  throw new Error(`Failed to parse Copilot hooks content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
8213
8848
  }
8214
8849
  const hooks = copilotHooksToCanonical(parsed.hooks, options?.logger);
8215
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
8216
- version: 1,
8217
- hooks
8218
- }, null, 2) });
8850
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
8851
+ hooks,
8852
+ overrideKey: "copilot"
8853
+ }), null, 2) });
8219
8854
  }
8220
8855
  validate() {
8221
8856
  return {
@@ -8234,20 +8869,6 @@ var CopilotHooks = class CopilotHooks extends ToolHooks {
8234
8869
  }
8235
8870
  };
8236
8871
  //#endregion
8237
- //#region src/utils/object.ts
8238
- /**
8239
- * Return a shallow copy of `obj` keeping only the entries whose value is
8240
- * neither `undefined` nor `null`.
8241
- *
8242
- * Used to assemble generated config objects without one conditional spread per
8243
- * optional field (which would otherwise exceed the lint complexity budget).
8244
- */
8245
- function compact(obj) {
8246
- const result = {};
8247
- for (const [key, value] of Object.entries(obj)) if (value !== void 0 && value !== null) result[key] = value;
8248
- return result;
8249
- }
8250
- //#endregion
8251
8872
  //#region src/features/hooks/copilotcli-hooks.ts
8252
8873
  /**
8253
8874
  * GitHub Copilot CLI hooks.
@@ -8380,7 +9001,7 @@ function buildCopilotCliEntriesForEvent({ eventName, definitions, canonicalSchem
8380
9001
  ...timeoutPart,
8381
9002
  ...rest
8382
9003
  });
8383
- else entries.push({
9004
+ else if (hookType === "command") entries.push({
8384
9005
  type: "command",
8385
9006
  ...matcherPart,
8386
9007
  ...compact({
@@ -8532,10 +9153,10 @@ var CopilotcliHooks = class CopilotcliHooks extends ToolHooks {
8532
9153
  throw new Error(`Failed to parse Copilot CLI hooks content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
8533
9154
  }
8534
9155
  const hooks = copilotCliHooksToCanonical(parsed.hooks, options?.logger);
8535
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
8536
- version: 1,
8537
- hooks
8538
- }, null, 2) });
9156
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
9157
+ hooks,
9158
+ overrideKey: "copilotcli"
9159
+ }), null, 2) });
8539
9160
  }
8540
9161
  validate() {
8541
9162
  return {
@@ -8590,9 +9211,10 @@ var CursorHooks = class CursorHooks extends ToolHooks {
8590
9211
  ...config.cursor?.hooks
8591
9212
  };
8592
9213
  const mappedHooks = {};
9214
+ const cursorSupportedTypes = /* @__PURE__ */ new Set(["command", "prompt"]);
8593
9215
  for (const [eventName, defs] of Object.entries(mergedHooks)) {
8594
9216
  const cursorEventName = CANONICAL_TO_CURSOR_EVENT_NAMES[eventName] ?? eventName;
8595
- mappedHooks[cursorEventName] = defs.map((def) => ({
9217
+ const mappedDefs = defs.filter((def) => cursorSupportedTypes.has(def.type ?? "command")).map((def) => ({
8596
9218
  ...def.type !== void 0 && def.type !== null && { type: def.type },
8597
9219
  ...def.command !== void 0 && def.command !== null && { command: def.command },
8598
9220
  ...def.timeout !== void 0 && def.timeout !== null && { timeout: def.timeout },
@@ -8601,6 +9223,7 @@ var CursorHooks = class CursorHooks extends ToolHooks {
8601
9223
  ...def.prompt !== void 0 && def.prompt !== null && { prompt: def.prompt },
8602
9224
  ...def.failClosed !== void 0 && def.failClosed !== null && { failClosed: def.failClosed }
8603
9225
  }));
9226
+ if (mappedDefs.length > 0) mappedHooks[cursorEventName] = mappedDefs;
8604
9227
  }
8605
9228
  const cursorConfig = {
8606
9229
  version: config.version ?? 1,
@@ -8627,10 +9250,11 @@ var CursorHooks = class CursorHooks extends ToolHooks {
8627
9250
  canonicalHooks[eventName] = defs;
8628
9251
  }
8629
9252
  const version = parsed.version ?? 1;
8630
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
8631
- version,
8632
- hooks: canonicalHooks
8633
- }, null, 2) });
9253
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
9254
+ hooks: canonicalHooks,
9255
+ overrideKey: "cursor",
9256
+ version
9257
+ }), null, 2) });
8634
9258
  }
8635
9259
  validate() {
8636
9260
  return {
@@ -8685,7 +9309,7 @@ function canonicalToDeepagentsHooks(config) {
8685
9309
  const deepagentsEvent = CANONICAL_TO_DEEPAGENTS_EVENT_NAMES[canonicalEvent];
8686
9310
  if (!deepagentsEvent) continue;
8687
9311
  for (const def of definitions) {
8688
- if (def.type === "prompt") continue;
9312
+ if ((def.type ?? "command") !== "command") continue;
8689
9313
  if (!def.command) continue;
8690
9314
  if (def.matcher) continue;
8691
9315
  entries.push({
@@ -8775,10 +9399,10 @@ var DeepagentsHooks = class DeepagentsHooks extends ToolHooks {
8775
9399
  throw new Error(`Failed to parse deepagents hooks content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
8776
9400
  }
8777
9401
  const hooks = deepagentsToCanonicalHooks(isDeepagentsHooksFile(parsed) ? parsed.hooks : []);
8778
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
8779
- version: 1,
8780
- hooks
8781
- }, null, 2) });
9402
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
9403
+ hooks,
9404
+ overrideKey: "deepagents"
9405
+ }), null, 2) });
8782
9406
  }
8783
9407
  validate() {
8784
9408
  return {
@@ -8907,10 +9531,10 @@ var DevinHooks = class DevinHooks extends ToolHooks {
8907
9531
  hooks: this.getRelativeFilePath() === "config.json" ? isRecord(parsed) && isRecord(parsed.hooks) ? parsed.hooks : {} : parsed,
8908
9532
  converterConfig: DEVIN_CONVERTER_CONFIG
8909
9533
  });
8910
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
8911
- version: 1,
8912
- hooks
8913
- }, null, 2) });
9534
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
9535
+ hooks,
9536
+ overrideKey: "devin"
9537
+ }), null, 2) });
8914
9538
  }
8915
9539
  validate() {
8916
9540
  return {
@@ -8934,7 +9558,8 @@ const FACTORYDROID_CONVERTER_CONFIG = {
8934
9558
  supportedEvents: FACTORYDROID_HOOK_EVENTS,
8935
9559
  canonicalToToolEventNames: CANONICAL_TO_FACTORYDROID_EVENT_NAMES,
8936
9560
  toolToCanonicalEventNames: FACTORYDROID_TO_CANONICAL_EVENT_NAMES,
8937
- projectDirVar: "$FACTORY_PROJECT_DIR"
9561
+ projectDirVar: "$FACTORY_PROJECT_DIR",
9562
+ supportedHookTypes: /* @__PURE__ */ new Set(["command", "prompt"])
8938
9563
  };
8939
9564
  var FactorydroidHooks = class FactorydroidHooks extends ToolHooks {
8940
9565
  constructor(params) {
@@ -9002,10 +9627,10 @@ var FactorydroidHooks = class FactorydroidHooks extends ToolHooks {
9002
9627
  hooks: settings.hooks,
9003
9628
  converterConfig: FACTORYDROID_CONVERTER_CONFIG
9004
9629
  });
9005
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
9006
- version: 1,
9007
- hooks
9008
- }, null, 2) });
9630
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
9631
+ hooks,
9632
+ overrideKey: "factorydroid"
9633
+ }), null, 2) });
9009
9634
  }
9010
9635
  validate() {
9011
9636
  return {
@@ -9096,10 +9721,10 @@ var GooseHooks = class GooseHooks extends ToolHooks {
9096
9721
  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,
9097
9722
  converterConfig: GOOSE_CONVERTER_CONFIG
9098
9723
  });
9099
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
9100
- version: 1,
9101
- hooks
9102
- }, null, 2) });
9724
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
9725
+ hooks,
9726
+ overrideKey: "goose"
9727
+ }), null, 2) });
9103
9728
  }
9104
9729
  validate() {
9105
9730
  return {
@@ -9257,10 +9882,10 @@ var GrokcliHooks = class GrokcliHooks extends ToolHooks {
9257
9882
  hooks: isRecord(parsed) && isRecord(parsed.hooks) ? parsed.hooks : {},
9258
9883
  converterConfig: GROKCLI_CONVERTER_CONFIG
9259
9884
  });
9260
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
9261
- version: 1,
9262
- hooks
9263
- }, null, 2) });
9885
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
9886
+ hooks,
9887
+ overrideKey: "grokcli"
9888
+ }), null, 2) });
9264
9889
  }
9265
9890
  validate() {
9266
9891
  return {
@@ -9421,10 +10046,10 @@ var HermesagentHooks = class HermesagentHooks extends ToolHooks {
9421
10046
  format: "yaml",
9422
10047
  fileContent: this.getFileContent()
9423
10048
  }).hooks);
9424
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
9425
- version: 1,
9426
- hooks
9427
- }, null, 2) });
10049
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
10050
+ hooks,
10051
+ overrideKey: "hermesagent"
10052
+ }), null, 2) });
9428
10053
  }
9429
10054
  static fromRulesyncHooks({ outputRoot, rulesyncHooks, logger }) {
9430
10055
  const config = rulesyncHooks.getJson();
@@ -9527,10 +10152,10 @@ var JunieHooks = class JunieHooks extends ToolHooks {
9527
10152
  hooks: settings.hooks,
9528
10153
  converterConfig: JUNIE_CONVERTER_CONFIG
9529
10154
  });
9530
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
9531
- version: 1,
9532
- hooks
9533
- }, null, 2) });
10155
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
10156
+ hooks,
10157
+ overrideKey: "junie"
10158
+ }), null, 2) });
9534
10159
  }
9535
10160
  validate() {
9536
10161
  return {
@@ -9575,7 +10200,7 @@ function collectOpencodeStyleHandlers({ effectiveHooks, eventMap, namedEventHand
9575
10200
  if (!toolEvent) continue;
9576
10201
  const handlers = [];
9577
10202
  for (const def of definitions) {
9578
- if (def.type === "prompt") continue;
10203
+ if ((def.type ?? "command") !== "command") continue;
9579
10204
  if (!def.command) continue;
9580
10205
  handlers.push({
9581
10206
  command: def.command,
@@ -9880,10 +10505,11 @@ var KiroHooks = class KiroHooks extends ToolHooks {
9880
10505
  throw new Error(`Failed to parse Kiro hooks content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
9881
10506
  }
9882
10507
  const hooks = kiroHooksToCanonical(agentConfig.hooks);
9883
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
9884
- version: 1,
9885
- hooks
9886
- }, null, 2) });
10508
+ const overrideKey = this.constructor.getOverrideKey();
10509
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
10510
+ hooks,
10511
+ overrideKey
10512
+ }), null, 2) });
9887
10513
  }
9888
10514
  validate() {
9889
10515
  return {
@@ -10092,10 +10718,10 @@ var KiroIdeHooks = class KiroIdeHooks extends ToolHooks {
10092
10718
  throw new Error(`Failed to parse Kiro IDE hooks content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
10093
10719
  }
10094
10720
  const hooks = kiroIdeHooksToCanonical(parsed.hooks ?? []);
10095
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
10096
- version: 1,
10097
- hooks
10098
- }, null, 2) });
10721
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
10722
+ hooks,
10723
+ overrideKey: "kiro-ide"
10724
+ }), null, 2) });
10099
10725
  }
10100
10726
  validate() {
10101
10727
  return {
@@ -10219,11 +10845,18 @@ function canonicalToQwencodeHooks(config) {
10219
10845
  ...sharedHooks,
10220
10846
  ...config.qwencode?.hooks
10221
10847
  };
10848
+ const qwencodeSupportedTypes = /* @__PURE__ */ new Set([
10849
+ "command",
10850
+ "prompt",
10851
+ "http",
10852
+ "function"
10853
+ ]);
10222
10854
  const qwencode = {};
10223
10855
  for (const [eventName, definitions] of Object.entries(effectiveHooks)) {
10224
10856
  const qwencodeEventName = CANONICAL_TO_QWENCODE_EVENT_NAMES[eventName] ?? eventName;
10225
10857
  const byMatcher = /* @__PURE__ */ new Map();
10226
10858
  for (const def of definitions) {
10859
+ if (!qwencodeSupportedTypes.has(def.type ?? "command")) continue;
10227
10860
  const key = def.matcher ?? "";
10228
10861
  const list = byMatcher.get(key);
10229
10862
  if (list) list.push(def);
@@ -10283,9 +10916,10 @@ function qwencodeMatcherEntryToCanonical(entry) {
10283
10916
  const sequential = entry.sequential === true;
10284
10917
  const matcher = entry.matcher !== void 0 && entry.matcher !== null && entry.matcher !== "" ? entry.matcher : void 0;
10285
10918
  for (const h of hooks) {
10286
- const hookType = h.type === "command" || h.type === "prompt" || h.type === "http" ? h.type : "command";
10919
+ const hookType = h.type === "command" || h.type === "prompt" || h.type === "http" || h.type === "function" ? h.type : "command";
10287
10920
  const isHttp = hookType === "http";
10288
10921
  const isCommand = hookType === "command";
10922
+ const shell = h.shell === "bash" || h.shell === "powershell" ? h.shell : void 0;
10289
10923
  defs.push({
10290
10924
  type: hookType,
10291
10925
  ...compact({
@@ -10297,7 +10931,7 @@ function qwencodeMatcherEntryToCanonical(entry) {
10297
10931
  statusMessage: h.statusMessage,
10298
10932
  async: isCommand ? h.async : void 0,
10299
10933
  env: isCommand ? h.env : void 0,
10300
- shell: isCommand ? h.shell : void 0,
10934
+ shell: isCommand ? shell : void 0,
10301
10935
  headers: isHttp ? h.headers : void 0,
10302
10936
  allowedEnvVars: isHttp ? h.allowedEnvVars : void 0,
10303
10937
  once: isHttp ? h.once : void 0,
@@ -10384,15 +11018,11 @@ var QwencodeHooks = class QwencodeHooks extends ToolHooks {
10384
11018
  } catch (error) {
10385
11019
  throw new Error(`Failed to parse Qwen Code hooks content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
10386
11020
  }
10387
- const hooks = qwencodeHooksToCanonical(settings.hooks);
10388
- const canonical = typeof settings.disableAllHooks === "boolean" ? {
10389
- version: 1,
10390
- hooks,
10391
- qwencode: { disableAllHooks: settings.disableAllHooks }
10392
- } : {
10393
- version: 1,
10394
- hooks
10395
- };
11021
+ const canonical = buildImportedHooksConfig({
11022
+ hooks: qwencodeHooksToCanonical(settings.hooks),
11023
+ overrideKey: "qwencode",
11024
+ ...typeof settings.disableAllHooks === "boolean" && { extraOverride: { disableAllHooks: settings.disableAllHooks } }
11025
+ });
10396
11026
  return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(canonical, null, 2) });
10397
11027
  }
10398
11028
  validate() {
@@ -10554,10 +11184,10 @@ var ReasonixHooks = class ReasonixHooks extends ToolHooks {
10554
11184
  throw new Error(`Failed to parse Reasonix hooks content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
10555
11185
  }
10556
11186
  const hooks = reasonixHooksToCanonical(settings.hooks);
10557
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
10558
- version: 1,
10559
- hooks
10560
- }, null, 2) });
11187
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
11188
+ hooks,
11189
+ overrideKey: "reasonix"
11190
+ }), null, 2) });
10561
11191
  }
10562
11192
  validate() {
10563
11193
  return {
@@ -10780,10 +11410,10 @@ var VibeHooks = class VibeHooks extends ToolHooks {
10780
11410
  throw new Error(`Failed to parse Vibe hooks content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
10781
11411
  }
10782
11412
  const hooks = vibeHooksToCanonical(parsed);
10783
- return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
10784
- version: 1,
10785
- hooks
10786
- }, null, 2) });
11413
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
11414
+ hooks,
11415
+ overrideKey: "vibe"
11416
+ }), null, 2) });
10787
11417
  }
10788
11418
  validate() {
10789
11419
  try {
@@ -10870,7 +11500,13 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
10870
11500
  supportsImport: true
10871
11501
  },
10872
11502
  supportedEvents: CLAUDE_HOOK_EVENTS,
10873
- supportedHookTypes: ["command", "prompt"],
11503
+ supportedHookTypes: [
11504
+ "command",
11505
+ "prompt",
11506
+ "http",
11507
+ "mcp_tool",
11508
+ "agent"
11509
+ ],
10874
11510
  supportsMatcher: true
10875
11511
  }],
10876
11512
  ["codexcli", {
@@ -11062,7 +11698,12 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
11062
11698
  supportsImport: true
11063
11699
  },
11064
11700
  supportedEvents: QWENCODE_HOOK_EVENTS,
11065
- supportedHookTypes: ["command"],
11701
+ supportedHookTypes: [
11702
+ "command",
11703
+ "prompt",
11704
+ "http",
11705
+ "function"
11706
+ ],
11066
11707
  supportsMatcher: true
11067
11708
  }],
11068
11709
  ["reasonix", {
@@ -12364,16 +13005,6 @@ var IgnoreProcessor = class extends FeatureProcessor {
12364
13005
  }
12365
13006
  };
12366
13007
  //#endregion
12367
- //#region src/constants/amp-paths.ts
12368
- const AMP_DIR = ".amp";
12369
- const AMP_GLOBAL_DIR = join(".config", "amp");
12370
- const AMP_AGENTS_DIR = ".agents";
12371
- const AMP_SKILLS_PROJECT_DIR = join(AMP_AGENTS_DIR, "skills");
12372
- const AMP_SKILLS_GLOBAL_DIR = join(".config", "agents", "skills");
12373
- const AMP_RULE_FILE_NAME = "AGENTS.md";
12374
- const AMP_SETTINGS_FILE_NAME = "settings.json";
12375
- const AMP_SETTINGS_JSONC_FILE_NAME = "settings.jsonc";
12376
- //#endregion
12377
13008
  //#region src/types/mcp.ts
12378
13009
  const EnvVarNameSchema = z.string().check(refine((value) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(value), "envVars entries must be valid environment variable names"));
12379
13010
  const McpServerSchema = z.looseObject({
@@ -13465,10 +14096,19 @@ function mapOauthFromCodex(oauth) {
13465
14096
  }
13466
14097
  const CODEX_MCP_SERVER_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
13467
14098
  function normalizeCodexMcpServerName(name) {
13468
- if (PROTOTYPE_POLLUTION_KEYS.has(name)) return null;
13469
- if (CODEX_MCP_SERVER_NAME_PATTERN.test(name)) return name;
14099
+ if (!PROTOTYPE_POLLUTION_KEYS.has(name) && CODEX_MCP_SERVER_NAME_PATTERN.test(name)) return {
14100
+ codexName: name,
14101
+ usedFallback: false
14102
+ };
13470
14103
  const normalizedName = name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
13471
- return normalizedName && !PROTOTYPE_POLLUTION_KEYS.has(normalizedName) ? normalizedName : null;
14104
+ if (normalizedName && !PROTOTYPE_POLLUTION_KEYS.has(normalizedName)) return {
14105
+ codexName: normalizedName,
14106
+ usedFallback: false
14107
+ };
14108
+ return {
14109
+ codexName: `mcp_${createHash("sha256").update(name).digest("hex").slice(0, 8)}`,
14110
+ usedFallback: true
14111
+ };
13472
14112
  }
13473
14113
  function convertFromCodexFormat(codexMcp) {
13474
14114
  const result = {};
@@ -13482,7 +14122,7 @@ function convertFromCodexFormat(codexMcp) {
13482
14122
  } else if (key === "oauth" && isRecord(value)) converted[key] = mapOauthFromCodex(value);
13483
14123
  else if (Object.hasOwn(CODEX_TO_RULESYNC_FIELD_MAP, key)) {
13484
14124
  const mappedKey = CODEX_TO_RULESYNC_FIELD_MAP[key];
13485
- if (mappedKey) if (isStringArray(value)) converted[mappedKey] = value;
14125
+ if (mappedKey) if (isStringArray$1(value)) converted[mappedKey] = value;
13486
14126
  else warnWithFallback(void 0, `Ignored malformed array for ${key} in MCP server ${name}`);
13487
14127
  } else converted[key] = value;
13488
14128
  }
@@ -13494,12 +14134,9 @@ function convertToCodexFormat(mcpServers) {
13494
14134
  const result = {};
13495
14135
  const originalNames = /* @__PURE__ */ new Map();
13496
14136
  for (const [name, config] of Object.entries(mcpServers)) {
13497
- const codexName = normalizeCodexMcpServerName(name);
13498
- if (codexName === null) {
13499
- warnWithFallback(void 0, `MCP server "${name}" could not be normalized to a valid Codex MCP server name and was skipped.`);
13500
- continue;
13501
- }
13502
14137
  if (!isRecord(config)) continue;
14138
+ const { codexName, usedFallback } = normalizeCodexMcpServerName(name);
14139
+ 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.`);
13503
14140
  const converted = {};
13504
14141
  for (const [key, value] of Object.entries(config)) {
13505
14142
  if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
@@ -13508,12 +14145,12 @@ function convertToCodexFormat(mcpServers) {
13508
14145
  } else if (key === "oauth" && isRecord(value)) converted[key] = mapOauthToCodex(value);
13509
14146
  else if (Object.hasOwn(RULESYNC_TO_CODEX_FIELD_MAP, key)) {
13510
14147
  const mappedKey = RULESYNC_TO_CODEX_FIELD_MAP[key];
13511
- if (mappedKey) if (isStringArray(value)) converted[mappedKey] = value;
14148
+ if (mappedKey) if (isStringArray$1(value)) converted[mappedKey] = value;
13512
14149
  else warnWithFallback(void 0, `[CodexCliMcp] Skipping invalid value type for mapped key '${key}': expected string array, got ${typeof value}`);
13513
14150
  } else converted[key] = value;
13514
14151
  }
13515
14152
  const previousName = originalNames.get(codexName);
13516
- 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.`);
14153
+ if (previousName !== void 0) warnWithFallback(void 0, `Codex MCP server name collision: "${previousName}" and "${name}" both normalize to "${codexName}"; "${name}" (processed last) overwrites "${previousName}".`);
13517
14154
  originalNames.set(codexName, name);
13518
14155
  result[codexName] = converted;
13519
14156
  }
@@ -13580,7 +14217,7 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
13580
14217
  const rawServer = isRecord(rawMcpServers) ? rawMcpServers[serverName] : void 0;
13581
14218
  return [serverName, {
13582
14219
  ...serverConfig,
13583
- ...isRecord(rawServer) && isStringArray(rawServer.envVars) ? { envVars: rawServer.envVars } : {}
14220
+ ...isRecord(rawServer) && isStringArray$1(rawServer.envVars) ? { envVars: rawServer.envVars } : {}
13584
14221
  }];
13585
14222
  })));
13586
14223
  const filteredMcpServers = this.removeEmptyEntries(converted);
@@ -14317,11 +14954,11 @@ function applyGooseStdioFields(ext, config) {
14317
14954
  if (Array.isArray(command)) {
14318
14955
  if (typeof command[0] === "string") ext.cmd = command[0];
14319
14956
  const rest = command.slice(1).filter((c) => typeof c === "string");
14320
- const args = isStringArray(config.args) ? config.args : [];
14957
+ const args = isStringArray$1(config.args) ? config.args : [];
14321
14958
  if (rest.length > 0 || args.length > 0) ext.args = [...rest, ...args];
14322
14959
  } else if (typeof command === "string") {
14323
14960
  ext.cmd = command;
14324
- if (isStringArray(config.args)) ext.args = config.args;
14961
+ if (isStringArray$1(config.args)) ext.args = config.args;
14325
14962
  }
14326
14963
  if (isPlainObject$1(config.env)) ext.envs = omitPrototypePollutionKeys(config.env);
14327
14964
  }
@@ -14379,7 +15016,7 @@ function convertFromGooseFormat(extensions) {
14379
15016
  else if (type === "streamable_http") server.type = "http";
14380
15017
  else if (type === "stdio") server.type = "stdio";
14381
15018
  if (typeof ext.cmd === "string") server.command = ext.cmd;
14382
- if (isStringArray(ext.args)) server.args = ext.args;
15019
+ if (isStringArray$1(ext.args)) server.args = ext.args;
14383
15020
  if (isPlainObject$1(ext.envs)) server.env = omitPrototypePollutionKeys(ext.envs);
14384
15021
  if (typeof ext.uri === "string") server.url = ext.uri;
14385
15022
  if (isPlainObject$1(ext.headers)) server.headers = omitPrototypePollutionKeys(ext.headers);
@@ -14399,11 +15036,11 @@ function buildGoosePluginStdioServer(config) {
14399
15036
  if (Array.isArray(command)) {
14400
15037
  if (typeof command[0] === "string") server.command = command[0];
14401
15038
  const rest = command.slice(1).filter((c) => typeof c === "string");
14402
- const args = isStringArray(config.args) ? config.args : [];
15039
+ const args = isStringArray$1(config.args) ? config.args : [];
14403
15040
  if (rest.length > 0 || args.length > 0) server.args = [...rest, ...args];
14404
15041
  } else if (typeof command === "string") {
14405
15042
  server.command = command;
14406
- if (isStringArray(config.args)) server.args = config.args;
15043
+ if (isStringArray$1(config.args)) server.args = config.args;
14407
15044
  }
14408
15045
  if (isPlainObject$1(config.env)) server.env = omitPrototypePollutionKeys(config.env);
14409
15046
  if (typeof config.cwd === "string") server.cwd = config.cwd;
@@ -14716,7 +15353,7 @@ function resolveHermesTimeout(config) {
14716
15353
  */
14717
15354
  function copyHermesAdvancedFields(source, target) {
14718
15355
  if (typeof source.auth === "string") target.auth = source.auth;
14719
- if (typeof source.client_cert === "string" || isStringArray(source.client_cert)) target.client_cert = source.client_cert;
15356
+ if (typeof source.client_cert === "string" || isStringArray$1(source.client_cert)) target.client_cert = source.client_cert;
14720
15357
  if (typeof source.client_key === "string") target.client_key = source.client_key;
14721
15358
  if (typeof source.connect_timeout === "number") target.connect_timeout = source.connect_timeout;
14722
15359
  if (typeof source.supports_parallel_tool_calls === "boolean") target.supports_parallel_tool_calls = source.supports_parallel_tool_calls;
@@ -14735,8 +15372,8 @@ function copyHermesAdvancedFields(source, target) {
14735
15372
  */
14736
15373
  function buildHermesToolsBlock(config) {
14737
15374
  const tools = {};
14738
- if (isStringArray(config.enabledTools)) tools.include = config.enabledTools;
14739
- if (isStringArray(config.disabledTools)) tools.exclude = config.disabledTools;
15375
+ if (isStringArray$1(config.enabledTools)) tools.include = config.enabledTools;
15376
+ if (isStringArray$1(config.disabledTools)) tools.exclude = config.disabledTools;
14740
15377
  if (typeof config.promptsEnabled === "boolean") tools.prompts = config.promptsEnabled;
14741
15378
  if (typeof config.resourcesEnabled === "boolean") tools.resources = config.resourcesEnabled;
14742
15379
  return tools;
@@ -14748,8 +15385,8 @@ function buildHermesToolsBlock(config) {
14748
15385
  * `promptsEnabled`/`resourcesEnabled` top-level toggles.
14749
15386
  */
14750
15387
  function applyHermesToolsBlock(hermesTools, server) {
14751
- if (isStringArray(hermesTools.include)) server.enabledTools = hermesTools.include;
14752
- if (isStringArray(hermesTools.exclude)) server.disabledTools = hermesTools.exclude;
15388
+ if (isStringArray$1(hermesTools.include)) server.enabledTools = hermesTools.include;
15389
+ if (isStringArray$1(hermesTools.exclude)) server.disabledTools = hermesTools.exclude;
14753
15390
  if (typeof hermesTools.prompts === "boolean") server.promptsEnabled = hermesTools.prompts;
14754
15391
  if (typeof hermesTools.resources === "boolean") server.resourcesEnabled = hermesTools.resources;
14755
15392
  }
@@ -14773,11 +15410,11 @@ function convertServerToHermes(config) {
14773
15410
  if (Array.isArray(command)) {
14774
15411
  if (typeof command[0] === "string") out.command = command[0];
14775
15412
  const rest = command.slice(1).filter((c) => typeof c === "string");
14776
- const args = isStringArray(config.args) ? config.args : [];
15413
+ const args = isStringArray$1(config.args) ? config.args : [];
14777
15414
  if (rest.length > 0 || args.length > 0) out.args = [...rest, ...args];
14778
15415
  } else if (typeof command === "string") {
14779
15416
  out.command = command;
14780
- if (isStringArray(config.args)) out.args = config.args;
15417
+ if (isStringArray$1(config.args)) out.args = config.args;
14781
15418
  }
14782
15419
  if (isPlainObject$1(config.env)) out.env = omitPrototypePollutionKeys(config.env);
14783
15420
  } else if (url !== void 0) {
@@ -14825,7 +15462,7 @@ function convertFromHermesFormat(mcpServers) {
14825
15462
  if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord(config)) continue;
14826
15463
  const server = {};
14827
15464
  if (typeof config.command === "string") server.command = config.command;
14828
- if (isStringArray(config.args)) server.args = config.args;
15465
+ if (isStringArray$1(config.args)) server.args = config.args;
14829
15466
  if (isPlainObject$1(config.env)) server.env = omitPrototypePollutionKeys(config.env);
14830
15467
  if (typeof config.url === "string") server.url = config.url;
14831
15468
  if (isPlainObject$1(config.headers)) server.headers = omitPrototypePollutionKeys(config.headers);
@@ -17150,7 +17787,11 @@ const VibePermissionsOverrideSchema = z.looseObject({ permission: z.optional(z.r
17150
17787
  */
17151
17788
  const CursorPermissionsOverrideSchema = z.looseObject({
17152
17789
  permission: z.optional(ToolScopedPermissionSchema),
17153
- approvalMode: z.optional(z.string()),
17790
+ approvalMode: z.optional(z.enum([
17791
+ "allowlist",
17792
+ "auto-review",
17793
+ "unrestricted"
17794
+ ])),
17154
17795
  sandbox: z.optional(z.looseObject({}))
17155
17796
  });
17156
17797
  /**
@@ -17238,7 +17879,11 @@ const FactorydroidPermissionsOverrideSchema = z.looseObject({
17238
17879
  */
17239
17880
  const WarpPermissionsOverrideSchema = z.looseObject({
17240
17881
  permission: z.optional(ToolScopedPermissionSchema),
17241
- agent_mode_coding_permissions: z.optional(z.string()),
17882
+ agent_mode_coding_permissions: z.optional(z.enum([
17883
+ "always_ask_before_reading",
17884
+ "always_allow_reading",
17885
+ "allow_reading_specific_files"
17886
+ ])),
17242
17887
  agent_mode_coding_file_read_allowlist: z.optional(z.array(z.string())),
17243
17888
  agent_mode_execute_readonly_commands: z.optional(z.boolean())
17244
17889
  });
@@ -17284,7 +17929,11 @@ const JuniePermissionsOverrideSchema = z.looseObject({
17284
17929
  */
17285
17930
  const TaktPermissionsOverrideSchema = z.looseObject({
17286
17931
  permission: z.optional(ToolScopedPermissionSchema),
17287
- step_permission_overrides: z.optional(z.record(z.string(), z.string())),
17932
+ step_permission_overrides: z.optional(z.record(z.string(), z.enum([
17933
+ "readonly",
17934
+ "edit",
17935
+ "full"
17936
+ ]))),
17288
17937
  provider_options: z.optional(z.looseObject({}))
17289
17938
  });
17290
17939
  /**
@@ -17314,9 +17963,15 @@ const AmpPermissionsOverrideSchema = z.looseObject({
17314
17963
  permission: z.optional(ToolScopedPermissionSchema),
17315
17964
  permissions: z.optional(z.array(z.looseObject({
17316
17965
  tool: z.string(),
17317
- action: z.string()
17966
+ action: z.enum([
17967
+ "allow",
17968
+ "ask",
17969
+ "reject",
17970
+ "delegate"
17971
+ ]),
17972
+ context: z.optional(z.enum(["thread", "subagent"]))
17318
17973
  }))),
17319
- mcpPermissions: z.optional(z.array(z.looseObject({}))),
17974
+ mcpPermissions: z.optional(z.array(z.looseObject({ action: z.enum(["allow", "reject"]) }))),
17320
17975
  guardedFiles: z.optional(z.looseObject({ allowlist: z.optional(z.array(z.string())) })),
17321
17976
  dangerouslyAllowAll: z.optional(z.boolean())
17322
17977
  });
@@ -17341,7 +17996,12 @@ const AmpPermissionsOverrideSchema = z.looseObject({
17341
17996
  */
17342
17997
  const AntigravityCliPermissionsOverrideSchema = z.looseObject({
17343
17998
  permission: z.optional(ToolScopedPermissionSchema),
17344
- toolPermission: z.optional(z.string()),
17999
+ toolPermission: z.optional(z.enum([
18000
+ "request-review",
18001
+ "proceed-in-sandbox",
18002
+ "always-proceed",
18003
+ "strict"
18004
+ ])),
17345
18005
  enableTerminalSandbox: z.optional(z.boolean())
17346
18006
  });
17347
18007
  /**
@@ -17370,7 +18030,14 @@ const AugmentcodePermissionsOverrideSchema = z.looseObject({
17370
18030
  permission: z.optional(ToolScopedPermissionSchema),
17371
18031
  toolPermissions: z.optional(z.array(z.looseObject({
17372
18032
  toolName: z.string(),
17373
- permission: z.looseObject({ type: z.string() })
18033
+ eventType: z.optional(z.enum(["tool-call", "tool-response"])),
18034
+ permission: z.looseObject({ type: z.enum([
18035
+ "allow",
18036
+ "deny",
18037
+ "ask-user",
18038
+ "webhook-policy",
18039
+ "script-policy"
18040
+ ]) })
17374
18041
  })))
17375
18042
  });
17376
18043
  /**
@@ -17460,12 +18127,20 @@ const CodexApprovalsReviewerSchema = z.enum([
17460
18127
  * `[permissions.rulesync]` profile may extend. Codex ships three built-in
17461
18128
  * profiles (`:read-only`, `:workspace`, `:danger-full-access`; the leading
17462
18129
  * colon is reserved for built-ins), but `extends` rejects
17463
- * `:danger-full-access` at config load time, so only the two extendable
17464
- * baselines are accepted here. The value list is exported so the Codex CLI
17465
- * translator derives its import-side baseline check from the same source.
18130
+ * `:danger-full-access` at config load time, so only these two are valid
18131
+ * `extends` parents. The value list is exported so the Codex CLI translator
18132
+ * derives its import-side baseline check from the same source.
17466
18133
  * @see https://learn.chatgpt.com/docs/permissions
17467
18134
  */
17468
- const CODEX_BASE_PERMISSION_PROFILES = [":read-only", ":workspace"];
18135
+ const CODEX_EXTENDABLE_BASELINE_PROFILES = [":read-only", ":workspace"];
18136
+ /**
18137
+ * All accepted `codexcli.base_permission_profile` values. `:danger-full-access`
18138
+ * cannot be an `extends` parent, so selecting it makes rulesync emit
18139
+ * `default_permissions = ":danger-full-access"` directly and skip the managed
18140
+ * `[permissions.rulesync]` profile entirely (there is no sandbox for
18141
+ * filesystem/network rules to refine in that mode).
18142
+ */
18143
+ const CODEX_BASE_PERMISSION_PROFILES = [...CODEX_EXTENDABLE_BASELINE_PROFILES, ":danger-full-access"];
17469
18144
  const CodexBasePermissionProfileSchema = z.enum(CODEX_BASE_PERMISSION_PROFILES);
17470
18145
  /**
17471
18146
  * Codex CLI-scoped permission override.
@@ -17476,10 +18151,14 @@ const CodexBasePermissionProfileSchema = z.enum(CODEX_BASE_PERMISSION_PROFILES);
17476
18151
  * override whose fields are written verbatim as top-level `.codex/config.toml`
17477
18152
  * keys (the override wins per key; existing sibling keys the user set directly
17478
18153
  * are preserved):
17479
- * - `base_permission_profile` — the built-in profile the managed
17480
- * `[permissions.rulesync]` profile extends (`:read-only` | `:workspace`).
17481
- * Unlike the other keys it is not a top-level config key: it is emitted as
17482
- * the profile's `extends` value. Defaults to `:workspace` when unspecified.
18154
+ * - `base_permission_profile` — the built-in baseline profile (`:read-only` |
18155
+ * `:workspace` | `:danger-full-access`). Unlike the other keys it is not a
18156
+ * top-level config key: the extendable baselines are emitted as the managed
18157
+ * `[permissions.rulesync]` profile's `extends` value, while
18158
+ * `:danger-full-access` (which Codex rejects as an `extends` parent) is
18159
+ * selected directly via `default_permissions` and skips the managed profile
18160
+ * entirely — canonical filesystem/network rules are ignored in that mode.
18161
+ * Defaults to `:workspace` when unspecified.
17483
18162
  * - `approval_policy` — `untrusted` | `on-request` (legacy alias `on-failure`) |
17484
18163
  * `never`, or a `{ granular = { … } }` table (kept verbatim; the granular
17485
18164
  * schema has required fields that are brittle to model as typed keys).
@@ -17500,13 +18179,14 @@ const CodexBasePermissionProfileSchema = z.enum(CODEX_BASE_PERMISSION_PROFILES);
17500
18179
  * Defaults to `auto_review` when neither the override nor the existing
17501
18180
  * config sets it.
17502
18181
  * - `git_write_rules` — whether the managed profile's `:workspace_roots` table
17503
- * emits the default `.git` carve-outs (`".git/**" = "write"` with
17504
- * `".git/config" = "read"` kept read-only as a security guard). Codex's
18182
+ * emits the default `.git` carve-out (`".git/**" = "write"`). Codex's
17505
18183
  * `:workspace` baseline makes `.git` read-only, which denies basic git
17506
18184
  * workflows (commit/stage writes to `.git/index`, `.git/objects`, refs,
17507
- * logs), so the carve-outs are emitted by default. Defaults to `true`; only
17508
- * an explicit `false` suppresses them. Like `base_permission_profile` it is
17509
- * consumed by the profile builder, not written as a top-level config key.
18185
+ * logs; everyday commands like `git remote add` or `git push -u` write to
18186
+ * `.git/config`), so the carve-out is emitted by default. Defaults to
18187
+ * `true`; only an explicit `false` suppresses it. Like
18188
+ * `base_permission_profile` it is consumed by the profile builder, not
18189
+ * written as a top-level config key.
17510
18190
  *
17511
18191
  * Two surfaces are deliberately NOT authorable here so the override can never
17512
18192
  * clobber a feature-owned key: `mcp_servers.*` per-MCP gating is owned by the
@@ -17583,6 +18263,7 @@ const PermissionsConfigSchema = z.looseObject({
17583
18263
  kiro: z.optional(KiroPermissionsOverrideSchema),
17584
18264
  codexcli: z.optional(CodexcliPermissionsOverrideSchema),
17585
18265
  "antigravity-ide": z.optional(CanonicalPermissionsOverrideSchema),
18266
+ copilot: z.optional(CanonicalPermissionsOverrideSchema),
17586
18267
  devin: z.optional(CanonicalPermissionsOverrideSchema),
17587
18268
  goose: z.optional(CanonicalPermissionsOverrideSchema),
17588
18269
  grokcli: z.optional(CanonicalPermissionsOverrideSchema),
@@ -19503,15 +20184,13 @@ const RULESYNC_PROFILE_NAME = "rulesync";
19503
20184
  const CODEX_WORKSPACE_ROOTS_KEY = ":workspace_roots";
19504
20185
  const CODEX_WORKSPACE_BASELINE = ":workspace";
19505
20186
  const CODEX_READ_ONLY_BASELINE = ":read-only";
19506
- const CODEX_EXTENDABLE_BASELINES = new Set(CODEX_BASE_PERMISSION_PROFILES);
20187
+ const CODEX_DANGER_FULL_ACCESS_BASELINE = ":danger-full-access";
20188
+ const CODEX_EXTENDABLE_BASELINES = new Set(CODEX_EXTENDABLE_BASELINE_PROFILES);
19507
20189
  const CODEX_DEFAULT_APPROVAL_POLICY = "on-request";
19508
20190
  const CODEX_DEFAULT_APPROVALS_REVIEWER = "auto_review";
19509
20191
  const CODEX_GLOB_SCAN_MAX_DEPTH = 8;
19510
20192
  const CODEX_MINIMAL_KEY = ":minimal";
19511
- const CODEX_GIT_WRITE_RULES = {
19512
- ".git/**": "write",
19513
- ".git/config": "read"
19514
- };
20193
+ const CODEX_GIT_WRITE_RULES = { ".git/**": "write" };
19515
20194
  const GLOBAL_WILDCARD_DOMAIN = "*";
19516
20195
  var CodexcliPermissions = class CodexcliPermissions extends ToolPermissions {
19517
20196
  static getSettablePaths(_options = {}) {
@@ -19539,8 +20218,40 @@ var CodexcliPermissions = class CodexcliPermissions extends ToolPermissions {
19539
20218
  const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
19540
20219
  const existingContent = await readFileContentOrNull(filePath) ?? "";
19541
20220
  const existing = toMutableTable(smolToml.parse(existingContent || smolToml.stringify({})));
20221
+ const canonicalConfig = rulesyncPermissions.getJson();
20222
+ if (canonicalConfig.codexcli?.base_permission_profile === CODEX_DANGER_FULL_ACCESS_BASELINE) {
20223
+ const ignoredCategories = Object.entries(canonicalConfig.permission).filter(([category, rules]) => category !== "bash" && Object.keys(rules).length > 0).map(([category]) => category);
20224
+ 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.`);
20225
+ const permissionsTable = toMutableTable(existing.permissions);
20226
+ if (permissionsTable[RULESYNC_PROFILE_NAME] !== void 0) {
20227
+ 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.`);
20228
+ delete permissionsTable[RULESYNC_PROFILE_NAME];
20229
+ }
20230
+ const overridePatch = computeCodexcliOverridePatch({
20231
+ existing,
20232
+ override: canonicalConfig.codexcli,
20233
+ logger
20234
+ });
20235
+ return new CodexcliPermissions({
20236
+ outputRoot,
20237
+ relativeDirPath: paths.relativeDirPath,
20238
+ relativeFilePath: paths.relativeFilePath,
20239
+ fileContent: applySharedConfigPatch({
20240
+ fileKey: sharedConfigFileKey(paths),
20241
+ feature: "permissions",
20242
+ existingContent,
20243
+ patch: {
20244
+ permissions: Object.keys(permissionsTable).length > 0 ? permissionsTable : void 0,
20245
+ default_permissions: CODEX_DANGER_FULL_ACCESS_BASELINE,
20246
+ ...overridePatch
20247
+ },
20248
+ filePath
20249
+ }),
20250
+ validate
20251
+ });
20252
+ }
19542
20253
  const newProfile = convertRulesyncToCodexProfile({
19543
- config: rulesyncPermissions.getJson(),
20254
+ config: canonicalConfig,
19544
20255
  logger
19545
20256
  });
19546
20257
  const permissionsTable = toMutableTable(existing.permissions);
@@ -19601,6 +20312,7 @@ var CodexcliPermissions = class CodexcliPermissions extends ToolPermissions {
19601
20312
  });
19602
20313
  const override = extractCodexcliOverride(table);
19603
20314
  if (typeof profile?.extends === "string" && CODEX_EXTENDABLE_BASELINES.has(profile.extends)) override.base_permission_profile = profile.extends;
20315
+ if (defaultProfile === CODEX_DANGER_FULL_ACCESS_BASELINE) override.base_permission_profile = CODEX_DANGER_FULL_ACCESS_BASELINE;
19604
20316
  const result = Object.keys(override).length > 0 ? {
19605
20317
  ...config,
19606
20318
  codexcli: override
@@ -19656,16 +20368,10 @@ function convertRulesyncToCodexProfile({ config, logger }) {
19656
20368
  const filesystem = { [CODEX_MINIMAL_KEY]: "read" };
19657
20369
  const workspaceRootFilesystem = {};
19658
20370
  const domains = {};
20371
+ const filesystemCategoryRules = {};
19659
20372
  for (const [toolName, rules] of Object.entries(config.permission)) {
19660
20373
  if (toolName === "read" || toolName === "edit" || toolName === "write") {
19661
- const mapAction = toolName === "read" ? mapReadAction : mapWriteAction;
19662
- for (const [pattern, action] of Object.entries(rules)) addFilesystemRule({
19663
- filesystem,
19664
- workspaceRootFilesystem,
19665
- pattern,
19666
- access: mapAction(action),
19667
- logger
19668
- });
20374
+ filesystemCategoryRules[toolName] = rules;
19669
20375
  continue;
19670
20376
  }
19671
20377
  if (toolName === "webfetch") {
@@ -19678,6 +20384,16 @@ function convertRulesyncToCodexProfile({ config, logger }) {
19678
20384
  }
19679
20385
  logger?.warn(`Codex CLI permissions support only read/edit/write/webfetch categories. Skipping: ${toolName}`);
19680
20386
  }
20387
+ for (const [pattern, access] of mergeFilesystemCategoryRules({
20388
+ categoryRules: filesystemCategoryRules,
20389
+ logger
20390
+ })) addFilesystemRule({
20391
+ filesystem,
20392
+ workspaceRootFilesystem,
20393
+ pattern,
20394
+ access,
20395
+ logger
20396
+ });
19681
20397
  applyDefaultGitWriteRules({
19682
20398
  config,
19683
20399
  filesystem,
@@ -19958,6 +20674,67 @@ function mapReadAction(action) {
19958
20674
  function mapWriteAction(action) {
19959
20675
  return action === "allow" ? "write" : "deny";
19960
20676
  }
20677
+ /**
20678
+ * Merge the canonical read/edit/write category rules into one Codex access
20679
+ * level per path pattern (Codex models a single `deny` < `read` < `write`
20680
+ * level, with no `ask`).
20681
+ *
20682
+ * - `edit` and `write` collapse onto Codex's write side; when both carry the
20683
+ * same pattern, the more restrictive action wins (`deny` > `ask` > `allow`).
20684
+ * - `read: allow` + write-side `allow` → `"write"`.
20685
+ * - `read: allow` + write-side non-allow → `"read"` (readable but not
20686
+ * writable — exactly what Codex's `"read"` level expresses).
20687
+ * - `read` non-allow → `"deny"` regardless of the write side; a contradictory
20688
+ * write-side `allow` (unreadable but writable is not expressible in Codex)
20689
+ * is warned about.
20690
+ * - Single-category patterns keep the existing mapReadAction/mapWriteAction
20691
+ * mappings.
20692
+ *
20693
+ * Iteration order is read → edit → write with first-seen pattern order, so
20694
+ * the emitted table is stable regardless of the authored category order.
20695
+ * Note the merge is one-way: `"{path}" = "read"` imports back as
20696
+ * `read: allow` only (the explicit write-side deny is implied by Codex's
20697
+ * access level and not re-materialized).
20698
+ */
20699
+ function mergeFilesystemCategoryRules({ categoryRules, logger }) {
20700
+ const readRules = categoryRules.read ?? {};
20701
+ const writeSideRestrictiveness = {
20702
+ deny: 2,
20703
+ ask: 1,
20704
+ allow: 0
20705
+ };
20706
+ const writeSideRules = {};
20707
+ for (const category of ["edit", "write"]) for (const [pattern, action] of Object.entries(categoryRules[category] ?? {})) {
20708
+ const existing = writeSideRules[pattern];
20709
+ if (existing === void 0 || writeSideRestrictiveness[action] > writeSideRestrictiveness[existing]) writeSideRules[pattern] = action;
20710
+ }
20711
+ const patterns = [];
20712
+ const seen = /* @__PURE__ */ new Set();
20713
+ for (const pattern of [...Object.keys(readRules), ...Object.keys(writeSideRules)]) if (!seen.has(pattern)) {
20714
+ seen.add(pattern);
20715
+ patterns.push(pattern);
20716
+ }
20717
+ const merged = [];
20718
+ for (const pattern of patterns) {
20719
+ const readAction = readRules[pattern];
20720
+ const writeAction = writeSideRules[pattern];
20721
+ if (readAction === void 0) {
20722
+ merged.push([pattern, mapWriteAction(writeAction)]);
20723
+ continue;
20724
+ }
20725
+ if (writeAction === void 0) {
20726
+ merged.push([pattern, mapReadAction(readAction)]);
20727
+ continue;
20728
+ }
20729
+ if (readAction === "allow") {
20730
+ merged.push([pattern, writeAction === "allow" ? "write" : "read"]);
20731
+ continue;
20732
+ }
20733
+ 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".`);
20734
+ merged.push([pattern, "deny"]);
20735
+ }
20736
+ return merged;
20737
+ }
19961
20738
  function buildCodexBashRulesContent(config) {
19962
20739
  const bashRules = config.permission.bash ?? {};
19963
20740
  const entries = Object.entries(bashRules);
@@ -19990,6 +20767,135 @@ function mapBashActionToDecision(action) {
19990
20767
  return "forbidden";
19991
20768
  }
19992
20769
  //#endregion
20770
+ //#region src/features/permissions/copilot-permissions.ts
20771
+ /**
20772
+ * The flat, dotted VS Code setting key this adapter manages. VS Code stores
20773
+ * settings with dotted keys flat at the document top level, so this is a single
20774
+ * literal key — not a nested `chat.tools.terminal` path.
20775
+ * @see https://code.visualstudio.com/docs/agents/approvals
20776
+ */
20777
+ const AUTO_APPROVE_KEY = "chat.tools.terminal.autoApprove";
20778
+ /**
20779
+ * The canonical permission category this adapter maps. Only shell/terminal
20780
+ * commands (`bash`) have a clean, high-fidelity representation in VS Code's
20781
+ * `chat.tools.terminal.autoApprove` map; other categories (read/edit/webfetch/
20782
+ * …) have no terminal-command equivalent and are intentionally not mapped.
20783
+ */
20784
+ const TERMINAL_CATEGORY = "bash";
20785
+ function asAutoApproveMap(value) {
20786
+ if (!isPlainObject$1(value)) return {};
20787
+ const result = {};
20788
+ for (const [pattern, flag] of Object.entries(value)) if (typeof flag === "boolean") result[pattern] = flag;
20789
+ return result;
20790
+ }
20791
+ /**
20792
+ * Permissions generator for GitHub Copilot Chat in VS Code.
20793
+ *
20794
+ * VS Code has no standalone, environment-agnostic Copilot policy file (like
20795
+ * `.claude/settings.json`); project-level terminal auto-approvals are managed
20796
+ * through the workspace `chat.tools.terminal.autoApprove` map inside
20797
+ * `.vscode/settings.json`. That file is a general workspace settings file with
20798
+ * many unrelated keys, so reads and writes merge into the existing JSON
20799
+ * (touching only the one managed key) and the file is never deleted.
20800
+ *
20801
+ * Scope is deliberately limited to `chat.tools.terminal.autoApprove` — the one
20802
+ * clean, non-lossy mapping. The canonical `bash` category's per-pattern rules
20803
+ * map as: `allow` → `true` (auto-approve), `deny` → `false` (never approve),
20804
+ * and `ask` → the entry is OMITTED (VS Code then falls through to its default
20805
+ * in-chat approval prompt, i.e. "ask"). Only project scope is modeled: VS Code's
20806
+ * user-scope settings.json lives at a platform-dependent path outside rulesync's
20807
+ * home-relative global model.
20808
+ */
20809
+ var CopilotPermissions = class CopilotPermissions extends ToolPermissions {
20810
+ constructor(params) {
20811
+ super({
20812
+ ...params,
20813
+ fileContent: params.fileContent ?? "{}"
20814
+ });
20815
+ }
20816
+ /**
20817
+ * `.vscode/settings.json` is a user-managed workspace file with unrelated
20818
+ * settings, so it must not be deleted.
20819
+ */
20820
+ isDeletable() {
20821
+ return false;
20822
+ }
20823
+ static getSettablePaths(_options = {}) {
20824
+ return {
20825
+ relativeDirPath: COPILOT_MCP_DIR,
20826
+ relativeFilePath: COPILOT_VSCODE_SETTINGS_FILE_NAME
20827
+ };
20828
+ }
20829
+ static async fromFile({ outputRoot = process.cwd(), validate = true }) {
20830
+ const paths = CopilotPermissions.getSettablePaths();
20831
+ const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{}";
20832
+ return new CopilotPermissions({
20833
+ outputRoot,
20834
+ relativeDirPath: paths.relativeDirPath,
20835
+ relativeFilePath: paths.relativeFilePath,
20836
+ fileContent,
20837
+ validate
20838
+ });
20839
+ }
20840
+ static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions }) {
20841
+ const paths = CopilotPermissions.getSettablePaths();
20842
+ const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
20843
+ const existingContent = await readFileContentOrNull(filePath) ?? "{}";
20844
+ const rules = rulesyncPermissions.getJson().permission[TERMINAL_CATEGORY] ?? {};
20845
+ const autoApprove = {};
20846
+ for (const [pattern, action] of Object.entries(rules)) if (action === "allow") autoApprove[pattern] = true;
20847
+ else if (action === "deny") autoApprove[pattern] = false;
20848
+ const patchValue = Object.keys(autoApprove).length > 0 ? autoApprove : void 0;
20849
+ return new CopilotPermissions({
20850
+ outputRoot,
20851
+ relativeDirPath: paths.relativeDirPath,
20852
+ relativeFilePath: paths.relativeFilePath,
20853
+ fileContent: applySharedConfigPatch({
20854
+ fileKey: sharedConfigFileKey(paths),
20855
+ feature: "permissions",
20856
+ existingContent,
20857
+ patch: { [AUTO_APPROVE_KEY]: patchValue },
20858
+ filePath
20859
+ }),
20860
+ validate: true
20861
+ });
20862
+ }
20863
+ toRulesyncPermissions() {
20864
+ let settings;
20865
+ try {
20866
+ settings = parseSharedConfig({
20867
+ format: "jsonc",
20868
+ fileContent: this.getFileContent() || "{}",
20869
+ filePath: join(this.getRelativeDirPath(), this.getRelativeFilePath()),
20870
+ invalidRootPolicy: "error",
20871
+ jsoncParseErrors: "error"
20872
+ });
20873
+ } catch (error) {
20874
+ throw new Error(`Failed to parse Copilot VS Code settings in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
20875
+ }
20876
+ const autoApprove = asAutoApproveMap(settings[AUTO_APPROVE_KEY]);
20877
+ const rules = {};
20878
+ for (const [pattern, flag] of Object.entries(autoApprove)) rules[pattern] = flag ? "allow" : "deny";
20879
+ const permission = Object.keys(rules).length > 0 ? { [TERMINAL_CATEGORY]: rules } : {};
20880
+ return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({ permission }, null, 2) });
20881
+ }
20882
+ validate() {
20883
+ return {
20884
+ success: true,
20885
+ error: null
20886
+ };
20887
+ }
20888
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
20889
+ return new CopilotPermissions({
20890
+ outputRoot,
20891
+ relativeDirPath,
20892
+ relativeFilePath,
20893
+ fileContent: "{}",
20894
+ validate: false
20895
+ });
20896
+ }
20897
+ };
20898
+ //#endregion
19993
20899
  //#region src/features/permissions/cursor-permissions.ts
19994
20900
  /**
19995
20901
  * Mapping from rulesync canonical tool category names (lowercase) to Cursor CLI
@@ -20909,7 +21815,7 @@ function convertRulesyncToGoosePermissionConfig({ config, logger }) {
20909
21815
  function convertGoosePermissionConfigToRulesync(userPermission) {
20910
21816
  const permission = {};
20911
21817
  for (const key of GOOSE_PERMISSION_LIST_KEYS) {
20912
- const toolNames = isStringArray(userPermission[key]) ? userPermission[key] : [];
21818
+ const toolNames = isStringArray$1(userPermission[key]) ? userPermission[key] : [];
20913
21819
  const action = GOOSE_LIST_TO_ACTION[key];
20914
21820
  for (const toolName of toolNames) {
20915
21821
  const category = GOOSE_TO_RULESYNC_TOOL_NAME[toolName] ?? toolName;
@@ -21149,7 +22055,7 @@ const ACTION_RANK = {
21149
22055
  * (mirrors the Cursor adapter's preservation of unmanaged types).
21150
22056
  */
21151
22057
  function unmanagedEntries(existingPermission, key) {
21152
- return (isStringArray(existingPermission[key]) ? existingPermission[key] : []).filter((entry) => parseGrokEntry(entry) === null);
22058
+ return (isStringArray$1(existingPermission[key]) ? existingPermission[key] : []).filter((entry) => parseGrokEntry(entry) === null);
21153
22059
  }
21154
22060
  /**
21155
22061
  * Bucket canonical rules into Grok's `[permission]` allow/deny/ask arrays of
@@ -21190,9 +22096,9 @@ function buildGrokPermissionArrays(config, existingPermission, logger) {
21190
22096
  * arrays resolves to the strictest action.
21191
22097
  */
21192
22098
  function parseGrokPermissionArrays(permission) {
21193
- const allow = isStringArray(permission.allow) ? permission.allow : void 0;
21194
- const deny = isStringArray(permission.deny) ? permission.deny : void 0;
21195
- const ask = isStringArray(permission.ask) ? permission.ask : void 0;
22099
+ const allow = isStringArray$1(permission.allow) ? permission.allow : void 0;
22100
+ const deny = isStringArray$1(permission.deny) ? permission.deny : void 0;
22101
+ const ask = isStringArray$1(permission.ask) ? permission.ask : void 0;
21196
22102
  if ((allow?.length ?? 0) + (deny?.length ?? 0) + (ask?.length ?? 0) === 0) return null;
21197
22103
  const result = {};
21198
22104
  const apply = (entries, action) => {
@@ -22973,7 +23879,7 @@ function convertRovodevToolPermissionsToRulesync(toolPermissions) {
22973
23879
  permission[category][CATCH_ALL_PATTERN$1] = value;
22974
23880
  }
22975
23881
  }
22976
- if (isStringArray(toolPermissions.allowedExternalPaths)) for (const path of toolPermissions.allowedExternalPaths) {
23882
+ if (isStringArray$1(toolPermissions.allowedExternalPaths)) for (const path of toolPermissions.allowedExternalPaths) {
22977
23883
  permission.read ??= {};
22978
23884
  permission.read[path] = "allow";
22979
23885
  }
@@ -23533,8 +24439,8 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
23533
24439
  const agents = isRecord(settings.agents) ? settings.agents : {};
23534
24440
  const profiles = isRecord(agents.profiles) ? agents.profiles : {};
23535
24441
  const config = convertWarpToRulesyncPermissions({
23536
- allow: isStringArray(profiles[ALLOWLIST_KEY]) ? profiles[ALLOWLIST_KEY] : [],
23537
- deny: isStringArray(profiles[DENYLIST_KEY]) ? profiles[DENYLIST_KEY] : []
24442
+ allow: isStringArray$1(profiles[ALLOWLIST_KEY]) ? profiles[ALLOWLIST_KEY] : [],
24443
+ deny: isStringArray$1(profiles[DENYLIST_KEY]) ? profiles[DENYLIST_KEY] : []
23538
24444
  });
23539
24445
  const warpOverride = {};
23540
24446
  for (const key of WARP_OVERRIDE_KEYS) if (profiles[key] !== void 0) warpOverride[key] = profiles[key];
@@ -23880,6 +24786,14 @@ const toolPermissionsFactories = /* @__PURE__ */ new Map([
23880
24786
  supportsImport: true
23881
24787
  }
23882
24788
  }],
24789
+ ["copilot", {
24790
+ class: CopilotPermissions,
24791
+ meta: {
24792
+ supportsProject: true,
24793
+ supportsGlobal: false,
24794
+ supportsImport: true
24795
+ }
24796
+ }],
23883
24797
  ["cursor", {
23884
24798
  class: CursorPermissions,
23885
24799
  meta: {
@@ -24114,9 +25028,6 @@ var PermissionsProcessor = class extends FeatureProcessor {
24114
25028
  }
24115
25029
  };
24116
25030
  //#endregion
24117
- //#region src/constants/general.ts
24118
- const SKILL_FILE_NAME = "SKILL.md";
24119
- //#endregion
24120
25031
  //#region src/types/ai-dir.ts
24121
25032
  var AiDir = class {
24122
25033
  /**
@@ -24317,10 +25228,10 @@ var ToolSkill = class extends AiDir {
24317
25228
  const settablePaths = getSettablePaths({ global });
24318
25229
  const actualRelativeDirPath = relativeDirPath ?? settablePaths.relativeDirPath;
24319
25230
  const skillDirPath = join(outputRoot, actualRelativeDirPath, dirName);
24320
- const skillFilePath = join(skillDirPath, SKILL_FILE_NAME);
24321
- if (!await fileExists(skillFilePath)) throw new Error(`${SKILL_FILE_NAME} not found in ${skillDirPath}`);
25231
+ const skillFilePath = join(skillDirPath, SKILL_FILE_NAME$1);
25232
+ if (!await fileExists(skillFilePath)) throw new Error(`${SKILL_FILE_NAME$1} not found in ${skillDirPath}`);
24322
25233
  const { frontmatter, body: content } = parseFrontmatter(await readFileContent(skillFilePath), skillFilePath);
24323
- const otherFiles = await this.collectOtherFiles(outputRoot, actualRelativeDirPath, dirName, SKILL_FILE_NAME);
25234
+ const otherFiles = await this.collectOtherFiles(outputRoot, actualRelativeDirPath, dirName, SKILL_FILE_NAME$1);
24324
25235
  return {
24325
25236
  outputRoot,
24326
25237
  relativeDirPath: actualRelativeDirPath,
@@ -24362,7 +25273,7 @@ var SimulatedSkill = class extends ToolSkill {
24362
25273
  relativeDirPath,
24363
25274
  dirName,
24364
25275
  mainFile: {
24365
- name: SKILL_FILE_NAME,
25276
+ name: SKILL_FILE_NAME$1,
24366
25277
  body,
24367
25278
  frontmatter: { ...frontmatter }
24368
25279
  },
@@ -24420,12 +25331,12 @@ var SimulatedSkill = class extends ToolSkill {
24420
25331
  const settablePaths = this.getSettablePaths();
24421
25332
  const actualRelativeDirPath = relativeDirPath ?? settablePaths.relativeDirPath;
24422
25333
  const skillDirPath = join(outputRoot, actualRelativeDirPath, dirName);
24423
- const skillFilePath = join(skillDirPath, SKILL_FILE_NAME);
24424
- if (!await fileExists(skillFilePath)) throw new Error(`${SKILL_FILE_NAME} not found in ${skillDirPath}`);
25334
+ const skillFilePath = join(skillDirPath, SKILL_FILE_NAME$1);
25335
+ if (!await fileExists(skillFilePath)) throw new Error(`${SKILL_FILE_NAME$1} not found in ${skillDirPath}`);
24425
25336
  const { frontmatter, body: content } = parseFrontmatter(await readFileContent(skillFilePath), skillFilePath);
24426
25337
  const result = SimulatedSkillFrontmatterSchema.safeParse(frontmatter);
24427
25338
  if (!result.success) throw new Error(`Invalid frontmatter in ${skillFilePath}: ${formatError(result.error)}`);
24428
- const otherFiles = await this.collectOtherFiles(outputRoot, actualRelativeDirPath, dirName, SKILL_FILE_NAME);
25339
+ const otherFiles = await this.collectOtherFiles(outputRoot, actualRelativeDirPath, dirName, SKILL_FILE_NAME$1);
24429
25340
  return {
24430
25341
  outputRoot,
24431
25342
  relativeDirPath: actualRelativeDirPath,
@@ -24635,7 +25546,7 @@ var RulesyncSkill = class RulesyncSkill extends AiDir {
24635
25546
  relativeDirPath,
24636
25547
  dirName,
24637
25548
  mainFile: {
24638
- name: SKILL_FILE_NAME,
25549
+ name: SKILL_FILE_NAME$1,
24639
25550
  body,
24640
25551
  frontmatter: { ...frontmatter }
24641
25552
  },
@@ -24670,13 +25581,13 @@ var RulesyncSkill = class RulesyncSkill extends AiDir {
24670
25581
  }
24671
25582
  static async fromDir({ outputRoot = process.cwd(), relativeDirPath = RULESYNC_SKILLS_RELATIVE_DIR_PATH, dirName, global = false }) {
24672
25583
  const skillDirPath = join(outputRoot, relativeDirPath, dirName);
24673
- const skillFilePath = join(skillDirPath, SKILL_FILE_NAME);
24674
- if (!await fileExists(skillFilePath)) throw new Error(`${SKILL_FILE_NAME} not found in ${skillDirPath}`);
25584
+ const skillFilePath = join(skillDirPath, SKILL_FILE_NAME$1);
25585
+ if (!await fileExists(skillFilePath)) throw new Error(`${SKILL_FILE_NAME$1} not found in ${skillDirPath}`);
24675
25586
  const { frontmatter, body: content, hasFrontmatter } = parseFrontmatter(await readFileContent(skillFilePath), skillFilePath);
24676
25587
  if (!hasFrontmatter) throw new Error(`Missing frontmatter in ${skillFilePath}. Rulesync files must begin with a YAML frontmatter block delimited by '---'.`);
24677
25588
  const result = RulesyncSkillFrontmatterSchema.safeParse(frontmatter);
24678
25589
  if (!result.success) throw new Error(`Invalid frontmatter in ${skillFilePath}: ${formatError(result.error)}`);
24679
- const otherFiles = await this.collectOtherFiles(outputRoot, relativeDirPath, dirName, SKILL_FILE_NAME);
25590
+ const otherFiles = await this.collectOtherFiles(outputRoot, relativeDirPath, dirName, SKILL_FILE_NAME$1);
24680
25591
  return new RulesyncSkill({
24681
25592
  outputRoot,
24682
25593
  relativeDirPath,
@@ -24716,7 +25627,7 @@ var RovodevSkill = class RovodevSkill extends ToolSkill {
24716
25627
  relativeDirPath,
24717
25628
  dirName,
24718
25629
  mainFile: {
24719
- name: SKILL_FILE_NAME,
25630
+ name: SKILL_FILE_NAME$1,
24720
25631
  body,
24721
25632
  frontmatter: { ...frontmatter }
24722
25633
  },
@@ -24743,7 +25654,7 @@ var RovodevSkill = class RovodevSkill extends ToolSkill {
24743
25654
  validate() {
24744
25655
  if (!this.mainFile) return {
24745
25656
  success: false,
24746
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
25657
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
24747
25658
  };
24748
25659
  const result = RovodevSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
24749
25660
  if (!result.success) return {
@@ -24819,10 +25730,10 @@ var RovodevSkill = class RovodevSkill extends ToolSkill {
24819
25730
  const result = RovodevSkillFrontmatterSchema.safeParse(loaded.frontmatter);
24820
25731
  if (!result.success) {
24821
25732
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
24822
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
25733
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
24823
25734
  }
24824
25735
  if (result.data.name !== loaded.dirName) {
24825
- const skillFilePath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME);
25736
+ const skillFilePath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME$1);
24826
25737
  throw new Error(`Frontmatter name (${result.data.name}) must match directory name (${loaded.dirName}) in ${skillFilePath}`);
24827
25738
  }
24828
25739
  return new RovodevSkill({
@@ -24987,7 +25898,7 @@ var AgentsSkillsSkill = class AgentsSkillsSkill extends ToolSkill {
24987
25898
  relativeDirPath,
24988
25899
  dirName,
24989
25900
  mainFile: {
24990
- name: SKILL_FILE_NAME,
25901
+ name: SKILL_FILE_NAME$1,
24991
25902
  body,
24992
25903
  frontmatter: { ...frontmatter }
24993
25904
  },
@@ -25011,7 +25922,7 @@ var AgentsSkillsSkill = class AgentsSkillsSkill extends ToolSkill {
25011
25922
  validate() {
25012
25923
  if (!this.mainFile) return {
25013
25924
  success: false,
25014
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
25925
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
25015
25926
  };
25016
25927
  const result = AgentsSkillsSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
25017
25928
  if (!result.success) return {
@@ -25083,7 +25994,7 @@ var AgentsSkillsSkill = class AgentsSkillsSkill extends ToolSkill {
25083
25994
  const result = AgentsSkillsSkillFrontmatterSchema.safeParse(loaded.frontmatter);
25084
25995
  if (!result.success) {
25085
25996
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
25086
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
25997
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
25087
25998
  }
25088
25999
  return new this({
25089
26000
  outputRoot: loaded.outputRoot,
@@ -25140,7 +26051,7 @@ var AiassistantSkill = class AiassistantSkill extends ToolSkill {
25140
26051
  relativeDirPath,
25141
26052
  dirName,
25142
26053
  mainFile: {
25143
- name: SKILL_FILE_NAME,
26054
+ name: SKILL_FILE_NAME$1,
25144
26055
  body,
25145
26056
  frontmatter: { ...frontmatter }
25146
26057
  },
@@ -25165,7 +26076,7 @@ var AiassistantSkill = class AiassistantSkill extends ToolSkill {
25165
26076
  validate() {
25166
26077
  if (!this.mainFile) return {
25167
26078
  success: false,
25168
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
26079
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
25169
26080
  };
25170
26081
  const result = AiassistantSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
25171
26082
  if (!result.success) return {
@@ -25225,7 +26136,7 @@ var AiassistantSkill = class AiassistantSkill extends ToolSkill {
25225
26136
  const result = AiassistantSkillFrontmatterSchema.safeParse(loaded.frontmatter);
25226
26137
  if (!result.success) {
25227
26138
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
25228
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
26139
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
25229
26140
  }
25230
26141
  return new AiassistantSkill({
25231
26142
  outputRoot: loaded.outputRoot,
@@ -25276,7 +26187,7 @@ var AmpSkill = class AmpSkill extends ToolSkill {
25276
26187
  relativeDirPath,
25277
26188
  dirName,
25278
26189
  mainFile: {
25279
- name: SKILL_FILE_NAME,
26190
+ name: SKILL_FILE_NAME$1,
25280
26191
  body,
25281
26192
  frontmatter: { ...frontmatter }
25282
26193
  },
@@ -25300,7 +26211,7 @@ var AmpSkill = class AmpSkill extends ToolSkill {
25300
26211
  validate() {
25301
26212
  if (!this.mainFile) return {
25302
26213
  success: false,
25303
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
26214
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
25304
26215
  };
25305
26216
  const result = AmpSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
25306
26217
  if (!result.success) return {
@@ -25360,7 +26271,7 @@ var AmpSkill = class AmpSkill extends ToolSkill {
25360
26271
  const result = AmpSkillFrontmatterSchema.safeParse(loaded.frontmatter);
25361
26272
  if (!result.success) {
25362
26273
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
25363
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
26274
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
25364
26275
  }
25365
26276
  return new AmpSkill({
25366
26277
  outputRoot: loaded.outputRoot,
@@ -25416,7 +26327,7 @@ var AntigravitySharedSkill = class extends ToolSkill {
25416
26327
  relativeDirPath,
25417
26328
  dirName,
25418
26329
  mainFile: {
25419
- name: SKILL_FILE_NAME,
26330
+ name: SKILL_FILE_NAME$1,
25420
26331
  body,
25421
26332
  frontmatter: { ...frontmatter }
25422
26333
  },
@@ -25449,7 +26360,7 @@ var AntigravitySharedSkill = class extends ToolSkill {
25449
26360
  validate() {
25450
26361
  if (this.mainFile === void 0) return {
25451
26362
  success: false,
25452
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
26363
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
25453
26364
  };
25454
26365
  const result = AntigravitySkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
25455
26366
  if (!result.success) return {
@@ -25509,7 +26420,7 @@ var AntigravitySharedSkill = class extends ToolSkill {
25509
26420
  const result = AntigravitySkillFrontmatterSchema.safeParse(loaded.frontmatter);
25510
26421
  if (!result.success) {
25511
26422
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
25512
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
26423
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
25513
26424
  }
25514
26425
  return new this({
25515
26426
  outputRoot: loaded.outputRoot,
@@ -25593,7 +26504,7 @@ var AugmentcodeSkill = class AugmentcodeSkill extends ToolSkill {
25593
26504
  relativeDirPath,
25594
26505
  dirName,
25595
26506
  mainFile: {
25596
- name: SKILL_FILE_NAME,
26507
+ name: SKILL_FILE_NAME$1,
25597
26508
  body,
25598
26509
  frontmatter: { ...frontmatter }
25599
26510
  },
@@ -25620,7 +26531,7 @@ var AugmentcodeSkill = class AugmentcodeSkill extends ToolSkill {
25620
26531
  validate() {
25621
26532
  if (this.mainFile === void 0) return {
25622
26533
  success: false,
25623
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
26534
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
25624
26535
  };
25625
26536
  const result = AugmentcodeSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
25626
26537
  if (!result.success) return {
@@ -25680,7 +26591,7 @@ var AugmentcodeSkill = class AugmentcodeSkill extends ToolSkill {
25680
26591
  const result = AugmentcodeSkillFrontmatterSchema.safeParse(loaded.frontmatter);
25681
26592
  if (!result.success) {
25682
26593
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
25683
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
26594
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
25684
26595
  }
25685
26596
  return new AugmentcodeSkill({
25686
26597
  outputRoot: loaded.outputRoot,
@@ -25821,7 +26732,7 @@ var ClaudecodeSkill = class ClaudecodeSkill extends ToolSkill {
25821
26732
  relativeDirPath,
25822
26733
  dirName,
25823
26734
  mainFile: {
25824
- name: SKILL_FILE_NAME,
26735
+ name: SKILL_FILE_NAME$1,
25825
26736
  body,
25826
26737
  frontmatter: { ...frontmatter }
25827
26738
  },
@@ -25848,7 +26759,7 @@ var ClaudecodeSkill = class ClaudecodeSkill extends ToolSkill {
25848
26759
  validate() {
25849
26760
  if (this.mainFile === void 0) return {
25850
26761
  success: false,
25851
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
26762
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
25852
26763
  };
25853
26764
  const result = ClaudecodeSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
25854
26765
  if (!result.success) return {
@@ -25936,7 +26847,7 @@ var ClaudecodeSkill = class ClaudecodeSkill extends ToolSkill {
25936
26847
  const result = ClaudecodeSkillFrontmatterSchema.safeParse(loaded.frontmatter);
25937
26848
  if (!result.success) {
25938
26849
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
25939
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
26850
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
25940
26851
  }
25941
26852
  return new ClaudecodeSkill({
25942
26853
  outputRoot: loaded.outputRoot,
@@ -25982,7 +26893,7 @@ var ClineSkill = class ClineSkill extends ToolSkill {
25982
26893
  relativeDirPath,
25983
26894
  dirName,
25984
26895
  mainFile: {
25985
- name: SKILL_FILE_NAME,
26896
+ name: SKILL_FILE_NAME$1,
25986
26897
  body,
25987
26898
  frontmatter: { ...frontmatter }
25988
26899
  },
@@ -26006,7 +26917,7 @@ var ClineSkill = class ClineSkill extends ToolSkill {
26006
26917
  validate() {
26007
26918
  if (!this.mainFile) return {
26008
26919
  success: false,
26009
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
26920
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
26010
26921
  };
26011
26922
  const result = ClineSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
26012
26923
  if (!result.success) return {
@@ -26070,10 +26981,10 @@ var ClineSkill = class ClineSkill extends ToolSkill {
26070
26981
  const result = ClineSkillFrontmatterSchema.safeParse(loaded.frontmatter);
26071
26982
  if (!result.success) {
26072
26983
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
26073
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
26984
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
26074
26985
  }
26075
26986
  if (result.data.name !== loaded.dirName) {
26076
- const skillFilePath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME);
26987
+ const skillFilePath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME$1);
26077
26988
  throw new Error(`Frontmatter name (${result.data.name}) must match directory name (${loaded.dirName}) in ${skillFilePath}`);
26078
26989
  }
26079
26990
  return new ClineSkill({
@@ -26187,7 +27098,7 @@ var CodexCliSkill = class CodexCliSkill extends ToolSkill {
26187
27098
  relativeDirPath,
26188
27099
  dirName,
26189
27100
  mainFile: {
26190
- name: SKILL_FILE_NAME,
27101
+ name: SKILL_FILE_NAME$1,
26191
27102
  body,
26192
27103
  frontmatter: { ...frontmatter }
26193
27104
  },
@@ -26211,7 +27122,7 @@ var CodexCliSkill = class CodexCliSkill extends ToolSkill {
26211
27122
  validate() {
26212
27123
  if (!this.mainFile) return {
26213
27124
  success: false,
26214
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
27125
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
26215
27126
  };
26216
27127
  const result = CodexCliSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
26217
27128
  if (!result.success) return {
@@ -26289,7 +27200,7 @@ var CodexCliSkill = class CodexCliSkill extends ToolSkill {
26289
27200
  const result = CodexCliSkillFrontmatterSchema.safeParse(loaded.frontmatter);
26290
27201
  if (!result.success) {
26291
27202
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
26292
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
27203
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
26293
27204
  }
26294
27205
  return new CodexCliSkill({
26295
27206
  outputRoot: loaded.outputRoot,
@@ -26341,7 +27252,7 @@ var CopilotSkill = class CopilotSkill extends ToolSkill {
26341
27252
  relativeDirPath,
26342
27253
  dirName,
26343
27254
  mainFile: {
26344
- name: SKILL_FILE_NAME,
27255
+ name: SKILL_FILE_NAME$1,
26345
27256
  body,
26346
27257
  frontmatter: { ...frontmatter }
26347
27258
  },
@@ -26366,7 +27277,7 @@ var CopilotSkill = class CopilotSkill extends ToolSkill {
26366
27277
  validate() {
26367
27278
  if (!this.mainFile) return {
26368
27279
  success: false,
26369
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
27280
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
26370
27281
  };
26371
27282
  const result = CopilotSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
26372
27283
  if (!result.success) return {
@@ -26433,7 +27344,7 @@ var CopilotSkill = class CopilotSkill extends ToolSkill {
26433
27344
  const result = CopilotSkillFrontmatterSchema.safeParse(loaded.frontmatter);
26434
27345
  if (!result.success) {
26435
27346
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
26436
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
27347
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
26437
27348
  }
26438
27349
  return new CopilotSkill({
26439
27350
  outputRoot: loaded.outputRoot,
@@ -26487,7 +27398,7 @@ var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
26487
27398
  relativeDirPath,
26488
27399
  dirName,
26489
27400
  mainFile: {
26490
- name: SKILL_FILE_NAME,
27401
+ name: SKILL_FILE_NAME$1,
26491
27402
  body,
26492
27403
  frontmatter: { ...frontmatter }
26493
27404
  },
@@ -26512,7 +27423,7 @@ var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
26512
27423
  validate() {
26513
27424
  if (!this.mainFile) return {
26514
27425
  success: false,
26515
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
27426
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
26516
27427
  };
26517
27428
  const result = CopilotcliSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
26518
27429
  if (!result.success) return {
@@ -26581,7 +27492,7 @@ var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
26581
27492
  const result = CopilotcliSkillFrontmatterSchema.safeParse(loaded.frontmatter);
26582
27493
  if (!result.success) {
26583
27494
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
26584
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
27495
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
26585
27496
  }
26586
27497
  return new CopilotcliSkill({
26587
27498
  outputRoot: loaded.outputRoot,
@@ -26631,7 +27542,7 @@ var CursorSkill = class CursorSkill extends ToolSkill {
26631
27542
  relativeDirPath,
26632
27543
  dirName,
26633
27544
  mainFile: {
26634
- name: SKILL_FILE_NAME,
27545
+ name: SKILL_FILE_NAME$1,
26635
27546
  body,
26636
27547
  frontmatter: { ...frontmatter }
26637
27548
  },
@@ -26655,7 +27566,7 @@ var CursorSkill = class CursorSkill extends ToolSkill {
26655
27566
  validate() {
26656
27567
  if (!this.mainFile) return {
26657
27568
  success: false,
26658
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
27569
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
26659
27570
  };
26660
27571
  const result = CursorSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
26661
27572
  if (!result.success) return {
@@ -26729,7 +27640,7 @@ var CursorSkill = class CursorSkill extends ToolSkill {
26729
27640
  const result = CursorSkillFrontmatterSchema.safeParse(loaded.frontmatter);
26730
27641
  if (!result.success) {
26731
27642
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
26732
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
27643
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
26733
27644
  }
26734
27645
  return new CursorSkill({
26735
27646
  outputRoot: loaded.outputRoot,
@@ -26776,7 +27687,7 @@ var DeepagentsSkill = class DeepagentsSkill extends ToolSkill {
26776
27687
  relativeDirPath,
26777
27688
  dirName,
26778
27689
  mainFile: {
26779
- name: SKILL_FILE_NAME,
27690
+ name: SKILL_FILE_NAME$1,
26780
27691
  body,
26781
27692
  frontmatter: { ...frontmatter }
26782
27693
  },
@@ -26800,7 +27711,7 @@ var DeepagentsSkill = class DeepagentsSkill extends ToolSkill {
26800
27711
  validate() {
26801
27712
  if (!this.mainFile) return {
26802
27713
  success: false,
26803
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
27714
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
26804
27715
  };
26805
27716
  const result = DeepagentsSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
26806
27717
  if (!result.success) return {
@@ -26876,7 +27787,7 @@ var DeepagentsSkill = class DeepagentsSkill extends ToolSkill {
26876
27787
  const result = DeepagentsSkillFrontmatterSchema.safeParse(loaded.frontmatter);
26877
27788
  if (!result.success) {
26878
27789
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
26879
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
27790
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
26880
27791
  }
26881
27792
  return new DeepagentsSkill({
26882
27793
  outputRoot: loaded.outputRoot,
@@ -26927,7 +27838,7 @@ var DevinSkill = class DevinSkill extends ToolSkill {
26927
27838
  relativeDirPath,
26928
27839
  dirName,
26929
27840
  mainFile: {
26930
- name: SKILL_FILE_NAME,
27841
+ name: SKILL_FILE_NAME$1,
26931
27842
  body,
26932
27843
  frontmatter: { ...frontmatter }
26933
27844
  },
@@ -26952,7 +27863,7 @@ var DevinSkill = class DevinSkill extends ToolSkill {
26952
27863
  validate() {
26953
27864
  if (!this.mainFile) return {
26954
27865
  success: false,
26955
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
27866
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
26956
27867
  };
26957
27868
  const result = DevinSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
26958
27869
  if (!result.success) return {
@@ -27004,6 +27915,18 @@ var DevinSkill = class DevinSkill extends ToolSkill {
27004
27915
  const targets = rulesyncSkill.getFrontmatter().targets;
27005
27916
  return targets.includes("*") || targets.includes("devin");
27006
27917
  }
27918
+ /**
27919
+ * Commands are emitted into this same skills tree as `<slug>/SKILL.md`
27920
+ * (see `DevinCommand`), so a directory matching a current rulesync command
27921
+ * slug is owned by the commands feature: it must not be imported as a
27922
+ * skill nor deleted as an orphan skill.
27923
+ */
27924
+ static async isDirOwned({ dirName, inputRoot }) {
27925
+ return !await rulesyncCommandSlugExists({
27926
+ inputRoot,
27927
+ dirName
27928
+ });
27929
+ }
27007
27930
  static async fromDir(params) {
27008
27931
  const loaded = await this.loadSkillDirContent({
27009
27932
  ...params,
@@ -27012,7 +27935,7 @@ var DevinSkill = class DevinSkill extends ToolSkill {
27012
27935
  const result = DevinSkillFrontmatterSchema.safeParse(loaded.frontmatter);
27013
27936
  if (!result.success) {
27014
27937
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
27015
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
27938
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
27016
27939
  }
27017
27940
  return new DevinSkill({
27018
27941
  outputRoot: loaded.outputRoot,
@@ -27064,7 +27987,7 @@ var FactorydroidSkill = class FactorydroidSkill extends ToolSkill {
27064
27987
  relativeDirPath,
27065
27988
  dirName,
27066
27989
  mainFile: {
27067
- name: SKILL_FILE_NAME,
27990
+ name: SKILL_FILE_NAME$1,
27068
27991
  body,
27069
27992
  frontmatter: { ...frontmatter }
27070
27993
  },
@@ -27088,7 +28011,7 @@ var FactorydroidSkill = class FactorydroidSkill extends ToolSkill {
27088
28011
  validate() {
27089
28012
  if (this.mainFile === void 0) return {
27090
28013
  success: false,
27091
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
28014
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
27092
28015
  };
27093
28016
  const result = FactorydroidSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
27094
28017
  if (!result.success) return {
@@ -27163,7 +28086,7 @@ var FactorydroidSkill = class FactorydroidSkill extends ToolSkill {
27163
28086
  const result = FactorydroidSkillFrontmatterSchema.safeParse(loaded.frontmatter);
27164
28087
  if (!result.success) {
27165
28088
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
27166
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
28089
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
27167
28090
  }
27168
28091
  return new FactorydroidSkill({
27169
28092
  outputRoot: loaded.outputRoot,
@@ -27210,7 +28133,7 @@ var GooseSkill = class GooseSkill extends ToolSkill {
27210
28133
  relativeDirPath,
27211
28134
  dirName,
27212
28135
  mainFile: {
27213
- name: SKILL_FILE_NAME,
28136
+ name: SKILL_FILE_NAME$1,
27214
28137
  body,
27215
28138
  frontmatter: { ...frontmatter }
27216
28139
  },
@@ -27235,7 +28158,7 @@ var GooseSkill = class GooseSkill extends ToolSkill {
27235
28158
  validate() {
27236
28159
  if (!this.mainFile) return {
27237
28160
  success: false,
27238
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
28161
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
27239
28162
  };
27240
28163
  const result = GooseSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
27241
28164
  if (!result.success) return {
@@ -27295,7 +28218,7 @@ var GooseSkill = class GooseSkill extends ToolSkill {
27295
28218
  const result = GooseSkillFrontmatterSchema.safeParse(loaded.frontmatter);
27296
28219
  if (!result.success) {
27297
28220
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
27298
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
28221
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
27299
28222
  }
27300
28223
  return new GooseSkill({
27301
28224
  outputRoot: loaded.outputRoot,
@@ -27348,7 +28271,7 @@ var GrokcliSkill = class GrokcliSkill extends ToolSkill {
27348
28271
  relativeDirPath,
27349
28272
  dirName,
27350
28273
  mainFile: {
27351
- name: SKILL_FILE_NAME,
28274
+ name: SKILL_FILE_NAME$1,
27352
28275
  body,
27353
28276
  frontmatter: { ...frontmatter }
27354
28277
  },
@@ -27372,7 +28295,7 @@ var GrokcliSkill = class GrokcliSkill extends ToolSkill {
27372
28295
  validate() {
27373
28296
  if (this.mainFile === void 0) return {
27374
28297
  success: false,
27375
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
28298
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
27376
28299
  };
27377
28300
  const result = GrokcliSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
27378
28301
  if (!result.success) return {
@@ -27432,7 +28355,7 @@ var GrokcliSkill = class GrokcliSkill extends ToolSkill {
27432
28355
  const result = GrokcliSkillFrontmatterSchema.safeParse(loaded.frontmatter);
27433
28356
  if (!result.success) {
27434
28357
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
27435
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
28358
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
27436
28359
  }
27437
28360
  return new GrokcliSkill({
27438
28361
  outputRoot: loaded.outputRoot,
@@ -27467,6 +28390,18 @@ var HermesagentSkill = class extends AgentsSkillsSkill {
27467
28390
  static getSettablePaths() {
27468
28391
  return { relativeDirPath: HERMESAGENT_SKILLS_DIR_PATH };
27469
28392
  }
28393
+ /**
28394
+ * Commands are emitted into this same skills tree as `<slug>/SKILL.md`
28395
+ * (see `HermesagentCommand`), so a directory matching a current rulesync
28396
+ * command slug is owned by the commands feature: it must not be imported
28397
+ * as a skill nor deleted as an orphan skill.
28398
+ */
28399
+ static async isDirOwned({ dirName, inputRoot }) {
28400
+ return !await rulesyncCommandSlugExists({
28401
+ inputRoot,
28402
+ dirName
28403
+ });
28404
+ }
27470
28405
  constructor(params) {
27471
28406
  super({
27472
28407
  ...params,
@@ -27491,7 +28426,7 @@ var JunieSkill = class JunieSkill extends ToolSkill {
27491
28426
  relativeDirPath,
27492
28427
  dirName,
27493
28428
  mainFile: {
27494
- name: SKILL_FILE_NAME,
28429
+ name: SKILL_FILE_NAME$1,
27495
28430
  body,
27496
28431
  frontmatter: { ...frontmatter }
27497
28432
  },
@@ -27515,7 +28450,7 @@ var JunieSkill = class JunieSkill extends ToolSkill {
27515
28450
  validate() {
27516
28451
  if (!this.mainFile) return {
27517
28452
  success: false,
27518
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
28453
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
27519
28454
  };
27520
28455
  const result = JunieSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
27521
28456
  if (!result.success) return {
@@ -27579,10 +28514,10 @@ var JunieSkill = class JunieSkill extends ToolSkill {
27579
28514
  const result = JunieSkillFrontmatterSchema.safeParse(loaded.frontmatter);
27580
28515
  if (!result.success) {
27581
28516
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
27582
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
28517
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
27583
28518
  }
27584
28519
  if (result.data.name !== loaded.dirName) {
27585
- const skillFilePath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME);
28520
+ const skillFilePath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME$1);
27586
28521
  throw new Error(`Frontmatter name (${result.data.name}) must match directory name (${loaded.dirName}) in ${skillFilePath}`);
27587
28522
  }
27588
28523
  return new JunieSkill({
@@ -27630,7 +28565,7 @@ var KiloSkill = class KiloSkill extends ToolSkill {
27630
28565
  relativeDirPath,
27631
28566
  dirName,
27632
28567
  mainFile: {
27633
- name: SKILL_FILE_NAME,
28568
+ name: SKILL_FILE_NAME$1,
27634
28569
  body,
27635
28570
  frontmatter: { ...frontmatter }
27636
28571
  },
@@ -27654,7 +28589,7 @@ var KiloSkill = class KiloSkill extends ToolSkill {
27654
28589
  validate() {
27655
28590
  if (this.mainFile === void 0) return {
27656
28591
  success: false,
27657
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
28592
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
27658
28593
  };
27659
28594
  const result = KiloSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
27660
28595
  if (!result.success) return {
@@ -27726,7 +28661,7 @@ var KiloSkill = class KiloSkill extends ToolSkill {
27726
28661
  const result = KiloSkillFrontmatterSchema.safeParse(loaded.frontmatter);
27727
28662
  if (!result.success) {
27728
28663
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
27729
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
28664
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
27730
28665
  }
27731
28666
  return new KiloSkill({
27732
28667
  outputRoot: loaded.outputRoot,
@@ -27772,7 +28707,7 @@ var KiroSkill = class KiroSkill extends ToolSkill {
27772
28707
  relativeDirPath,
27773
28708
  dirName,
27774
28709
  mainFile: {
27775
- name: SKILL_FILE_NAME,
28710
+ name: SKILL_FILE_NAME$1,
27776
28711
  body,
27777
28712
  frontmatter: { ...frontmatter }
27778
28713
  },
@@ -27796,7 +28731,7 @@ var KiroSkill = class KiroSkill extends ToolSkill {
27796
28731
  validate() {
27797
28732
  if (!this.mainFile) return {
27798
28733
  success: false,
27799
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
28734
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
27800
28735
  };
27801
28736
  const result = KiroSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
27802
28737
  if (!result.success) return {
@@ -27860,10 +28795,10 @@ var KiroSkill = class KiroSkill extends ToolSkill {
27860
28795
  const result = KiroSkillFrontmatterSchema.safeParse(loaded.frontmatter);
27861
28796
  if (!result.success) {
27862
28797
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
27863
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
28798
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
27864
28799
  }
27865
28800
  if (result.data.name !== loaded.dirName) {
27866
- const skillFilePath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME);
28801
+ const skillFilePath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME$1);
27867
28802
  throw new Error(`Frontmatter name (${result.data.name}) must match directory name (${loaded.dirName}) in ${skillFilePath}`);
27868
28803
  }
27869
28804
  return new KiroSkill({
@@ -27946,7 +28881,7 @@ var OpenCodeSkill = class OpenCodeSkill extends ToolSkill {
27946
28881
  relativeDirPath,
27947
28882
  dirName,
27948
28883
  mainFile: {
27949
- name: SKILL_FILE_NAME,
28884
+ name: SKILL_FILE_NAME$1,
27950
28885
  body,
27951
28886
  frontmatter: { ...frontmatter }
27952
28887
  },
@@ -27973,7 +28908,7 @@ var OpenCodeSkill = class OpenCodeSkill extends ToolSkill {
27973
28908
  validate() {
27974
28909
  if (this.mainFile === void 0) return {
27975
28910
  success: false,
27976
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
28911
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
27977
28912
  };
27978
28913
  const result = OpenCodeSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
27979
28914
  if (!result.success) return {
@@ -28052,7 +28987,7 @@ var OpenCodeSkill = class OpenCodeSkill extends ToolSkill {
28052
28987
  const result = OpenCodeSkillFrontmatterSchema.safeParse(loaded.frontmatter);
28053
28988
  if (!result.success) {
28054
28989
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
28055
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
28990
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
28056
28991
  }
28057
28992
  return new OpenCodeSkill({
28058
28993
  outputRoot: loaded.outputRoot,
@@ -28113,7 +29048,7 @@ var PiSkill = class PiSkill extends ToolSkill {
28113
29048
  relativeDirPath: resolvedDirPath,
28114
29049
  dirName,
28115
29050
  mainFile: {
28116
- name: SKILL_FILE_NAME,
29051
+ name: SKILL_FILE_NAME$1,
28117
29052
  body,
28118
29053
  frontmatter: { ...frontmatter }
28119
29054
  },
@@ -28138,7 +29073,7 @@ var PiSkill = class PiSkill extends ToolSkill {
28138
29073
  validate() {
28139
29074
  if (!this.mainFile) return {
28140
29075
  success: false,
28141
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
29076
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
28142
29077
  };
28143
29078
  const result = PiSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
28144
29079
  if (!result.success) return {
@@ -28213,7 +29148,7 @@ var PiSkill = class PiSkill extends ToolSkill {
28213
29148
  const result = PiSkillFrontmatterSchema.safeParse(loaded.frontmatter);
28214
29149
  if (!result.success) {
28215
29150
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
28216
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
29151
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
28217
29152
  }
28218
29153
  return new PiSkill({
28219
29154
  outputRoot: loaded.outputRoot,
@@ -28266,7 +29201,7 @@ var QwencodeSkill = class QwencodeSkill extends ToolSkill {
28266
29201
  relativeDirPath,
28267
29202
  dirName,
28268
29203
  mainFile: {
28269
- name: SKILL_FILE_NAME,
29204
+ name: SKILL_FILE_NAME$1,
28270
29205
  body,
28271
29206
  frontmatter: { ...frontmatter }
28272
29207
  },
@@ -28290,7 +29225,7 @@ var QwencodeSkill = class QwencodeSkill extends ToolSkill {
28290
29225
  validate() {
28291
29226
  if (this.mainFile === void 0) return {
28292
29227
  success: false,
28293
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
29228
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
28294
29229
  };
28295
29230
  const result = QwencodeSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
28296
29231
  if (!result.success) return {
@@ -28370,7 +29305,7 @@ var QwencodeSkill = class QwencodeSkill extends ToolSkill {
28370
29305
  const result = QwencodeSkillFrontmatterSchema.safeParse(loaded.frontmatter);
28371
29306
  if (!result.success) {
28372
29307
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
28373
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
29308
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
28374
29309
  }
28375
29310
  return new QwencodeSkill({
28376
29311
  outputRoot: loaded.outputRoot,
@@ -28420,7 +29355,7 @@ var ReasonixSkill = class ReasonixSkill extends ToolSkill {
28420
29355
  relativeDirPath,
28421
29356
  dirName,
28422
29357
  mainFile: {
28423
- name: SKILL_FILE_NAME,
29358
+ name: SKILL_FILE_NAME$1,
28424
29359
  body,
28425
29360
  frontmatter: { ...frontmatter }
28426
29361
  },
@@ -28444,7 +29379,7 @@ var ReasonixSkill = class ReasonixSkill extends ToolSkill {
28444
29379
  validate() {
28445
29380
  if (this.mainFile === void 0) return {
28446
29381
  success: false,
28447
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
29382
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
28448
29383
  };
28449
29384
  const result = ReasonixSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
28450
29385
  if (!result.success) return {
@@ -28504,7 +29439,7 @@ var ReasonixSkill = class ReasonixSkill extends ToolSkill {
28504
29439
  const result = ReasonixSkillFrontmatterSchema.safeParse(loaded.frontmatter);
28505
29440
  if (!result.success) {
28506
29441
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
28507
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
29442
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
28508
29443
  }
28509
29444
  return new ReasonixSkill({
28510
29445
  outputRoot: loaded.outputRoot,
@@ -28527,7 +29462,7 @@ var ReasonixSkill = class ReasonixSkill extends ToolSkill {
28527
29462
  * skills-feature ownership, matching the previous behavior for such dirs.
28528
29463
  */
28529
29464
  static async isDirOwned({ outputRoot, relativeDirPath, dirName }) {
28530
- const skillFilePath = join(outputRoot, relativeDirPath, dirName, SKILL_FILE_NAME);
29465
+ const skillFilePath = join(outputRoot, relativeDirPath, dirName, SKILL_FILE_NAME$1);
28531
29466
  try {
28532
29467
  const { frontmatter } = parseFrontmatter(await readFileContent(skillFilePath), skillFilePath);
28533
29468
  return frontmatter["runAs"] !== REASONIX_SUBAGENT_RUN_AS;
@@ -28577,7 +29512,7 @@ var ReplitSkill = class ReplitSkill extends ToolSkill {
28577
29512
  relativeDirPath,
28578
29513
  dirName,
28579
29514
  mainFile: {
28580
- name: SKILL_FILE_NAME,
29515
+ name: SKILL_FILE_NAME$1,
28581
29516
  body,
28582
29517
  frontmatter: { ...frontmatter }
28583
29518
  },
@@ -28601,7 +29536,7 @@ var ReplitSkill = class ReplitSkill extends ToolSkill {
28601
29536
  validate() {
28602
29537
  if (!this.mainFile) return {
28603
29538
  success: false,
28604
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
29539
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
28605
29540
  };
28606
29541
  const result = ReplitSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
28607
29542
  if (!result.success) return {
@@ -28669,7 +29604,7 @@ var ReplitSkill = class ReplitSkill extends ToolSkill {
28669
29604
  const result = ReplitSkillFrontmatterSchema.safeParse(loaded.frontmatter);
28670
29605
  if (!result.success) {
28671
29606
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
28672
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
29607
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
28673
29608
  }
28674
29609
  return new ReplitSkill({
28675
29610
  outputRoot: loaded.outputRoot,
@@ -28716,7 +29651,7 @@ var RooSkill = class RooSkill extends ToolSkill {
28716
29651
  relativeDirPath,
28717
29652
  dirName,
28718
29653
  mainFile: {
28719
- name: SKILL_FILE_NAME,
29654
+ name: SKILL_FILE_NAME$1,
28720
29655
  body,
28721
29656
  frontmatter: { ...frontmatter }
28722
29657
  },
@@ -28740,7 +29675,7 @@ var RooSkill = class RooSkill extends ToolSkill {
28740
29675
  validate() {
28741
29676
  if (!this.mainFile) return {
28742
29677
  success: false,
28743
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
29678
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
28744
29679
  };
28745
29680
  const result = RooSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
28746
29681
  if (!result.success) return {
@@ -28804,10 +29739,10 @@ var RooSkill = class RooSkill extends ToolSkill {
28804
29739
  const result = RooSkillFrontmatterSchema.safeParse(loaded.frontmatter);
28805
29740
  if (!result.success) {
28806
29741
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
28807
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
29742
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
28808
29743
  }
28809
29744
  if (result.data.name !== loaded.dirName) {
28810
- const skillFilePath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME);
29745
+ const skillFilePath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME$1);
28811
29746
  throw new Error(`Frontmatter name (${result.data.name}) must match directory name (${loaded.dirName}) in ${skillFilePath}`);
28812
29747
  }
28813
29748
  return new RooSkill({
@@ -29024,7 +29959,7 @@ var VibeSkill = class VibeSkill extends ToolSkill {
29024
29959
  relativeDirPath,
29025
29960
  dirName,
29026
29961
  mainFile: {
29027
- name: SKILL_FILE_NAME,
29962
+ name: SKILL_FILE_NAME$1,
29028
29963
  body,
29029
29964
  frontmatter: { ...frontmatter }
29030
29965
  },
@@ -29051,7 +29986,7 @@ var VibeSkill = class VibeSkill extends ToolSkill {
29051
29986
  validate() {
29052
29987
  if (!this.mainFile) return {
29053
29988
  success: false,
29054
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
29989
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
29055
29990
  };
29056
29991
  const result = VibeSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
29057
29992
  if (!result.success) return {
@@ -29115,7 +30050,7 @@ var VibeSkill = class VibeSkill extends ToolSkill {
29115
30050
  const result = VibeSkillFrontmatterSchema.safeParse(loaded.frontmatter);
29116
30051
  if (!result.success) {
29117
30052
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
29118
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
30053
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
29119
30054
  }
29120
30055
  return new VibeSkill({
29121
30056
  outputRoot: loaded.outputRoot,
@@ -29171,7 +30106,7 @@ var WarpSkill = class WarpSkill extends ToolSkill {
29171
30106
  relativeDirPath,
29172
30107
  dirName,
29173
30108
  mainFile: {
29174
- name: SKILL_FILE_NAME,
30109
+ name: SKILL_FILE_NAME$1,
29175
30110
  body,
29176
30111
  frontmatter: { ...frontmatter }
29177
30112
  },
@@ -29195,7 +30130,7 @@ var WarpSkill = class WarpSkill extends ToolSkill {
29195
30130
  validate() {
29196
30131
  if (this.mainFile === void 0) return {
29197
30132
  success: false,
29198
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
30133
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
29199
30134
  };
29200
30135
  const result = WarpSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
29201
30136
  if (!result.success) return {
@@ -29255,7 +30190,7 @@ var WarpSkill = class WarpSkill extends ToolSkill {
29255
30190
  const result = WarpSkillFrontmatterSchema.safeParse(loaded.frontmatter);
29256
30191
  if (!result.success) {
29257
30192
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
29258
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
30193
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
29259
30194
  }
29260
30195
  return new WarpSkill({
29261
30196
  outputRoot: loaded.outputRoot,
@@ -29303,7 +30238,7 @@ var ZedSkill = class ZedSkill extends ToolSkill {
29303
30238
  relativeDirPath,
29304
30239
  dirName,
29305
30240
  mainFile: {
29306
- name: SKILL_FILE_NAME,
30241
+ name: SKILL_FILE_NAME$1,
29307
30242
  body,
29308
30243
  frontmatter: { ...frontmatter }
29309
30244
  },
@@ -29327,7 +30262,7 @@ var ZedSkill = class ZedSkill extends ToolSkill {
29327
30262
  validate() {
29328
30263
  if (!this.mainFile) return {
29329
30264
  success: false,
29330
- error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
30265
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME$1} file does not exist`)
29331
30266
  };
29332
30267
  const result = ZedSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
29333
30268
  if (!result.success) return {
@@ -29396,7 +30331,7 @@ var ZedSkill = class ZedSkill extends ToolSkill {
29396
30331
  const result = ZedSkillFrontmatterSchema.safeParse(loaded.frontmatter);
29397
30332
  if (!result.success) {
29398
30333
  const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
29399
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
30334
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME$1)}: ${formatError(result.error)}`);
29400
30335
  }
29401
30336
  return new ZedSkill({
29402
30337
  outputRoot: loaded.outputRoot,
@@ -29836,7 +30771,8 @@ var SkillsProcessor = class extends DirFeatureProcessor {
29836
30771
  if (factory.class.isDirOwned && !await factory.class.isDirOwned({
29837
30772
  outputRoot: this.outputRoot,
29838
30773
  relativeDirPath: root,
29839
- dirName
30774
+ dirName,
30775
+ inputRoot: this.inputRoot
29840
30776
  })) continue;
29841
30777
  seenDirNames.add(dirName);
29842
30778
  loadEntries.push({
@@ -29867,7 +30803,8 @@ var SkillsProcessor = class extends DirFeatureProcessor {
29867
30803
  if (factory.class.isDirOwned && !await factory.class.isDirOwned({
29868
30804
  outputRoot: this.outputRoot,
29869
30805
  relativeDirPath: root,
29870
- dirName
30806
+ dirName,
30807
+ inputRoot: this.inputRoot
29871
30808
  })) continue;
29872
30809
  const toolSkill = factory.class.forDeletion({
29873
30810
  outputRoot: this.outputRoot,
@@ -30970,7 +31907,6 @@ var CodexCliSubagent = class CodexCliSubagent extends ToolSubagent {
30970
31907
  };
30971
31908
  //#endregion
30972
31909
  //#region src/features/subagents/copilot-subagent.ts
30973
- const REQUIRED_TOOL = "agent/runSubagent";
30974
31910
  const CopilotSubagentFrontmatterSchema = z.looseObject({
30975
31911
  name: z.string(),
30976
31912
  description: z.optional(z.string()),
@@ -30980,10 +31916,6 @@ const normalizeTools = (tools) => {
30980
31916
  if (!tools) return [];
30981
31917
  return Array.isArray(tools) ? tools : [tools];
30982
31918
  };
30983
- const ensureRequiredTool = (tools) => {
30984
- const mergedTools = /* @__PURE__ */ new Set([REQUIRED_TOOL, ...tools]);
30985
- return Array.from(mergedTools);
30986
- };
30987
31919
  const toCopilotAgentFilePath = (relativeFilePath) => {
30988
31920
  if (relativeFilePath.endsWith(".agent.md")) return relativeFilePath;
30989
31921
  if (relativeFilePath.endsWith(".md")) return relativeFilePath.replace(/\.md$/, ".agent.md");
@@ -31037,14 +31969,14 @@ var CopilotSubagent = class CopilotSubagent extends ToolSubagent {
31037
31969
  static fromRulesyncSubagent({ outputRoot = process.cwd(), rulesyncSubagent, validate = true, global = false }) {
31038
31970
  const rulesyncFrontmatter = rulesyncSubagent.getFrontmatter();
31039
31971
  const copilotSection = rulesyncFrontmatter.copilot ?? {};
31972
+ const { tools: _rawTools, ...copilotRest } = copilotSection;
31040
31973
  const toolsField = copilotSection.tools;
31041
31974
  const userTools = normalizeTools(Array.isArray(toolsField) || typeof toolsField === "string" ? toolsField : void 0);
31042
- const mergedTools = ensureRequiredTool(userTools);
31043
31975
  const copilotFrontmatter = {
31044
31976
  name: rulesyncFrontmatter.name,
31045
31977
  description: rulesyncFrontmatter.description,
31046
- ...copilotSection,
31047
- ...mergedTools.length > 0 && { tools: mergedTools }
31978
+ ...copilotRest,
31979
+ ...userTools.length > 0 && { tools: userTools }
31048
31980
  };
31049
31981
  const body = rulesyncSubagent.getBody();
31050
31982
  const fileContent = stringifyFrontmatter(body, copilotFrontmatter);
@@ -33268,7 +34200,7 @@ var ReasonixSubagent = class ReasonixSubagent extends ToolSubagent {
33268
34200
  frontmatter: reasonixFrontmatter,
33269
34201
  body,
33270
34202
  relativeDirPath: paths.relativeDirPath,
33271
- relativeFilePath: join(subagentName, SKILL_FILE_NAME),
34203
+ relativeFilePath: join(subagentName, SKILL_FILE_NAME$1),
33272
34204
  fileContent,
33273
34205
  validate,
33274
34206
  global
@@ -34274,6 +35206,7 @@ const RulesyncRuleFrontmatterSchema = z.object({
34274
35206
  name: z.optional(z.string()),
34275
35207
  description: z.optional(z.string())
34276
35208
  })),
35209
+ pi: z.optional(z.looseObject({ systemPrompt: z.optional(z.enum(["append"])) })),
34277
35210
  takt: z.optional(z.looseObject({
34278
35211
  name: z.optional(z.string()),
34279
35212
  extends: z.optional(z.string()),
@@ -34437,6 +35370,16 @@ var ToolRule = class extends ToolFile {
34437
35370
  isRoot() {
34438
35371
  return this.root;
34439
35372
  }
35373
+ /**
35374
+ * Whether this rule must be left out of the root rule's reference/MCP
35375
+ * instruction listings even though it is a non-root survivor. Used by files
35376
+ * the tool loads through its own mechanism (e.g. Pi's `APPEND_SYSTEM.md`,
35377
+ * which Pi appends to the system prompt itself — referencing it from
35378
+ * `AGENTS.md` would double-load the content).
35379
+ */
35380
+ isExcludedFromRootReferences() {
35381
+ return false;
35382
+ }
34440
35383
  getDescription() {
34441
35384
  return this.description;
34442
35385
  }
@@ -36783,6 +37726,15 @@ var FactorydroidRule = class FactorydroidRule extends ToolRule {
36783
37726
  * is `undefined`). This mirrors the grokcli, warp, and deepagents targets.
36784
37727
  *
36785
37728
  * Goose uses plain markdown files (.goosehints) without frontmatter.
37729
+ *
37730
+ * Global scope emits only `~/.config/goose/.goosehints`. Goose v1.41.0 (PR
37731
+ * block/goose#9736) additionally loads the vendor-neutral
37732
+ * `~/.agents/AGENTS.md` alongside the config-dir hints, but rulesync
37733
+ * deliberately does not emit that shared path from the goose target: the
37734
+ * config-dir hints remain fully loaded (no capability loss), and the
37735
+ * cross-tool `~/.agents/AGENTS.md` is already written by targets that own it
37736
+ * (e.g. cline in global mode) — a second writer would need cross-target
37737
+ * shared-file coordination for zero gain (decision recorded in issue #2207).
36786
37738
  */
36787
37739
  var GooseRule = class GooseRule extends ToolRule {
36788
37740
  static getSettablePaths({ global } = {}) {
@@ -36984,14 +37936,21 @@ var HermesagentRule = class HermesagentRule extends ToolRule {
36984
37936
  /**
36985
37937
  * Rule generator for JetBrains Junie AI coding agent
36986
37938
  *
36987
- * Generates `.junie/AGENTS.md` files based on rulesync rule content. `.junie/AGENTS.md`
36988
- * is the preferred guideline file in current Junie; the legacy `.junie/guidelines.md`
36989
- * is still read by Junie and is accepted as an import fallback, but generation always
36990
- * targets `.junie/AGENTS.md`. Junie uses plain markdown without frontmatter requirements.
36991
- *
36992
- * Global (user) scope writes a single `~/.junie/AGENTS.md` file. Junie merges these
36993
- * user-scope guidelines with the project `.junie/AGENTS.md` (project takes priority on
36994
- * conflicts); memory files (`.junie/memories/`) remain project-scoped only.
37939
+ * Generates `.junie/AGENTS.md` files based on rulesync rule content. Junie CLI
37940
+ * resolves project guidelines **first-match-wins**: `.junie/AGENTS.md` → root
37941
+ * `AGENTS.md` legacy `.junie/guidelines.md` / `.junie/guidelines/`. Only the
37942
+ * first match is loaded, it documents no `@`-reference or file-inclusion
37943
+ * mechanism, and no `.junie/memories/` read path exists — so non-root rules
37944
+ * are folded into the single root `.junie/AGENTS.md` by the RulesProcessor
37945
+ * (`nonRoot` is `undefined`, mirroring the grokcli / warp / deepagents
37946
+ * targets; decision recorded in issue #2211). The legacy
37947
+ * `.junie/guidelines.md` is still accepted as an import fallback, but
37948
+ * generation always targets `.junie/AGENTS.md`. Junie uses plain markdown
37949
+ * without frontmatter requirements.
37950
+ *
37951
+ * Global (user) scope writes a single `~/.junie/AGENTS.md` file. Junie merges
37952
+ * these user-scope guidelines with the project guidelines (both are included
37953
+ * and marked clearly).
36995
37954
  *
36996
37955
  * @see https://junie.jetbrains.com/docs/junie-ide-plugin.html
36997
37956
  * @see https://junie.jetbrains.com/docs/guidelines-and-memory.html
@@ -37010,15 +37969,12 @@ var JunieRule = class JunieRule extends ToolRule {
37010
37969
  alternativeRoots: [{
37011
37970
  relativeDirPath: buildToolPath(JUNIE_DIR, ".", excludeToolDir),
37012
37971
  relativeFilePath: JUNIE_LEGACY_RULE_FILE_NAME
37013
- }],
37014
- nonRoot: { relativeDirPath: buildToolPath(JUNIE_DIR, "memories", excludeToolDir) }
37972
+ }]
37015
37973
  };
37016
37974
  }
37017
37975
  /**
37018
37976
  * Determines whether a given relative file path refers to a root guideline file.
37019
37977
  * The preferred file is `AGENTS.md`; the legacy `guidelines.md` is still accepted.
37020
- * Memory files live under `.junie/memories/` and are passed in as bare filenames
37021
- * (e.g. `memo.md`), so a top-level `AGENTS.md`/`guidelines.md` is the root entry.
37022
37978
  */
37023
37979
  static isRootRelativeFilePath(relativeFilePath) {
37024
37980
  return relativeFilePath === "AGENTS.md" || relativeFilePath === "guidelines.md";
@@ -37037,10 +37993,7 @@ var JunieRule = class JunieRule extends ToolRule {
37037
37993
  root: true
37038
37994
  });
37039
37995
  }
37040
- const isRoot = JunieRule.isRootRelativeFilePath(relativeFilePath);
37041
- const settablePaths = this.getSettablePaths();
37042
- if (!settablePaths.nonRoot) throw new Error("JunieRule project settable paths must include a nonRoot path");
37043
- const relativeDirPath = isRoot ? settablePaths.root.relativeDirPath : settablePaths.nonRoot.relativeDirPath;
37996
+ const relativeDirPath = this.getSettablePaths().root.relativeDirPath;
37044
37997
  const fileContent = await readFileContent(join(outputRoot, join(relativeDirPath, relativeFilePath)));
37045
37998
  return new JunieRule({
37046
37999
  outputRoot,
@@ -37048,7 +38001,7 @@ var JunieRule = class JunieRule extends ToolRule {
37048
38001
  relativeFilePath,
37049
38002
  fileContent,
37050
38003
  validate,
37051
- root: isRoot
38004
+ root: JunieRule.isRootRelativeFilePath(relativeFilePath)
37052
38005
  });
37053
38006
  }
37054
38007
  static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
@@ -37063,13 +38016,15 @@ var JunieRule = class JunieRule extends ToolRule {
37063
38016
  rootPath: paths.root
37064
38017
  }));
37065
38018
  }
37066
- return new JunieRule(this.buildToolRuleParamsDefault({
38019
+ const { root } = this.getSettablePaths();
38020
+ return new JunieRule({
37067
38021
  outputRoot,
37068
- rulesyncRule,
38022
+ relativeDirPath: root.relativeDirPath,
38023
+ relativeFilePath: root.relativeFilePath,
38024
+ fileContent: rulesyncRule.getBody(),
37069
38025
  validate,
37070
- rootPath: this.getSettablePaths().root,
37071
- nonRootPath: this.getSettablePaths().nonRoot
37072
- }));
38026
+ root: rulesyncRule.getFrontmatter().root ?? false
38027
+ });
37073
38028
  }
37074
38029
  toRulesyncRule() {
37075
38030
  return this.toRulesyncRuleDefault();
@@ -37472,31 +38427,68 @@ var OpenCodeRule = class OpenCodeRule extends ToolRule {
37472
38427
  * RulesProcessor (there is no separate non-root output location — `nonRoot` is
37473
38428
  * `undefined`). This mirrors the codexcli, grokcli, warp, and deepagents targets.
37474
38429
  *
37475
- * Pi also loads two system-prompt instruction files that rulesync does NOT emit:
38430
+ * Pi also loads two system-prompt instruction files. `.pi/APPEND_SYSTEM.md`
38431
+ * (global `~/.pi/agent/APPEND_SYSTEM.md`) *appends* to the default system prompt,
38432
+ * and rulesync emits it from any rule that opts in via the `pi.systemPrompt:
38433
+ * append` frontmatter block: those bodies are routed here instead of being folded
38434
+ * into `AGENTS.md`, and multiple opted-in rules concatenate in source order.
37476
38435
  * `.pi/SYSTEM.md` (global `~/.pi/agent/SYSTEM.md`) *replaces* the default system
37477
- * prompt entirely, and `.pi/APPEND_SYSTEM.md` (global
37478
- * `~/.pi/agent/APPEND_SYSTEM.md`) *appends* to it. rulesync's rules model only
37479
- * routes a designated `root` rule to a single context file and has no convention
37480
- * for marking a rule as "replace" vs "append" the system prompt, so these
37481
- * surfaces are documented in docs/reference/file-formats.md and left to be
37482
- * authored by hand rather than mapped to a speculative new frontmatter flag.
38436
+ * prompt entirely which silently disables Pi's built-in tool instructions — so
38437
+ * rulesync deliberately never emits it and leaves it to be authored by hand.
38438
+ * See docs/reference/file-formats.md.
37483
38439
  */
37484
38440
  var PiRule = class PiRule extends ToolRule {
37485
- constructor({ fileContent, root, ...rest }) {
38441
+ appendSystemPrompt;
38442
+ constructor({ fileContent, root, appendSystemPrompt = false, ...rest }) {
37486
38443
  super({
37487
38444
  ...rest,
37488
38445
  fileContent,
37489
38446
  root: root ?? false
37490
38447
  });
38448
+ this.appendSystemPrompt = appendSystemPrompt;
37491
38449
  }
37492
38450
  static getSettablePaths({ global = false, excludeToolDir } = {}) {
37493
- return { root: {
37494
- relativeDirPath: global ? buildToolPath(".pi", "agent", excludeToolDir) : ".",
37495
- relativeFilePath: PI_RULE_FILE_NAME
37496
- } };
38451
+ return {
38452
+ root: {
38453
+ relativeDirPath: global ? buildToolPath(".pi", "agent", excludeToolDir) : ".",
38454
+ relativeFilePath: PI_RULE_FILE_NAME
38455
+ },
38456
+ appendSystemPrompt: {
38457
+ relativeDirPath: global ? buildToolPath(".pi", "agent", excludeToolDir) : ".pi",
38458
+ relativeFilePath: PI_APPEND_SYSTEM_FILE_NAME
38459
+ }
38460
+ };
37497
38461
  }
37498
- static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, validate = true, global = false }) {
37499
- const { root } = this.getSettablePaths({ global });
38462
+ /**
38463
+ * Extra fixed files this tool manages beyond the root rule. The
38464
+ * RulesProcessor enumerates these for import and deletion so a stale
38465
+ * `APPEND_SYSTEM.md` is cleaned up once no rule opts in anymore.
38466
+ */
38467
+ static getExtraFixedFiles({ global = false } = {}) {
38468
+ return [this.getSettablePaths({ global }).appendSystemPrompt];
38469
+ }
38470
+ /**
38471
+ * Pi appends `APPEND_SYSTEM.md` to the system prompt itself, so listing it in
38472
+ * the root rule's reference section (toon/explicit discovery modes) would
38473
+ * double-load the content.
38474
+ */
38475
+ isExcludedFromRootReferences() {
38476
+ return this.appendSystemPrompt;
38477
+ }
38478
+ static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
38479
+ const { root, appendSystemPrompt } = this.getSettablePaths({ global });
38480
+ if (relativeFilePath === "APPEND_SYSTEM.md") {
38481
+ const fileContent = await readFileContent(join(outputRoot, join(appendSystemPrompt.relativeDirPath, appendSystemPrompt.relativeFilePath)));
38482
+ return new PiRule({
38483
+ outputRoot,
38484
+ relativeDirPath: appendSystemPrompt.relativeDirPath,
38485
+ relativeFilePath: appendSystemPrompt.relativeFilePath,
38486
+ fileContent,
38487
+ validate,
38488
+ root: false,
38489
+ appendSystemPrompt: true
38490
+ });
38491
+ }
37500
38492
  const fileContent = await readFileContent(join(outputRoot, join(root.relativeDirPath, root.relativeFilePath)));
37501
38493
  return new PiRule({
37502
38494
  outputRoot,
@@ -37508,8 +38500,18 @@ var PiRule = class PiRule extends ToolRule {
37508
38500
  });
37509
38501
  }
37510
38502
  static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
37511
- const { root } = this.getSettablePaths({ global });
37512
- const isRoot = rulesyncRule.getFrontmatter().root ?? false;
38503
+ const { root, appendSystemPrompt } = this.getSettablePaths({ global });
38504
+ const frontmatter = rulesyncRule.getFrontmatter();
38505
+ if (!frontmatter.root && frontmatter.pi?.systemPrompt === "append") return new PiRule({
38506
+ outputRoot,
38507
+ relativeDirPath: appendSystemPrompt.relativeDirPath,
38508
+ relativeFilePath: appendSystemPrompt.relativeFilePath,
38509
+ fileContent: rulesyncRule.getBody(),
38510
+ validate,
38511
+ root: false,
38512
+ appendSystemPrompt: true
38513
+ });
38514
+ const isRoot = frontmatter.root ?? false;
37513
38515
  return new PiRule({
37514
38516
  outputRoot,
37515
38517
  relativeDirPath: root.relativeDirPath,
@@ -37520,6 +38522,17 @@ var PiRule = class PiRule extends ToolRule {
37520
38522
  });
37521
38523
  }
37522
38524
  toRulesyncRule() {
38525
+ if (this.appendSystemPrompt) return new RulesyncRule({
38526
+ outputRoot: process.cwd(),
38527
+ relativeDirPath: RULESYNC_RULES_RELATIVE_DIR_PATH,
38528
+ relativeFilePath: PI_APPEND_SYSTEM_FILE_NAME,
38529
+ frontmatter: {
38530
+ root: false,
38531
+ targets: ["pi"],
38532
+ pi: { systemPrompt: "append" }
38533
+ },
38534
+ body: this.getFileContent()
38535
+ });
37523
38536
  return this.toRulesyncRuleDefault();
37524
38537
  }
37525
38538
  validate() {
@@ -37530,6 +38543,15 @@ var PiRule = class PiRule extends ToolRule {
37530
38543
  }
37531
38544
  static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
37532
38545
  const { root } = this.getSettablePaths({ global });
38546
+ if (relativeFilePath === "APPEND_SYSTEM.md") return new PiRule({
38547
+ outputRoot,
38548
+ relativeDirPath,
38549
+ relativeFilePath,
38550
+ fileContent: "",
38551
+ validate: false,
38552
+ root: false,
38553
+ appendSystemPrompt: true
38554
+ });
37533
38555
  const isRoot = relativeFilePath === "AGENTS.md" && (relativeDirPath === "." || relativeDirPath === root.relativeDirPath);
37534
38556
  return new PiRule({
37535
38557
  outputRoot,
@@ -38689,7 +39711,8 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
38689
39711
  meta: {
38690
39712
  extension: "md",
38691
39713
  supportsGlobal: true,
38692
- ruleDiscoveryMode: "toon"
39714
+ ruleDiscoveryMode: "auto",
39715
+ foldsNonRootIntoRoot: true
38693
39716
  }
38694
39717
  }],
38695
39718
  ["kilo", {
@@ -38955,7 +39978,7 @@ var RulesProcessor = class extends FeatureProcessor {
38955
39978
  */
38956
39979
  async buildMcpInstructionFiles({ toolRules, meta }) {
38957
39980
  if (!meta.mcpInstructionsRegistrar || this.global) return [];
38958
- const instructionPaths = toolRules.filter((rule) => !rule.isRoot()).map((rule) => toPosixPath(join(rule.getRelativeDirPath(), rule.getRelativeFilePath())));
39981
+ const instructionPaths = toolRules.filter((rule) => !rule.isRoot() && !rule.isExcludedFromRootReferences()).map((rule) => toPosixPath(join(rule.getRelativeDirPath(), rule.getRelativeFilePath())));
38959
39982
  if (instructionPaths.length === 0) return [];
38960
39983
  return [await meta.mcpInstructionsRegistrar.fromInstructions({
38961
39984
  outputRoot: this.outputRoot,
@@ -38987,7 +40010,7 @@ var RulesProcessor = class extends FeatureProcessor {
38987
40010
  const toolRelativeDirPath = skillClass.getSettablePaths({ global: this.global }).relativeDirPath;
38988
40011
  return this.skills.filter((skill) => skillClass.isTargetedByRulesyncSkill(skill)).map((skill) => {
38989
40012
  const frontmatter = skill.getFrontmatter();
38990
- const relativePath = join(toolRelativeDirPath, skill.getDirName(), SKILL_FILE_NAME);
40013
+ const relativePath = join(toolRelativeDirPath, skill.getDirName(), SKILL_FILE_NAME$1);
38991
40014
  return {
38992
40015
  name: frontmatter.name,
38993
40016
  description: frontmatter.description,
@@ -39011,11 +40034,25 @@ var RulesProcessor = class extends FeatureProcessor {
39011
40034
  */
39012
40035
  foldNonRootRulesIntoRootRule(toolRules) {
39013
40036
  if (toolRules.length <= 1) return;
39014
- const target = toolRules.find((rule) => rule.isRoot()) ?? toolRules[0];
39015
- if (!target) return;
39016
- const mergedContent = [target, ...toolRules.filter((rule) => rule !== target)].map((rule) => rule.getFileContent().trim()).filter((content) => content.length > 0).join("\n\n");
39017
- target.setFileContent(mergedContent);
39018
- for (let i = toolRules.length - 1; i >= 0; i--) if (toolRules[i] !== target) toolRules.splice(i, 1);
40037
+ const groups = /* @__PURE__ */ new Map();
40038
+ for (const rule of toolRules) {
40039
+ const path = join(rule.getRelativeDirPath(), rule.getRelativeFilePath());
40040
+ const group = groups.get(path);
40041
+ if (group) group.push(rule);
40042
+ else groups.set(path, [rule]);
40043
+ }
40044
+ const survivors = /* @__PURE__ */ new Set();
40045
+ for (const group of groups.values()) {
40046
+ const target = group.find((rule) => rule.isRoot()) ?? group[0];
40047
+ if (!target) continue;
40048
+ const mergedContent = [target, ...group.filter((rule) => rule !== target)].map((rule) => rule.getFileContent().trim()).filter((content) => content.length > 0).join("\n\n");
40049
+ target.setFileContent(mergedContent);
40050
+ survivors.add(target);
40051
+ }
40052
+ for (let i = toolRules.length - 1; i >= 0; i--) {
40053
+ const rule = toolRules[i];
40054
+ if (rule && !survivors.has(rule)) toolRules.splice(i, 1);
40055
+ }
39019
40056
  }
39020
40057
  /**
39021
40058
  * Handle localRoot rule generation based on tool target.
@@ -39225,6 +40262,27 @@ As this project's AI coding tool, you must follow the additional conventions bel
39225
40262
  const mirrorPaths = await findFilesByGlobs(mirrorGlob);
39226
40263
  return buildDeletionRulesFromPaths(mirrorPaths);
39227
40264
  })();
40265
+ const extraFixedToolRules = await (async () => {
40266
+ const extraFiles = factory.class.getExtraFixedFiles?.({ global: this.global });
40267
+ if (!extraFiles || extraFiles.length === 0) return [];
40268
+ const filePaths = await findFilesByGlobs(extraFiles.map((file) => join(this.outputRoot, file.relativeDirPath, file.relativeFilePath)));
40269
+ if (filePaths.length === 0) return [];
40270
+ if (forDeletion) return buildDeletionRulesFromPaths(filePaths);
40271
+ return await Promise.all(filePaths.map((filePath) => {
40272
+ const relativeDirPath = resolveRelativeDirPath(filePath);
40273
+ checkPathTraversal({
40274
+ relativePath: relativeDirPath,
40275
+ intendedRootDir: this.outputRoot
40276
+ });
40277
+ return factory.class.fromFile({
40278
+ outputRoot: this.outputRoot,
40279
+ relativeDirPath,
40280
+ relativeFilePath: basename(filePath),
40281
+ global: this.global
40282
+ });
40283
+ }));
40284
+ })();
40285
+ this.logger.debug(`Found ${extraFixedToolRules.length} extra fixed tool rule files`);
39228
40286
  const nonRootToolRules = await (async () => {
39229
40287
  if (!settablePaths.nonRoot) return [];
39230
40288
  const nonRootOutputRoot = join(this.outputRoot, settablePaths.nonRoot.relativeDirPath);
@@ -39260,6 +40318,7 @@ As this project's AI coding tool, you must follow the additional conventions bel
39260
40318
  ...rootToolRules,
39261
40319
  ...localRootToolRules,
39262
40320
  ...rootMirrorDeletionRules,
40321
+ ...extraFixedToolRules,
39263
40322
  ...nonRootToolRules
39264
40323
  ];
39265
40324
  } catch (error) {
@@ -39287,7 +40346,7 @@ As this project's AI coding tool, you must follow the additional conventions bel
39287
40346
  return toolRuleFactories.get(result.data);
39288
40347
  }
39289
40348
  generateToonReferencesSection(toolRules) {
39290
- const toolRulesWithoutRoot = toolRules.filter((rule) => !rule.isRoot());
40349
+ const toolRulesWithoutRoot = toolRules.filter((rule) => !rule.isRoot() && !rule.isExcludedFromRootReferences());
39291
40350
  if (toolRulesWithoutRoot.length === 0) return "";
39292
40351
  const lines = [];
39293
40352
  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.");
@@ -39303,7 +40362,7 @@ As this project's AI coding tool, you must follow the additional conventions bel
39303
40362
  return lines.join("\n") + "\n\n";
39304
40363
  }
39305
40364
  generateReferencesSection(toolRules) {
39306
- const toolRulesWithoutRoot = toolRules.filter((rule) => !rule.isRoot());
40365
+ const toolRulesWithoutRoot = toolRules.filter((rule) => !rule.isRoot() && !rule.isExcludedFromRootReferences());
39307
40366
  if (toolRulesWithoutRoot.length === 0) return "";
39308
40367
  const lines = [];
39309
40368
  lines.push("Please also reference the following rules as needed:");
@@ -39324,7 +40383,7 @@ As this project's AI coding tool, you must follow the additional conventions bel
39324
40383
  */
39325
40384
  async function convertFromTool(params) {
39326
40385
  const ctx = params;
39327
- const [rulesCount, ignoreCount, mcpCount, commandsCount, subagentsCount, skillsCount, hooksCount, permissionsCount] = [
40386
+ const [rulesCount, ignoreCount, mcpCount, commandsCount, subagentsCount, skillsCount, hooksCount, permissionsCount, checksCount] = [
39328
40387
  await runFeatureConvert(ctx, buildRulesStrategy(ctx)),
39329
40388
  await runFeatureConvert(ctx, buildIgnoreStrategy(ctx)),
39330
40389
  await runFeatureConvert(ctx, buildMcpStrategy(ctx)),
@@ -39332,7 +40391,8 @@ async function convertFromTool(params) {
39332
40391
  await runFeatureConvert(ctx, buildSubagentsStrategy(ctx)),
39333
40392
  await runFeatureConvert(ctx, buildSkillsStrategy(ctx)),
39334
40393
  await runFeatureConvert(ctx, buildHooksStrategy(ctx)),
39335
- await runFeatureConvert(ctx, buildPermissionsStrategy(ctx))
40394
+ await runFeatureConvert(ctx, buildPermissionsStrategy(ctx)),
40395
+ await runFeatureConvert(ctx, buildChecksStrategy(ctx))
39336
40396
  ];
39337
40397
  return {
39338
40398
  rulesCount,
@@ -39342,7 +40402,8 @@ async function convertFromTool(params) {
39342
40402
  subagentsCount,
39343
40403
  skillsCount,
39344
40404
  hooksCount,
39345
- permissionsCount
40405
+ permissionsCount,
40406
+ checksCount
39346
40407
  };
39347
40408
  }
39348
40409
  async function runFeatureConvert(ctx, strategy) {
@@ -39575,6 +40636,27 @@ function buildPermissionsStrategy(ctx) {
39575
40636
  write: (p, files) => p.writeAiFiles(files)
39576
40637
  };
39577
40638
  }
40639
+ function buildChecksStrategy(ctx) {
40640
+ const { config, logger } = ctx;
40641
+ const global = config.getGlobal();
40642
+ const outputRoot = getOutputRoot(config);
40643
+ return {
40644
+ feature: "checks",
40645
+ itemLabel: "check file(s)",
40646
+ allTargets: ChecksProcessor.getToolTargets({ global }),
40647
+ createProcessor: ({ toolTarget, dryRun }) => new ChecksProcessor({
40648
+ outputRoot,
40649
+ toolTarget,
40650
+ global,
40651
+ dryRun,
40652
+ logger
40653
+ }),
40654
+ loadSource: (p) => p.loadToolFiles(),
40655
+ toRulesync: (p, files) => p.convertToolFilesToRulesyncFiles(files),
40656
+ fromRulesync: (p, files) => p.convertRulesyncFilesToToolFiles(files),
40657
+ write: (p, files) => p.writeAiFiles(files)
40658
+ };
40659
+ }
39578
40660
  //#endregion
39579
40661
  //#region src/types/processor-registry.ts
39580
40662
  const PROCESSOR_REGISTRY = [
@@ -39625,6 +40707,12 @@ const PROCESSOR_REGISTRY = [
39625
40707
  processor: PermissionsProcessor,
39626
40708
  schema: PermissionsProcessorToolTargetSchema,
39627
40709
  factory: toolPermissionsFactories
40710
+ },
40711
+ {
40712
+ feature: "checks",
40713
+ processor: ChecksProcessor,
40714
+ schema: ChecksProcessorToolTargetSchema,
40715
+ factory: toolCheckFactories
39628
40716
  }
39629
40717
  ];
39630
40718
  const getProcessorRegistryEntry = (feature) => {
@@ -39974,6 +41062,7 @@ const GENERATION_STEP_GRAPH = [
39974
41062
  id: "permissions",
39975
41063
  ...sharedWriteMeta("permissions")
39976
41064
  },
41065
+ { id: "checks" },
39977
41066
  {
39978
41067
  id: "rules",
39979
41068
  ...sharedWriteMeta("rules"),
@@ -40059,6 +41148,10 @@ async function generate(params) {
40059
41148
  config,
40060
41149
  logger
40061
41150
  }),
41151
+ checks: () => generateChecksCore({
41152
+ config,
41153
+ logger
41154
+ }),
40062
41155
  rules: () => generateRulesCore({
40063
41156
  config,
40064
41157
  logger,
@@ -40095,6 +41188,8 @@ async function generate(params) {
40095
41188
  hooksPaths: get("hooks").paths,
40096
41189
  permissionsCount: get("permissions").count,
40097
41190
  permissionsPaths: get("permissions").paths,
41191
+ checksCount: get("checks").count,
41192
+ checksPaths: get("checks").paths,
40098
41193
  skills: skillsResult.skills,
40099
41194
  hasDiff
40100
41195
  };
@@ -40463,6 +41558,44 @@ async function generatePermissionsCore(params) {
40463
41558
  hasDiff
40464
41559
  };
40465
41560
  }
41561
+ async function generateChecksCore(params) {
41562
+ const { config, logger } = params;
41563
+ let totalCount = 0;
41564
+ const allPaths = [];
41565
+ let hasDiff = false;
41566
+ const supportedChecksTargets = ChecksProcessor.getToolTargets({ global: config.getGlobal() });
41567
+ const toolTargets = intersection(config.getTargets(), supportedChecksTargets);
41568
+ warnUnsupportedTargets({
41569
+ config,
41570
+ supportedTargets: supportedChecksTargets,
41571
+ featureName: "checks",
41572
+ logger
41573
+ });
41574
+ for (const toolTarget of toolTargets) for (const outputRoot of config.getOutputRoots(toolTarget)) {
41575
+ if (!config.getFeatures(toolTarget).includes("checks")) continue;
41576
+ const processor = new ChecksProcessor({
41577
+ outputRoot,
41578
+ inputRoot: config.getInputRoot(),
41579
+ toolTarget,
41580
+ global: config.getGlobal(),
41581
+ dryRun: config.isPreviewMode(),
41582
+ logger
41583
+ });
41584
+ const result = await processFeatureWithRulesyncFiles({
41585
+ config,
41586
+ processor,
41587
+ rulesyncFiles: await processor.loadRulesyncFiles()
41588
+ });
41589
+ totalCount += result.count;
41590
+ allPaths.push(...result.paths);
41591
+ if (result.hasDiff) hasDiff = true;
41592
+ }
41593
+ return {
41594
+ count: totalCount,
41595
+ paths: allPaths,
41596
+ hasDiff
41597
+ };
41598
+ }
40466
41599
  //#endregion
40467
41600
  //#region src/lib/import.ts
40468
41601
  /**
@@ -40520,6 +41653,11 @@ async function importFromTool(params) {
40520
41653
  config,
40521
41654
  tool,
40522
41655
  logger
41656
+ }),
41657
+ checksCount: await importChecksCore({
41658
+ config,
41659
+ tool,
41660
+ logger
40523
41661
  })
40524
41662
  };
40525
41663
  }
@@ -40736,7 +41874,28 @@ async function importPermissionsCore(params) {
40736
41874
  if (config.getVerbose() && writtenCount > 0) logger.success(`Created ${writtenCount} permissions file(s)`);
40737
41875
  return writtenCount;
40738
41876
  }
41877
+ async function importChecksCore(params) {
41878
+ const { config, tool, logger } = params;
41879
+ if (!config.getFeatures(tool).includes("checks")) return 0;
41880
+ const global = config.getGlobal();
41881
+ if (!ChecksProcessor.getToolTargets({ global }).includes(tool)) return 0;
41882
+ const checksProcessor = new ChecksProcessor({
41883
+ outputRoot: config.getOutputRoots()[0] ?? ".",
41884
+ toolTarget: tool,
41885
+ global,
41886
+ logger
41887
+ });
41888
+ const toolFiles = await checksProcessor.loadToolFiles();
41889
+ if (toolFiles.length === 0) {
41890
+ logger.warn(`No check files found for ${tool}. Skipping import.`);
41891
+ return 0;
41892
+ }
41893
+ const rulesyncFiles = await checksProcessor.convertToolFilesToRulesyncFiles(toolFiles);
41894
+ const { count: writtenCount } = await checksProcessor.writeAiFiles(rulesyncFiles);
41895
+ if (config.getVerbose() && writtenCount > 0) logger.success(`Created ${writtenCount} check files`);
41896
+ return writtenCount;
41897
+ }
40739
41898
  //#endregion
40740
- 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 };
41899
+ export { isSymlink as $, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as A, RULESYNC_PERMISSIONS_JSONC_FILE_NAME as At, findControlCharacter as B, ALL_FEATURES_WITH_WILDCARD as Bt, CommandsProcessor as C, RULESYNC_MCP_FILE_NAME as Ct, CLAUDECODE_DIR as D, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as Dt, CODEXCLI_DIR as E, RULESYNC_MCP_SCHEMA_URL as Et, RulesyncCheck as F, RULESYNC_SKILLS_RELATIVE_DIR_PATH as Ft, checkPathTraversal as G, JsonLogger as H, RulesyncCheckFrontmatterSchema as I, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as It, ensureDir as J, createTempDirectory as K, stringifyFrontmatter as L, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as Lt, RulesyncCommand as M, RULESYNC_PERMISSIONS_SCHEMA_URL as Mt, RulesyncCommandFrontmatterSchema as N, RULESYNC_RELATIVE_DIR_PATH as Nt, CLAUDECODE_LOCAL_RULE_FILE_NAME as O, RULESYNC_OVERVIEW_FILE_NAME as Ot, ChecksProcessor as P, RULESYNC_RULES_RELATIVE_DIR_PATH as Pt, getHomeDirectory as Q, loadYaml as R, formatError 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_RELATIVE_FILE_PATH as jt, CLAUDECODE_MEMORIES_DIR_NAME as k, RULESYNC_PERMISSIONS_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 as zt };
40741
41900
 
40742
- //# sourceMappingURL=import-hM4jDZn1.js.map
41901
+ //# sourceMappingURL=import-CSnC5vl7.js.map