rulesync 16.1.0 → 16.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/cli/index.cjs +298 -5
- package/dist/cli/index.js +299 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-BmXT7mRS.js → import-CArKOPG_.js} +1328 -390
- package/dist/import-CArKOPG_.js.map +1 -0
- package/dist/{import-eG7fZPXI.cjs → import-DNfxS6QZ.cjs} +1342 -404
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/import-BmXT7mRS.js.map +0 -1
|
@@ -127,27 +127,27 @@ const isFeatureValueEnabled = (value) => {
|
|
|
127
127
|
};
|
|
128
128
|
//#endregion
|
|
129
129
|
//#region src/constants/rulesync-paths.ts
|
|
130
|
-
const { join: join$
|
|
130
|
+
const { join: join$281 } = node_path.posix;
|
|
131
131
|
const RULESYNC_CONFIG_RELATIVE_FILE_PATH = "rulesync.jsonc";
|
|
132
132
|
const RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH = "rulesync.local.jsonc";
|
|
133
133
|
const RULESYNC_RELATIVE_DIR_PATH = ".rulesync";
|
|
134
|
-
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$
|
|
135
|
-
const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$
|
|
136
|
-
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$
|
|
137
|
-
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$
|
|
138
|
-
const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$
|
|
139
|
-
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$
|
|
140
|
-
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$
|
|
141
|
-
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$
|
|
142
|
-
join$
|
|
143
|
-
const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$
|
|
144
|
-
const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$
|
|
134
|
+
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$281(RULESYNC_RELATIVE_DIR_PATH, "rules");
|
|
135
|
+
const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$281(RULESYNC_RULES_RELATIVE_DIR_PATH, ".curated");
|
|
136
|
+
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$281(RULESYNC_RELATIVE_DIR_PATH, "commands");
|
|
137
|
+
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$281(RULESYNC_RELATIVE_DIR_PATH, "subagents");
|
|
138
|
+
const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$281(RULESYNC_RELATIVE_DIR_PATH, "checks");
|
|
139
|
+
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$281(RULESYNC_RELATIVE_DIR_PATH, "mcp.jsonc");
|
|
140
|
+
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$281(RULESYNC_RELATIVE_DIR_PATH, "hooks.jsonc");
|
|
141
|
+
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$281(RULESYNC_RELATIVE_DIR_PATH, "permissions.jsonc");
|
|
142
|
+
join$281(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
|
|
143
|
+
const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$281(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
|
|
144
|
+
const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$281(RULESYNC_RELATIVE_DIR_PATH, "permissions.json");
|
|
145
145
|
const RULESYNC_AIIGNORE_FILE_NAME = ".aiignore";
|
|
146
|
-
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$
|
|
146
|
+
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$281(RULESYNC_RELATIVE_DIR_PATH, ".aiignore");
|
|
147
147
|
const RULESYNC_IGNORE_RELATIVE_FILE_PATH = ".rulesyncignore";
|
|
148
148
|
const RULESYNC_OVERVIEW_FILE_NAME = "overview.md";
|
|
149
|
-
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$
|
|
150
|
-
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$
|
|
149
|
+
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$281(RULESYNC_RELATIVE_DIR_PATH, "skills");
|
|
150
|
+
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$281(RULESYNC_SKILLS_RELATIVE_DIR_PATH, ".curated");
|
|
151
151
|
const RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync.lock";
|
|
152
152
|
const RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync-npm.lock.json";
|
|
153
153
|
const RULESYNC_MCP_FILE_NAME = "mcp.jsonc";
|
|
@@ -217,6 +217,7 @@ const ignoreProcessorToolTargetTuple = [
|
|
|
217
217
|
"kiro-cli",
|
|
218
218
|
"kiro-ide",
|
|
219
219
|
"qwencode",
|
|
220
|
+
"reasonix",
|
|
220
221
|
"roo",
|
|
221
222
|
"devin",
|
|
222
223
|
"vibe",
|
|
@@ -274,6 +275,7 @@ const commandsProcessorToolTargetTuple = [
|
|
|
274
275
|
"cursor",
|
|
275
276
|
"factorydroid",
|
|
276
277
|
"goose",
|
|
278
|
+
"grokcli",
|
|
277
279
|
"hermesagent",
|
|
278
280
|
"junie",
|
|
279
281
|
"kilo",
|
|
@@ -429,6 +431,7 @@ const checksProcessorToolTargetTuple = [
|
|
|
429
431
|
"amp",
|
|
430
432
|
"cursor",
|
|
431
433
|
"hermesagent",
|
|
434
|
+
"rovodev",
|
|
432
435
|
"takt"
|
|
433
436
|
];
|
|
434
437
|
//#endregion
|
|
@@ -1082,6 +1085,17 @@ const ConfigFileSchema = zod_mini.z.object({
|
|
|
1082
1085
|
});
|
|
1083
1086
|
zod_mini.z.required(ConfigParamsSchema);
|
|
1084
1087
|
/**
|
|
1088
|
+
* Normalizes the configuration file location to an absolute path.
|
|
1089
|
+
*
|
|
1090
|
+
* `ConfigResolver` always supplies the path it actually loaded; the fallback
|
|
1091
|
+
* only covers direct programmatic construction, where the conventional
|
|
1092
|
+
* location next to the input root is the best guess.
|
|
1093
|
+
*/
|
|
1094
|
+
function normalizeConfigFilePath({ configFilePath, inputRoot }) {
|
|
1095
|
+
if (configFilePath === void 0) return (0, node_path.join)(inputRoot, RULESYNC_CONFIG_RELATIVE_FILE_PATH);
|
|
1096
|
+
return (0, node_path.isAbsolute)(configFilePath) ? configFilePath : (0, node_path.resolve)(configFilePath);
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1085
1099
|
* Conflicting target pairs that cannot be used together
|
|
1086
1100
|
*/
|
|
1087
1101
|
const CONFLICTING_TARGET_PAIRS = [["augmentcode", "augmentcode-legacy"], ["claudecode", "claudecode-legacy"]];
|
|
@@ -1152,8 +1166,9 @@ var Config = class Config {
|
|
|
1152
1166
|
dryRun;
|
|
1153
1167
|
check;
|
|
1154
1168
|
inputRoot;
|
|
1169
|
+
configFilePath;
|
|
1155
1170
|
sources;
|
|
1156
|
-
constructor({ outputRoots, targets, features, verbose, delete: isDelete, global, silent, simulateCommands, simulateSubagents, simulateSkills, flattenedCommandNaming, gitignoreTargetsOnly, gitignoreDestination, dryRun, check, inputRoot, sources, configFileTargets }) {
|
|
1171
|
+
constructor({ outputRoots, targets, features, verbose, delete: isDelete, global, silent, simulateCommands, simulateSubagents, simulateSkills, flattenedCommandNaming, gitignoreTargetsOnly, gitignoreDestination, dryRun, check, inputRoot, configFilePath, sources, configFileTargets }) {
|
|
1157
1172
|
assertTargetsFeaturesExclusive({
|
|
1158
1173
|
targets,
|
|
1159
1174
|
features
|
|
@@ -1186,6 +1201,10 @@ var Config = class Config {
|
|
|
1186
1201
|
this.dryRun = dryRun ?? false;
|
|
1187
1202
|
this.check = check ?? false;
|
|
1188
1203
|
this.inputRoot = inputRoot === void 0 ? process.cwd() : (0, node_path.isAbsolute)(inputRoot) ? inputRoot : (0, node_path.resolve)(inputRoot);
|
|
1204
|
+
this.configFilePath = normalizeConfigFilePath({
|
|
1205
|
+
configFilePath,
|
|
1206
|
+
inputRoot: this.inputRoot
|
|
1207
|
+
});
|
|
1189
1208
|
this.sources = sources ?? [];
|
|
1190
1209
|
}
|
|
1191
1210
|
/**
|
|
@@ -1379,6 +1398,14 @@ var Config = class Config {
|
|
|
1379
1398
|
getInputRoot() {
|
|
1380
1399
|
return this.inputRoot;
|
|
1381
1400
|
}
|
|
1401
|
+
/**
|
|
1402
|
+
* Returns the absolute path of the configuration file this config was
|
|
1403
|
+
* resolved from. The file itself may not exist — `rulesync` runs fine
|
|
1404
|
+
* without one — so callers must treat this as a location, not a guarantee.
|
|
1405
|
+
*/
|
|
1406
|
+
getConfigFilePath() {
|
|
1407
|
+
return this.configFilePath;
|
|
1408
|
+
}
|
|
1382
1409
|
getSources() {
|
|
1383
1410
|
return this.sources;
|
|
1384
1411
|
}
|
|
@@ -1601,6 +1628,7 @@ var ConfigResolver = class {
|
|
|
1601
1628
|
fallback: getDefaults().check
|
|
1602
1629
|
}),
|
|
1603
1630
|
inputRoot: resolvedInputRoot !== void 0 ? (0, node_path.resolve)(resolvedInputRoot) : cwd,
|
|
1631
|
+
configFilePath: validatedConfigPath,
|
|
1604
1632
|
sources: configByFile.sources ?? getDefaults().sources,
|
|
1605
1633
|
flattenedCommandNaming: configByFile.flattenedCommandNaming ?? getDefaults().flattenedCommandNaming,
|
|
1606
1634
|
configFileTargets: extractConfigFileTargets(configByFile.targets)
|
|
@@ -4011,10 +4039,30 @@ const ClinePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
4011
4039
|
* portable and keeps them out of other tools' configs. Mirrors the OpenCode
|
|
4012
4040
|
* override; each value may be a bare action string or a pattern map.
|
|
4013
4041
|
*
|
|
4042
|
+
* `sandbox` is the sibling top-level block that governs the sandbox Kilo runs
|
|
4043
|
+
* commands in: `enabled` (boolean), `network` (`"deny"` and friends),
|
|
4044
|
+
* `allowed_hosts` (a list of `host` / `host:port` destination exceptions) and
|
|
4045
|
+
* `writable_paths`. It has no canonical permission category, so it is authored
|
|
4046
|
+
* here and emitted only for Kilo.
|
|
4047
|
+
*
|
|
4048
|
+
* Upstream restricts what a *project* config may say: `allowed_hosts` and
|
|
4049
|
+
* `writable_paths` are honored from the global config only, and a project
|
|
4050
|
+
* config may merely tighten (`enabled: true`, `network: "deny"`) — a
|
|
4051
|
+
* project-level network denial even clears the global destination exceptions.
|
|
4052
|
+
* rulesync mirrors that: at project scope only `enabled` and `network` are
|
|
4053
|
+
* written, and the rest are dropped with a warning rather than emitted into a
|
|
4054
|
+
* file Kilo would ignore.
|
|
4055
|
+
*
|
|
4014
4056
|
* @example
|
|
4015
4057
|
* { "permission": { "external_directory": "deny", "doom_loop": "ask" } }
|
|
4058
|
+
* @example
|
|
4059
|
+
* { "sandbox": { "enabled": true, "network": "deny" } }
|
|
4060
|
+
* @see https://kilo.ai/docs/getting-started/settings/sandboxing
|
|
4016
4061
|
*/
|
|
4017
|
-
const KiloPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
4062
|
+
const KiloPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
4063
|
+
permission: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), OpencodeOverridePermissionValueSchema)),
|
|
4064
|
+
sandbox: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
4065
|
+
});
|
|
4018
4066
|
/**
|
|
4019
4067
|
* Tool-scoped override block for Claude Code. Claude Code's `permissions` object
|
|
4020
4068
|
* (in `.claude/settings.json`) carries non-list fields that have no canonical
|
|
@@ -5323,6 +5371,26 @@ const CURSOR_IGNORE_FILE_NAME = ".cursorignore";
|
|
|
5323
5371
|
const CURSOR_PERMISSIONS_FILE_NAME = "cli.json";
|
|
5324
5372
|
const CURSOR_PERMISSIONS_GLOBAL_FILE_NAME = "cli-config.json";
|
|
5325
5373
|
//#endregion
|
|
5374
|
+
//#region src/constants/rovodev-paths.ts
|
|
5375
|
+
const ROVODEV_DIR = ".rovodev";
|
|
5376
|
+
const ROVODEV_SKILLS_DIR_PATH = (0, node_path.join)(ROVODEV_DIR, "skills");
|
|
5377
|
+
const ROVODEV_SUBAGENTS_DIR_PATH = (0, node_path.join)(ROVODEV_DIR, "subagents");
|
|
5378
|
+
const ROVODEV_MODULAR_RULES_DIR_PATH = (0, node_path.join)(ROVODEV_DIR, ".rulesync", "modular-rules");
|
|
5379
|
+
const ROVODEV_RULE_FILE_NAME = "AGENTS.md";
|
|
5380
|
+
const ROVODEV_LEGACY_RULE_FILE_NAME = "AGENTS.local.md";
|
|
5381
|
+
const ROVODEV_MCP_FILE_NAME = "mcp.json";
|
|
5382
|
+
const ROVODEV_CONFIG_FILE_NAME = "config.yml";
|
|
5383
|
+
const ROVODEV_AGENTS_SKILLS_DIR_PATH = (0, node_path.join)(".agents", "skills");
|
|
5384
|
+
const ROVODEV_PROMPTS_FILE_NAME = "prompts.yml";
|
|
5385
|
+
const ROVODEV_PROMPTS_DIR_PATH = (0, node_path.join)(ROVODEV_DIR, "prompts");
|
|
5386
|
+
/**
|
|
5387
|
+
* Custom instructions for Rovo Dev's code reviews: a plain-Markdown file (no
|
|
5388
|
+
* frontmatter) in the repository root's `.rovodev/` folder. Note the leading
|
|
5389
|
+
* dot in the file name.
|
|
5390
|
+
* @see https://support.atlassian.com/rovo/docs/set-custom-instructions-for-code-reviews/
|
|
5391
|
+
*/
|
|
5392
|
+
const ROVODEV_REVIEW_AGENT_FILE_NAME = ".review-agent.md";
|
|
5393
|
+
//#endregion
|
|
5326
5394
|
//#region src/constants/takt-paths.ts
|
|
5327
5395
|
const TAKT_DIR = ".takt";
|
|
5328
5396
|
const TAKT_FACETS_SUBDIR = "facets";
|
|
@@ -5784,12 +5852,19 @@ function slugifyCheckName(value) {
|
|
|
5784
5852
|
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48).replace(/-+$/, "");
|
|
5785
5853
|
}
|
|
5786
5854
|
//#endregion
|
|
5787
|
-
//#region src/features/checks/
|
|
5855
|
+
//#region src/features/checks/aggregated-check-file.ts
|
|
5788
5856
|
/**
|
|
5789
|
-
*
|
|
5790
|
-
*
|
|
5791
|
-
*
|
|
5857
|
+
* Shared machinery for the tools whose checks surface is **one aggregated
|
|
5858
|
+
* instruction file** rather than a file per check — Cursor Bugbot's
|
|
5859
|
+
* `.cursor/BUGBOT.md` and Rovo Dev's `.rovodev/.review-agent.md`.
|
|
5860
|
+
*
|
|
5861
|
+
* Both read the file as free prose, so the check identities have to be carried
|
|
5862
|
+
* in something invisible to the reader: an HTML-comment marker per section.
|
|
5863
|
+
* That marker convention, the escaping that keeps a check body from splitting
|
|
5864
|
+
* itself, and the import-side split are the same for both files, so they live
|
|
5865
|
+
* here once.
|
|
5792
5866
|
*/
|
|
5867
|
+
/** Marks where one check starts inside the single instruction file. */
|
|
5793
5868
|
const CHECK_MARKER_PATTERN = /^<!--\s*rulesync:check:(.+?)\s*-->[ \t]*$/gm;
|
|
5794
5869
|
/**
|
|
5795
5870
|
* A marker line a check body wrote itself — a rulesync doc fragment quoted in a
|
|
@@ -5800,17 +5875,16 @@ const CHECK_MARKER_PATTERN = /^<!--\s*rulesync:check:(.+?)\s*-->[ \t]*$/gm;
|
|
|
5800
5875
|
*/
|
|
5801
5876
|
const ESCAPABLE_MARKER_PATTERN = /^(<!--\s*rulesync:)((?:literal-)*check:.+?\s*-->[ \t]*)$/gm;
|
|
5802
5877
|
const ESCAPED_MARKER_PATTERN = /^(<!--\s*rulesync:)literal-((?:literal-)*check:.+?\s*-->[ \t]*)$/gm;
|
|
5803
|
-
|
|
5804
|
-
function renderMarker(name) {
|
|
5878
|
+
function renderCheckMarker(name) {
|
|
5805
5879
|
return `<!-- rulesync:check:${name} -->`;
|
|
5806
5880
|
}
|
|
5807
|
-
function
|
|
5881
|
+
function escapeCheckMarkers(content) {
|
|
5808
5882
|
return content.replace(ESCAPABLE_MARKER_PATTERN, "$1literal-$2");
|
|
5809
5883
|
}
|
|
5810
|
-
function
|
|
5884
|
+
function unescapeCheckMarkers(content) {
|
|
5811
5885
|
return content.replace(ESCAPED_MARKER_PATTERN, "$1$2");
|
|
5812
5886
|
}
|
|
5813
|
-
function
|
|
5887
|
+
function findCheckMarkers(fileContent) {
|
|
5814
5888
|
CHECK_MARKER_PATTERN.lastIndex = 0;
|
|
5815
5889
|
const markers = [];
|
|
5816
5890
|
let match = CHECK_MARKER_PATTERN.exec(fileContent);
|
|
@@ -5825,23 +5899,47 @@ function findMarkers(fileContent) {
|
|
|
5825
5899
|
return markers;
|
|
5826
5900
|
}
|
|
5827
5901
|
/**
|
|
5828
|
-
*
|
|
5829
|
-
*
|
|
5830
|
-
*
|
|
5902
|
+
* Whether the file holds instruction text ahead of the first marker — the
|
|
5903
|
+
* question the "generating replaces this" warning asks. A file with no marker
|
|
5904
|
+
* at all is entirely hand-written, so it qualifies; an empty one does not,
|
|
5905
|
+
* since there is nothing to replace.
|
|
5906
|
+
*/
|
|
5907
|
+
function hasHandWrittenPreamble(fileContent) {
|
|
5908
|
+
const firstMarkerStart = findCheckMarkers(fileContent)[0]?.start ?? fileContent.length;
|
|
5909
|
+
return fileContent.slice(0, firstMarkerStart).trim().length > 0;
|
|
5910
|
+
}
|
|
5911
|
+
/**
|
|
5912
|
+
* Whether the file is nothing but sections rulesync generated — the question
|
|
5913
|
+
* the deletion guard asks, and a stricter one than
|
|
5914
|
+
* {@link hasHandWrittenPreamble}. A file carrying no marker at all is not
|
|
5915
|
+
* rulesync's to remove even when it is empty: rulesync never wrote it, so an
|
|
5916
|
+
* empty one is somebody's placeholder rather than our leftover.
|
|
5917
|
+
*/
|
|
5918
|
+
function isOnlyGeneratedSections(fileContent) {
|
|
5919
|
+
const firstMarkerStart = findCheckMarkers(fileContent)[0]?.start;
|
|
5920
|
+
if (firstMarkerStart === void 0) return false;
|
|
5921
|
+
return fileContent.slice(0, firstMarkerStart).trim().length === 0;
|
|
5922
|
+
}
|
|
5923
|
+
/**
|
|
5924
|
+
* The instruction text one check contributes. Neither file has a field to put a
|
|
5925
|
+
* summary in, so `description` is used only when there is no body.
|
|
5831
5926
|
*/
|
|
5832
5927
|
function toInstruction(rulesyncCheck) {
|
|
5833
5928
|
const body = rulesyncCheck.getBody().trim();
|
|
5834
5929
|
if (body.length > 0) return body;
|
|
5835
5930
|
return rulesyncCheck.getFrontmatter().description?.trim() ?? "";
|
|
5836
5931
|
}
|
|
5837
|
-
function
|
|
5932
|
+
function renderCheckSection(rulesyncCheck) {
|
|
5838
5933
|
const name = (0, node_path.basename)(rulesyncCheck.getRelativeFilePath(), ".md");
|
|
5839
5934
|
const heading = `## ${name}`;
|
|
5840
5935
|
const instruction = toInstruction(rulesyncCheck);
|
|
5841
|
-
const lines = [
|
|
5842
|
-
if (instruction.length > 0) lines.push("",
|
|
5936
|
+
const lines = [renderCheckMarker(name), heading];
|
|
5937
|
+
if (instruction.length > 0) lines.push("", escapeCheckMarkers(instruction));
|
|
5843
5938
|
return lines.join("\n");
|
|
5844
5939
|
}
|
|
5940
|
+
function renderCheckFile(rulesyncChecks) {
|
|
5941
|
+
return `${rulesyncChecks.map(renderCheckSection).join("\n\n")}\n`;
|
|
5942
|
+
}
|
|
5845
5943
|
/** Drop the heading generate writes, so a round trip does not stack headings. */
|
|
5846
5944
|
function stripGeneratedHeading(section, name) {
|
|
5847
5945
|
const [firstLine, ...rest] = section.split("\n");
|
|
@@ -5849,6 +5947,53 @@ function stripGeneratedHeading(section, name) {
|
|
|
5849
5947
|
return section.trim();
|
|
5850
5948
|
}
|
|
5851
5949
|
/**
|
|
5950
|
+
* Split an aggregated instruction file back into one check per section.
|
|
5951
|
+
*
|
|
5952
|
+
* Content ahead of the first marker — and a hand-written file with no markers
|
|
5953
|
+
* at all — becomes a single check named `fallbackName`, so nothing in the file
|
|
5954
|
+
* is dropped.
|
|
5955
|
+
*/
|
|
5956
|
+
function splitCheckFile({ fileContent, fallbackName }) {
|
|
5957
|
+
const sections = [];
|
|
5958
|
+
const markers = findCheckMarkers(fileContent);
|
|
5959
|
+
const preambleEnd = markers[0]?.start ?? fileContent.length;
|
|
5960
|
+
const preamble = fileContent.slice(0, preambleEnd).trim();
|
|
5961
|
+
if (preamble.length > 0) sections.push({
|
|
5962
|
+
name: fallbackName,
|
|
5963
|
+
content: unescapeCheckMarkers(preamble)
|
|
5964
|
+
});
|
|
5965
|
+
for (const [index, marker] of markers.entries()) {
|
|
5966
|
+
const sectionEnd = markers[index + 1]?.start ?? fileContent.length;
|
|
5967
|
+
const markerName = marker.name.trim();
|
|
5968
|
+
const name = slugifyCheckName(markerName) || fallbackName;
|
|
5969
|
+
const content = stripGeneratedHeading(fileContent.slice(marker.end, sectionEnd).trim(), markerName);
|
|
5970
|
+
sections.push({
|
|
5971
|
+
name,
|
|
5972
|
+
content: unescapeCheckMarkers(content)
|
|
5973
|
+
});
|
|
5974
|
+
}
|
|
5975
|
+
const used = /* @__PURE__ */ new Set();
|
|
5976
|
+
return sections.map(({ name, content }) => {
|
|
5977
|
+
let uniqueName = name;
|
|
5978
|
+
let suffix = 2;
|
|
5979
|
+
while (used.has(uniqueName)) {
|
|
5980
|
+
uniqueName = `${name}-${suffix}`;
|
|
5981
|
+
suffix += 1;
|
|
5982
|
+
}
|
|
5983
|
+
used.add(uniqueName);
|
|
5984
|
+
return new RulesyncCheck({
|
|
5985
|
+
outputRoot: ".",
|
|
5986
|
+
relativeDirPath: RULESYNC_CHECKS_RELATIVE_DIR_PATH,
|
|
5987
|
+
relativeFilePath: `${uniqueName}.md`,
|
|
5988
|
+
frontmatter: { targets: ["*"] },
|
|
5989
|
+
body: content
|
|
5990
|
+
});
|
|
5991
|
+
});
|
|
5992
|
+
}
|
|
5993
|
+
//#endregion
|
|
5994
|
+
//#region src/features/checks/cursor-check.ts
|
|
5995
|
+
const FALLBACK_CHECK_NAME$1 = "bugbot";
|
|
5996
|
+
/**
|
|
5852
5997
|
* Checks adapter for Cursor Bugbot (`.cursor/BUGBOT.md`).
|
|
5853
5998
|
*
|
|
5854
5999
|
* Bugbot takes one aggregated instruction file per directory rather than a file
|
|
@@ -5905,9 +6050,7 @@ var CursorCheck = class CursorCheck extends ToolCheck {
|
|
|
5905
6050
|
const paths = CursorCheck.getSettablePaths();
|
|
5906
6051
|
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath ?? "BUGBOT.md"));
|
|
5907
6052
|
if (fileContent === null) return true;
|
|
5908
|
-
|
|
5909
|
-
if (firstMarkerStart === void 0) return false;
|
|
5910
|
-
return fileContent.slice(0, firstMarkerStart).trim().length === 0;
|
|
6053
|
+
return isOnlyGeneratedSections(fileContent);
|
|
5911
6054
|
}
|
|
5912
6055
|
static fromRulesyncCheck(_params) {
|
|
5913
6056
|
throw new Error("Cursor checks are built from all checks at once; use fromRulesyncChecks.");
|
|
@@ -5917,10 +6060,8 @@ var CursorCheck = class CursorCheck extends ToolCheck {
|
|
|
5917
6060
|
const paths = CursorCheck.getSettablePaths({ global });
|
|
5918
6061
|
const relativeFilePath = paths.relativeFilePath ?? "BUGBOT.md";
|
|
5919
6062
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
5920
|
-
|
|
5921
|
-
const
|
|
5922
|
-
if (existingContent.slice(0, firstMarkerStart).trim().length > 0) logger?.warn(`Cursor checks: ${filePath} holds instructions rulesync did not write, and generating replaces the whole file. Run \`rulesync import --targets cursor --features checks\` first to keep them.`);
|
|
5923
|
-
const fileContent = `${rulesyncChecks.map(renderSection).join("\n\n")}\n`;
|
|
6063
|
+
if (hasHandWrittenPreamble(await readFileContentOrNull(filePath) ?? "")) logger?.warn(`Cursor checks: ${filePath} holds instructions rulesync did not write, and generating replaces the whole file. Run \`rulesync import --targets cursor --features checks\` first to keep them.`);
|
|
6064
|
+
const fileContent = renderCheckFile(rulesyncChecks);
|
|
5924
6065
|
return [new CursorCheck({
|
|
5925
6066
|
outputRoot,
|
|
5926
6067
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -5963,41 +6104,9 @@ var CursorCheck = class CursorCheck extends ToolCheck {
|
|
|
5963
6104
|
return first;
|
|
5964
6105
|
}
|
|
5965
6106
|
toRulesyncChecks() {
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
const preambleEnd = markers[0]?.start ?? fileContent.length;
|
|
5970
|
-
const preamble = fileContent.slice(0, preambleEnd).trim();
|
|
5971
|
-
if (preamble.length > 0) sections.push({
|
|
5972
|
-
name: FALLBACK_CHECK_NAME,
|
|
5973
|
-
content: unescapeMarkers(preamble)
|
|
5974
|
-
});
|
|
5975
|
-
for (const [index, marker] of markers.entries()) {
|
|
5976
|
-
const sectionEnd = markers[index + 1]?.start ?? fileContent.length;
|
|
5977
|
-
const markerName = marker.name.trim();
|
|
5978
|
-
const name = slugifyCheckName(markerName) || FALLBACK_CHECK_NAME;
|
|
5979
|
-
const content = stripGeneratedHeading(fileContent.slice(marker.end, sectionEnd).trim(), markerName);
|
|
5980
|
-
sections.push({
|
|
5981
|
-
name,
|
|
5982
|
-
content: unescapeMarkers(content)
|
|
5983
|
-
});
|
|
5984
|
-
}
|
|
5985
|
-
const used = /* @__PURE__ */ new Set();
|
|
5986
|
-
return sections.map(({ name, content }) => {
|
|
5987
|
-
let uniqueName = name;
|
|
5988
|
-
let suffix = 2;
|
|
5989
|
-
while (used.has(uniqueName)) {
|
|
5990
|
-
uniqueName = `${name}-${suffix}`;
|
|
5991
|
-
suffix += 1;
|
|
5992
|
-
}
|
|
5993
|
-
used.add(uniqueName);
|
|
5994
|
-
return new RulesyncCheck({
|
|
5995
|
-
outputRoot: ".",
|
|
5996
|
-
relativeDirPath: RULESYNC_CHECKS_RELATIVE_DIR_PATH,
|
|
5997
|
-
relativeFilePath: `${uniqueName}.md`,
|
|
5998
|
-
frontmatter: { targets: ["*"] },
|
|
5999
|
-
body: content
|
|
6000
|
-
});
|
|
6107
|
+
return splitCheckFile({
|
|
6108
|
+
fileContent: this.getFileContent(),
|
|
6109
|
+
fallbackName: FALLBACK_CHECK_NAME$1
|
|
6001
6110
|
});
|
|
6002
6111
|
}
|
|
6003
6112
|
};
|
|
@@ -6019,8 +6128,22 @@ var CursorCheck = class CursorCheck extends ToolCheck {
|
|
|
6019
6128
|
*/
|
|
6020
6129
|
/** Project-root instruction file auto-injected by Hermes Agent. */
|
|
6021
6130
|
const HERMESAGENT_RULE_FILE_NAME = ".hermes.md";
|
|
6022
|
-
/**
|
|
6131
|
+
/**
|
|
6132
|
+
* Root directory for Hermes Agent global configuration (the HERMES_HOME dir).
|
|
6133
|
+
* Also the project-local plugin tree, which is `.hermes/` on every platform.
|
|
6134
|
+
*/
|
|
6023
6135
|
const HERMESAGENT_GLOBAL_DIR = ".hermes";
|
|
6136
|
+
/**
|
|
6137
|
+
* Home-relative global profile root on Windows: upstream defaults to
|
|
6138
|
+
* `%LOCALAPPDATA%\hermes` there, not `~/.hermes`.
|
|
6139
|
+
* Resolve it through `getHermesagentGlobalDir()` rather than reading it directly.
|
|
6140
|
+
*
|
|
6141
|
+
* Home-relative rather than read from `LOCALAPPDATA`, matching how every other
|
|
6142
|
+
* Windows global path in rulesync is spelled (`ZED_GLOBAL_WIN32_DIR`,
|
|
6143
|
+
* `WARP_WIN32_DIR`). A profile with `LOCALAPPDATA` redirected elsewhere is not
|
|
6144
|
+
* followed; those users should set `HERMES_HOME` explicitly.
|
|
6145
|
+
*/
|
|
6146
|
+
const HERMESAGENT_GLOBAL_WIN32_DIR = (0, node_path.join)("AppData", "Local", "hermes");
|
|
6024
6147
|
/** MCP servers and other settings live in `config.yaml` under `~/.hermes/`. */
|
|
6025
6148
|
const HERMESAGENT_CONFIG_FILE_NAME = "config.yaml";
|
|
6026
6149
|
const HERMESAGENT_CONFIG_FILE_PATH = (0, node_path.join)(HERMESAGENT_GLOBAL_DIR, HERMESAGENT_CONFIG_FILE_NAME);
|
|
@@ -6243,6 +6366,114 @@ var HermesagentCheck = class HermesagentCheck extends ToolCheck {
|
|
|
6243
6366
|
}
|
|
6244
6367
|
};
|
|
6245
6368
|
//#endregion
|
|
6369
|
+
//#region src/features/checks/rovodev-check.ts
|
|
6370
|
+
const FALLBACK_CHECK_NAME = "review-agent";
|
|
6371
|
+
/**
|
|
6372
|
+
* Checks adapter for Rovo Dev CLI's code-review custom instructions
|
|
6373
|
+
* (`.rovodev/.review-agent.md`).
|
|
6374
|
+
*
|
|
6375
|
+
* Rovo Dev takes one plain-Markdown instruction file at the repository root's
|
|
6376
|
+
* `.rovodev/` folder — no frontmatter, and note the leading dot in the file
|
|
6377
|
+
* name. Like Cursor Bugbot it is a single aggregated file rather than a file
|
|
6378
|
+
* per check, so every `.rulesync/checks/*.md` targeting Rovo Dev collapses into
|
|
6379
|
+
* it via {@link fromRulesyncChecks}, with each check written as a marked
|
|
6380
|
+
* section (see `aggregated-check-file.ts` for the marker convention the two
|
|
6381
|
+
* adapters share).
|
|
6382
|
+
*
|
|
6383
|
+
* Rovo Dev reads the file as free prose, so a check's `severity` and `tools`
|
|
6384
|
+
* have no equivalent there: they are not written and do not come back on
|
|
6385
|
+
* import. Neither does `description` whenever the check also has a body.
|
|
6386
|
+
*
|
|
6387
|
+
* Project scope only — these are per-repository review instructions, and Rovo
|
|
6388
|
+
* Dev documents no user-level equivalent. (The `permissions` adapter for the
|
|
6389
|
+
* same tool is the opposite: global only.)
|
|
6390
|
+
*
|
|
6391
|
+
* @see https://support.atlassian.com/rovo/docs/set-custom-instructions-for-code-reviews/
|
|
6392
|
+
*/
|
|
6393
|
+
var RovodevCheck = class RovodevCheck extends ToolCheck {
|
|
6394
|
+
static getSettablePaths(_options = {}) {
|
|
6395
|
+
return {
|
|
6396
|
+
relativeDirPath: ROVODEV_DIR,
|
|
6397
|
+
relativeFilePath: ROVODEV_REVIEW_AGENT_FILE_NAME
|
|
6398
|
+
};
|
|
6399
|
+
}
|
|
6400
|
+
static isTargetedByRulesyncCheck(rulesyncCheck) {
|
|
6401
|
+
return this.isTargetedByRulesyncCheckDefault({
|
|
6402
|
+
rulesyncCheck,
|
|
6403
|
+
toolTarget: "rovodev"
|
|
6404
|
+
});
|
|
6405
|
+
}
|
|
6406
|
+
/**
|
|
6407
|
+
* Ownership guard the processor consults before it deletes anything for this
|
|
6408
|
+
* tool. `.review-agent.md` is a file Rovo Dev's own documentation tells users
|
|
6409
|
+
* to hand-write, so anything in it that rulesync did not write is not
|
|
6410
|
+
* rulesync's to remove — dropping the last check targeting Rovo Dev must not
|
|
6411
|
+
* take somebody's hand-written review instructions with it.
|
|
6412
|
+
*/
|
|
6413
|
+
static async canDeleteAuxiliaryFiles({ outputRoot }) {
|
|
6414
|
+
const paths = RovodevCheck.getSettablePaths();
|
|
6415
|
+
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath ?? ".review-agent.md"));
|
|
6416
|
+
if (fileContent === null) return true;
|
|
6417
|
+
return isOnlyGeneratedSections(fileContent);
|
|
6418
|
+
}
|
|
6419
|
+
static fromRulesyncCheck(_params) {
|
|
6420
|
+
throw new Error("Rovo Dev checks are built from all checks at once; use fromRulesyncChecks.");
|
|
6421
|
+
}
|
|
6422
|
+
static async fromRulesyncChecks({ outputRoot = process.cwd(), rulesyncChecks, global = false, logger }) {
|
|
6423
|
+
if (rulesyncChecks.length === 0) return [];
|
|
6424
|
+
const paths = RovodevCheck.getSettablePaths({ global });
|
|
6425
|
+
const relativeFilePath = paths.relativeFilePath ?? ".review-agent.md";
|
|
6426
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
6427
|
+
if (hasHandWrittenPreamble(await readFileContentOrNull(filePath) ?? "")) logger?.warn(`Rovo Dev checks: ${filePath} holds instructions rulesync did not write, and generating replaces the whole file. Run \`rulesync import --targets rovodev --features checks\` first to keep them.`);
|
|
6428
|
+
return [new RovodevCheck({
|
|
6429
|
+
outputRoot,
|
|
6430
|
+
relativeDirPath: paths.relativeDirPath,
|
|
6431
|
+
relativeFilePath,
|
|
6432
|
+
fileContent: renderCheckFile(rulesyncChecks),
|
|
6433
|
+
global
|
|
6434
|
+
})];
|
|
6435
|
+
}
|
|
6436
|
+
static async fromFile({ outputRoot = process.cwd(), global = false }) {
|
|
6437
|
+
const paths = RovodevCheck.getSettablePaths({ global });
|
|
6438
|
+
const relativeFilePath = paths.relativeFilePath ?? ".review-agent.md";
|
|
6439
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
6440
|
+
return new RovodevCheck({
|
|
6441
|
+
outputRoot,
|
|
6442
|
+
relativeDirPath: paths.relativeDirPath,
|
|
6443
|
+
relativeFilePath,
|
|
6444
|
+
fileContent: await readFileContentOrNull(filePath) ?? "",
|
|
6445
|
+
global
|
|
6446
|
+
});
|
|
6447
|
+
}
|
|
6448
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
6449
|
+
return new RovodevCheck({
|
|
6450
|
+
outputRoot,
|
|
6451
|
+
relativeDirPath,
|
|
6452
|
+
relativeFilePath,
|
|
6453
|
+
fileContent: "",
|
|
6454
|
+
validate: false,
|
|
6455
|
+
global
|
|
6456
|
+
});
|
|
6457
|
+
}
|
|
6458
|
+
validate() {
|
|
6459
|
+
return {
|
|
6460
|
+
success: true,
|
|
6461
|
+
error: null
|
|
6462
|
+
};
|
|
6463
|
+
}
|
|
6464
|
+
toRulesyncCheck() {
|
|
6465
|
+
const first = this.toRulesyncChecks()[0];
|
|
6466
|
+
if (!first) throw new Error(`No check instructions found in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}.`);
|
|
6467
|
+
return first;
|
|
6468
|
+
}
|
|
6469
|
+
toRulesyncChecks() {
|
|
6470
|
+
return splitCheckFile({
|
|
6471
|
+
fileContent: this.getFileContent(),
|
|
6472
|
+
fallbackName: FALLBACK_CHECK_NAME
|
|
6473
|
+
});
|
|
6474
|
+
}
|
|
6475
|
+
};
|
|
6476
|
+
//#endregion
|
|
6246
6477
|
//#region src/constants/codexcli-paths.ts
|
|
6247
6478
|
const CODEXCLI_DIR = ".codex";
|
|
6248
6479
|
const CODEXCLI_PROMPTS_DIR_PATH = (0, node_path.join)(CODEXCLI_DIR, "prompts");
|
|
@@ -6355,11 +6586,14 @@ function mergeSharedConfigDeep({ base, patch }) {
|
|
|
6355
6586
|
}
|
|
6356
6587
|
const CLAUDE_SETTINGS_SHARED_FILE_KEY = ".claude/settings.json";
|
|
6357
6588
|
const HERMES_CONFIG_SHARED_FILE_KEY = ".hermes/config.yaml";
|
|
6589
|
+
const HERMES_WIN32_CONFIG_SHARED_FILE_KEY = "AppData/Local/hermes/config.yaml";
|
|
6590
|
+
const HERMES_HOME_CONFIG_SHARED_FILE_KEY = "config.yaml";
|
|
6358
6591
|
const TAKT_CONFIG_SHARED_FILE_KEY = ".takt/config.yaml";
|
|
6359
6592
|
const CODEXCLI_CONFIG_SHARED_FILE_KEY = ".codex/config.toml";
|
|
6360
6593
|
const GROKCLI_CONFIG_SHARED_FILE_KEY = ".grok/config.toml";
|
|
6361
6594
|
const VIBE_CONFIG_SHARED_FILE_KEY = ".vibe/config.toml";
|
|
6362
6595
|
const KIMI_CODE_CONFIG_SHARED_FILE_KEY = ".kimi-code/config.toml";
|
|
6596
|
+
const KIMI_CODE_HOME_CONFIG_SHARED_FILE_KEY = "config.toml";
|
|
6363
6597
|
const REASONIX_PROJECT_CONFIG_SHARED_FILE_KEY = "reasonix.toml";
|
|
6364
6598
|
const REASONIX_GLOBAL_CONFIG_SHARED_FILE_KEY = ".reasonix/config.toml";
|
|
6365
6599
|
/**
|
|
@@ -6383,6 +6617,65 @@ const sharedConfigFileKey = ({ relativeDirPath, relativeFilePath }) => {
|
|
|
6383
6617
|
* lock-step with the writers derived from the processor registry, so an
|
|
6384
6618
|
* undeclared writer fails CI instead of merging by accident.
|
|
6385
6619
|
*/
|
|
6620
|
+
/**
|
|
6621
|
+
* Hermes writes one `config.yaml`, but its global profile root has three
|
|
6622
|
+
* spellings (`~/.hermes`, the win32 `%LOCALAPPDATA%\hermes`, and `HERMES_HOME`
|
|
6623
|
+
* itself). They are the same file with the same owners, so the declaration is
|
|
6624
|
+
* written once and shared — a policy edit cannot land on one spelling only.
|
|
6625
|
+
*/
|
|
6626
|
+
const HERMES_CONFIG_DECLARATION = {
|
|
6627
|
+
format: "yaml",
|
|
6628
|
+
features: {
|
|
6629
|
+
commands: {
|
|
6630
|
+
kind: "replace-owned-keys",
|
|
6631
|
+
ownedKeys: ["plugins"]
|
|
6632
|
+
},
|
|
6633
|
+
subagents: {
|
|
6634
|
+
kind: "replace-owned-keys",
|
|
6635
|
+
ownedKeys: ["plugins"]
|
|
6636
|
+
},
|
|
6637
|
+
mcp: {
|
|
6638
|
+
kind: "replace-owned-keys",
|
|
6639
|
+
ownedKeys: ["mcp_servers"]
|
|
6640
|
+
},
|
|
6641
|
+
hooks: {
|
|
6642
|
+
kind: "replace-owned-keys",
|
|
6643
|
+
ownedKeys: ["hooks"]
|
|
6644
|
+
},
|
|
6645
|
+
permissions: {
|
|
6646
|
+
kind: "deep-merge",
|
|
6647
|
+
replaceKeys: ["permissions"]
|
|
6648
|
+
}
|
|
6649
|
+
}
|
|
6650
|
+
};
|
|
6651
|
+
/**
|
|
6652
|
+
* Kimi Code's user config: hooks owns the flat `hooks` array; permissions owns
|
|
6653
|
+
* the ordered rule list and optional coarse default mode. `KIMI_CODE_HOME` can
|
|
6654
|
+
* name the profile directory itself, so the file has two spellings that share
|
|
6655
|
+
* one declaration — a policy edit cannot land on only one of them.
|
|
6656
|
+
*/
|
|
6657
|
+
const KIMI_CODE_CONFIG_DECLARATION = {
|
|
6658
|
+
format: "toml",
|
|
6659
|
+
invalidRootPolicy: "error",
|
|
6660
|
+
features: {
|
|
6661
|
+
hooks: {
|
|
6662
|
+
kind: "replace-owned-keys",
|
|
6663
|
+
ownedKeys: ["hooks"]
|
|
6664
|
+
},
|
|
6665
|
+
mcp: {
|
|
6666
|
+
kind: "replace-owned-keys",
|
|
6667
|
+
ownedKeys: ["mcp"]
|
|
6668
|
+
},
|
|
6669
|
+
permissions: {
|
|
6670
|
+
kind: "replace-owned-keys",
|
|
6671
|
+
ownedKeys: [
|
|
6672
|
+
"permission",
|
|
6673
|
+
"default_permission_mode",
|
|
6674
|
+
"tools"
|
|
6675
|
+
]
|
|
6676
|
+
}
|
|
6677
|
+
}
|
|
6678
|
+
};
|
|
6386
6679
|
const SHARED_CONFIG_OWNERSHIP = {
|
|
6387
6680
|
[CLAUDE_SETTINGS_SHARED_FILE_KEY]: {
|
|
6388
6681
|
format: "json",
|
|
@@ -6401,31 +6694,9 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6401
6694
|
}
|
|
6402
6695
|
}
|
|
6403
6696
|
},
|
|
6404
|
-
[HERMES_CONFIG_SHARED_FILE_KEY]:
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
commands: {
|
|
6408
|
-
kind: "replace-owned-keys",
|
|
6409
|
-
ownedKeys: ["plugins"]
|
|
6410
|
-
},
|
|
6411
|
-
subagents: {
|
|
6412
|
-
kind: "replace-owned-keys",
|
|
6413
|
-
ownedKeys: ["plugins"]
|
|
6414
|
-
},
|
|
6415
|
-
mcp: {
|
|
6416
|
-
kind: "replace-owned-keys",
|
|
6417
|
-
ownedKeys: ["mcp_servers"]
|
|
6418
|
-
},
|
|
6419
|
-
hooks: {
|
|
6420
|
-
kind: "replace-owned-keys",
|
|
6421
|
-
ownedKeys: ["hooks"]
|
|
6422
|
-
},
|
|
6423
|
-
permissions: {
|
|
6424
|
-
kind: "deep-merge",
|
|
6425
|
-
replaceKeys: ["permissions"]
|
|
6426
|
-
}
|
|
6427
|
-
}
|
|
6428
|
-
},
|
|
6697
|
+
[HERMES_CONFIG_SHARED_FILE_KEY]: HERMES_CONFIG_DECLARATION,
|
|
6698
|
+
[HERMES_WIN32_CONFIG_SHARED_FILE_KEY]: HERMES_CONFIG_DECLARATION,
|
|
6699
|
+
[HERMES_HOME_CONFIG_SHARED_FILE_KEY]: HERMES_CONFIG_DECLARATION,
|
|
6429
6700
|
[TAKT_CONFIG_SHARED_FILE_KEY]: {
|
|
6430
6701
|
format: "yaml",
|
|
6431
6702
|
invalidRootPolicy: "error",
|
|
@@ -6471,6 +6742,10 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6471
6742
|
".config/zed/settings.json": {
|
|
6472
6743
|
format: "json",
|
|
6473
6744
|
features: {
|
|
6745
|
+
ignore: {
|
|
6746
|
+
kind: "replace-owned-keys",
|
|
6747
|
+
ownedKeys: ["private_files"]
|
|
6748
|
+
},
|
|
6474
6749
|
mcp: {
|
|
6475
6750
|
kind: "replace-owned-keys",
|
|
6476
6751
|
ownedKeys: ["context_servers"]
|
|
@@ -6484,6 +6759,10 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6484
6759
|
"AppData/Roaming/Zed/settings.json": {
|
|
6485
6760
|
format: "json",
|
|
6486
6761
|
features: {
|
|
6762
|
+
ignore: {
|
|
6763
|
+
kind: "replace-owned-keys",
|
|
6764
|
+
ownedKeys: ["private_files"]
|
|
6765
|
+
},
|
|
6487
6766
|
mcp: {
|
|
6488
6767
|
kind: "replace-owned-keys",
|
|
6489
6768
|
ownedKeys: ["context_servers"]
|
|
@@ -6740,31 +7019,15 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6740
7019
|
}
|
|
6741
7020
|
}
|
|
6742
7021
|
},
|
|
6743
|
-
[KIMI_CODE_CONFIG_SHARED_FILE_KEY]:
|
|
6744
|
-
|
|
6745
|
-
invalidRootPolicy: "error",
|
|
6746
|
-
features: {
|
|
6747
|
-
hooks: {
|
|
6748
|
-
kind: "replace-owned-keys",
|
|
6749
|
-
ownedKeys: ["hooks"]
|
|
6750
|
-
},
|
|
6751
|
-
mcp: {
|
|
6752
|
-
kind: "replace-owned-keys",
|
|
6753
|
-
ownedKeys: ["mcp"]
|
|
6754
|
-
},
|
|
6755
|
-
permissions: {
|
|
6756
|
-
kind: "replace-owned-keys",
|
|
6757
|
-
ownedKeys: [
|
|
6758
|
-
"permission",
|
|
6759
|
-
"default_permission_mode",
|
|
6760
|
-
"tools"
|
|
6761
|
-
]
|
|
6762
|
-
}
|
|
6763
|
-
}
|
|
6764
|
-
},
|
|
7022
|
+
[KIMI_CODE_CONFIG_SHARED_FILE_KEY]: KIMI_CODE_CONFIG_DECLARATION,
|
|
7023
|
+
[KIMI_CODE_HOME_CONFIG_SHARED_FILE_KEY]: KIMI_CODE_CONFIG_DECLARATION,
|
|
6765
7024
|
[REASONIX_PROJECT_CONFIG_SHARED_FILE_KEY]: {
|
|
6766
7025
|
format: "toml",
|
|
6767
7026
|
features: {
|
|
7027
|
+
ignore: {
|
|
7028
|
+
kind: "custom",
|
|
7029
|
+
policyFunction: "applyIgnoreReadDenies"
|
|
7030
|
+
},
|
|
6768
7031
|
mcp: {
|
|
6769
7032
|
kind: "replace-owned-keys",
|
|
6770
7033
|
ownedKeys: ["plugins"]
|
|
@@ -6782,6 +7045,10 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6782
7045
|
[REASONIX_GLOBAL_CONFIG_SHARED_FILE_KEY]: {
|
|
6783
7046
|
format: "toml",
|
|
6784
7047
|
features: {
|
|
7048
|
+
ignore: {
|
|
7049
|
+
kind: "custom",
|
|
7050
|
+
policyFunction: "applyIgnoreReadDenies"
|
|
7051
|
+
},
|
|
6785
7052
|
mcp: {
|
|
6786
7053
|
kind: "replace-owned-keys",
|
|
6787
7054
|
ownedKeys: ["plugins"]
|
|
@@ -7226,6 +7493,13 @@ const toolCheckFactories = /* @__PURE__ */ new Map([
|
|
|
7226
7493
|
filePattern: "*.json"
|
|
7227
7494
|
}
|
|
7228
7495
|
}],
|
|
7496
|
+
["rovodev", {
|
|
7497
|
+
class: RovodevCheck,
|
|
7498
|
+
meta: {
|
|
7499
|
+
supportsGlobal: false,
|
|
7500
|
+
filePattern: ROVODEV_REVIEW_AGENT_FILE_NAME
|
|
7501
|
+
}
|
|
7502
|
+
}],
|
|
7229
7503
|
["takt", {
|
|
7230
7504
|
class: TaktCheck,
|
|
7231
7505
|
meta: {
|
|
@@ -7381,19 +7655,65 @@ var ChecksProcessor = class extends FeatureProcessor {
|
|
|
7381
7655
|
}
|
|
7382
7656
|
};
|
|
7383
7657
|
//#endregion
|
|
7658
|
+
//#region src/utils/tool-home.ts
|
|
7659
|
+
/**
|
|
7660
|
+
* Where the rulesync-side source files of a tool with a home override belong.
|
|
7661
|
+
*
|
|
7662
|
+
* A home override redirects the tool's OWN output, but the `.rulesync/` sources
|
|
7663
|
+
* imported back out of it are not part of the tool's profile — they stay under
|
|
7664
|
+
* the rulesync home. When no override is set, the native output root already is
|
|
7665
|
+
* that place.
|
|
7666
|
+
*/
|
|
7667
|
+
function getToolRulesyncOutputRoot({ nativeOutputRoot, global, toolHome }) {
|
|
7668
|
+
return global && toolHome() ? getHomeDirectory() : nativeOutputRoot;
|
|
7669
|
+
}
|
|
7670
|
+
//#endregion
|
|
7384
7671
|
//#region src/utils/hermesagent.ts
|
|
7385
7672
|
function getHermesagentHome() {
|
|
7386
7673
|
const configuredHome = process.env.HERMES_HOME?.trim();
|
|
7387
7674
|
return configuredHome ? (0, node_path.resolve)(configuredHome) : void 0;
|
|
7388
7675
|
}
|
|
7676
|
+
/**
|
|
7677
|
+
* The home-relative Hermes profile directory used when `HERMES_HOME` is unset.
|
|
7678
|
+
*
|
|
7679
|
+
* Upstream `_get_platform_default_hermes_home()` returns `%LOCALAPPDATA%\hermes`
|
|
7680
|
+
* on win32 and `~/.hermes` everywhere else, so the global output directory is
|
|
7681
|
+
* platform-dependent — a global generate on Windows that wrote `~/.hermes`
|
|
7682
|
+
* would land where Hermes never reads.
|
|
7683
|
+
*
|
|
7684
|
+
* @see https://github.com/NousResearch/hermes-agent `hermes_constants.py`
|
|
7685
|
+
*/
|
|
7686
|
+
function getHermesagentGlobalDir() {
|
|
7687
|
+
return process.platform === "win32" ? HERMESAGENT_GLOBAL_WIN32_DIR : HERMESAGENT_GLOBAL_DIR;
|
|
7688
|
+
}
|
|
7389
7689
|
function resolveHermesagentOutputRoot({ outputRoot, global }) {
|
|
7390
7690
|
return global ? getHermesagentHome() ?? outputRoot : outputRoot;
|
|
7391
7691
|
}
|
|
7692
|
+
/**
|
|
7693
|
+
* Map a canonical `.hermes/...` path constant onto the directory rulesync
|
|
7694
|
+
* actually writes in the requested scope.
|
|
7695
|
+
*
|
|
7696
|
+
* Project scope keeps the constant as-is (the project tree is `.hermes/`
|
|
7697
|
+
* everywhere). Global scope strips the `.hermes` prefix and re-anchors it:
|
|
7698
|
+
* `HERMES_HOME` *is* the profile root, so nothing is prepended; otherwise the
|
|
7699
|
+
* platform default directory takes its place.
|
|
7700
|
+
*/
|
|
7392
7701
|
function getHermesagentRelativeDirPath({ global, relativeDirPath }) {
|
|
7393
|
-
if (!global
|
|
7702
|
+
if (!global) return relativeDirPath;
|
|
7394
7703
|
const relativePath = (0, node_path.relative)(HERMESAGENT_GLOBAL_DIR, relativeDirPath);
|
|
7395
|
-
|
|
7396
|
-
|
|
7704
|
+
try {
|
|
7705
|
+
checkPathTraversal({
|
|
7706
|
+
relativePath: relativeDirPath,
|
|
7707
|
+
intendedRootDir: "."
|
|
7708
|
+
});
|
|
7709
|
+
checkPathTraversal({
|
|
7710
|
+
relativePath,
|
|
7711
|
+
intendedRootDir: HERMESAGENT_GLOBAL_DIR
|
|
7712
|
+
});
|
|
7713
|
+
} catch {
|
|
7714
|
+
throw new Error(`Hermes Agent global path must be within ${HERMESAGENT_GLOBAL_DIR}: ${relativeDirPath}`);
|
|
7715
|
+
}
|
|
7716
|
+
return getHermesagentHome() ? relativePath || "." : (0, node_path.join)(getHermesagentGlobalDir(), relativePath);
|
|
7397
7717
|
}
|
|
7398
7718
|
function getHermesagentRelativeFilePath({ global, relativeFilePath }) {
|
|
7399
7719
|
return (0, node_path.join)(getHermesagentRelativeDirPath({
|
|
@@ -7401,8 +7721,53 @@ function getHermesagentRelativeFilePath({ global, relativeFilePath }) {
|
|
|
7401
7721
|
relativeDirPath: (0, node_path.dirname)(relativeFilePath)
|
|
7402
7722
|
}), (0, node_path.basename)(relativeFilePath));
|
|
7403
7723
|
}
|
|
7724
|
+
/**
|
|
7725
|
+
* Every spelling `config.yaml` can take in global scope, so that the
|
|
7726
|
+
* shared-write derivation and the gateway ownership table it is checked against
|
|
7727
|
+
* see the same set of keys on every platform and with or without `HERMES_HOME`.
|
|
7728
|
+
*
|
|
7729
|
+
* `getHermesagentRelativeDirPath` resolves exactly one of these per process,
|
|
7730
|
+
* which would otherwise make the derived shared-file key depend on the ambient
|
|
7731
|
+
* environment — the drift guards would then go blind in precisely the
|
|
7732
|
+
* configuration this feature exists for.
|
|
7733
|
+
*/
|
|
7734
|
+
function getHermesagentSharedConfigWritePaths() {
|
|
7735
|
+
return [
|
|
7736
|
+
{
|
|
7737
|
+
relativeDirPath: HERMESAGENT_GLOBAL_DIR,
|
|
7738
|
+
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
7739
|
+
},
|
|
7740
|
+
{
|
|
7741
|
+
relativeDirPath: HERMESAGENT_GLOBAL_WIN32_DIR,
|
|
7742
|
+
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
7743
|
+
},
|
|
7744
|
+
{
|
|
7745
|
+
relativeDirPath: ".",
|
|
7746
|
+
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
7747
|
+
}
|
|
7748
|
+
];
|
|
7749
|
+
}
|
|
7750
|
+
/**
|
|
7751
|
+
* The `SHARED_CONFIG_OWNERSHIP` key of the `config.yaml` this scope actually
|
|
7752
|
+
* writes. All three spellings carry the same declaration, but passing the key of
|
|
7753
|
+
* the file being written keeps the write path and the drift guards reading the
|
|
7754
|
+
* same entry.
|
|
7755
|
+
*/
|
|
7756
|
+
function getHermesagentConfigSharedFileKey({ global }) {
|
|
7757
|
+
return sharedConfigFileKey({
|
|
7758
|
+
relativeDirPath: getHermesagentRelativeDirPath({
|
|
7759
|
+
global,
|
|
7760
|
+
relativeDirPath: HERMESAGENT_GLOBAL_DIR
|
|
7761
|
+
}),
|
|
7762
|
+
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
7763
|
+
});
|
|
7764
|
+
}
|
|
7404
7765
|
function getHermesagentRulesyncOutputRoot({ nativeOutputRoot, global }) {
|
|
7405
|
-
return
|
|
7766
|
+
return getToolRulesyncOutputRoot({
|
|
7767
|
+
nativeOutputRoot,
|
|
7768
|
+
global,
|
|
7769
|
+
toolHome: getHermesagentHome
|
|
7770
|
+
});
|
|
7406
7771
|
}
|
|
7407
7772
|
//#endregion
|
|
7408
7773
|
//#region src/constants/agentsmd-paths.ts
|
|
@@ -9106,6 +9471,223 @@ var GooseCommand = class GooseCommand extends ToolCommand {
|
|
|
9106
9471
|
}
|
|
9107
9472
|
};
|
|
9108
9473
|
//#endregion
|
|
9474
|
+
//#region src/constants/grokcli-paths.ts
|
|
9475
|
+
/**
|
|
9476
|
+
* Grok Build CLI (xAI) configuration-layout conventions.
|
|
9477
|
+
*
|
|
9478
|
+
* Single source of truth for where Grok Build expects its files. Grok Build
|
|
9479
|
+
* stores MCP servers (and other settings) in a `config.toml` under `.grok/`,
|
|
9480
|
+
* with project/global scopes resolved by the directory the CLI runs in
|
|
9481
|
+
* (`./.grok/config.toml` vs `~/.grok/config.toml`).
|
|
9482
|
+
*
|
|
9483
|
+
* Verified against `grok` 0.2.54 (`grok mcp add --help`, `grok mcp add`):
|
|
9484
|
+
* `-s project` writes `./.grok/config.toml`, `-s user` writes
|
|
9485
|
+
* `~/.grok/config.toml`, both as a TOML `[mcp_servers.<name>]` table.
|
|
9486
|
+
* @see https://docs.x.ai/build/overview
|
|
9487
|
+
*/
|
|
9488
|
+
/** Root directory for Grok Build configuration, relative to the scope root. */
|
|
9489
|
+
const GROKCLI_DIR = ".grok";
|
|
9490
|
+
/** MCP servers and other settings live in `config.toml` under `.grok/`. */
|
|
9491
|
+
const GROKCLI_MCP_FILE_NAME = "config.toml";
|
|
9492
|
+
/**
|
|
9493
|
+
* Shared Grok CLI config file (`config.toml`). MCP servers, the `[ui]`
|
|
9494
|
+
* permission mode, and other settings all live here; permissions reuse the same
|
|
9495
|
+
* file name as MCP since Grok consolidates everything into one config.
|
|
9496
|
+
*/
|
|
9497
|
+
const GROKCLI_CONFIG_FILE_NAME = "config.toml";
|
|
9498
|
+
/** Skills directory under `.grok/` (project: `./.grok/skills`, global: `~/.grok/skills`). */
|
|
9499
|
+
const GROKCLI_SKILLS_DIR_PATH = (0, node_path.join)(GROKCLI_DIR, "skills");
|
|
9500
|
+
/**
|
|
9501
|
+
* Hooks directory under `.grok/`. Grok Build discovers hook config files from
|
|
9502
|
+
* `.grok/hooks/*.json` (project) and `~/.grok/hooks/*.json` (global), each a
|
|
9503
|
+
* standalone JSON file using the Claude-Code-compatible nested `{ hooks: { … } }`
|
|
9504
|
+
* shape. rulesync writes all its hooks into a single `rulesync.json`.
|
|
9505
|
+
* @see https://docs.x.ai/build/features/hooks
|
|
9506
|
+
*/
|
|
9507
|
+
const GROKCLI_HOOKS_DIR_PATH = (0, node_path.join)(GROKCLI_DIR, "hooks");
|
|
9508
|
+
/** rulesync-managed Grok hooks file under `.grok/hooks/`. */
|
|
9509
|
+
const GROKCLI_HOOKS_FILE_NAME = "rulesync.json";
|
|
9510
|
+
/**
|
|
9511
|
+
* Subagents (agent profiles) directory under `.grok/`. Grok Build discovers
|
|
9512
|
+
* agent definitions from `.grok/agents/*.md` (project) and `~/.grok/agents/*.md`
|
|
9513
|
+
* (global), each a Markdown file with YAML frontmatter (verified via
|
|
9514
|
+
* `grok inspect`; format matches the bundled `~/.grok/bundled/agents/*.md`).
|
|
9515
|
+
*/
|
|
9516
|
+
const GROKCLI_AGENTS_DIR_PATH = (0, node_path.join)(GROKCLI_DIR, "agents");
|
|
9517
|
+
/**
|
|
9518
|
+
* Instruction file. Grok reads the AGENTS.md instruction-file family natively,
|
|
9519
|
+
* including the user-level `~/.grok/AGENTS.md` for global rules (verified via
|
|
9520
|
+
* `grok inspect`, consistent with the `.grok/` global discovery used by the
|
|
9521
|
+
* MCP/skills/subagents adapters).
|
|
9522
|
+
*/
|
|
9523
|
+
const GROKCLI_RULE_FILE_NAME = "AGENTS.md";
|
|
9524
|
+
/**
|
|
9525
|
+
* Custom slash commands directory. Grok's `find_command_paths` scans
|
|
9526
|
+
* `commands/*.md` under every discovered config dir — `.grok/commands/`
|
|
9527
|
+
* (project, walked from cwd up to the git root) and `~/.grok/commands/`
|
|
9528
|
+
* (global). The scan is **flat and non-recursive**, so subdirectory
|
|
9529
|
+
* namespacing (`git/commit.md` → `/git:commit`) is not supported the way it is
|
|
9530
|
+
* for Claude Code.
|
|
9531
|
+
*
|
|
9532
|
+
* Skills are collected before commands and win name collisions, so a
|
|
9533
|
+
* `.grok/skills/<name>/` shadows `.grok/commands/<name>.md`.
|
|
9534
|
+
* @see https://docs.x.ai/build/features/skills-plugins-marketplaces
|
|
9535
|
+
*/
|
|
9536
|
+
const GROKCLI_COMMANDS_DIR_PATH = (0, node_path.join)(GROKCLI_DIR, "commands");
|
|
9537
|
+
/**
|
|
9538
|
+
* Non-root rules directory. Grok scans `*.md` here — flat, sorted by name —
|
|
9539
|
+
* alongside the AGENTS.md family: `.grok/rules/` in each project directory it
|
|
9540
|
+
* walks, and `~/.grok/rules/` in the home scope.
|
|
9541
|
+
* @see https://docs.x.ai/build/overview
|
|
9542
|
+
*/
|
|
9543
|
+
const GROKCLI_RULES_DIR_PATH = (0, node_path.join)(GROKCLI_DIR, "rules");
|
|
9544
|
+
//#endregion
|
|
9545
|
+
//#region src/features/commands/grokcli-command.ts
|
|
9546
|
+
/**
|
|
9547
|
+
* Grok CLI custom slash commands are Markdown files under `.grok/commands/`
|
|
9548
|
+
* (project) / `~/.grok/commands/` (global), discovered by the same
|
|
9549
|
+
* Claude-Code-compatible frontmatter parser Grok uses for skills.
|
|
9550
|
+
*
|
|
9551
|
+
* Two upstream constraints shape this adapter:
|
|
9552
|
+
*
|
|
9553
|
+
* - The scan is **flat and non-recursive**, so nested namespacing is not
|
|
9554
|
+
* modelled (`supportsSubdirectory: false` flattens nested rulesync commands
|
|
9555
|
+
* onto their basename).
|
|
9556
|
+
* - Skills are collected before commands and win name collisions, so a
|
|
9557
|
+
* `.grok/skills/<name>/` shadows `.grok/commands/<name>.md`.
|
|
9558
|
+
*
|
|
9559
|
+
* `description` and `argument-hint` describe the command, while
|
|
9560
|
+
* `user-invocable` (default true) and `disable-model-invocation` (default
|
|
9561
|
+
* false) control who may invoke it — the same pair `GrokcliSkill` emits.
|
|
9562
|
+
* @see https://docs.x.ai/build/features/skills-plugins-marketplaces
|
|
9563
|
+
*/
|
|
9564
|
+
const GrokcliCommandFrontmatterSchema = zod_mini.z.looseObject({
|
|
9565
|
+
description: zod_mini.z.optional(zod_mini.z.string()),
|
|
9566
|
+
"argument-hint": zod_mini.z.optional(zod_mini.z.string()),
|
|
9567
|
+
"user-invocable": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
9568
|
+
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean())
|
|
9569
|
+
});
|
|
9570
|
+
var GrokcliCommand = class GrokcliCommand extends ToolCommand {
|
|
9571
|
+
frontmatter;
|
|
9572
|
+
body;
|
|
9573
|
+
constructor({ frontmatter, body, ...rest }) {
|
|
9574
|
+
if (rest.validate) {
|
|
9575
|
+
const result = GrokcliCommandFrontmatterSchema.safeParse(frontmatter);
|
|
9576
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${(0, node_path.join)(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
|
|
9577
|
+
}
|
|
9578
|
+
super({
|
|
9579
|
+
...rest,
|
|
9580
|
+
fileContent: stringifyFrontmatter(body, frontmatter)
|
|
9581
|
+
});
|
|
9582
|
+
this.frontmatter = frontmatter;
|
|
9583
|
+
this.body = body;
|
|
9584
|
+
}
|
|
9585
|
+
static getSettablePaths(_options = {}) {
|
|
9586
|
+
return { relativeDirPath: GROKCLI_COMMANDS_DIR_PATH };
|
|
9587
|
+
}
|
|
9588
|
+
getBody() {
|
|
9589
|
+
return this.body;
|
|
9590
|
+
}
|
|
9591
|
+
getFrontmatter() {
|
|
9592
|
+
return this.frontmatter;
|
|
9593
|
+
}
|
|
9594
|
+
toRulesyncCommand() {
|
|
9595
|
+
const { description, ...restFields } = this.frontmatter;
|
|
9596
|
+
const rulesyncFrontmatter = {
|
|
9597
|
+
targets: ["*"],
|
|
9598
|
+
description,
|
|
9599
|
+
...Object.keys(restFields).length > 0 && { grokcli: restFields }
|
|
9600
|
+
};
|
|
9601
|
+
return new RulesyncCommand({
|
|
9602
|
+
outputRoot: ".",
|
|
9603
|
+
frontmatter: rulesyncFrontmatter,
|
|
9604
|
+
body: this.body,
|
|
9605
|
+
relativeDirPath: RulesyncCommand.getSettablePaths().relativeDirPath,
|
|
9606
|
+
relativeFilePath: this.relativeFilePath,
|
|
9607
|
+
fileContent: stringifyFrontmatter(this.body, rulesyncFrontmatter),
|
|
9608
|
+
validate: true
|
|
9609
|
+
});
|
|
9610
|
+
}
|
|
9611
|
+
static fromRulesyncCommand({ outputRoot = process.cwd(), rulesyncCommand, validate = true, global = false }) {
|
|
9612
|
+
const rulesyncFrontmatter = rulesyncCommand.getFrontmatter();
|
|
9613
|
+
const grokcliFields = rulesyncFrontmatter.grokcli ?? {};
|
|
9614
|
+
const grokcliFrontmatter = {
|
|
9615
|
+
description: rulesyncFrontmatter.description,
|
|
9616
|
+
...grokcliFields
|
|
9617
|
+
};
|
|
9618
|
+
const paths = this.getSettablePaths({ global });
|
|
9619
|
+
return new GrokcliCommand({
|
|
9620
|
+
outputRoot,
|
|
9621
|
+
frontmatter: grokcliFrontmatter,
|
|
9622
|
+
body: rulesyncCommand.getBody(),
|
|
9623
|
+
relativeDirPath: paths.relativeDirPath,
|
|
9624
|
+
relativeFilePath: rulesyncCommand.getRelativeFilePath(),
|
|
9625
|
+
validate
|
|
9626
|
+
});
|
|
9627
|
+
}
|
|
9628
|
+
validate() {
|
|
9629
|
+
if (!this.frontmatter) return {
|
|
9630
|
+
success: true,
|
|
9631
|
+
error: null
|
|
9632
|
+
};
|
|
9633
|
+
const result = GrokcliCommandFrontmatterSchema.safeParse(this.frontmatter);
|
|
9634
|
+
if (result.success) return {
|
|
9635
|
+
success: true,
|
|
9636
|
+
error: null
|
|
9637
|
+
};
|
|
9638
|
+
return {
|
|
9639
|
+
success: false,
|
|
9640
|
+
error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${(0, node_path.join)(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
|
|
9641
|
+
};
|
|
9642
|
+
}
|
|
9643
|
+
static isTargetedByRulesyncCommand(rulesyncCommand) {
|
|
9644
|
+
return this.isTargetedByRulesyncCommandDefault({
|
|
9645
|
+
rulesyncCommand,
|
|
9646
|
+
toolTarget: "grokcli"
|
|
9647
|
+
});
|
|
9648
|
+
}
|
|
9649
|
+
/**
|
|
9650
|
+
* Warn when a rulesync skill would shadow a rulesync command.
|
|
9651
|
+
*
|
|
9652
|
+
* Grok collects skills before commands and lets skills win name collisions,
|
|
9653
|
+
* so `.grok/skills/<name>/` makes `.grok/commands/<name>.md` unreachable.
|
|
9654
|
+
* Both files are still written correctly — nothing is overwritten and no
|
|
9655
|
+
* output is lost — so this warns rather than failing the run the way the
|
|
9656
|
+
* Hermes check does, where the two surfaces really do write the same path.
|
|
9657
|
+
*/
|
|
9658
|
+
static async validateRulesyncCommands({ inputRoot, rulesyncCommands, logger }) {
|
|
9659
|
+
const commandNames = new Set(rulesyncCommands.filter((command) => this.isTargetedByRulesyncCommand(command)).map((command) => (0, node_path.basename)(command.getRelativeFilePath(), ".md")));
|
|
9660
|
+
if (commandNames.size === 0) return;
|
|
9661
|
+
const shadowed = (await findFilesByGlobs((0, node_path.join)((0, node_path.join)(inputRoot, RULESYNC_SKILLS_RELATIVE_DIR_PATH), "**", "SKILL.md"))).map((filePath) => (0, node_path.basename)((0, node_path.dirname)(filePath))).filter((skillName) => commandNames.has(skillName));
|
|
9662
|
+
if (shadowed.length > 0) logger.warn(`Grok CLI resolves skills before commands, so these skills shadow the same-named commands, which will never be reachable: ${[...new Set(shadowed)].toSorted().join(", ")}. Rename either side to make both invocable.`);
|
|
9663
|
+
}
|
|
9664
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
9665
|
+
const paths = this.getSettablePaths({ global });
|
|
9666
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
9667
|
+
const { frontmatter, body: content } = parseFrontmatter(await readFileContent(filePath), filePath);
|
|
9668
|
+
const result = GrokcliCommandFrontmatterSchema.safeParse(frontmatter);
|
|
9669
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
9670
|
+
return new GrokcliCommand({
|
|
9671
|
+
outputRoot,
|
|
9672
|
+
relativeDirPath: paths.relativeDirPath,
|
|
9673
|
+
relativeFilePath,
|
|
9674
|
+
frontmatter: result.data,
|
|
9675
|
+
body: content.trim(),
|
|
9676
|
+
validate
|
|
9677
|
+
});
|
|
9678
|
+
}
|
|
9679
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
9680
|
+
return new GrokcliCommand({
|
|
9681
|
+
outputRoot,
|
|
9682
|
+
relativeDirPath,
|
|
9683
|
+
relativeFilePath,
|
|
9684
|
+
frontmatter: { description: "" },
|
|
9685
|
+
body: "",
|
|
9686
|
+
validate: false
|
|
9687
|
+
});
|
|
9688
|
+
}
|
|
9689
|
+
};
|
|
9690
|
+
//#endregion
|
|
9109
9691
|
//#region src/features/skills/tool-skill.ts
|
|
9110
9692
|
/** Ordered skill directory roots: primary first. */
|
|
9111
9693
|
function toolSkillSearchRoots(paths) {
|
|
@@ -9740,7 +10322,7 @@ def register(ctx):
|
|
|
9740
10322
|
_register_command(ctx, command)
|
|
9741
10323
|
`;
|
|
9742
10324
|
}
|
|
9743
|
-
function getEnabledPluginConfigContent$1(currentContent) {
|
|
10325
|
+
function getEnabledPluginConfigContent$1({ currentContent, global }) {
|
|
9744
10326
|
const config = parseSharedConfig({
|
|
9745
10327
|
format: "yaml",
|
|
9746
10328
|
fileContent: currentContent
|
|
@@ -9748,7 +10330,7 @@ function getEnabledPluginConfigContent$1(currentContent) {
|
|
|
9748
10330
|
const plugins = config.plugins && typeof config.plugins === "object" ? config.plugins : {};
|
|
9749
10331
|
const enabled = Array.isArray(plugins.enabled) ? plugins.enabled : [];
|
|
9750
10332
|
return applySharedConfigPatch({
|
|
9751
|
-
fileKey:
|
|
10333
|
+
fileKey: getHermesagentConfigSharedFileKey({ global }),
|
|
9752
10334
|
feature: "commands",
|
|
9753
10335
|
existingContent: currentContent,
|
|
9754
10336
|
patch: { plugins: {
|
|
@@ -9757,7 +10339,7 @@ function getEnabledPluginConfigContent$1(currentContent) {
|
|
|
9757
10339
|
} }
|
|
9758
10340
|
});
|
|
9759
10341
|
}
|
|
9760
|
-
function getDisabledHermesCommandsPluginConfigContent(currentContent) {
|
|
10342
|
+
function getDisabledHermesCommandsPluginConfigContent({ currentContent, global }) {
|
|
9761
10343
|
const config = parseSharedConfig({
|
|
9762
10344
|
format: "yaml",
|
|
9763
10345
|
fileContent: currentContent
|
|
@@ -9765,7 +10347,7 @@ function getDisabledHermesCommandsPluginConfigContent(currentContent) {
|
|
|
9765
10347
|
const plugins = config.plugins && typeof config.plugins === "object" ? config.plugins : {};
|
|
9766
10348
|
const enabled = Array.isArray(plugins.enabled) ? plugins.enabled : [];
|
|
9767
10349
|
return applySharedConfigPatch({
|
|
9768
|
-
fileKey:
|
|
10350
|
+
fileKey: getHermesagentConfigSharedFileKey({ global }),
|
|
9769
10351
|
feature: "commands",
|
|
9770
10352
|
existingContent: currentContent,
|
|
9771
10353
|
patch: { plugins: {
|
|
@@ -9781,35 +10363,36 @@ var HermesagentCommandAuxiliaryFile = class extends ToolFile {
|
|
|
9781
10363
|
error: null
|
|
9782
10364
|
};
|
|
9783
10365
|
}
|
|
9784
|
-
|
|
10366
|
+
/**
|
|
10367
|
+
* Whether this auxiliary file is the one at `relativeFilePath`, comparing
|
|
10368
|
+
* against the scope-resolved location of that canonical `.hermes/...` path.
|
|
10369
|
+
*/
|
|
10370
|
+
matchesPath(relativeFilePath) {
|
|
9785
10371
|
return this.getRelativePathFromCwd() === toPosixPath(getHermesagentRelativeFilePath({
|
|
9786
10372
|
global: this.global,
|
|
9787
|
-
relativeFilePath
|
|
10373
|
+
relativeFilePath
|
|
9788
10374
|
}));
|
|
9789
10375
|
}
|
|
10376
|
+
shouldMergeExistingFileContent() {
|
|
10377
|
+
return this.matchesPath(HERMESAGENT_CONFIG_FILE_PATH);
|
|
10378
|
+
}
|
|
9790
10379
|
setFileContent(newFileContent) {
|
|
9791
|
-
if (this.
|
|
9792
|
-
|
|
9793
|
-
|
|
9794
|
-
|
|
9795
|
-
|
|
10380
|
+
if (this.matchesPath(HERMESAGENT_CONFIG_FILE_PATH)) {
|
|
10381
|
+
super.setFileContent(getEnabledPluginConfigContent$1({
|
|
10382
|
+
currentContent: newFileContent,
|
|
10383
|
+
global: this.global
|
|
10384
|
+
}));
|
|
9796
10385
|
return;
|
|
9797
10386
|
}
|
|
9798
10387
|
super.setFileContent(newFileContent);
|
|
9799
10388
|
}
|
|
9800
10389
|
getFileContent() {
|
|
9801
|
-
if (this.
|
|
9802
|
-
|
|
9803
|
-
|
|
9804
|
-
|
|
9805
|
-
|
|
9806
|
-
|
|
9807
|
-
relativeFilePath: HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_INIT_PATH
|
|
9808
|
-
}))) return getPluginInitContent$2();
|
|
9809
|
-
if (this.getRelativePathFromCwd() === toPosixPath(getHermesagentRelativeFilePath({
|
|
9810
|
-
global: this.global,
|
|
9811
|
-
relativeFilePath: HERMESAGENT_CONFIG_FILE_PATH
|
|
9812
|
-
}))) return getEnabledPluginConfigContent$1(super.getFileContent());
|
|
10390
|
+
if (this.matchesPath(HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_MANIFEST_PATH)) return getPluginManifestContent$2();
|
|
10391
|
+
if (this.matchesPath(HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_INIT_PATH)) return getPluginInitContent$2();
|
|
10392
|
+
if (this.matchesPath(HERMESAGENT_CONFIG_FILE_PATH)) return getEnabledPluginConfigContent$1({
|
|
10393
|
+
currentContent: super.getFileContent(),
|
|
10394
|
+
global: this.global
|
|
10395
|
+
});
|
|
9813
10396
|
return super.getFileContent();
|
|
9814
10397
|
}
|
|
9815
10398
|
};
|
|
@@ -9826,14 +10409,12 @@ var HermesagentCommand = class HermesagentCommand extends ToolCommand {
|
|
|
9826
10409
|
relativeDirPath: HERMESAGENT_RULESYNC_COMMANDS_DIR_PATH
|
|
9827
10410
|
}) };
|
|
9828
10411
|
}
|
|
9829
|
-
|
|
9830
|
-
|
|
9831
|
-
|
|
9832
|
-
|
|
9833
|
-
|
|
9834
|
-
|
|
9835
|
-
relativeFilePath: (0, node_path.basename)(HERMESAGENT_CONFIG_FILE_PATH)
|
|
9836
|
-
}];
|
|
10412
|
+
/**
|
|
10413
|
+
* `config.yaml` under every spelling the global profile root can take.
|
|
10414
|
+
* @see getHermesagentSharedConfigWritePaths
|
|
10415
|
+
*/
|
|
10416
|
+
static getExtraSharedWritePaths() {
|
|
10417
|
+
return getHermesagentSharedConfigWritePaths();
|
|
9837
10418
|
}
|
|
9838
10419
|
static async validateRulesyncCommands({ inputRoot, rulesyncCommands }) {
|
|
9839
10420
|
const commandSlugs = /* @__PURE__ */ new Set();
|
|
@@ -9858,33 +10439,28 @@ var HermesagentCommand = class HermesagentCommand extends ToolCommand {
|
|
|
9858
10439
|
}
|
|
9859
10440
|
static async getAuxiliaryFiles({ toolCommands, outputRoot, global = false, forDeletion = false }) {
|
|
9860
10441
|
if (toolCommands.length === 0 && !forDeletion) return [];
|
|
10442
|
+
const pluginDirPath = getHermesagentRelativeDirPath({
|
|
10443
|
+
global,
|
|
10444
|
+
relativeDirPath: HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_DIR_PATH
|
|
10445
|
+
});
|
|
9861
10446
|
const pluginFiles = [
|
|
9862
10447
|
new HermesagentCommandAuxiliaryFile({
|
|
9863
10448
|
outputRoot,
|
|
9864
|
-
relativeDirPath:
|
|
9865
|
-
global,
|
|
9866
|
-
relativeDirPath: HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_DIR_PATH
|
|
9867
|
-
}),
|
|
10449
|
+
relativeDirPath: pluginDirPath,
|
|
9868
10450
|
relativeFilePath: (0, node_path.basename)(HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_MANIFEST_PATH),
|
|
9869
10451
|
fileContent: "",
|
|
9870
10452
|
global
|
|
9871
10453
|
}),
|
|
9872
10454
|
new HermesagentCommandAuxiliaryFile({
|
|
9873
10455
|
outputRoot,
|
|
9874
|
-
relativeDirPath:
|
|
9875
|
-
global,
|
|
9876
|
-
relativeDirPath: HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_DIR_PATH
|
|
9877
|
-
}),
|
|
10456
|
+
relativeDirPath: pluginDirPath,
|
|
9878
10457
|
relativeFilePath: (0, node_path.basename)(HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_OWNERSHIP_PATH),
|
|
9879
10458
|
fileContent: "Generated and owned by RuleSync.\n",
|
|
9880
10459
|
global
|
|
9881
10460
|
}),
|
|
9882
10461
|
new HermesagentCommandAuxiliaryFile({
|
|
9883
10462
|
outputRoot,
|
|
9884
|
-
relativeDirPath:
|
|
9885
|
-
global,
|
|
9886
|
-
relativeDirPath: HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_DIR_PATH
|
|
9887
|
-
}),
|
|
10463
|
+
relativeDirPath: pluginDirPath,
|
|
9888
10464
|
relativeFilePath: (0, node_path.basename)(HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_INIT_PATH),
|
|
9889
10465
|
fileContent: "",
|
|
9890
10466
|
global
|
|
@@ -11119,19 +11695,6 @@ var RooCommand = class RooCommand extends ToolCommand {
|
|
|
11119
11695
|
}
|
|
11120
11696
|
};
|
|
11121
11697
|
//#endregion
|
|
11122
|
-
//#region src/constants/rovodev-paths.ts
|
|
11123
|
-
const ROVODEV_DIR = ".rovodev";
|
|
11124
|
-
const ROVODEV_SKILLS_DIR_PATH = (0, node_path.join)(ROVODEV_DIR, "skills");
|
|
11125
|
-
const ROVODEV_SUBAGENTS_DIR_PATH = (0, node_path.join)(ROVODEV_DIR, "subagents");
|
|
11126
|
-
const ROVODEV_MODULAR_RULES_DIR_PATH = (0, node_path.join)(ROVODEV_DIR, ".rulesync", "modular-rules");
|
|
11127
|
-
const ROVODEV_RULE_FILE_NAME = "AGENTS.md";
|
|
11128
|
-
const ROVODEV_LEGACY_RULE_FILE_NAME = "AGENTS.local.md";
|
|
11129
|
-
const ROVODEV_MCP_FILE_NAME = "mcp.json";
|
|
11130
|
-
const ROVODEV_CONFIG_FILE_NAME = "config.yml";
|
|
11131
|
-
const ROVODEV_AGENTS_SKILLS_DIR_PATH = (0, node_path.join)(".agents", "skills");
|
|
11132
|
-
const ROVODEV_PROMPTS_FILE_NAME = "prompts.yml";
|
|
11133
|
-
const ROVODEV_PROMPTS_DIR_PATH = (0, node_path.join)(ROVODEV_DIR, "prompts");
|
|
11134
|
-
//#endregion
|
|
11135
11698
|
//#region src/features/commands/rovodev-command.ts
|
|
11136
11699
|
/**
|
|
11137
11700
|
* Rovo Dev CLI "saved prompts": a file-based custom-command surface made of a
|
|
@@ -11715,6 +12278,16 @@ const toolCommandFactories = /* @__PURE__ */ new Map([
|
|
|
11715
12278
|
supportsSubdirectory: false
|
|
11716
12279
|
}
|
|
11717
12280
|
}],
|
|
12281
|
+
["grokcli", {
|
|
12282
|
+
class: GrokcliCommand,
|
|
12283
|
+
meta: {
|
|
12284
|
+
extension: "md",
|
|
12285
|
+
supportsProject: true,
|
|
12286
|
+
supportsGlobal: true,
|
|
12287
|
+
isSimulated: false,
|
|
12288
|
+
supportsSubdirectory: false
|
|
12289
|
+
}
|
|
12290
|
+
}],
|
|
11718
12291
|
["hermesagent", {
|
|
11719
12292
|
class: HermesagentCommand,
|
|
11720
12293
|
meta: {
|
|
@@ -11908,7 +12481,8 @@ var CommandsProcessor = class extends FeatureProcessor {
|
|
|
11908
12481
|
const factory = this.getFactory(this.toolTarget);
|
|
11909
12482
|
await factory.class.validateRulesyncCommands?.({
|
|
11910
12483
|
inputRoot: this.inputRoot,
|
|
11911
|
-
rulesyncCommands
|
|
12484
|
+
rulesyncCommands,
|
|
12485
|
+
logger: this.logger
|
|
11912
12486
|
});
|
|
11913
12487
|
const flattenedPathOrigins = /* @__PURE__ */ new Map();
|
|
11914
12488
|
const toolCommands = rulesyncCommands.map((rulesyncCommand) => {
|
|
@@ -12046,7 +12620,10 @@ var CommandsProcessor = class extends FeatureProcessor {
|
|
|
12046
12620
|
}));
|
|
12047
12621
|
const currentContent = await readFileContentOrNull(configPath);
|
|
12048
12622
|
if (currentContent === null) return changedCount;
|
|
12049
|
-
const nextContent = getDisabledHermesCommandsPluginConfigContent(
|
|
12623
|
+
const nextContent = getDisabledHermesCommandsPluginConfigContent({
|
|
12624
|
+
currentContent,
|
|
12625
|
+
global: this.global
|
|
12626
|
+
});
|
|
12050
12627
|
if (nextContent === currentContent) return changedCount;
|
|
12051
12628
|
if (this.dryRun) this.logger.info(`[DRY RUN] Would write: ${configPath}`);
|
|
12052
12629
|
else await writeFileContent(configPath, nextContent);
|
|
@@ -14508,64 +15085,6 @@ var GooseHooks = class GooseHooks extends ToolHooks {
|
|
|
14508
15085
|
}
|
|
14509
15086
|
};
|
|
14510
15087
|
//#endregion
|
|
14511
|
-
//#region src/constants/grokcli-paths.ts
|
|
14512
|
-
/**
|
|
14513
|
-
* Grok Build CLI (xAI) configuration-layout conventions.
|
|
14514
|
-
*
|
|
14515
|
-
* Single source of truth for where Grok Build expects its files. Grok Build
|
|
14516
|
-
* stores MCP servers (and other settings) in a `config.toml` under `.grok/`,
|
|
14517
|
-
* with project/global scopes resolved by the directory the CLI runs in
|
|
14518
|
-
* (`./.grok/config.toml` vs `~/.grok/config.toml`).
|
|
14519
|
-
*
|
|
14520
|
-
* Verified against `grok` 0.2.54 (`grok mcp add --help`, `grok mcp add`):
|
|
14521
|
-
* `-s project` writes `./.grok/config.toml`, `-s user` writes
|
|
14522
|
-
* `~/.grok/config.toml`, both as a TOML `[mcp_servers.<name>]` table.
|
|
14523
|
-
* @see https://docs.x.ai/build/overview
|
|
14524
|
-
*/
|
|
14525
|
-
/** Root directory for Grok Build configuration, relative to the scope root. */
|
|
14526
|
-
const GROKCLI_DIR = ".grok";
|
|
14527
|
-
/** MCP servers and other settings live in `config.toml` under `.grok/`. */
|
|
14528
|
-
const GROKCLI_MCP_FILE_NAME = "config.toml";
|
|
14529
|
-
/**
|
|
14530
|
-
* Shared Grok CLI config file (`config.toml`). MCP servers, the `[ui]`
|
|
14531
|
-
* permission mode, and other settings all live here; permissions reuse the same
|
|
14532
|
-
* file name as MCP since Grok consolidates everything into one config.
|
|
14533
|
-
*/
|
|
14534
|
-
const GROKCLI_CONFIG_FILE_NAME = "config.toml";
|
|
14535
|
-
/** Skills directory under `.grok/` (project: `./.grok/skills`, global: `~/.grok/skills`). */
|
|
14536
|
-
const GROKCLI_SKILLS_DIR_PATH = (0, node_path.join)(GROKCLI_DIR, "skills");
|
|
14537
|
-
/**
|
|
14538
|
-
* Hooks directory under `.grok/`. Grok Build discovers hook config files from
|
|
14539
|
-
* `.grok/hooks/*.json` (project) and `~/.grok/hooks/*.json` (global), each a
|
|
14540
|
-
* standalone JSON file using the Claude-Code-compatible nested `{ hooks: { … } }`
|
|
14541
|
-
* shape. rulesync writes all its hooks into a single `rulesync.json`.
|
|
14542
|
-
* @see https://docs.x.ai/build/features/hooks
|
|
14543
|
-
*/
|
|
14544
|
-
const GROKCLI_HOOKS_DIR_PATH = (0, node_path.join)(GROKCLI_DIR, "hooks");
|
|
14545
|
-
/** rulesync-managed Grok hooks file under `.grok/hooks/`. */
|
|
14546
|
-
const GROKCLI_HOOKS_FILE_NAME = "rulesync.json";
|
|
14547
|
-
/**
|
|
14548
|
-
* Subagents (agent profiles) directory under `.grok/`. Grok Build discovers
|
|
14549
|
-
* agent definitions from `.grok/agents/*.md` (project) and `~/.grok/agents/*.md`
|
|
14550
|
-
* (global), each a Markdown file with YAML frontmatter (verified via
|
|
14551
|
-
* `grok inspect`; format matches the bundled `~/.grok/bundled/agents/*.md`).
|
|
14552
|
-
*/
|
|
14553
|
-
const GROKCLI_AGENTS_DIR_PATH = (0, node_path.join)(GROKCLI_DIR, "agents");
|
|
14554
|
-
/**
|
|
14555
|
-
* Instruction file. Grok reads the AGENTS.md instruction-file family natively,
|
|
14556
|
-
* including the user-level `~/.grok/AGENTS.md` for global rules (verified via
|
|
14557
|
-
* `grok inspect`, consistent with the `.grok/` global discovery used by the
|
|
14558
|
-
* MCP/skills/subagents adapters).
|
|
14559
|
-
*/
|
|
14560
|
-
const GROKCLI_RULE_FILE_NAME = "AGENTS.md";
|
|
14561
|
-
/**
|
|
14562
|
-
* Non-root rules directory. Grok scans `*.md` here — flat, sorted by name —
|
|
14563
|
-
* alongside the AGENTS.md family: `.grok/rules/` in each project directory it
|
|
14564
|
-
* walks, and `~/.grok/rules/` in the home scope.
|
|
14565
|
-
* @see https://docs.x.ai/build/overview
|
|
14566
|
-
*/
|
|
14567
|
-
const GROKCLI_RULES_DIR_PATH = (0, node_path.join)(GROKCLI_DIR, "rules");
|
|
14568
|
-
//#endregion
|
|
14569
15088
|
//#region src/features/hooks/grokcli-hooks.ts
|
|
14570
15089
|
const GROKCLI_CONVERTER_CONFIG = {
|
|
14571
15090
|
supportedEvents: GROKCLI_HOOK_EVENTS,
|
|
@@ -14811,6 +15330,13 @@ var HermesagentHooks = class HermesagentHooks extends ToolHooks {
|
|
|
14811
15330
|
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
14812
15331
|
};
|
|
14813
15332
|
}
|
|
15333
|
+
/**
|
|
15334
|
+
* `config.yaml` under every spelling the global profile root can take.
|
|
15335
|
+
* @see getHermesagentSharedConfigWritePaths
|
|
15336
|
+
*/
|
|
15337
|
+
static getExtraSharedWritePaths() {
|
|
15338
|
+
return getHermesagentSharedConfigWritePaths();
|
|
15339
|
+
}
|
|
14814
15340
|
constructor(params) {
|
|
14815
15341
|
super({
|
|
14816
15342
|
...params,
|
|
@@ -14848,7 +15374,7 @@ var HermesagentHooks = class HermesagentHooks extends ToolHooks {
|
|
|
14848
15374
|
}
|
|
14849
15375
|
setFileContent(fileContent) {
|
|
14850
15376
|
this.fileContent = applySharedConfigPatch({
|
|
14851
|
-
fileKey:
|
|
15377
|
+
fileKey: getHermesagentConfigSharedFileKey({ global: this.global }),
|
|
14852
15378
|
feature: "hooks",
|
|
14853
15379
|
existingContent: fileContent,
|
|
14854
15380
|
patch: parseSharedConfig({
|
|
@@ -15224,8 +15750,38 @@ function getKimiCodeHome() {
|
|
|
15224
15750
|
function getKimiCodeRelativeDirPath({ global, relativeDirPath = "." }) {
|
|
15225
15751
|
return global && getKimiCodeHome() ? relativeDirPath : (0, node_path.join)(KIMI_CODE_DIR, relativeDirPath);
|
|
15226
15752
|
}
|
|
15753
|
+
/**
|
|
15754
|
+
* Both spellings the shared user `config.toml` can take: under `.kimi-code/`,
|
|
15755
|
+
* or at the root of `KIMI_CODE_HOME` when that override names the profile dir.
|
|
15756
|
+
* Declared unconditionally so the derived shared-file keys — and the drift
|
|
15757
|
+
* guards checked against them — do not depend on the ambient environment.
|
|
15758
|
+
*/
|
|
15759
|
+
function getKimiCodeSharedConfigWritePaths() {
|
|
15760
|
+
return [{
|
|
15761
|
+
relativeDirPath: KIMI_CODE_DIR,
|
|
15762
|
+
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
15763
|
+
}, {
|
|
15764
|
+
relativeDirPath: ".",
|
|
15765
|
+
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
15766
|
+
}];
|
|
15767
|
+
}
|
|
15768
|
+
/**
|
|
15769
|
+
* The `SHARED_CONFIG_OWNERSHIP` key of the `config.toml` actually being written.
|
|
15770
|
+
* Both spellings carry the same declaration, but passing the key of the file
|
|
15771
|
+
* being written keeps the write path and the drift guards on the same entry.
|
|
15772
|
+
*/
|
|
15773
|
+
function getKimiCodeConfigSharedFileKey({ global }) {
|
|
15774
|
+
return sharedConfigFileKey({
|
|
15775
|
+
relativeDirPath: getKimiCodeRelativeDirPath({ global }),
|
|
15776
|
+
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
15777
|
+
});
|
|
15778
|
+
}
|
|
15227
15779
|
function getKimiCodeRulesyncOutputRoot({ nativeOutputRoot, global }) {
|
|
15228
|
-
return
|
|
15780
|
+
return getToolRulesyncOutputRoot({
|
|
15781
|
+
nativeOutputRoot,
|
|
15782
|
+
global,
|
|
15783
|
+
toolHome: getKimiCodeHome
|
|
15784
|
+
});
|
|
15229
15785
|
}
|
|
15230
15786
|
//#endregion
|
|
15231
15787
|
//#region src/features/hooks/kimi-code-hooks.ts
|
|
@@ -15323,13 +15879,20 @@ var KimiCodeHooks = class KimiCodeHooks extends ToolHooks {
|
|
|
15323
15879
|
isDeletable() {
|
|
15324
15880
|
return false;
|
|
15325
15881
|
}
|
|
15882
|
+
/**
|
|
15883
|
+
* `config.toml` under both spellings its directory can take.
|
|
15884
|
+
* @see getKimiCodeSharedConfigWritePaths
|
|
15885
|
+
*/
|
|
15886
|
+
static getExtraSharedWritePaths() {
|
|
15887
|
+
return getKimiCodeSharedConfigWritePaths();
|
|
15888
|
+
}
|
|
15326
15889
|
shouldMergeExistingFileContent() {
|
|
15327
15890
|
return true;
|
|
15328
15891
|
}
|
|
15329
15892
|
setFileContent(fileContent) {
|
|
15330
15893
|
const paths = KimiCodeHooks.getSettablePaths({ global: this.global });
|
|
15331
15894
|
this.fileContent = applySharedConfigPatch({
|
|
15332
|
-
fileKey:
|
|
15895
|
+
fileKey: getKimiCodeConfigSharedFileKey({ global: this.global }),
|
|
15333
15896
|
feature: "hooks",
|
|
15334
15897
|
existingContent: fileContent,
|
|
15335
15898
|
patch: parseSharedConfig({
|
|
@@ -18020,6 +18583,151 @@ var QwencodeIgnore = class QwencodeIgnore extends ToolIgnore {
|
|
|
18020
18583
|
}
|
|
18021
18584
|
};
|
|
18022
18585
|
//#endregion
|
|
18586
|
+
//#region src/features/shared/reasonix-config-table.ts
|
|
18587
|
+
/**
|
|
18588
|
+
* Shape-narrowing helpers for the Reasonix TOML config (`reasonix.toml` /
|
|
18589
|
+
* `~/.reasonix/config.toml`), shared by the features that read-modify-write it.
|
|
18590
|
+
*
|
|
18591
|
+
* TOML is only structurally validated on parse, so a hand-edited config can
|
|
18592
|
+
* hold any type under `permissions` or inside `allow`/`ask`/`deny`. Both the
|
|
18593
|
+
* `permissions` and `ignore` adapters have to narrow the same two shapes
|
|
18594
|
+
* before merging, so the narrowing lives here once rather than being re-spelled
|
|
18595
|
+
* (and re-diverging) per feature.
|
|
18596
|
+
*/
|
|
18597
|
+
/** Keep only the string entries of a TOML array; anything else becomes `[]`. */
|
|
18598
|
+
function toReasonixStringArray(value) {
|
|
18599
|
+
if (!Array.isArray(value)) return [];
|
|
18600
|
+
return value.filter((entry) => typeof entry === "string");
|
|
18601
|
+
}
|
|
18602
|
+
/** Copy a TOML table; a non-table (scalar, array, missing) becomes `{}`. */
|
|
18603
|
+
function toReasonixTable(value) {
|
|
18604
|
+
if (!isPlainObject$1(value)) return {};
|
|
18605
|
+
return { ...value };
|
|
18606
|
+
}
|
|
18607
|
+
//#endregion
|
|
18608
|
+
//#region src/features/ignore/reasonix-ignore.ts
|
|
18609
|
+
const permissionsTableOf = (document) => toReasonixTable(document.permissions);
|
|
18610
|
+
/**
|
|
18611
|
+
* Reshape the parsed TOML document into the `permissions.allow/ask/deny` shape
|
|
18612
|
+
* {@link applyIgnoreReadDenies} operates on. Reasonix's `[permissions]` table
|
|
18613
|
+
* is Claude-Code-shaped (SPEC.md §3.7), so the entry-level ownership rule the
|
|
18614
|
+
* gateway already implements applies verbatim; only the surrounding file
|
|
18615
|
+
* format differs. Sibling keys such as `mode` pass through untouched.
|
|
18616
|
+
*/
|
|
18617
|
+
const asClaudeStyleSettings = (document) => {
|
|
18618
|
+
const table = permissionsTableOf(document);
|
|
18619
|
+
return {
|
|
18620
|
+
...document,
|
|
18621
|
+
permissions: {
|
|
18622
|
+
...table,
|
|
18623
|
+
allow: toReasonixStringArray(table.allow),
|
|
18624
|
+
ask: toReasonixStringArray(table.ask),
|
|
18625
|
+
deny: toReasonixStringArray(table.deny)
|
|
18626
|
+
}
|
|
18627
|
+
};
|
|
18628
|
+
};
|
|
18629
|
+
/**
|
|
18630
|
+
* Drop a `[permissions]` table that ended up with nothing in it, so an empty
|
|
18631
|
+
* `.rulesyncignore` does not add a bare table header to a file that never had
|
|
18632
|
+
* one.
|
|
18633
|
+
*/
|
|
18634
|
+
const withoutEmptyPermissions = (settings) => {
|
|
18635
|
+
const document = { ...settings };
|
|
18636
|
+
const permissions = document.permissions;
|
|
18637
|
+
if (isPlainObject$1(permissions) && Object.keys(permissions).length === 0) delete document.permissions;
|
|
18638
|
+
return document;
|
|
18639
|
+
};
|
|
18640
|
+
/**
|
|
18641
|
+
* Writes `.rulesyncignore` patterns as `Read(<pattern>)` entries in the
|
|
18642
|
+
* `[permissions] deny` table of `reasonix.toml` (project) /
|
|
18643
|
+
* `~/.reasonix/config.toml` (global).
|
|
18644
|
+
*
|
|
18645
|
+
* `deny` is the right target rather than `[sandbox] forbid_read`: deny rules
|
|
18646
|
+
* take glob specifiers (`Edit(docs/**)`) and are "a hard block in every mode",
|
|
18647
|
+
* while `forbid_read` is documented as absolute paths with no glob support.
|
|
18648
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/SPEC.md
|
|
18649
|
+
*/
|
|
18650
|
+
var ReasonixIgnore = class ReasonixIgnore extends ToolIgnore {
|
|
18651
|
+
constructor(params) {
|
|
18652
|
+
super(params);
|
|
18653
|
+
const document = parseSharedConfig({
|
|
18654
|
+
format: "toml",
|
|
18655
|
+
fileContent: this.fileContent
|
|
18656
|
+
});
|
|
18657
|
+
this.patterns = toReasonixStringArray(permissionsTableOf(document).deny);
|
|
18658
|
+
}
|
|
18659
|
+
static getSettablePaths({ global = false } = {}) {
|
|
18660
|
+
return {
|
|
18661
|
+
relativeDirPath: global ? REASONIX_GLOBAL_DIR : ".",
|
|
18662
|
+
relativeFilePath: global ? REASONIX_GLOBAL_PERMISSIONS_FILE_NAME : REASONIX_PROJECT_PERMISSIONS_FILE_NAME
|
|
18663
|
+
};
|
|
18664
|
+
}
|
|
18665
|
+
/**
|
|
18666
|
+
* The config file also carries `[[plugins]]`, `[permissions]` rules from the
|
|
18667
|
+
* permissions feature and user-authored tables, so rulesync must never
|
|
18668
|
+
* delete it.
|
|
18669
|
+
*/
|
|
18670
|
+
isDeletable() {
|
|
18671
|
+
return false;
|
|
18672
|
+
}
|
|
18673
|
+
toRulesyncIgnore() {
|
|
18674
|
+
const rulesyncPatterns = this.patterns.filter((pattern) => isReadDenyEntry(pattern)).map((pattern) => pattern.slice(5, -1)).filter((pattern) => pattern.length > 0);
|
|
18675
|
+
return new RulesyncIgnore({
|
|
18676
|
+
outputRoot: this.outputRoot,
|
|
18677
|
+
relativeDirPath: RulesyncIgnore.getSettablePaths().recommended.relativeDirPath,
|
|
18678
|
+
relativeFilePath: RulesyncIgnore.getSettablePaths().recommended.relativeFilePath,
|
|
18679
|
+
fileContent: rulesyncPatterns.join("\n")
|
|
18680
|
+
});
|
|
18681
|
+
}
|
|
18682
|
+
static async fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore, global = false }) {
|
|
18683
|
+
const readDenies = rulesyncIgnore.getFileContent().split(/\r?\n|\r/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).map((pattern) => buildReadDenyEntry(pattern));
|
|
18684
|
+
const paths = this.getSettablePaths({ global });
|
|
18685
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
18686
|
+
const existingDocument = parseSharedConfig({
|
|
18687
|
+
format: "toml",
|
|
18688
|
+
fileContent: await readFileContentOrNull(filePath) ?? "",
|
|
18689
|
+
filePath
|
|
18690
|
+
});
|
|
18691
|
+
const document = withoutEmptyPermissions(applyIgnoreReadDenies({
|
|
18692
|
+
settings: asClaudeStyleSettings(existingDocument),
|
|
18693
|
+
readDenies
|
|
18694
|
+
}));
|
|
18695
|
+
return new ReasonixIgnore({
|
|
18696
|
+
outputRoot,
|
|
18697
|
+
relativeDirPath: paths.relativeDirPath,
|
|
18698
|
+
relativeFilePath: paths.relativeFilePath,
|
|
18699
|
+
fileContent: stringifySharedConfig({
|
|
18700
|
+
format: "toml",
|
|
18701
|
+
document
|
|
18702
|
+
}),
|
|
18703
|
+
validate: true,
|
|
18704
|
+
global
|
|
18705
|
+
});
|
|
18706
|
+
}
|
|
18707
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
18708
|
+
const paths = this.getSettablePaths({ global });
|
|
18709
|
+
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
|
|
18710
|
+
return new ReasonixIgnore({
|
|
18711
|
+
outputRoot,
|
|
18712
|
+
relativeDirPath: paths.relativeDirPath,
|
|
18713
|
+
relativeFilePath: paths.relativeFilePath,
|
|
18714
|
+
fileContent,
|
|
18715
|
+
validate,
|
|
18716
|
+
global
|
|
18717
|
+
});
|
|
18718
|
+
}
|
|
18719
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
18720
|
+
return new ReasonixIgnore({
|
|
18721
|
+
outputRoot,
|
|
18722
|
+
relativeDirPath,
|
|
18723
|
+
relativeFilePath,
|
|
18724
|
+
fileContent: "",
|
|
18725
|
+
validate: false,
|
|
18726
|
+
global
|
|
18727
|
+
});
|
|
18728
|
+
}
|
|
18729
|
+
};
|
|
18730
|
+
//#endregion
|
|
18023
18731
|
//#region src/features/ignore/roo-ignore.ts
|
|
18024
18732
|
/**
|
|
18025
18733
|
* RooIgnore represents ignore patterns for the Roo Code AI coding assistant.
|
|
@@ -18177,6 +18885,14 @@ const ZED_GLOBAL_WIN32_DIR = (0, node_path.join)("AppData", "Roaming", "Zed");
|
|
|
18177
18885
|
function getZedGlobalDir() {
|
|
18178
18886
|
return process.platform === "win32" ? ZED_GLOBAL_WIN32_DIR : ZED_GLOBAL_DIR;
|
|
18179
18887
|
}
|
|
18888
|
+
/**
|
|
18889
|
+
* The global config dir of the OTHER platform. `getZedGlobalDir()` resolves one
|
|
18890
|
+
* spelling per platform, but the shared-write derivation (and the gateway
|
|
18891
|
+
* ownership table it is checked against) must know both on every platform.
|
|
18892
|
+
*/
|
|
18893
|
+
function getZedOtherPlatformGlobalDir() {
|
|
18894
|
+
return process.platform === "win32" ? ZED_GLOBAL_DIR : ZED_GLOBAL_WIN32_DIR;
|
|
18895
|
+
}
|
|
18180
18896
|
const ZED_SETTINGS_FILE_NAME = "settings.json";
|
|
18181
18897
|
const ZED_RULE_FILE_NAME = ".rules";
|
|
18182
18898
|
const ZED_GLOBAL_RULE_FILE_NAME = "AGENTS.md";
|
|
@@ -18189,12 +18905,20 @@ var ZedIgnore = class ZedIgnore extends ToolIgnore {
|
|
|
18189
18905
|
const jsonValue = JSON.parse(this.fileContent);
|
|
18190
18906
|
this.patterns = jsonValue.private_files ?? [];
|
|
18191
18907
|
}
|
|
18192
|
-
static getSettablePaths() {
|
|
18908
|
+
static getSettablePaths({ global = false } = {}) {
|
|
18193
18909
|
return {
|
|
18194
|
-
relativeDirPath: ZED_DIR,
|
|
18910
|
+
relativeDirPath: global ? getZedGlobalDir() : ZED_DIR,
|
|
18195
18911
|
relativeFilePath: ZED_SETTINGS_FILE_NAME
|
|
18196
18912
|
};
|
|
18197
18913
|
}
|
|
18914
|
+
/** @see getZedOtherPlatformGlobalDir */
|
|
18915
|
+
static getExtraSharedWritePaths({ global = false } = {}) {
|
|
18916
|
+
if (!global) return [];
|
|
18917
|
+
return [{
|
|
18918
|
+
relativeDirPath: getZedOtherPlatformGlobalDir(),
|
|
18919
|
+
relativeFilePath: ZED_SETTINGS_FILE_NAME
|
|
18920
|
+
}];
|
|
18921
|
+
}
|
|
18198
18922
|
/**
|
|
18199
18923
|
* ZedIgnore uses settings.json which is a user-managed config file.
|
|
18200
18924
|
* It should not be deleted by rulesync.
|
|
@@ -18205,48 +18929,53 @@ var ZedIgnore = class ZedIgnore extends ToolIgnore {
|
|
|
18205
18929
|
toRulesyncIgnore() {
|
|
18206
18930
|
const fileContent = this.patterns.filter((pattern) => pattern.length > 0).join("\n");
|
|
18207
18931
|
return new RulesyncIgnore({
|
|
18208
|
-
outputRoot:
|
|
18932
|
+
outputRoot: ".",
|
|
18209
18933
|
relativeDirPath: RulesyncIgnore.getSettablePaths().recommended.relativeDirPath,
|
|
18210
18934
|
relativeFilePath: RulesyncIgnore.getSettablePaths().recommended.relativeFilePath,
|
|
18211
18935
|
fileContent
|
|
18212
18936
|
});
|
|
18213
18937
|
}
|
|
18214
|
-
static async fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore }) {
|
|
18938
|
+
static async fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore, global = false }) {
|
|
18215
18939
|
const patterns = rulesyncIgnore.getFileContent().split(/\r?\n|\r/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
18216
|
-
const
|
|
18940
|
+
const paths = this.getSettablePaths({ global });
|
|
18941
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
18217
18942
|
const existingFileContent = await fileExists(filePath) ? await readFileContent(filePath) : "{}";
|
|
18218
|
-
const
|
|
18943
|
+
const managedPatterns = patterns.length > 0 ? [...new Set(patterns)].toSorted() : void 0;
|
|
18219
18944
|
return new ZedIgnore({
|
|
18220
18945
|
outputRoot,
|
|
18221
|
-
relativeDirPath:
|
|
18222
|
-
relativeFilePath:
|
|
18946
|
+
relativeDirPath: paths.relativeDirPath,
|
|
18947
|
+
relativeFilePath: paths.relativeFilePath,
|
|
18223
18948
|
fileContent: applySharedConfigPatch({
|
|
18224
|
-
fileKey: sharedConfigFileKey(
|
|
18949
|
+
fileKey: sharedConfigFileKey(paths),
|
|
18225
18950
|
feature: "ignore",
|
|
18226
18951
|
existingContent: existingFileContent,
|
|
18227
|
-
patch: { private_files:
|
|
18952
|
+
patch: { private_files: managedPatterns },
|
|
18228
18953
|
filePath
|
|
18229
18954
|
}),
|
|
18230
|
-
validate: true
|
|
18955
|
+
validate: true,
|
|
18956
|
+
global
|
|
18231
18957
|
});
|
|
18232
18958
|
}
|
|
18233
|
-
static async fromFile({ outputRoot = process.cwd(), validate = true }) {
|
|
18234
|
-
const
|
|
18959
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
18960
|
+
const paths = this.getSettablePaths({ global });
|
|
18961
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath));
|
|
18235
18962
|
return new ZedIgnore({
|
|
18236
18963
|
outputRoot,
|
|
18237
|
-
relativeDirPath:
|
|
18238
|
-
relativeFilePath:
|
|
18964
|
+
relativeDirPath: paths.relativeDirPath,
|
|
18965
|
+
relativeFilePath: paths.relativeFilePath,
|
|
18239
18966
|
fileContent,
|
|
18240
|
-
validate
|
|
18967
|
+
validate,
|
|
18968
|
+
global
|
|
18241
18969
|
});
|
|
18242
18970
|
}
|
|
18243
|
-
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
18971
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
18244
18972
|
return new ZedIgnore({
|
|
18245
18973
|
outputRoot,
|
|
18246
18974
|
relativeDirPath,
|
|
18247
18975
|
relativeFilePath,
|
|
18248
18976
|
fileContent: "{}",
|
|
18249
|
-
validate: false
|
|
18977
|
+
validate: false,
|
|
18978
|
+
global
|
|
18250
18979
|
});
|
|
18251
18980
|
}
|
|
18252
18981
|
};
|
|
@@ -18268,6 +18997,7 @@ const toolIgnoreFactories = /* @__PURE__ */ new Map([
|
|
|
18268
18997
|
["kiro-cli", { class: KiroIgnore }],
|
|
18269
18998
|
["kiro-ide", { class: KiroIgnore }],
|
|
18270
18999
|
["qwencode", { class: QwencodeIgnore }],
|
|
19000
|
+
["reasonix", { class: ReasonixIgnore }],
|
|
18271
19001
|
["roo", { class: RooIgnore }],
|
|
18272
19002
|
["devin", { class: DevinIgnore }],
|
|
18273
19003
|
["vibe", { class: VibeIgnore }],
|
|
@@ -18278,7 +19008,9 @@ const ignoreProcessorToolTargets = [...toolIgnoreFactories.keys()];
|
|
|
18278
19008
|
const ignoreProcessorGlobalToolTargets = [
|
|
18279
19009
|
"kiro",
|
|
18280
19010
|
"kiro-cli",
|
|
18281
|
-
"kiro-ide"
|
|
19011
|
+
"kiro-ide",
|
|
19012
|
+
"reasonix",
|
|
19013
|
+
"zed"
|
|
18282
19014
|
];
|
|
18283
19015
|
const defaultGetFactory$4 = (target) => {
|
|
18284
19016
|
const factory = toolIgnoreFactories.get(target);
|
|
@@ -20874,7 +21606,7 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
20874
21606
|
}), isRecord(this.config.mcp_servers) ? this.config.mcp_servers : {});
|
|
20875
21607
|
this.config = merged;
|
|
20876
21608
|
super.setFileContent(applySharedConfigPatch({
|
|
20877
|
-
fileKey:
|
|
21609
|
+
fileKey: getHermesagentConfigSharedFileKey({ global: this.global }),
|
|
20878
21610
|
feature: "mcp",
|
|
20879
21611
|
existingContent: fileContent,
|
|
20880
21612
|
patch: { mcp_servers: merged.mcp_servers }
|
|
@@ -20892,6 +21624,13 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
20892
21624
|
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
20893
21625
|
};
|
|
20894
21626
|
}
|
|
21627
|
+
/**
|
|
21628
|
+
* `config.yaml` under every spelling the global profile root can take.
|
|
21629
|
+
* @see getHermesagentSharedConfigWritePaths
|
|
21630
|
+
*/
|
|
21631
|
+
static getExtraSharedWritePaths() {
|
|
21632
|
+
return getHermesagentSharedConfigWritePaths();
|
|
21633
|
+
}
|
|
20895
21634
|
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
20896
21635
|
if (!global) throw new Error(HERMESAGENT_GLOBAL_ONLY_MESSAGE);
|
|
20897
21636
|
const paths = this.getSettablePaths({ global });
|
|
@@ -20918,7 +21657,7 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
20918
21657
|
relativeDirPath: paths.relativeDirPath,
|
|
20919
21658
|
relativeFilePath: paths.relativeFilePath,
|
|
20920
21659
|
fileContent: applySharedConfigPatch({
|
|
20921
|
-
fileKey:
|
|
21660
|
+
fileKey: getHermesagentConfigSharedFileKey({ global }),
|
|
20922
21661
|
feature: "mcp",
|
|
20923
21662
|
existingContent: fileContent,
|
|
20924
21663
|
patch: { mcp_servers: merged.mcp_servers }
|
|
@@ -21379,9 +22118,12 @@ var KiloMcp = class KiloMcp extends ToolMcp {
|
|
|
21379
22118
|
* Merge a list of project rule file globs into the `instructions` array of the
|
|
21380
22119
|
* shared `kilo.jsonc` (or `kilo.json`) config, preserving every existing key
|
|
21381
22120
|
* (notably `mcp`/`tools` written by the MCP feature). In Kilo v7, files under
|
|
21382
|
-
* `.kilo/rules/` are NOT auto-loaded; they are only picked up
|
|
21383
|
-
* the `instructions` key. The
|
|
21384
|
-
*
|
|
22121
|
+
* a *project* `.kilo/rules/` are NOT auto-loaded; they are only picked up
|
|
22122
|
+
* when listed in the `instructions` key. (The home-scope `~/.kilo/rules/` is
|
|
22123
|
+
* different — the rules migrator's `globalRulesDirs()` walks it on every
|
|
22124
|
+
* config load — which is why `KiloRule` registers instructions in project
|
|
22125
|
+
* scope only.) The resulting `instructions` list is deduped and sorted for a
|
|
22126
|
+
* stable output.
|
|
21385
22127
|
*
|
|
21386
22128
|
* @see https://kilo.ai/docs/automate/mcp/using-in-kilo-code
|
|
21387
22129
|
*/
|
|
@@ -21594,7 +22336,7 @@ var KimiCodeMcpConfigToml = class KimiCodeMcpConfigToml extends ToolFile {
|
|
|
21594
22336
|
const existingContent = existing.content;
|
|
21595
22337
|
const existingSection = existing.mcp;
|
|
21596
22338
|
const fileContent = applySharedConfigPatch({
|
|
21597
|
-
fileKey:
|
|
22339
|
+
fileKey: getKimiCodeConfigSharedFileKey({ global: true }),
|
|
21598
22340
|
feature: "mcp",
|
|
21599
22341
|
existingContent,
|
|
21600
22342
|
patch: { mcp: {
|
|
@@ -21681,11 +22423,8 @@ var KimiCodeMcp = class KimiCodeMcp extends ToolMcp {
|
|
|
21681
22423
|
* derivation sees this feature as one of that file's writers — it is not a
|
|
21682
22424
|
* settable path, since the servers themselves live in `mcp.json`.
|
|
21683
22425
|
*/
|
|
21684
|
-
static getExtraSharedWritePaths(
|
|
21685
|
-
return
|
|
21686
|
-
relativeDirPath: getKimiCodeRelativeDirPath({ global: true }),
|
|
21687
|
-
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
21688
|
-
}] : [];
|
|
22426
|
+
static getExtraSharedWritePaths() {
|
|
22427
|
+
return getKimiCodeSharedConfigWritePaths();
|
|
21689
22428
|
}
|
|
21690
22429
|
/**
|
|
21691
22430
|
* The `[mcp]` defaults live in the shared user `config.toml`, not in
|
|
@@ -23321,16 +24060,11 @@ var ZedMcp = class ZedMcp extends ToolMcp {
|
|
|
23321
24060
|
relativeFilePath: ZED_SETTINGS_FILE_NAME
|
|
23322
24061
|
};
|
|
23323
24062
|
}
|
|
23324
|
-
/**
|
|
23325
|
-
* The global settings file of the OTHER platform: `getSettablePaths` resolves
|
|
23326
|
-
* `~/.config/zed` vs `%APPDATA%\Zed` per platform, but the shared-write
|
|
23327
|
-
* derivation (and the gateway ownership table it is checked against) must
|
|
23328
|
-
* know both spellings on every platform.
|
|
23329
|
-
*/
|
|
24063
|
+
/** @see getZedOtherPlatformGlobalDir */
|
|
23330
24064
|
static getExtraSharedWritePaths({ global = false } = {}) {
|
|
23331
24065
|
if (!global) return [];
|
|
23332
24066
|
return [{
|
|
23333
|
-
relativeDirPath:
|
|
24067
|
+
relativeDirPath: getZedOtherPlatformGlobalDir(),
|
|
23334
24068
|
relativeFilePath: ZED_SETTINGS_FILE_NAME
|
|
23335
24069
|
}];
|
|
23336
24070
|
}
|
|
@@ -27760,6 +28494,13 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
27760
28494
|
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
27761
28495
|
};
|
|
27762
28496
|
}
|
|
28497
|
+
/**
|
|
28498
|
+
* `config.yaml` under every spelling the global profile root can take.
|
|
28499
|
+
* @see getHermesagentSharedConfigWritePaths
|
|
28500
|
+
*/
|
|
28501
|
+
static getExtraSharedWritePaths() {
|
|
28502
|
+
return getHermesagentSharedConfigWritePaths();
|
|
28503
|
+
}
|
|
27763
28504
|
constructor(params) {
|
|
27764
28505
|
super({
|
|
27765
28506
|
...params,
|
|
@@ -27797,7 +28538,7 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
27797
28538
|
}
|
|
27798
28539
|
setFileContent(fileContent) {
|
|
27799
28540
|
this.fileContent = applySharedConfigPatch({
|
|
27800
|
-
fileKey:
|
|
28541
|
+
fileKey: getHermesagentConfigSharedFileKey({ global: this.global }),
|
|
27801
28542
|
feature: "permissions",
|
|
27802
28543
|
existingContent: fileContent,
|
|
27803
28544
|
patch: parseSharedConfig({
|
|
@@ -28210,7 +28951,10 @@ const KiloPermissionSchema = zod_mini.z.union([zod_mini.z.enum([
|
|
|
28210
28951
|
"ask",
|
|
28211
28952
|
"deny"
|
|
28212
28953
|
]))]);
|
|
28213
|
-
const KiloPermissionsConfigSchema = zod_mini.z.looseObject({
|
|
28954
|
+
const KiloPermissionsConfigSchema = zod_mini.z.looseObject({
|
|
28955
|
+
permission: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), KiloPermissionSchema)),
|
|
28956
|
+
sandbox: zod_mini.z.optional(zod_mini.z.unknown())
|
|
28957
|
+
});
|
|
28214
28958
|
/**
|
|
28215
28959
|
* Kilo permission keys that share a name with a canonical rulesync category and
|
|
28216
28960
|
* therefore stay in the shared `permission` block. Everything else (Kilo-only
|
|
@@ -28267,6 +29011,25 @@ function collectKiloDenyPatterns(value) {
|
|
|
28267
29011
|
}
|
|
28268
29012
|
return [];
|
|
28269
29013
|
}
|
|
29014
|
+
function asKiloRecord(value) {
|
|
29015
|
+
return isPlainObject$1(value) ? { ...value } : {};
|
|
29016
|
+
}
|
|
29017
|
+
/**
|
|
29018
|
+
* The `sandbox` keys a *project* `kilo.jsonc` may state. Kilo honors
|
|
29019
|
+
* `allowed_hosts` and `writable_paths` from the global config only, and lets a
|
|
29020
|
+
* project config merely tighten — so writing the wider keys into a project file
|
|
29021
|
+
* would produce config Kilo ignores.
|
|
29022
|
+
* @see https://kilo.ai/docs/getting-started/settings/sandboxing
|
|
29023
|
+
*/
|
|
29024
|
+
const KILO_PROJECT_SCOPE_SANDBOX_KEYS = /* @__PURE__ */ new Set(["enabled", "network"]);
|
|
29025
|
+
function narrowSandboxToProjectScope({ authored, logger }) {
|
|
29026
|
+
const emitted = {};
|
|
29027
|
+
const dropped = [];
|
|
29028
|
+
for (const [key, value] of Object.entries(authored)) if (KILO_PROJECT_SCOPE_SANDBOX_KEYS.has(key)) emitted[key] = value;
|
|
29029
|
+
else dropped.push(key);
|
|
29030
|
+
if (dropped.length > 0) logger?.warn(`Kilo honors these 'sandbox' keys from the global config only, so they were dropped from the project config: ${dropped.toSorted().join(", ")}. A project config may only tighten the sandbox ('enabled', 'network'); generate with --global to author the rest.`);
|
|
29031
|
+
return emitted;
|
|
29032
|
+
}
|
|
28270
29033
|
var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
28271
29034
|
json;
|
|
28272
29035
|
constructor(params) {
|
|
@@ -28346,8 +29109,7 @@ var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
|
28346
29109
|
const basePaths = KiloPermissions.getSettablePaths({ global });
|
|
28347
29110
|
const filePath = (0, node_path.join)(outputRoot, basePaths.relativeDirPath, basePaths.relativeFilePath);
|
|
28348
29111
|
const parsed = parseKiloJsoncStrict(await readFileContentOrNull(filePath) ?? "{}", filePath);
|
|
28349
|
-
const
|
|
28350
|
-
const existingPermission = parsedPermission && typeof parsedPermission === "object" && !Array.isArray(parsedPermission) ? { ...parsedPermission } : {};
|
|
29112
|
+
const existingPermission = asKiloRecord(parsed.permission);
|
|
28351
29113
|
const rulesyncJson = rulesyncPermissions.getJson();
|
|
28352
29114
|
const kiloOverride = rulesyncJson.kilo;
|
|
28353
29115
|
const incomingPermission = {
|
|
@@ -28373,6 +29135,18 @@ var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
|
28373
29135
|
...parsed,
|
|
28374
29136
|
permission: mergedPermission
|
|
28375
29137
|
};
|
|
29138
|
+
if (kiloOverride?.sandbox !== void 0) {
|
|
29139
|
+
const authored = asKiloRecord(kiloOverride.sandbox);
|
|
29140
|
+
const emitted = global ? authored : narrowSandboxToProjectScope({
|
|
29141
|
+
authored,
|
|
29142
|
+
logger
|
|
29143
|
+
});
|
|
29144
|
+
const merged = {
|
|
29145
|
+
...asKiloRecord(parsed.sandbox),
|
|
29146
|
+
...emitted
|
|
29147
|
+
};
|
|
29148
|
+
if (Object.keys(merged).length > 0) nextJson.sandbox = merged;
|
|
29149
|
+
}
|
|
28376
29150
|
return new KiloPermissions({
|
|
28377
29151
|
outputRoot,
|
|
28378
29152
|
relativeDirPath: basePaths.relativeDirPath,
|
|
@@ -28387,9 +29161,14 @@ var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
|
28387
29161
|
const overrideOnly = {};
|
|
28388
29162
|
for (const [key, value] of Object.entries(rawPermission)) if (isSharedKiloCategory(key)) shared[key] = typeof value === "string" ? { "*": value } : value;
|
|
28389
29163
|
else overrideOnly[key] = value;
|
|
28390
|
-
const
|
|
29164
|
+
const sandbox = this.json.sandbox;
|
|
29165
|
+
const override = {
|
|
29166
|
+
...Object.keys(overrideOnly).length > 0 && { permission: overrideOnly },
|
|
29167
|
+
...isPlainObject$1(sandbox) && { sandbox }
|
|
29168
|
+
};
|
|
29169
|
+
const json = Object.keys(override).length > 0 ? {
|
|
28391
29170
|
permission: shared,
|
|
28392
|
-
kilo:
|
|
29171
|
+
kilo: override
|
|
28393
29172
|
} : { permission: shared };
|
|
28394
29173
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(json, null, 2) });
|
|
28395
29174
|
}
|
|
@@ -28630,6 +29409,13 @@ var KimiCodePermissions = class KimiCodePermissions extends ToolPermissions {
|
|
|
28630
29409
|
isDeletable() {
|
|
28631
29410
|
return false;
|
|
28632
29411
|
}
|
|
29412
|
+
/**
|
|
29413
|
+
* `config.toml` under both spellings its directory can take.
|
|
29414
|
+
* @see getKimiCodeSharedConfigWritePaths
|
|
29415
|
+
*/
|
|
29416
|
+
static getExtraSharedWritePaths() {
|
|
29417
|
+
return getKimiCodeSharedConfigWritePaths();
|
|
29418
|
+
}
|
|
28633
29419
|
shouldMergeExistingFileContent() {
|
|
28634
29420
|
return true;
|
|
28635
29421
|
}
|
|
@@ -28644,7 +29430,7 @@ var KimiCodePermissions = class KimiCodePermissions extends ToolPermissions {
|
|
|
28644
29430
|
patch
|
|
28645
29431
|
});
|
|
28646
29432
|
this.fileContent = applySharedConfigPatch({
|
|
28647
|
-
fileKey:
|
|
29433
|
+
fileKey: getKimiCodeConfigSharedFileKey({ global: this.global }),
|
|
28648
29434
|
feature: "permissions",
|
|
28649
29435
|
existingContent: fileContent,
|
|
28650
29436
|
patch: {
|
|
@@ -29568,14 +30354,6 @@ function parseReasonixConfig(fileContent) {
|
|
|
29568
30354
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
29569
30355
|
return { ...parsed };
|
|
29570
30356
|
}
|
|
29571
|
-
function toStringArray$1(value) {
|
|
29572
|
-
if (!Array.isArray(value)) return [];
|
|
29573
|
-
return value.filter((entry) => typeof entry === "string");
|
|
29574
|
-
}
|
|
29575
|
-
function toPermissionsTable(value) {
|
|
29576
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
29577
|
-
return { ...value };
|
|
29578
|
-
}
|
|
29579
30357
|
const REASONIX_OVERRIDE_AGENT_KEYS = ["plan_mode_read_only_commands"];
|
|
29580
30358
|
/**
|
|
29581
30359
|
* `[agent]` keys an older `reasonix.toml` may carry that left the documented
|
|
@@ -29632,12 +30410,12 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
29632
30410
|
const config = rulesyncPermissions.getJson();
|
|
29633
30411
|
const { allow, ask, deny } = convertRulesyncToReasonixPermissions(config);
|
|
29634
30412
|
const managedToolNames = new Set(Object.keys(config.permission).map((category) => toReasonixToolName(category)));
|
|
29635
|
-
const existingPermissions =
|
|
29636
|
-
const preservedAllow =
|
|
29637
|
-
const preservedAsk =
|
|
29638
|
-
const preservedDeny =
|
|
30413
|
+
const existingPermissions = toReasonixTable(parsed.permissions);
|
|
30414
|
+
const preservedAllow = toReasonixStringArray(existingPermissions.allow).filter((entry) => !managedToolNames.has(parseReasonixPermissionEntry(entry).toolName));
|
|
30415
|
+
const preservedAsk = toReasonixStringArray(existingPermissions.ask).filter((entry) => !managedToolNames.has(parseReasonixPermissionEntry(entry).toolName));
|
|
30416
|
+
const preservedDeny = toReasonixStringArray(existingPermissions.deny).filter((entry) => !managedToolNames.has(parseReasonixPermissionEntry(entry).toolName));
|
|
29639
30417
|
if (logger && managedToolNames.has("Read")) {
|
|
29640
|
-
const droppedReadDenyEntries =
|
|
30418
|
+
const droppedReadDenyEntries = toReasonixStringArray(existingPermissions.deny).filter((entry) => {
|
|
29641
30419
|
const { toolName } = parseReasonixPermissionEntry(entry);
|
|
29642
30420
|
return toolName === "Read";
|
|
29643
30421
|
});
|
|
@@ -29684,11 +30462,11 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
29684
30462
|
});
|
|
29685
30463
|
}
|
|
29686
30464
|
toRulesyncPermissions() {
|
|
29687
|
-
const permissions =
|
|
30465
|
+
const permissions = toReasonixTable(this.toml.permissions);
|
|
29688
30466
|
const config = convertReasonixToRulesyncPermissions({
|
|
29689
|
-
allow:
|
|
29690
|
-
ask:
|
|
29691
|
-
deny:
|
|
30467
|
+
allow: toReasonixStringArray(permissions.allow),
|
|
30468
|
+
ask: toReasonixStringArray(permissions.ask),
|
|
30469
|
+
deny: toReasonixStringArray(permissions.deny)
|
|
29692
30470
|
});
|
|
29693
30471
|
const sandbox = asReasonixRecord(this.toml.sandbox);
|
|
29694
30472
|
const agentPlanMode = pickReasonixKeys(this.toml.agent, [...REASONIX_OVERRIDE_AGENT_KEYS, ...REASONIX_RETIRED_AGENT_KEYS]);
|
|
@@ -29780,19 +30558,31 @@ const CATEGORY_TO_TOOL_KEYS = {
|
|
|
29780
30558
|
"open_files",
|
|
29781
30559
|
"expand_code_chunks",
|
|
29782
30560
|
"expand_folder",
|
|
29783
|
-
"grep"
|
|
30561
|
+
"grep",
|
|
30562
|
+
"getJiraIssue",
|
|
30563
|
+
"getConfluencePage"
|
|
29784
30564
|
],
|
|
29785
30565
|
edit: [
|
|
29786
30566
|
"find_and_replace_code",
|
|
29787
30567
|
"create_file",
|
|
29788
30568
|
"delete_file",
|
|
29789
|
-
"move_file"
|
|
30569
|
+
"move_file",
|
|
30570
|
+
"createTechnicalPlan",
|
|
30571
|
+
"createJiraIssue",
|
|
30572
|
+
"updateJiraIssue",
|
|
30573
|
+
"createConfluencePage",
|
|
30574
|
+
"updateConfluencePage"
|
|
29790
30575
|
],
|
|
29791
30576
|
write: [
|
|
29792
30577
|
"create_file",
|
|
29793
30578
|
"delete_file",
|
|
29794
30579
|
"move_file",
|
|
29795
|
-
"find_and_replace_code"
|
|
30580
|
+
"find_and_replace_code",
|
|
30581
|
+
"createTechnicalPlan",
|
|
30582
|
+
"createJiraIssue",
|
|
30583
|
+
"updateJiraIssue",
|
|
30584
|
+
"createConfluencePage",
|
|
30585
|
+
"updateConfluencePage"
|
|
29796
30586
|
]
|
|
29797
30587
|
};
|
|
29798
30588
|
const TOOL_KEY_TO_CATEGORY = {
|
|
@@ -29800,13 +30590,24 @@ const TOOL_KEY_TO_CATEGORY = {
|
|
|
29800
30590
|
expand_code_chunks: "read",
|
|
29801
30591
|
expand_folder: "read",
|
|
29802
30592
|
grep: "read",
|
|
30593
|
+
getJiraIssue: "read",
|
|
30594
|
+
getConfluencePage: "read",
|
|
29803
30595
|
find_and_replace_code: "edit",
|
|
29804
30596
|
create_file: "edit",
|
|
29805
30597
|
delete_file: "edit",
|
|
29806
|
-
move_file: "edit"
|
|
30598
|
+
move_file: "edit",
|
|
30599
|
+
createTechnicalPlan: "edit",
|
|
30600
|
+
createJiraIssue: "edit",
|
|
30601
|
+
updateJiraIssue: "edit",
|
|
30602
|
+
createConfluencePage: "edit",
|
|
30603
|
+
updateConfluencePage: "edit"
|
|
29807
30604
|
};
|
|
29808
30605
|
const MANAGED_TOOL_KEYS = [.../* @__PURE__ */ new Set([...Object.values(CATEGORY_TO_TOOL_KEYS).flat(), ...Object.keys(TOOL_KEY_TO_CATEGORY)])];
|
|
29809
|
-
const OWNED_TOOL_PERMISSION_KEYS = [
|
|
30606
|
+
const OWNED_TOOL_PERMISSION_KEYS = [
|
|
30607
|
+
"bash",
|
|
30608
|
+
"allowedExternalPaths",
|
|
30609
|
+
"default"
|
|
30610
|
+
];
|
|
29810
30611
|
/**
|
|
29811
30612
|
* Permissions adapter for Rovo Dev CLI.
|
|
29812
30613
|
*
|
|
@@ -29820,9 +30621,16 @@ const OWNED_TOOL_PERMISSION_KEYS = ["bash", "allowedExternalPaths"];
|
|
|
29820
30621
|
* Mapping decisions (rulesync canonical -> Rovo Dev):
|
|
29821
30622
|
* - `bash`: the catch-all `*` pattern -> `bash.default`; every other pattern ->
|
|
29822
30623
|
* a `bash.commands[]` entry `{ command: <pattern as regex>, permission }`.
|
|
30624
|
+
* - the all-tools category `*`: its catch-all -> `toolPermissions.default`,
|
|
30625
|
+
* the level Rovo Dev falls back to for any tool with no more specific
|
|
30626
|
+
* setting (Rovo Dev's own default is `ask`).
|
|
29823
30627
|
* - `read` -> the inspection tools (`open_files`, `expand_code_chunks`,
|
|
29824
|
-
* `expand_folder`, `grep`
|
|
29825
|
-
*
|
|
30628
|
+
* `expand_folder`, `grep`, `getJiraIssue`, `getConfluencePage`);
|
|
30629
|
+
* `edit`/`write` -> the mutation tools (`find_and_replace_code`,
|
|
30630
|
+
* `create_file`, `delete_file`, `move_file`, `createTechnicalPlan`,
|
|
30631
|
+
* `createJiraIssue`, `updateJiraIssue`, `createConfluencePage`,
|
|
30632
|
+
* `updateConfluencePage`) — so these two categories reach Jira and
|
|
30633
|
+
* Confluence, not just the working tree.
|
|
29826
30634
|
* These Rovo Dev keys hold a single level (no per-pattern rules), so only the
|
|
29827
30635
|
* catch-all `*` of each category sets the level. Non-catch-all `allow` rules
|
|
29828
30636
|
* in those categories are surfaced as `allowedExternalPaths` so explicit path
|
|
@@ -30002,6 +30810,10 @@ function stripPermissiveOwnedValues(toolPermissions) {
|
|
|
30002
30810
|
delete toolPermissions.allowedExternalPaths;
|
|
30003
30811
|
strippedKeys.push("allowedExternalPaths");
|
|
30004
30812
|
}
|
|
30813
|
+
if (toolPermissions.default === "allow") {
|
|
30814
|
+
delete toolPermissions.default;
|
|
30815
|
+
strippedKeys.push("default");
|
|
30816
|
+
}
|
|
30005
30817
|
const bash = toolPermissions.bash;
|
|
30006
30818
|
if (isRecord(bash)) {
|
|
30007
30819
|
const stripped = { ...bash };
|
|
@@ -30026,10 +30838,19 @@ function stripPermissiveOwnedValues(toolPermissions) {
|
|
|
30026
30838
|
function convertRulesyncToRovodevToolPermissions({ config, logger }) {
|
|
30027
30839
|
const toolPermissions = {};
|
|
30028
30840
|
const allowedExternalPaths = [];
|
|
30029
|
-
|
|
30030
|
-
|
|
30031
|
-
|
|
30841
|
+
warnOnEditWriteConflict({
|
|
30842
|
+
config,
|
|
30843
|
+
logger
|
|
30844
|
+
});
|
|
30032
30845
|
for (const [category, rules] of Object.entries(config.permission)) {
|
|
30846
|
+
if (category === CATCH_ALL_PATTERN$1) {
|
|
30847
|
+
const toolWideDefault = convertAllToolsRules({
|
|
30848
|
+
rules,
|
|
30849
|
+
logger
|
|
30850
|
+
});
|
|
30851
|
+
if (toolWideDefault) toolPermissions.default = toolWideDefault;
|
|
30852
|
+
continue;
|
|
30853
|
+
}
|
|
30033
30854
|
if (category === "bash") {
|
|
30034
30855
|
const bash = convertBashRules(rules);
|
|
30035
30856
|
if (bash) toolPermissions.bash = bash;
|
|
@@ -30056,6 +30877,36 @@ function convertRulesyncToRovodevToolPermissions({ config, logger }) {
|
|
|
30056
30877
|
if (allowedExternalPaths.length > 0) toolPermissions.allowedExternalPaths = [...new Set(allowedExternalPaths)].toSorted();
|
|
30057
30878
|
return toolPermissions;
|
|
30058
30879
|
}
|
|
30880
|
+
/**
|
|
30881
|
+
* `edit` and `write` collapse onto the same Rovo Dev file-mutation tools, so a
|
|
30882
|
+
* conflicting catch-all between them cannot be represented. Warn that the loss
|
|
30883
|
+
* is happening; the conversion keeps the stricter of the two — the same
|
|
30884
|
+
* fail-closed rule the import direction uses when those tools disagree, so the
|
|
30885
|
+
* resolution never grants more than the author asked for.
|
|
30886
|
+
*/
|
|
30887
|
+
function warnOnEditWriteConflict({ config, logger }) {
|
|
30888
|
+
const editCatchAll = config.permission.edit?.[CATCH_ALL_PATTERN$1];
|
|
30889
|
+
const writeCatchAll = config.permission.write?.[CATCH_ALL_PATTERN$1];
|
|
30890
|
+
if (editCatchAll && writeCatchAll && editCatchAll !== writeCatchAll) logger?.warn(`Rovo Dev maps both "edit" and "write" onto the same file-mutation tools, but they have conflicting catch-all permissions ("edit": "${editCatchAll}", "write": "${writeCatchAll}"). The stricter of the two ("${strictestAction(editCatchAll, writeCatchAll)}") is used.`);
|
|
30891
|
+
}
|
|
30892
|
+
/**
|
|
30893
|
+
* The canonical all-tools category. Its catch-all sets the tool-wide
|
|
30894
|
+
* `toolPermissions.default`, the same way `bash`'s catch-all sets
|
|
30895
|
+
* `bash.default` — both are the level Rovo Dev falls back to. Pattern rules
|
|
30896
|
+
* under `*` have no counterpart (the default is a single level), so they are
|
|
30897
|
+
* reported and skipped like any other rule Rovo Dev cannot express.
|
|
30898
|
+
*/
|
|
30899
|
+
function convertAllToolsRules({ rules, logger }) {
|
|
30900
|
+
let toolWideDefault;
|
|
30901
|
+
for (const [pattern, action] of Object.entries(rules)) {
|
|
30902
|
+
if (pattern === CATCH_ALL_PATTERN$1) {
|
|
30903
|
+
toolWideDefault = action;
|
|
30904
|
+
continue;
|
|
30905
|
+
}
|
|
30906
|
+
logger?.warn(`Rovo Dev's tool-wide default is a single level, so it cannot express the pattern "${pattern}" in the "*" category. Skipping it.`);
|
|
30907
|
+
}
|
|
30908
|
+
return toolWideDefault;
|
|
30909
|
+
}
|
|
30059
30910
|
function convertBashRules(rules) {
|
|
30060
30911
|
const bash = {};
|
|
30061
30912
|
const commands = [];
|
|
@@ -30077,6 +30928,7 @@ function convertBashRules(rules) {
|
|
|
30077
30928
|
*/
|
|
30078
30929
|
function convertRovodevToolPermissionsToRulesync(toolPermissions) {
|
|
30079
30930
|
const permission = {};
|
|
30931
|
+
if (isPermissionAction(toolPermissions.default)) permission[CATCH_ALL_PATTERN$1] = { [CATCH_ALL_PATTERN$1]: toolPermissions.default };
|
|
30080
30932
|
const bash = toolPermissions.bash;
|
|
30081
30933
|
if (isRecord(bash)) {
|
|
30082
30934
|
const bashRules = {};
|
|
@@ -30087,12 +30939,15 @@ function convertRovodevToolPermissionsToRulesync(toolPermissions) {
|
|
|
30087
30939
|
if (Object.keys(bashRules).length > 0) permission.bash = bashRules;
|
|
30088
30940
|
}
|
|
30089
30941
|
const nestedTools = isRecord(toolPermissions.tools) ? toolPermissions.tools : {};
|
|
30090
|
-
|
|
30091
|
-
|
|
30092
|
-
|
|
30942
|
+
const implicitLevel = isPermissionAction(toolPermissions.default) ? toolPermissions.default : "ask";
|
|
30943
|
+
for (const category of new Set(Object.values(TOOL_KEY_TO_CATEGORY))) {
|
|
30944
|
+
const levels = Object.entries(TOOL_KEY_TO_CATEGORY).filter(([, mapped]) => mapped === category).map(([toolKey]) => {
|
|
30945
|
+
const value = Object.hasOwn(nestedTools, toolKey) ? nestedTools[toolKey] : toolPermissions[toolKey];
|
|
30946
|
+
return isPermissionAction(value) ? value : void 0;
|
|
30947
|
+
});
|
|
30948
|
+
if (levels.every((level) => level === void 0)) continue;
|
|
30093
30949
|
permission[category] ??= {};
|
|
30094
|
-
|
|
30095
|
-
permission[category][CATCH_ALL_PATTERN$1] = strictestAction(current, value);
|
|
30950
|
+
permission[category][CATCH_ALL_PATTERN$1] = levels.reduce((strictest, level) => strictestAction(strictest, level ?? implicitLevel), permission[category][CATCH_ALL_PATTERN$1]);
|
|
30096
30951
|
}
|
|
30097
30952
|
if (isStringArray$1(toolPermissions.allowedExternalPaths)) for (const path of toolPermissions.allowedExternalPaths) {
|
|
30098
30953
|
permission.read ??= {};
|
|
@@ -30924,16 +31779,11 @@ var ZedPermissions = class ZedPermissions extends ToolPermissions {
|
|
|
30924
31779
|
relativeFilePath: ZED_SETTINGS_FILE_NAME
|
|
30925
31780
|
};
|
|
30926
31781
|
}
|
|
30927
|
-
/**
|
|
30928
|
-
* The global settings file of the OTHER platform: `getSettablePaths` resolves
|
|
30929
|
-
* `~/.config/zed` vs `%APPDATA%\Zed` per platform, but the shared-write
|
|
30930
|
-
* derivation (and the gateway ownership table it is checked against) must
|
|
30931
|
-
* know both spellings on every platform.
|
|
30932
|
-
*/
|
|
31782
|
+
/** @see getZedOtherPlatformGlobalDir */
|
|
30933
31783
|
static getExtraSharedWritePaths({ global = false } = {}) {
|
|
30934
31784
|
if (!global) return [];
|
|
30935
31785
|
return [{
|
|
30936
|
-
relativeDirPath:
|
|
31786
|
+
relativeDirPath: getZedOtherPlatformGlobalDir(),
|
|
30937
31787
|
relativeFilePath: ZED_SETTINGS_FILE_NAME
|
|
30938
31788
|
}];
|
|
30939
31789
|
}
|
|
@@ -39505,7 +40355,7 @@ def register(ctx):
|
|
|
39505
40355
|
_register_subagent(ctx, subagent)
|
|
39506
40356
|
`;
|
|
39507
40357
|
}
|
|
39508
|
-
function getEnabledPluginConfigContent(currentContent) {
|
|
40358
|
+
function getEnabledPluginConfigContent({ currentContent, global }) {
|
|
39509
40359
|
const config = parseSharedConfig({
|
|
39510
40360
|
format: "yaml",
|
|
39511
40361
|
fileContent: currentContent
|
|
@@ -39513,7 +40363,7 @@ function getEnabledPluginConfigContent(currentContent) {
|
|
|
39513
40363
|
const plugins = config.plugins && typeof config.plugins === "object" ? config.plugins : {};
|
|
39514
40364
|
const enabled = Array.isArray(plugins.enabled) ? plugins.enabled : [];
|
|
39515
40365
|
return applySharedConfigPatch({
|
|
39516
|
-
fileKey:
|
|
40366
|
+
fileKey: getHermesagentConfigSharedFileKey({ global }),
|
|
39517
40367
|
feature: "subagents",
|
|
39518
40368
|
existingContent: currentContent,
|
|
39519
40369
|
patch: { plugins: {
|
|
@@ -39564,6 +40414,10 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
|
|
|
39564
40414
|
return !targets || targets.includes("*") || targets.includes("hermesagent");
|
|
39565
40415
|
}
|
|
39566
40416
|
static fromRulesyncSubagents({ rulesyncSubagents, outputRoot, global = false }) {
|
|
40417
|
+
const pluginDirPath = getHermesagentRelativeDirPath({
|
|
40418
|
+
global,
|
|
40419
|
+
relativeDirPath: HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_DIR_PATH
|
|
40420
|
+
});
|
|
39567
40421
|
return [
|
|
39568
40422
|
...rulesyncSubagents.map((rulesyncSubagent) => HermesagentSubagent.fromRulesyncSubagent({
|
|
39569
40423
|
relativeDirPath: this.getSettablePaths({ global }).relativeDirPath,
|
|
@@ -39572,20 +40426,14 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
|
|
|
39572
40426
|
global
|
|
39573
40427
|
})),
|
|
39574
40428
|
new HermesagentSubagent({
|
|
39575
|
-
relativeDirPath:
|
|
39576
|
-
global,
|
|
39577
|
-
relativeDirPath: HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_DIR_PATH
|
|
39578
|
-
}),
|
|
40429
|
+
relativeDirPath: pluginDirPath,
|
|
39579
40430
|
relativeFilePath: (0, node_path.basename)(HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_MANIFEST_PATH),
|
|
39580
40431
|
fileContent: "",
|
|
39581
40432
|
outputRoot,
|
|
39582
40433
|
global
|
|
39583
40434
|
}),
|
|
39584
40435
|
new HermesagentSubagent({
|
|
39585
|
-
relativeDirPath:
|
|
39586
|
-
global,
|
|
39587
|
-
relativeDirPath: HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_DIR_PATH
|
|
39588
|
-
}),
|
|
40436
|
+
relativeDirPath: pluginDirPath,
|
|
39589
40437
|
relativeFilePath: (0, node_path.basename)(HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_INIT_PATH),
|
|
39590
40438
|
fileContent: "",
|
|
39591
40439
|
outputRoot,
|
|
@@ -39625,14 +40473,12 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
|
|
|
39625
40473
|
* shared `~/.hermes/config.yaml` (enabling the `rulesync-subagents` plugin),
|
|
39626
40474
|
* so the write must be declared for the shared-file order derivation.
|
|
39627
40475
|
*/
|
|
39628
|
-
|
|
39629
|
-
|
|
39630
|
-
|
|
39631
|
-
|
|
39632
|
-
|
|
39633
|
-
|
|
39634
|
-
relativeFilePath: (0, node_path.basename)(HERMESAGENT_CONFIG_FILE_PATH)
|
|
39635
|
-
}] : [];
|
|
40476
|
+
/**
|
|
40477
|
+
* `config.yaml` under every spelling the global profile root can take.
|
|
40478
|
+
* @see getHermesagentSharedConfigWritePaths
|
|
40479
|
+
*/
|
|
40480
|
+
static getExtraSharedWritePaths() {
|
|
40481
|
+
return getHermesagentSharedConfigWritePaths();
|
|
39636
40482
|
}
|
|
39637
40483
|
static getSettablePathsForRulesyncSubagent(rulesyncSubagent) {
|
|
39638
40484
|
return [(0, node_path.join)(HERMESAGENT_RULESYNC_SUBAGENTS_DIR_PATH, `${subagentSlug(rulesyncSubagent.getRelativePathFromCwd())}.json`)];
|
|
@@ -39665,7 +40511,10 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
|
|
|
39665
40511
|
}
|
|
39666
40512
|
setFileContent(newFileContent) {
|
|
39667
40513
|
if (this.getRelativeFilePath() === (0, node_path.basename)(HERMESAGENT_CONFIG_FILE_PATH)) {
|
|
39668
|
-
super.setFileContent(getEnabledPluginConfigContent(
|
|
40514
|
+
super.setFileContent(getEnabledPluginConfigContent({
|
|
40515
|
+
currentContent: newFileContent,
|
|
40516
|
+
global: this.global
|
|
40517
|
+
}));
|
|
39669
40518
|
return;
|
|
39670
40519
|
}
|
|
39671
40520
|
super.setFileContent(newFileContent);
|
|
@@ -39673,7 +40522,10 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
|
|
|
39673
40522
|
getFileContent() {
|
|
39674
40523
|
if (this.getRelativeFilePath() === (0, node_path.basename)(HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_MANIFEST_PATH)) return getPluginManifestContent();
|
|
39675
40524
|
if (this.getRelativeFilePath() === (0, node_path.basename)(HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_INIT_PATH)) return getPluginInitContent();
|
|
39676
|
-
if (this.getRelativeFilePath() === (0, node_path.basename)(HERMESAGENT_CONFIG_FILE_PATH)) return getEnabledPluginConfigContent(
|
|
40525
|
+
if (this.getRelativeFilePath() === (0, node_path.basename)(HERMESAGENT_CONFIG_FILE_PATH)) return getEnabledPluginConfigContent({
|
|
40526
|
+
currentContent: super.getFileContent(),
|
|
40527
|
+
global: this.global
|
|
40528
|
+
});
|
|
39677
40529
|
return super.getFileContent();
|
|
39678
40530
|
}
|
|
39679
40531
|
};
|
|
@@ -44752,10 +45604,13 @@ var JunieRule = class JunieRule extends ToolRule {
|
|
|
44752
45604
|
//#region src/features/rules/kilo-rule.ts
|
|
44753
45605
|
var KiloRule = class KiloRule extends ToolRule {
|
|
44754
45606
|
static getSettablePaths({ global, excludeToolDir } = {}) {
|
|
44755
|
-
if (global) return {
|
|
44756
|
-
|
|
44757
|
-
|
|
44758
|
-
|
|
45607
|
+
if (global) return {
|
|
45608
|
+
root: {
|
|
45609
|
+
relativeDirPath: buildToolPath(KILO_GLOBAL_DIR, ".", excludeToolDir),
|
|
45610
|
+
relativeFilePath: KILO_RULE_FILE_NAME
|
|
45611
|
+
},
|
|
45612
|
+
nonRoot: { relativeDirPath: buildToolPath(KILO_DIR, KILO_RULES_DIR_NAME, excludeToolDir) }
|
|
45613
|
+
};
|
|
44759
45614
|
return {
|
|
44760
45615
|
root: {
|
|
44761
45616
|
relativeDirPath: ".",
|
|
@@ -46340,6 +47195,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46340
47195
|
extension: "md",
|
|
46341
47196
|
supportsGlobal: false,
|
|
46342
47197
|
ruleDiscoveryMode: "toon",
|
|
47198
|
+
collisionPolicy: "compose",
|
|
46343
47199
|
additionalConventions: {
|
|
46344
47200
|
commands: { commandClass: AgentsmdCommand },
|
|
46345
47201
|
subagents: { subagentClass: AgentsmdSubagent },
|
|
@@ -46360,7 +47216,8 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46360
47216
|
meta: {
|
|
46361
47217
|
extension: "md",
|
|
46362
47218
|
supportsGlobal: true,
|
|
46363
|
-
ruleDiscoveryMode: "toon"
|
|
47219
|
+
ruleDiscoveryMode: "toon",
|
|
47220
|
+
collisionPolicy: "compose"
|
|
46364
47221
|
}
|
|
46365
47222
|
}],
|
|
46366
47223
|
["antigravity-cli", {
|
|
@@ -46437,7 +47294,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46437
47294
|
extension: "md",
|
|
46438
47295
|
supportsGlobal: true,
|
|
46439
47296
|
ruleDiscoveryMode: "auto",
|
|
46440
|
-
|
|
47297
|
+
collisionPolicy: "fold"
|
|
46441
47298
|
}
|
|
46442
47299
|
}],
|
|
46443
47300
|
["copilot", {
|
|
@@ -46470,7 +47327,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46470
47327
|
extension: "md",
|
|
46471
47328
|
supportsGlobal: true,
|
|
46472
47329
|
ruleDiscoveryMode: "auto",
|
|
46473
|
-
|
|
47330
|
+
collisionPolicy: "fold"
|
|
46474
47331
|
}
|
|
46475
47332
|
}],
|
|
46476
47333
|
["factorydroid", {
|
|
@@ -46478,7 +47335,8 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46478
47335
|
meta: {
|
|
46479
47336
|
extension: "md",
|
|
46480
47337
|
supportsGlobal: true,
|
|
46481
|
-
ruleDiscoveryMode: "toon"
|
|
47338
|
+
ruleDiscoveryMode: "toon",
|
|
47339
|
+
collisionPolicy: "compose"
|
|
46482
47340
|
}
|
|
46483
47341
|
}],
|
|
46484
47342
|
["goose", {
|
|
@@ -46487,7 +47345,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46487
47345
|
extension: "md",
|
|
46488
47346
|
supportsGlobal: true,
|
|
46489
47347
|
ruleDiscoveryMode: "auto",
|
|
46490
|
-
|
|
47348
|
+
collisionPolicy: "fold"
|
|
46491
47349
|
}
|
|
46492
47350
|
}],
|
|
46493
47351
|
["hermesagent", {
|
|
@@ -46496,7 +47354,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46496
47354
|
extension: "md",
|
|
46497
47355
|
supportsGlobal: false,
|
|
46498
47356
|
ruleDiscoveryMode: "auto",
|
|
46499
|
-
|
|
47357
|
+
collisionPolicy: "fold"
|
|
46500
47358
|
}
|
|
46501
47359
|
}],
|
|
46502
47360
|
["grokcli", {
|
|
@@ -46513,7 +47371,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46513
47371
|
extension: "md",
|
|
46514
47372
|
supportsGlobal: true,
|
|
46515
47373
|
ruleDiscoveryMode: "auto",
|
|
46516
|
-
|
|
47374
|
+
collisionPolicy: "fold"
|
|
46517
47375
|
}
|
|
46518
47376
|
}],
|
|
46519
47377
|
["kilo", {
|
|
@@ -46522,7 +47380,8 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46522
47380
|
extension: "md",
|
|
46523
47381
|
supportsGlobal: true,
|
|
46524
47382
|
ruleDiscoveryMode: "auto",
|
|
46525
|
-
mcpInstructionsRegistrar: KiloMcp
|
|
47383
|
+
mcpInstructionsRegistrar: KiloMcp,
|
|
47384
|
+
collisionPolicy: "compose"
|
|
46526
47385
|
}
|
|
46527
47386
|
}],
|
|
46528
47387
|
["kimi-code", {
|
|
@@ -46531,7 +47390,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46531
47390
|
extension: "md",
|
|
46532
47391
|
supportsGlobal: true,
|
|
46533
47392
|
ruleDiscoveryMode: "auto",
|
|
46534
|
-
|
|
47393
|
+
collisionPolicy: "fold"
|
|
46535
47394
|
}
|
|
46536
47395
|
}],
|
|
46537
47396
|
["kiro", {
|
|
@@ -46564,7 +47423,8 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46564
47423
|
extension: "md",
|
|
46565
47424
|
supportsGlobal: true,
|
|
46566
47425
|
ruleDiscoveryMode: "toon",
|
|
46567
|
-
mcpInstructionsRegistrar: OpencodeMcp
|
|
47426
|
+
mcpInstructionsRegistrar: OpencodeMcp,
|
|
47427
|
+
collisionPolicy: "compose"
|
|
46568
47428
|
}
|
|
46569
47429
|
}],
|
|
46570
47430
|
["pi", {
|
|
@@ -46573,7 +47433,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46573
47433
|
extension: "md",
|
|
46574
47434
|
supportsGlobal: true,
|
|
46575
47435
|
ruleDiscoveryMode: "auto",
|
|
46576
|
-
|
|
47436
|
+
collisionPolicy: "fold"
|
|
46577
47437
|
}
|
|
46578
47438
|
}],
|
|
46579
47439
|
["qwencode", {
|
|
@@ -46591,7 +47451,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46591
47451
|
extension: "md",
|
|
46592
47452
|
supportsGlobal: true,
|
|
46593
47453
|
ruleDiscoveryMode: "auto",
|
|
46594
|
-
|
|
47454
|
+
collisionPolicy: "fold"
|
|
46595
47455
|
}
|
|
46596
47456
|
}],
|
|
46597
47457
|
["replit", {
|
|
@@ -46648,7 +47508,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46648
47508
|
extension: "md",
|
|
46649
47509
|
supportsGlobal: false,
|
|
46650
47510
|
ruleDiscoveryMode: "toon",
|
|
46651
|
-
|
|
47511
|
+
collisionPolicy: "fold"
|
|
46652
47512
|
}
|
|
46653
47513
|
}],
|
|
46654
47514
|
["devin", {
|
|
@@ -46717,16 +47577,23 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
46717
47577
|
const nonLocalRootRules = rulesyncRules.filter((rule) => !rule.getFrontmatter().localRoot);
|
|
46718
47578
|
const factory = this.getFactory(this.toolTarget);
|
|
46719
47579
|
const { meta } = factory;
|
|
46720
|
-
const
|
|
47580
|
+
const convertedRules = nonLocalRootRules.map((rulesyncRule) => {
|
|
46721
47581
|
if (!factory.class.isTargetedByRulesyncRule(rulesyncRule)) return null;
|
|
46722
|
-
return
|
|
46723
|
-
|
|
46724
|
-
|
|
46725
|
-
|
|
46726
|
-
|
|
46727
|
-
|
|
47582
|
+
return {
|
|
47583
|
+
toolRule: factory.class.fromRulesyncRule({
|
|
47584
|
+
outputRoot: this.outputRoot,
|
|
47585
|
+
rulesyncRule,
|
|
47586
|
+
validate: true,
|
|
47587
|
+
global: this.global
|
|
47588
|
+
}),
|
|
47589
|
+
rulesyncRule
|
|
47590
|
+
};
|
|
46728
47591
|
}).filter((rule) => rule !== null);
|
|
46729
|
-
|
|
47592
|
+
this.mergeRulesByOutputPath({
|
|
47593
|
+
convertedRules,
|
|
47594
|
+
collisionPolicy: meta.collisionPolicy ?? "preserve"
|
|
47595
|
+
});
|
|
47596
|
+
const toolRules = convertedRules.map(({ toolRule }) => toolRule);
|
|
46730
47597
|
this.applyLocalRootRules({
|
|
46731
47598
|
toolRules,
|
|
46732
47599
|
localRootRules,
|
|
@@ -46744,7 +47611,12 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
46744
47611
|
toolRules,
|
|
46745
47612
|
factory
|
|
46746
47613
|
});
|
|
46747
|
-
|
|
47614
|
+
const outputFiles = [...toolRules, ...extraFiles];
|
|
47615
|
+
this.warnForOutputPathCollisions({
|
|
47616
|
+
outputFiles,
|
|
47617
|
+
convertedRules
|
|
47618
|
+
});
|
|
47619
|
+
return outputFiles;
|
|
46748
47620
|
}
|
|
46749
47621
|
/**
|
|
46750
47622
|
* Handle localRoot rules (only in non-global mode and when enabled). Mutates
|
|
@@ -46831,39 +47703,80 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
46831
47703
|
});
|
|
46832
47704
|
}
|
|
46833
47705
|
/**
|
|
46834
|
-
*
|
|
47706
|
+
* Reconcile rules that resolve to the same output path.
|
|
46835
47707
|
*
|
|
46836
|
-
*
|
|
46837
|
-
*
|
|
46838
|
-
*
|
|
46839
|
-
* `.
|
|
46840
|
-
*
|
|
46841
|
-
*
|
|
47708
|
+
* Multiple root fragments are composed for tools that emit a fixed root file.
|
|
47709
|
+
* The `fold` policy is for tools whose rules engine reads only one root file and
|
|
47710
|
+
* neither scans a modular rules directory nor follows references. For example,
|
|
47711
|
+
* dcode reads `.deepagents/AGENTS.md`, while Warp reads root or subdirectory
|
|
47712
|
+
* `AGENTS.md` files but never `.warp/memories/`. Those adapters must fold every
|
|
47713
|
+
* body into one instance because last-writer-wins would silently drop content.
|
|
47714
|
+
* Plain-Markdown adapters can opt into `compose` for colliding modular outputs.
|
|
46842
47715
|
*
|
|
46843
|
-
*
|
|
46844
|
-
*
|
|
46845
|
-
*
|
|
47716
|
+
* A generated root rule becomes the merge target when present. A `fold` group
|
|
47717
|
+
* without one uses its first rule. A group only composes when every rendered
|
|
47718
|
+
* fragment is plain Markdown — a fragment carrying its own frontmatter block
|
|
47719
|
+
* (e.g. Amp's `globs:` gate) would end up mid-body where the tool ignores it.
|
|
47720
|
+
* Root-involved collisions that cannot be composed safely fail; other
|
|
47721
|
+
* collisions remain separate and are reported by the final output-path check.
|
|
47722
|
+
* Mutates `convertedRules` in place.
|
|
46846
47723
|
*/
|
|
46847
|
-
|
|
46848
|
-
if (
|
|
47724
|
+
mergeRulesByOutputPath({ convertedRules, collisionPolicy }) {
|
|
47725
|
+
if (convertedRules.length <= 1) return;
|
|
46849
47726
|
const groups = /* @__PURE__ */ new Map();
|
|
46850
|
-
for (const
|
|
46851
|
-
const path = (0, node_path.join)(
|
|
47727
|
+
for (const conversion of convertedRules) {
|
|
47728
|
+
const path = (0, node_path.join)(conversion.toolRule.getRelativeDirPath(), conversion.toolRule.getRelativeFilePath());
|
|
46852
47729
|
const group = groups.get(path);
|
|
46853
|
-
if (group) group.push(
|
|
46854
|
-
else groups.set(path, [
|
|
47730
|
+
if (group) group.push(conversion);
|
|
47731
|
+
else groups.set(path, [conversion]);
|
|
46855
47732
|
}
|
|
46856
47733
|
const survivors = /* @__PURE__ */ new Set();
|
|
46857
|
-
for (const group of groups
|
|
46858
|
-
|
|
47734
|
+
for (const [path, group] of groups) {
|
|
47735
|
+
if (group.length === 1) {
|
|
47736
|
+
const conversion = group[0];
|
|
47737
|
+
if (conversion) {
|
|
47738
|
+
if (collisionPolicy === "fold") conversion.toolRule.setFileContent(conversion.toolRule.getFileContent().trim());
|
|
47739
|
+
survivors.add(conversion);
|
|
47740
|
+
}
|
|
47741
|
+
continue;
|
|
47742
|
+
}
|
|
47743
|
+
const rootConversion = group.find(({ toolRule }) => toolRule.isRoot());
|
|
47744
|
+
const allGeneratedRulesAreRoots = group.every(({ toolRule }) => toolRule.isRoot());
|
|
47745
|
+
const hasSourceRoot = group.some(({ rulesyncRule }) => rulesyncRule.getFrontmatter().root === true);
|
|
47746
|
+
const allFragmentsArePlain = group.every(({ toolRule }) => !/^---\r?\n/.test(toolRule.getFileContent()));
|
|
47747
|
+
const shouldCompose = (collisionPolicy === "fold" || collisionPolicy === "compose" || allGeneratedRulesAreRoots) && allFragmentsArePlain;
|
|
47748
|
+
if (!shouldCompose && hasSourceRoot) throw new Error(`Multiple generated rules resolve to output path '${path}' for target '${this.toolTarget}', but this target cannot safely compose a collision involving a root rule. Source rules: ${formatRulePaths(group.map(({ rulesyncRule }) => rulesyncRule))}`);
|
|
47749
|
+
if (!shouldCompose) {
|
|
47750
|
+
for (const conversion of group) survivors.add(conversion);
|
|
47751
|
+
continue;
|
|
47752
|
+
}
|
|
47753
|
+
const target = rootConversion ?? group[0];
|
|
46859
47754
|
if (!target) continue;
|
|
46860
|
-
const mergedContent = [target, ...group.filter((rule) => rule !== target)].map((
|
|
46861
|
-
target.setFileContent(mergedContent);
|
|
47755
|
+
const mergedContent = [target, ...group.filter((rule) => rule !== target)].map(({ toolRule }) => toolRule.getFileContent().trim()).filter((content) => content.length > 0).join("\n\n");
|
|
47756
|
+
target.toolRule.setFileContent(mergedContent);
|
|
46862
47757
|
survivors.add(target);
|
|
46863
47758
|
}
|
|
46864
|
-
for (let i =
|
|
46865
|
-
const
|
|
46866
|
-
if (
|
|
47759
|
+
for (let i = convertedRules.length - 1; i >= 0; i--) {
|
|
47760
|
+
const conversion = convertedRules[i];
|
|
47761
|
+
if (conversion && !survivors.has(conversion)) convertedRules.splice(i, 1);
|
|
47762
|
+
}
|
|
47763
|
+
}
|
|
47764
|
+
warnForOutputPathCollisions({ outputFiles, convertedRules }) {
|
|
47765
|
+
const seen = /* @__PURE__ */ new Map();
|
|
47766
|
+
const describeSource = (file) => {
|
|
47767
|
+
const source = convertedRules.find(({ toolRule }) => toolRule === file)?.rulesyncRule;
|
|
47768
|
+
return source ? formatRulePaths([source]) : (0, node_path.join)(file.getRelativeDirPath(), file.getRelativeFilePath());
|
|
47769
|
+
};
|
|
47770
|
+
for (const file of outputFiles) {
|
|
47771
|
+
const path = (0, node_path.join)(file.getRelativeDirPath(), file.getRelativeFilePath());
|
|
47772
|
+
const key = path.toLowerCase();
|
|
47773
|
+
const previous = seen.get(key);
|
|
47774
|
+
if (previous) {
|
|
47775
|
+
const previousPath = (0, node_path.join)(previous.getRelativeDirPath(), previous.getRelativeFilePath());
|
|
47776
|
+
const pathDescription = previousPath === path ? `'${path}'` : `'${previousPath}' and '${path}' (compared case-insensitively, as on macOS and Windows)`;
|
|
47777
|
+
this.logger.warn(`Both ${describeSource(previous)} and ${describeSource(file)} generate to ${pathDescription}; the last one wins wherever they collide.`);
|
|
47778
|
+
}
|
|
47779
|
+
seen.set(key, file);
|
|
46867
47780
|
}
|
|
46868
47781
|
}
|
|
46869
47782
|
/**
|
|
@@ -47023,14 +47936,13 @@ As this project's AI coding tool, you must follow the additional conventions bel
|
|
|
47023
47936
|
}));
|
|
47024
47937
|
const factory = this.getFactory(this.toolTarget);
|
|
47025
47938
|
const targetedRootRules = rulesyncRules.filter((rule) => rule.getFrontmatter().root).filter((rule) => factory.class.isTargetedByRulesyncRule(rule));
|
|
47026
|
-
if (targetedRootRules.length > 1) throw new Error(`Multiple root rulesync rules found for target '${this.toolTarget}': ${formatRulePaths(targetedRootRules)}`);
|
|
47027
47939
|
if (targetedRootRules.length === 0 && rulesyncRules.length > 0) this.logger.warn(`No root rulesync rule file found for target '${this.toolTarget}'. Consider adding 'root: true' to one of your rule files in ${RULESYNC_RULES_RELATIVE_DIR_PATH}.`);
|
|
47028
47940
|
const targetedLocalRootRules = rulesyncRules.filter((rule) => rule.getFrontmatter().localRoot).filter((rule) => factory.class.isTargetedByRulesyncRule(rule));
|
|
47029
47941
|
if (targetedLocalRootRules.length > 1) throw new Error(`Multiple localRoot rules found for target '${this.toolTarget}': ${formatRulePaths(targetedLocalRootRules)}. Only one rule can have localRoot: true`);
|
|
47030
47942
|
if (targetedLocalRootRules.length > 0 && targetedRootRules.length === 0) throw new Error(`localRoot: true requires a root: true rule to exist for target '${this.toolTarget}' (found in ${formatRulePaths(targetedLocalRootRules)})`);
|
|
47031
47943
|
if (this.global) {
|
|
47032
47944
|
const globalPaths = factory.class.getSettablePaths({ global: true });
|
|
47033
|
-
const supportsGlobalNonRoot = "nonRoot" in globalPaths && globalPaths.nonRoot !== null || factory.meta.supportsGlobal && factory.meta.
|
|
47945
|
+
const supportsGlobalNonRoot = "nonRoot" in globalPaths && globalPaths.nonRoot !== null || factory.meta.supportsGlobal && factory.meta.collisionPolicy === "fold";
|
|
47034
47946
|
const nonRootRules = rulesyncRules.filter((rule) => !rule.getFrontmatter().root && !rule.getFrontmatter().localRoot && factory.class.isTargetedByRulesyncRule(rule));
|
|
47035
47947
|
if (nonRootRules.length > 0 && !supportsGlobalNonRoot) this.logger.warn(`${nonRootRules.length} non-root rulesync rules found, but it's in global mode, so ignoring them: ${formatRulePaths(nonRootRules)}`);
|
|
47036
47948
|
if (targetedLocalRootRules.length > 0) this.logger.warn(`${targetedLocalRootRules.length} localRoot rules found, but localRoot is not supported in global mode, ignoring them: ${formatRulePaths(targetedLocalRootRules)}`);
|
|
@@ -47273,14 +48185,36 @@ async function assertPluginRootSafe(params) {
|
|
|
47273
48185
|
}
|
|
47274
48186
|
//#endregion
|
|
47275
48187
|
//#region src/utils/tool-output-root.ts
|
|
48188
|
+
/** The environment variable each tool reads for its profile root. */
|
|
48189
|
+
const TOOL_HOME_ENV_VARS = {
|
|
48190
|
+
hermesagent: "HERMES_HOME",
|
|
48191
|
+
"kimi-code": "KIMI_CODE_HOME"
|
|
48192
|
+
};
|
|
48193
|
+
/**
|
|
48194
|
+
* Substitute a tool's home override (`HERMES_HOME`, `KIMI_CODE_HOME`) for the
|
|
48195
|
+
* output root in global scope.
|
|
48196
|
+
*
|
|
48197
|
+
* The override wins over `--output-roots`: it names where the tool itself reads
|
|
48198
|
+
* its profile, so writing anywhere else would produce files the tool ignores.
|
|
48199
|
+
*
|
|
48200
|
+
* A substituted value goes through the same `validateOutputRoot` the CLI and
|
|
48201
|
+
* config paths use, so an override of `/` or an unnormalized path is rejected
|
|
48202
|
+
* instead of silently becoming the output root. The rejection is re-thrown
|
|
48203
|
+
* naming the variable, since the user never passed an `--output-roots` flag.
|
|
48204
|
+
*/
|
|
47276
48205
|
function resolveToolOutputRoot({ outputRoot, toolTarget, global }) {
|
|
47277
48206
|
if (!global) return outputRoot;
|
|
47278
|
-
|
|
48207
|
+
const resolved = toolTarget === "hermesagent" ? resolveHermesagentOutputRoot({
|
|
47279
48208
|
outputRoot,
|
|
47280
48209
|
global
|
|
47281
|
-
});
|
|
47282
|
-
if (
|
|
47283
|
-
|
|
48210
|
+
}) : toolTarget === "kimi-code" ? getKimiCodeHome() ?? outputRoot : outputRoot;
|
|
48211
|
+
if (resolved === outputRoot) return resolved;
|
|
48212
|
+
try {
|
|
48213
|
+
validateOutputRoot(resolved);
|
|
48214
|
+
} catch (error) {
|
|
48215
|
+
throw new Error(`${TOOL_HOME_ENV_VARS[toolTarget] ?? "The tool home override"} is not a usable output root: ${formatError(error)}`, { cause: error });
|
|
48216
|
+
}
|
|
48217
|
+
return resolved;
|
|
47284
48218
|
}
|
|
47285
48219
|
//#endregion
|
|
47286
48220
|
//#region src/lib/convert.ts
|
|
@@ -48681,7 +49615,11 @@ async function generateChecksCore(params) {
|
|
|
48681
49615
|
for (const toolTarget of toolTargets) for (const outputRoot of config.getOutputRoots(toolTarget)) {
|
|
48682
49616
|
if (!config.getFeatures(toolTarget).includes("checks")) continue;
|
|
48683
49617
|
const processor = new ChecksProcessor({
|
|
48684
|
-
outputRoot
|
|
49618
|
+
outputRoot: resolveToolOutputRoot({
|
|
49619
|
+
outputRoot,
|
|
49620
|
+
toolTarget,
|
|
49621
|
+
global: config.getGlobal()
|
|
49622
|
+
}),
|
|
48685
49623
|
inputRoot: config.getInputRoot(),
|
|
48686
49624
|
toolTarget,
|
|
48687
49625
|
global: config.getGlobal(),
|