rulesync 16.1.0 → 16.3.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 +859 -5
- package/dist/cli/index.js +860 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-BmXT7mRS.js → import-Bj5HxY9p.js} +1348 -393
- package/dist/import-Bj5HxY9p.js.map +1 -0
- package/dist/{import-eG7fZPXI.cjs → import-DD-GRmB9.cjs} +1379 -406
- 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
|
@@ -86,6 +86,13 @@ const ALL_FEATURES = [
|
|
|
86
86
|
...ACTIVE_FEATURES_AFTER_IGNORE
|
|
87
87
|
];
|
|
88
88
|
const ALL_FEATURES_WITH_WILDCARD = [...ALL_FEATURES, "*"];
|
|
89
|
+
/**
|
|
90
|
+
* Features that remain accepted for compatibility but are superseded by a
|
|
91
|
+
* newer feature. Maps each deprecated feature to its replacement; consumers
|
|
92
|
+
* (e.g. `rulesync doctor`) derive their deprecation warnings from this map so
|
|
93
|
+
* the set cannot drift from the schema above.
|
|
94
|
+
*/
|
|
95
|
+
const DEPRECATED_FEATURE_REPLACEMENTS = { ignore: "permissions" };
|
|
89
96
|
const ACTIVE_FEATURES = [...ACTIVE_FEATURES_BEFORE_IGNORE, ...ACTIVE_FEATURES_AFTER_IGNORE];
|
|
90
97
|
const DeprecatedIgnoreFeatureSchema = zod_mini.z.literal("ignore").check((0, zod_mini.meta)({
|
|
91
98
|
deprecated: true,
|
|
@@ -127,27 +134,27 @@ const isFeatureValueEnabled = (value) => {
|
|
|
127
134
|
};
|
|
128
135
|
//#endregion
|
|
129
136
|
//#region src/constants/rulesync-paths.ts
|
|
130
|
-
const { join: join$
|
|
137
|
+
const { join: join$281 } = node_path.posix;
|
|
131
138
|
const RULESYNC_CONFIG_RELATIVE_FILE_PATH = "rulesync.jsonc";
|
|
132
139
|
const RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH = "rulesync.local.jsonc";
|
|
133
140
|
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$
|
|
141
|
+
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$281(RULESYNC_RELATIVE_DIR_PATH, "rules");
|
|
142
|
+
const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$281(RULESYNC_RULES_RELATIVE_DIR_PATH, ".curated");
|
|
143
|
+
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$281(RULESYNC_RELATIVE_DIR_PATH, "commands");
|
|
144
|
+
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$281(RULESYNC_RELATIVE_DIR_PATH, "subagents");
|
|
145
|
+
const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$281(RULESYNC_RELATIVE_DIR_PATH, "checks");
|
|
146
|
+
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$281(RULESYNC_RELATIVE_DIR_PATH, "mcp.jsonc");
|
|
147
|
+
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$281(RULESYNC_RELATIVE_DIR_PATH, "hooks.jsonc");
|
|
148
|
+
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$281(RULESYNC_RELATIVE_DIR_PATH, "permissions.jsonc");
|
|
149
|
+
join$281(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
|
|
150
|
+
const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$281(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
|
|
151
|
+
const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$281(RULESYNC_RELATIVE_DIR_PATH, "permissions.json");
|
|
145
152
|
const RULESYNC_AIIGNORE_FILE_NAME = ".aiignore";
|
|
146
|
-
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$
|
|
153
|
+
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$281(RULESYNC_RELATIVE_DIR_PATH, ".aiignore");
|
|
147
154
|
const RULESYNC_IGNORE_RELATIVE_FILE_PATH = ".rulesyncignore";
|
|
148
155
|
const RULESYNC_OVERVIEW_FILE_NAME = "overview.md";
|
|
149
|
-
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$
|
|
150
|
-
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$
|
|
156
|
+
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$281(RULESYNC_RELATIVE_DIR_PATH, "skills");
|
|
157
|
+
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$281(RULESYNC_SKILLS_RELATIVE_DIR_PATH, ".curated");
|
|
151
158
|
const RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync.lock";
|
|
152
159
|
const RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync-npm.lock.json";
|
|
153
160
|
const RULESYNC_MCP_FILE_NAME = "mcp.jsonc";
|
|
@@ -217,6 +224,7 @@ const ignoreProcessorToolTargetTuple = [
|
|
|
217
224
|
"kiro-cli",
|
|
218
225
|
"kiro-ide",
|
|
219
226
|
"qwencode",
|
|
227
|
+
"reasonix",
|
|
220
228
|
"roo",
|
|
221
229
|
"devin",
|
|
222
230
|
"vibe",
|
|
@@ -274,6 +282,7 @@ const commandsProcessorToolTargetTuple = [
|
|
|
274
282
|
"cursor",
|
|
275
283
|
"factorydroid",
|
|
276
284
|
"goose",
|
|
285
|
+
"grokcli",
|
|
277
286
|
"hermesagent",
|
|
278
287
|
"junie",
|
|
279
288
|
"kilo",
|
|
@@ -429,6 +438,7 @@ const checksProcessorToolTargetTuple = [
|
|
|
429
438
|
"amp",
|
|
430
439
|
"cursor",
|
|
431
440
|
"hermesagent",
|
|
441
|
+
"rovodev",
|
|
432
442
|
"takt"
|
|
433
443
|
];
|
|
434
444
|
//#endregion
|
|
@@ -832,6 +842,7 @@ const ErrorCodes = {
|
|
|
832
842
|
GITIGNORE_FAILED: "GITIGNORE_FAILED",
|
|
833
843
|
INIT_FAILED: "INIT_FAILED",
|
|
834
844
|
MCP_FAILED: "MCP_FAILED",
|
|
845
|
+
DOCTOR_FAILED: "DOCTOR_FAILED",
|
|
835
846
|
UNKNOWN_ERROR: "UNKNOWN_ERROR"
|
|
836
847
|
};
|
|
837
848
|
/**
|
|
@@ -840,10 +851,12 @@ const ErrorCodes = {
|
|
|
840
851
|
var CLIError = class extends Error {
|
|
841
852
|
code;
|
|
842
853
|
exitCode;
|
|
843
|
-
|
|
854
|
+
details;
|
|
855
|
+
constructor(message, code = ErrorCodes.UNKNOWN_ERROR, exitCode = 1, details) {
|
|
844
856
|
super(message);
|
|
845
857
|
this.code = code;
|
|
846
858
|
this.exitCode = exitCode;
|
|
859
|
+
this.details = details;
|
|
847
860
|
this.name = "CLIError";
|
|
848
861
|
}
|
|
849
862
|
};
|
|
@@ -967,6 +980,7 @@ var JsonLogger = class extends BaseLogger {
|
|
|
967
980
|
message: errorMessage
|
|
968
981
|
};
|
|
969
982
|
if (this._verbose && message instanceof Error && message.stack) errorInfo.stack = message.stack;
|
|
983
|
+
if (message instanceof CLIError && message.details !== void 0) errorInfo.details = message.details;
|
|
970
984
|
this.outputJson(false, errorInfo);
|
|
971
985
|
}
|
|
972
986
|
debug(_message, ..._args) {}
|
|
@@ -1027,6 +1041,10 @@ function hasControlCharacters(value) {
|
|
|
1027
1041
|
}
|
|
1028
1042
|
//#endregion
|
|
1029
1043
|
//#region src/config/config.ts
|
|
1044
|
+
/**
|
|
1045
|
+
* Key accepted alongside feature names in the per-feature object form of
|
|
1046
|
+
* `targets`. Exported so `rulesync doctor` treats the same key as valid.
|
|
1047
|
+
*/
|
|
1030
1048
|
const GITIGNORE_DESTINATION_KEY = "gitignoreDestination";
|
|
1031
1049
|
/**
|
|
1032
1050
|
* Schema for a single source entry in the sources array.
|
|
@@ -1082,7 +1100,20 @@ const ConfigFileSchema = zod_mini.z.object({
|
|
|
1082
1100
|
});
|
|
1083
1101
|
zod_mini.z.required(ConfigParamsSchema);
|
|
1084
1102
|
/**
|
|
1085
|
-
*
|
|
1103
|
+
* Normalizes the configuration file location to an absolute path.
|
|
1104
|
+
*
|
|
1105
|
+
* `ConfigResolver` always supplies the path it actually loaded; the fallback
|
|
1106
|
+
* only covers direct programmatic construction, where the conventional
|
|
1107
|
+
* location next to the input root is the best guess.
|
|
1108
|
+
*/
|
|
1109
|
+
function normalizeConfigFilePath({ configFilePath, inputRoot }) {
|
|
1110
|
+
if (configFilePath === void 0) return (0, node_path.join)(inputRoot, RULESYNC_CONFIG_RELATIVE_FILE_PATH);
|
|
1111
|
+
return (0, node_path.isAbsolute)(configFilePath) ? configFilePath : (0, node_path.resolve)(configFilePath);
|
|
1112
|
+
}
|
|
1113
|
+
/**
|
|
1114
|
+
* Conflicting target pairs that cannot be used together.
|
|
1115
|
+
* Exported so `rulesync doctor` can report the same conflicts as diagnostics
|
|
1116
|
+
* without duplicating the list.
|
|
1086
1117
|
*/
|
|
1087
1118
|
const CONFLICTING_TARGET_PAIRS = [["augmentcode", "augmentcode-legacy"], ["claudecode", "claudecode-legacy"]];
|
|
1088
1119
|
/**
|
|
@@ -1152,8 +1183,9 @@ var Config = class Config {
|
|
|
1152
1183
|
dryRun;
|
|
1153
1184
|
check;
|
|
1154
1185
|
inputRoot;
|
|
1186
|
+
configFilePath;
|
|
1155
1187
|
sources;
|
|
1156
|
-
constructor({ outputRoots, targets, features, verbose, delete: isDelete, global, silent, simulateCommands, simulateSubagents, simulateSkills, flattenedCommandNaming, gitignoreTargetsOnly, gitignoreDestination, dryRun, check, inputRoot, sources, configFileTargets }) {
|
|
1188
|
+
constructor({ outputRoots, targets, features, verbose, delete: isDelete, global, silent, simulateCommands, simulateSubagents, simulateSkills, flattenedCommandNaming, gitignoreTargetsOnly, gitignoreDestination, dryRun, check, inputRoot, configFilePath, sources, configFileTargets }) {
|
|
1157
1189
|
assertTargetsFeaturesExclusive({
|
|
1158
1190
|
targets,
|
|
1159
1191
|
features
|
|
@@ -1186,6 +1218,10 @@ var Config = class Config {
|
|
|
1186
1218
|
this.dryRun = dryRun ?? false;
|
|
1187
1219
|
this.check = check ?? false;
|
|
1188
1220
|
this.inputRoot = inputRoot === void 0 ? process.cwd() : (0, node_path.isAbsolute)(inputRoot) ? inputRoot : (0, node_path.resolve)(inputRoot);
|
|
1221
|
+
this.configFilePath = normalizeConfigFilePath({
|
|
1222
|
+
configFilePath,
|
|
1223
|
+
inputRoot: this.inputRoot
|
|
1224
|
+
});
|
|
1189
1225
|
this.sources = sources ?? [];
|
|
1190
1226
|
}
|
|
1191
1227
|
/**
|
|
@@ -1379,6 +1415,14 @@ var Config = class Config {
|
|
|
1379
1415
|
getInputRoot() {
|
|
1380
1416
|
return this.inputRoot;
|
|
1381
1417
|
}
|
|
1418
|
+
/**
|
|
1419
|
+
* Returns the absolute path of the configuration file this config was
|
|
1420
|
+
* resolved from. The file itself may not exist — `rulesync` runs fine
|
|
1421
|
+
* without one — so callers must treat this as a location, not a guarantee.
|
|
1422
|
+
*/
|
|
1423
|
+
getConfigFilePath() {
|
|
1424
|
+
return this.configFilePath;
|
|
1425
|
+
}
|
|
1382
1426
|
getSources() {
|
|
1383
1427
|
return this.sources;
|
|
1384
1428
|
}
|
|
@@ -1601,6 +1645,7 @@ var ConfigResolver = class {
|
|
|
1601
1645
|
fallback: getDefaults().check
|
|
1602
1646
|
}),
|
|
1603
1647
|
inputRoot: resolvedInputRoot !== void 0 ? (0, node_path.resolve)(resolvedInputRoot) : cwd,
|
|
1648
|
+
configFilePath: validatedConfigPath,
|
|
1604
1649
|
sources: configByFile.sources ?? getDefaults().sources,
|
|
1605
1650
|
flattenedCommandNaming: configByFile.flattenedCommandNaming ?? getDefaults().flattenedCommandNaming,
|
|
1606
1651
|
configFileTargets: extractConfigFileTargets(configByFile.targets)
|
|
@@ -4011,10 +4056,30 @@ const ClinePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
4011
4056
|
* portable and keeps them out of other tools' configs. Mirrors the OpenCode
|
|
4012
4057
|
* override; each value may be a bare action string or a pattern map.
|
|
4013
4058
|
*
|
|
4059
|
+
* `sandbox` is the sibling top-level block that governs the sandbox Kilo runs
|
|
4060
|
+
* commands in: `enabled` (boolean), `network` (`"deny"` and friends),
|
|
4061
|
+
* `allowed_hosts` (a list of `host` / `host:port` destination exceptions) and
|
|
4062
|
+
* `writable_paths`. It has no canonical permission category, so it is authored
|
|
4063
|
+
* here and emitted only for Kilo.
|
|
4064
|
+
*
|
|
4065
|
+
* Upstream restricts what a *project* config may say: `allowed_hosts` and
|
|
4066
|
+
* `writable_paths` are honored from the global config only, and a project
|
|
4067
|
+
* config may merely tighten (`enabled: true`, `network: "deny"`) — a
|
|
4068
|
+
* project-level network denial even clears the global destination exceptions.
|
|
4069
|
+
* rulesync mirrors that: at project scope only `enabled` and `network` are
|
|
4070
|
+
* written, and the rest are dropped with a warning rather than emitted into a
|
|
4071
|
+
* file Kilo would ignore.
|
|
4072
|
+
*
|
|
4014
4073
|
* @example
|
|
4015
4074
|
* { "permission": { "external_directory": "deny", "doom_loop": "ask" } }
|
|
4075
|
+
* @example
|
|
4076
|
+
* { "sandbox": { "enabled": true, "network": "deny" } }
|
|
4077
|
+
* @see https://kilo.ai/docs/getting-started/settings/sandboxing
|
|
4016
4078
|
*/
|
|
4017
|
-
const KiloPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
4079
|
+
const KiloPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
4080
|
+
permission: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), OpencodeOverridePermissionValueSchema)),
|
|
4081
|
+
sandbox: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
4082
|
+
});
|
|
4018
4083
|
/**
|
|
4019
4084
|
* Tool-scoped override block for Claude Code. Claude Code's `permissions` object
|
|
4020
4085
|
* (in `.claude/settings.json`) carries non-list fields that have no canonical
|
|
@@ -5323,6 +5388,26 @@ const CURSOR_IGNORE_FILE_NAME = ".cursorignore";
|
|
|
5323
5388
|
const CURSOR_PERMISSIONS_FILE_NAME = "cli.json";
|
|
5324
5389
|
const CURSOR_PERMISSIONS_GLOBAL_FILE_NAME = "cli-config.json";
|
|
5325
5390
|
//#endregion
|
|
5391
|
+
//#region src/constants/rovodev-paths.ts
|
|
5392
|
+
const ROVODEV_DIR = ".rovodev";
|
|
5393
|
+
const ROVODEV_SKILLS_DIR_PATH = (0, node_path.join)(ROVODEV_DIR, "skills");
|
|
5394
|
+
const ROVODEV_SUBAGENTS_DIR_PATH = (0, node_path.join)(ROVODEV_DIR, "subagents");
|
|
5395
|
+
const ROVODEV_MODULAR_RULES_DIR_PATH = (0, node_path.join)(ROVODEV_DIR, ".rulesync", "modular-rules");
|
|
5396
|
+
const ROVODEV_RULE_FILE_NAME = "AGENTS.md";
|
|
5397
|
+
const ROVODEV_LEGACY_RULE_FILE_NAME = "AGENTS.local.md";
|
|
5398
|
+
const ROVODEV_MCP_FILE_NAME = "mcp.json";
|
|
5399
|
+
const ROVODEV_CONFIG_FILE_NAME = "config.yml";
|
|
5400
|
+
const ROVODEV_AGENTS_SKILLS_DIR_PATH = (0, node_path.join)(".agents", "skills");
|
|
5401
|
+
const ROVODEV_PROMPTS_FILE_NAME = "prompts.yml";
|
|
5402
|
+
const ROVODEV_PROMPTS_DIR_PATH = (0, node_path.join)(ROVODEV_DIR, "prompts");
|
|
5403
|
+
/**
|
|
5404
|
+
* Custom instructions for Rovo Dev's code reviews: a plain-Markdown file (no
|
|
5405
|
+
* frontmatter) in the repository root's `.rovodev/` folder. Note the leading
|
|
5406
|
+
* dot in the file name.
|
|
5407
|
+
* @see https://support.atlassian.com/rovo/docs/set-custom-instructions-for-code-reviews/
|
|
5408
|
+
*/
|
|
5409
|
+
const ROVODEV_REVIEW_AGENT_FILE_NAME = ".review-agent.md";
|
|
5410
|
+
//#endregion
|
|
5326
5411
|
//#region src/constants/takt-paths.ts
|
|
5327
5412
|
const TAKT_DIR = ".takt";
|
|
5328
5413
|
const TAKT_FACETS_SUBDIR = "facets";
|
|
@@ -5784,12 +5869,19 @@ function slugifyCheckName(value) {
|
|
|
5784
5869
|
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48).replace(/-+$/, "");
|
|
5785
5870
|
}
|
|
5786
5871
|
//#endregion
|
|
5787
|
-
//#region src/features/checks/
|
|
5872
|
+
//#region src/features/checks/aggregated-check-file.ts
|
|
5788
5873
|
/**
|
|
5789
|
-
*
|
|
5790
|
-
*
|
|
5791
|
-
*
|
|
5874
|
+
* Shared machinery for the tools whose checks surface is **one aggregated
|
|
5875
|
+
* instruction file** rather than a file per check — Cursor Bugbot's
|
|
5876
|
+
* `.cursor/BUGBOT.md` and Rovo Dev's `.rovodev/.review-agent.md`.
|
|
5877
|
+
*
|
|
5878
|
+
* Both read the file as free prose, so the check identities have to be carried
|
|
5879
|
+
* in something invisible to the reader: an HTML-comment marker per section.
|
|
5880
|
+
* That marker convention, the escaping that keeps a check body from splitting
|
|
5881
|
+
* itself, and the import-side split are the same for both files, so they live
|
|
5882
|
+
* here once.
|
|
5792
5883
|
*/
|
|
5884
|
+
/** Marks where one check starts inside the single instruction file. */
|
|
5793
5885
|
const CHECK_MARKER_PATTERN = /^<!--\s*rulesync:check:(.+?)\s*-->[ \t]*$/gm;
|
|
5794
5886
|
/**
|
|
5795
5887
|
* A marker line a check body wrote itself — a rulesync doc fragment quoted in a
|
|
@@ -5800,17 +5892,16 @@ const CHECK_MARKER_PATTERN = /^<!--\s*rulesync:check:(.+?)\s*-->[ \t]*$/gm;
|
|
|
5800
5892
|
*/
|
|
5801
5893
|
const ESCAPABLE_MARKER_PATTERN = /^(<!--\s*rulesync:)((?:literal-)*check:.+?\s*-->[ \t]*)$/gm;
|
|
5802
5894
|
const ESCAPED_MARKER_PATTERN = /^(<!--\s*rulesync:)literal-((?:literal-)*check:.+?\s*-->[ \t]*)$/gm;
|
|
5803
|
-
|
|
5804
|
-
function renderMarker(name) {
|
|
5895
|
+
function renderCheckMarker(name) {
|
|
5805
5896
|
return `<!-- rulesync:check:${name} -->`;
|
|
5806
5897
|
}
|
|
5807
|
-
function
|
|
5898
|
+
function escapeCheckMarkers(content) {
|
|
5808
5899
|
return content.replace(ESCAPABLE_MARKER_PATTERN, "$1literal-$2");
|
|
5809
5900
|
}
|
|
5810
|
-
function
|
|
5901
|
+
function unescapeCheckMarkers(content) {
|
|
5811
5902
|
return content.replace(ESCAPED_MARKER_PATTERN, "$1$2");
|
|
5812
5903
|
}
|
|
5813
|
-
function
|
|
5904
|
+
function findCheckMarkers(fileContent) {
|
|
5814
5905
|
CHECK_MARKER_PATTERN.lastIndex = 0;
|
|
5815
5906
|
const markers = [];
|
|
5816
5907
|
let match = CHECK_MARKER_PATTERN.exec(fileContent);
|
|
@@ -5825,23 +5916,47 @@ function findMarkers(fileContent) {
|
|
|
5825
5916
|
return markers;
|
|
5826
5917
|
}
|
|
5827
5918
|
/**
|
|
5828
|
-
*
|
|
5829
|
-
*
|
|
5830
|
-
*
|
|
5919
|
+
* Whether the file holds instruction text ahead of the first marker — the
|
|
5920
|
+
* question the "generating replaces this" warning asks. A file with no marker
|
|
5921
|
+
* at all is entirely hand-written, so it qualifies; an empty one does not,
|
|
5922
|
+
* since there is nothing to replace.
|
|
5923
|
+
*/
|
|
5924
|
+
function hasHandWrittenPreamble(fileContent) {
|
|
5925
|
+
const firstMarkerStart = findCheckMarkers(fileContent)[0]?.start ?? fileContent.length;
|
|
5926
|
+
return fileContent.slice(0, firstMarkerStart).trim().length > 0;
|
|
5927
|
+
}
|
|
5928
|
+
/**
|
|
5929
|
+
* Whether the file is nothing but sections rulesync generated — the question
|
|
5930
|
+
* the deletion guard asks, and a stricter one than
|
|
5931
|
+
* {@link hasHandWrittenPreamble}. A file carrying no marker at all is not
|
|
5932
|
+
* rulesync's to remove even when it is empty: rulesync never wrote it, so an
|
|
5933
|
+
* empty one is somebody's placeholder rather than our leftover.
|
|
5934
|
+
*/
|
|
5935
|
+
function isOnlyGeneratedSections(fileContent) {
|
|
5936
|
+
const firstMarkerStart = findCheckMarkers(fileContent)[0]?.start;
|
|
5937
|
+
if (firstMarkerStart === void 0) return false;
|
|
5938
|
+
return fileContent.slice(0, firstMarkerStart).trim().length === 0;
|
|
5939
|
+
}
|
|
5940
|
+
/**
|
|
5941
|
+
* The instruction text one check contributes. Neither file has a field to put a
|
|
5942
|
+
* summary in, so `description` is used only when there is no body.
|
|
5831
5943
|
*/
|
|
5832
5944
|
function toInstruction(rulesyncCheck) {
|
|
5833
5945
|
const body = rulesyncCheck.getBody().trim();
|
|
5834
5946
|
if (body.length > 0) return body;
|
|
5835
5947
|
return rulesyncCheck.getFrontmatter().description?.trim() ?? "";
|
|
5836
5948
|
}
|
|
5837
|
-
function
|
|
5949
|
+
function renderCheckSection(rulesyncCheck) {
|
|
5838
5950
|
const name = (0, node_path.basename)(rulesyncCheck.getRelativeFilePath(), ".md");
|
|
5839
5951
|
const heading = `## ${name}`;
|
|
5840
5952
|
const instruction = toInstruction(rulesyncCheck);
|
|
5841
|
-
const lines = [
|
|
5842
|
-
if (instruction.length > 0) lines.push("",
|
|
5953
|
+
const lines = [renderCheckMarker(name), heading];
|
|
5954
|
+
if (instruction.length > 0) lines.push("", escapeCheckMarkers(instruction));
|
|
5843
5955
|
return lines.join("\n");
|
|
5844
5956
|
}
|
|
5957
|
+
function renderCheckFile(rulesyncChecks) {
|
|
5958
|
+
return `${rulesyncChecks.map(renderCheckSection).join("\n\n")}\n`;
|
|
5959
|
+
}
|
|
5845
5960
|
/** Drop the heading generate writes, so a round trip does not stack headings. */
|
|
5846
5961
|
function stripGeneratedHeading(section, name) {
|
|
5847
5962
|
const [firstLine, ...rest] = section.split("\n");
|
|
@@ -5849,6 +5964,53 @@ function stripGeneratedHeading(section, name) {
|
|
|
5849
5964
|
return section.trim();
|
|
5850
5965
|
}
|
|
5851
5966
|
/**
|
|
5967
|
+
* Split an aggregated instruction file back into one check per section.
|
|
5968
|
+
*
|
|
5969
|
+
* Content ahead of the first marker — and a hand-written file with no markers
|
|
5970
|
+
* at all — becomes a single check named `fallbackName`, so nothing in the file
|
|
5971
|
+
* is dropped.
|
|
5972
|
+
*/
|
|
5973
|
+
function splitCheckFile({ fileContent, fallbackName }) {
|
|
5974
|
+
const sections = [];
|
|
5975
|
+
const markers = findCheckMarkers(fileContent);
|
|
5976
|
+
const preambleEnd = markers[0]?.start ?? fileContent.length;
|
|
5977
|
+
const preamble = fileContent.slice(0, preambleEnd).trim();
|
|
5978
|
+
if (preamble.length > 0) sections.push({
|
|
5979
|
+
name: fallbackName,
|
|
5980
|
+
content: unescapeCheckMarkers(preamble)
|
|
5981
|
+
});
|
|
5982
|
+
for (const [index, marker] of markers.entries()) {
|
|
5983
|
+
const sectionEnd = markers[index + 1]?.start ?? fileContent.length;
|
|
5984
|
+
const markerName = marker.name.trim();
|
|
5985
|
+
const name = slugifyCheckName(markerName) || fallbackName;
|
|
5986
|
+
const content = stripGeneratedHeading(fileContent.slice(marker.end, sectionEnd).trim(), markerName);
|
|
5987
|
+
sections.push({
|
|
5988
|
+
name,
|
|
5989
|
+
content: unescapeCheckMarkers(content)
|
|
5990
|
+
});
|
|
5991
|
+
}
|
|
5992
|
+
const used = /* @__PURE__ */ new Set();
|
|
5993
|
+
return sections.map(({ name, content }) => {
|
|
5994
|
+
let uniqueName = name;
|
|
5995
|
+
let suffix = 2;
|
|
5996
|
+
while (used.has(uniqueName)) {
|
|
5997
|
+
uniqueName = `${name}-${suffix}`;
|
|
5998
|
+
suffix += 1;
|
|
5999
|
+
}
|
|
6000
|
+
used.add(uniqueName);
|
|
6001
|
+
return new RulesyncCheck({
|
|
6002
|
+
outputRoot: ".",
|
|
6003
|
+
relativeDirPath: RULESYNC_CHECKS_RELATIVE_DIR_PATH,
|
|
6004
|
+
relativeFilePath: `${uniqueName}.md`,
|
|
6005
|
+
frontmatter: { targets: ["*"] },
|
|
6006
|
+
body: content
|
|
6007
|
+
});
|
|
6008
|
+
});
|
|
6009
|
+
}
|
|
6010
|
+
//#endregion
|
|
6011
|
+
//#region src/features/checks/cursor-check.ts
|
|
6012
|
+
const FALLBACK_CHECK_NAME$1 = "bugbot";
|
|
6013
|
+
/**
|
|
5852
6014
|
* Checks adapter for Cursor Bugbot (`.cursor/BUGBOT.md`).
|
|
5853
6015
|
*
|
|
5854
6016
|
* Bugbot takes one aggregated instruction file per directory rather than a file
|
|
@@ -5905,9 +6067,7 @@ var CursorCheck = class CursorCheck extends ToolCheck {
|
|
|
5905
6067
|
const paths = CursorCheck.getSettablePaths();
|
|
5906
6068
|
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath ?? "BUGBOT.md"));
|
|
5907
6069
|
if (fileContent === null) return true;
|
|
5908
|
-
|
|
5909
|
-
if (firstMarkerStart === void 0) return false;
|
|
5910
|
-
return fileContent.slice(0, firstMarkerStart).trim().length === 0;
|
|
6070
|
+
return isOnlyGeneratedSections(fileContent);
|
|
5911
6071
|
}
|
|
5912
6072
|
static fromRulesyncCheck(_params) {
|
|
5913
6073
|
throw new Error("Cursor checks are built from all checks at once; use fromRulesyncChecks.");
|
|
@@ -5917,10 +6077,8 @@ var CursorCheck = class CursorCheck extends ToolCheck {
|
|
|
5917
6077
|
const paths = CursorCheck.getSettablePaths({ global });
|
|
5918
6078
|
const relativeFilePath = paths.relativeFilePath ?? "BUGBOT.md";
|
|
5919
6079
|
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`;
|
|
6080
|
+
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.`);
|
|
6081
|
+
const fileContent = renderCheckFile(rulesyncChecks);
|
|
5924
6082
|
return [new CursorCheck({
|
|
5925
6083
|
outputRoot,
|
|
5926
6084
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -5963,41 +6121,9 @@ var CursorCheck = class CursorCheck extends ToolCheck {
|
|
|
5963
6121
|
return first;
|
|
5964
6122
|
}
|
|
5965
6123
|
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
|
-
});
|
|
6124
|
+
return splitCheckFile({
|
|
6125
|
+
fileContent: this.getFileContent(),
|
|
6126
|
+
fallbackName: FALLBACK_CHECK_NAME$1
|
|
6001
6127
|
});
|
|
6002
6128
|
}
|
|
6003
6129
|
};
|
|
@@ -6019,8 +6145,22 @@ var CursorCheck = class CursorCheck extends ToolCheck {
|
|
|
6019
6145
|
*/
|
|
6020
6146
|
/** Project-root instruction file auto-injected by Hermes Agent. */
|
|
6021
6147
|
const HERMESAGENT_RULE_FILE_NAME = ".hermes.md";
|
|
6022
|
-
/**
|
|
6148
|
+
/**
|
|
6149
|
+
* Root directory for Hermes Agent global configuration (the HERMES_HOME dir).
|
|
6150
|
+
* Also the project-local plugin tree, which is `.hermes/` on every platform.
|
|
6151
|
+
*/
|
|
6023
6152
|
const HERMESAGENT_GLOBAL_DIR = ".hermes";
|
|
6153
|
+
/**
|
|
6154
|
+
* Home-relative global profile root on Windows: upstream defaults to
|
|
6155
|
+
* `%LOCALAPPDATA%\hermes` there, not `~/.hermes`.
|
|
6156
|
+
* Resolve it through `getHermesagentGlobalDir()` rather than reading it directly.
|
|
6157
|
+
*
|
|
6158
|
+
* Home-relative rather than read from `LOCALAPPDATA`, matching how every other
|
|
6159
|
+
* Windows global path in rulesync is spelled (`ZED_GLOBAL_WIN32_DIR`,
|
|
6160
|
+
* `WARP_WIN32_DIR`). A profile with `LOCALAPPDATA` redirected elsewhere is not
|
|
6161
|
+
* followed; those users should set `HERMES_HOME` explicitly.
|
|
6162
|
+
*/
|
|
6163
|
+
const HERMESAGENT_GLOBAL_WIN32_DIR = (0, node_path.join)("AppData", "Local", "hermes");
|
|
6024
6164
|
/** MCP servers and other settings live in `config.yaml` under `~/.hermes/`. */
|
|
6025
6165
|
const HERMESAGENT_CONFIG_FILE_NAME = "config.yaml";
|
|
6026
6166
|
const HERMESAGENT_CONFIG_FILE_PATH = (0, node_path.join)(HERMESAGENT_GLOBAL_DIR, HERMESAGENT_CONFIG_FILE_NAME);
|
|
@@ -6243,6 +6383,114 @@ var HermesagentCheck = class HermesagentCheck extends ToolCheck {
|
|
|
6243
6383
|
}
|
|
6244
6384
|
};
|
|
6245
6385
|
//#endregion
|
|
6386
|
+
//#region src/features/checks/rovodev-check.ts
|
|
6387
|
+
const FALLBACK_CHECK_NAME = "review-agent";
|
|
6388
|
+
/**
|
|
6389
|
+
* Checks adapter for Rovo Dev CLI's code-review custom instructions
|
|
6390
|
+
* (`.rovodev/.review-agent.md`).
|
|
6391
|
+
*
|
|
6392
|
+
* Rovo Dev takes one plain-Markdown instruction file at the repository root's
|
|
6393
|
+
* `.rovodev/` folder — no frontmatter, and note the leading dot in the file
|
|
6394
|
+
* name. Like Cursor Bugbot it is a single aggregated file rather than a file
|
|
6395
|
+
* per check, so every `.rulesync/checks/*.md` targeting Rovo Dev collapses into
|
|
6396
|
+
* it via {@link fromRulesyncChecks}, with each check written as a marked
|
|
6397
|
+
* section (see `aggregated-check-file.ts` for the marker convention the two
|
|
6398
|
+
* adapters share).
|
|
6399
|
+
*
|
|
6400
|
+
* Rovo Dev reads the file as free prose, so a check's `severity` and `tools`
|
|
6401
|
+
* have no equivalent there: they are not written and do not come back on
|
|
6402
|
+
* import. Neither does `description` whenever the check also has a body.
|
|
6403
|
+
*
|
|
6404
|
+
* Project scope only — these are per-repository review instructions, and Rovo
|
|
6405
|
+
* Dev documents no user-level equivalent. (The `permissions` adapter for the
|
|
6406
|
+
* same tool is the opposite: global only.)
|
|
6407
|
+
*
|
|
6408
|
+
* @see https://support.atlassian.com/rovo/docs/set-custom-instructions-for-code-reviews/
|
|
6409
|
+
*/
|
|
6410
|
+
var RovodevCheck = class RovodevCheck extends ToolCheck {
|
|
6411
|
+
static getSettablePaths(_options = {}) {
|
|
6412
|
+
return {
|
|
6413
|
+
relativeDirPath: ROVODEV_DIR,
|
|
6414
|
+
relativeFilePath: ROVODEV_REVIEW_AGENT_FILE_NAME
|
|
6415
|
+
};
|
|
6416
|
+
}
|
|
6417
|
+
static isTargetedByRulesyncCheck(rulesyncCheck) {
|
|
6418
|
+
return this.isTargetedByRulesyncCheckDefault({
|
|
6419
|
+
rulesyncCheck,
|
|
6420
|
+
toolTarget: "rovodev"
|
|
6421
|
+
});
|
|
6422
|
+
}
|
|
6423
|
+
/**
|
|
6424
|
+
* Ownership guard the processor consults before it deletes anything for this
|
|
6425
|
+
* tool. `.review-agent.md` is a file Rovo Dev's own documentation tells users
|
|
6426
|
+
* to hand-write, so anything in it that rulesync did not write is not
|
|
6427
|
+
* rulesync's to remove — dropping the last check targeting Rovo Dev must not
|
|
6428
|
+
* take somebody's hand-written review instructions with it.
|
|
6429
|
+
*/
|
|
6430
|
+
static async canDeleteAuxiliaryFiles({ outputRoot }) {
|
|
6431
|
+
const paths = RovodevCheck.getSettablePaths();
|
|
6432
|
+
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath ?? ".review-agent.md"));
|
|
6433
|
+
if (fileContent === null) return true;
|
|
6434
|
+
return isOnlyGeneratedSections(fileContent);
|
|
6435
|
+
}
|
|
6436
|
+
static fromRulesyncCheck(_params) {
|
|
6437
|
+
throw new Error("Rovo Dev checks are built from all checks at once; use fromRulesyncChecks.");
|
|
6438
|
+
}
|
|
6439
|
+
static async fromRulesyncChecks({ outputRoot = process.cwd(), rulesyncChecks, global = false, logger }) {
|
|
6440
|
+
if (rulesyncChecks.length === 0) return [];
|
|
6441
|
+
const paths = RovodevCheck.getSettablePaths({ global });
|
|
6442
|
+
const relativeFilePath = paths.relativeFilePath ?? ".review-agent.md";
|
|
6443
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
6444
|
+
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.`);
|
|
6445
|
+
return [new RovodevCheck({
|
|
6446
|
+
outputRoot,
|
|
6447
|
+
relativeDirPath: paths.relativeDirPath,
|
|
6448
|
+
relativeFilePath,
|
|
6449
|
+
fileContent: renderCheckFile(rulesyncChecks),
|
|
6450
|
+
global
|
|
6451
|
+
})];
|
|
6452
|
+
}
|
|
6453
|
+
static async fromFile({ outputRoot = process.cwd(), global = false }) {
|
|
6454
|
+
const paths = RovodevCheck.getSettablePaths({ global });
|
|
6455
|
+
const relativeFilePath = paths.relativeFilePath ?? ".review-agent.md";
|
|
6456
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
6457
|
+
return new RovodevCheck({
|
|
6458
|
+
outputRoot,
|
|
6459
|
+
relativeDirPath: paths.relativeDirPath,
|
|
6460
|
+
relativeFilePath,
|
|
6461
|
+
fileContent: await readFileContentOrNull(filePath) ?? "",
|
|
6462
|
+
global
|
|
6463
|
+
});
|
|
6464
|
+
}
|
|
6465
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
6466
|
+
return new RovodevCheck({
|
|
6467
|
+
outputRoot,
|
|
6468
|
+
relativeDirPath,
|
|
6469
|
+
relativeFilePath,
|
|
6470
|
+
fileContent: "",
|
|
6471
|
+
validate: false,
|
|
6472
|
+
global
|
|
6473
|
+
});
|
|
6474
|
+
}
|
|
6475
|
+
validate() {
|
|
6476
|
+
return {
|
|
6477
|
+
success: true,
|
|
6478
|
+
error: null
|
|
6479
|
+
};
|
|
6480
|
+
}
|
|
6481
|
+
toRulesyncCheck() {
|
|
6482
|
+
const first = this.toRulesyncChecks()[0];
|
|
6483
|
+
if (!first) throw new Error(`No check instructions found in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}.`);
|
|
6484
|
+
return first;
|
|
6485
|
+
}
|
|
6486
|
+
toRulesyncChecks() {
|
|
6487
|
+
return splitCheckFile({
|
|
6488
|
+
fileContent: this.getFileContent(),
|
|
6489
|
+
fallbackName: FALLBACK_CHECK_NAME
|
|
6490
|
+
});
|
|
6491
|
+
}
|
|
6492
|
+
};
|
|
6493
|
+
//#endregion
|
|
6246
6494
|
//#region src/constants/codexcli-paths.ts
|
|
6247
6495
|
const CODEXCLI_DIR = ".codex";
|
|
6248
6496
|
const CODEXCLI_PROMPTS_DIR_PATH = (0, node_path.join)(CODEXCLI_DIR, "prompts");
|
|
@@ -6355,11 +6603,14 @@ function mergeSharedConfigDeep({ base, patch }) {
|
|
|
6355
6603
|
}
|
|
6356
6604
|
const CLAUDE_SETTINGS_SHARED_FILE_KEY = ".claude/settings.json";
|
|
6357
6605
|
const HERMES_CONFIG_SHARED_FILE_KEY = ".hermes/config.yaml";
|
|
6606
|
+
const HERMES_WIN32_CONFIG_SHARED_FILE_KEY = "AppData/Local/hermes/config.yaml";
|
|
6607
|
+
const HERMES_HOME_CONFIG_SHARED_FILE_KEY = "config.yaml";
|
|
6358
6608
|
const TAKT_CONFIG_SHARED_FILE_KEY = ".takt/config.yaml";
|
|
6359
6609
|
const CODEXCLI_CONFIG_SHARED_FILE_KEY = ".codex/config.toml";
|
|
6360
6610
|
const GROKCLI_CONFIG_SHARED_FILE_KEY = ".grok/config.toml";
|
|
6361
6611
|
const VIBE_CONFIG_SHARED_FILE_KEY = ".vibe/config.toml";
|
|
6362
6612
|
const KIMI_CODE_CONFIG_SHARED_FILE_KEY = ".kimi-code/config.toml";
|
|
6613
|
+
const KIMI_CODE_HOME_CONFIG_SHARED_FILE_KEY = "config.toml";
|
|
6363
6614
|
const REASONIX_PROJECT_CONFIG_SHARED_FILE_KEY = "reasonix.toml";
|
|
6364
6615
|
const REASONIX_GLOBAL_CONFIG_SHARED_FILE_KEY = ".reasonix/config.toml";
|
|
6365
6616
|
/**
|
|
@@ -6383,6 +6634,65 @@ const sharedConfigFileKey = ({ relativeDirPath, relativeFilePath }) => {
|
|
|
6383
6634
|
* lock-step with the writers derived from the processor registry, so an
|
|
6384
6635
|
* undeclared writer fails CI instead of merging by accident.
|
|
6385
6636
|
*/
|
|
6637
|
+
/**
|
|
6638
|
+
* Hermes writes one `config.yaml`, but its global profile root has three
|
|
6639
|
+
* spellings (`~/.hermes`, the win32 `%LOCALAPPDATA%\hermes`, and `HERMES_HOME`
|
|
6640
|
+
* itself). They are the same file with the same owners, so the declaration is
|
|
6641
|
+
* written once and shared — a policy edit cannot land on one spelling only.
|
|
6642
|
+
*/
|
|
6643
|
+
const HERMES_CONFIG_DECLARATION = {
|
|
6644
|
+
format: "yaml",
|
|
6645
|
+
features: {
|
|
6646
|
+
commands: {
|
|
6647
|
+
kind: "replace-owned-keys",
|
|
6648
|
+
ownedKeys: ["plugins"]
|
|
6649
|
+
},
|
|
6650
|
+
subagents: {
|
|
6651
|
+
kind: "replace-owned-keys",
|
|
6652
|
+
ownedKeys: ["plugins"]
|
|
6653
|
+
},
|
|
6654
|
+
mcp: {
|
|
6655
|
+
kind: "replace-owned-keys",
|
|
6656
|
+
ownedKeys: ["mcp_servers"]
|
|
6657
|
+
},
|
|
6658
|
+
hooks: {
|
|
6659
|
+
kind: "replace-owned-keys",
|
|
6660
|
+
ownedKeys: ["hooks"]
|
|
6661
|
+
},
|
|
6662
|
+
permissions: {
|
|
6663
|
+
kind: "deep-merge",
|
|
6664
|
+
replaceKeys: ["permissions"]
|
|
6665
|
+
}
|
|
6666
|
+
}
|
|
6667
|
+
};
|
|
6668
|
+
/**
|
|
6669
|
+
* Kimi Code's user config: hooks owns the flat `hooks` array; permissions owns
|
|
6670
|
+
* the ordered rule list and optional coarse default mode. `KIMI_CODE_HOME` can
|
|
6671
|
+
* name the profile directory itself, so the file has two spellings that share
|
|
6672
|
+
* one declaration — a policy edit cannot land on only one of them.
|
|
6673
|
+
*/
|
|
6674
|
+
const KIMI_CODE_CONFIG_DECLARATION = {
|
|
6675
|
+
format: "toml",
|
|
6676
|
+
invalidRootPolicy: "error",
|
|
6677
|
+
features: {
|
|
6678
|
+
hooks: {
|
|
6679
|
+
kind: "replace-owned-keys",
|
|
6680
|
+
ownedKeys: ["hooks"]
|
|
6681
|
+
},
|
|
6682
|
+
mcp: {
|
|
6683
|
+
kind: "replace-owned-keys",
|
|
6684
|
+
ownedKeys: ["mcp"]
|
|
6685
|
+
},
|
|
6686
|
+
permissions: {
|
|
6687
|
+
kind: "replace-owned-keys",
|
|
6688
|
+
ownedKeys: [
|
|
6689
|
+
"permission",
|
|
6690
|
+
"default_permission_mode",
|
|
6691
|
+
"tools"
|
|
6692
|
+
]
|
|
6693
|
+
}
|
|
6694
|
+
}
|
|
6695
|
+
};
|
|
6386
6696
|
const SHARED_CONFIG_OWNERSHIP = {
|
|
6387
6697
|
[CLAUDE_SETTINGS_SHARED_FILE_KEY]: {
|
|
6388
6698
|
format: "json",
|
|
@@ -6401,31 +6711,9 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6401
6711
|
}
|
|
6402
6712
|
}
|
|
6403
6713
|
},
|
|
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
|
-
},
|
|
6714
|
+
[HERMES_CONFIG_SHARED_FILE_KEY]: HERMES_CONFIG_DECLARATION,
|
|
6715
|
+
[HERMES_WIN32_CONFIG_SHARED_FILE_KEY]: HERMES_CONFIG_DECLARATION,
|
|
6716
|
+
[HERMES_HOME_CONFIG_SHARED_FILE_KEY]: HERMES_CONFIG_DECLARATION,
|
|
6429
6717
|
[TAKT_CONFIG_SHARED_FILE_KEY]: {
|
|
6430
6718
|
format: "yaml",
|
|
6431
6719
|
invalidRootPolicy: "error",
|
|
@@ -6471,6 +6759,10 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6471
6759
|
".config/zed/settings.json": {
|
|
6472
6760
|
format: "json",
|
|
6473
6761
|
features: {
|
|
6762
|
+
ignore: {
|
|
6763
|
+
kind: "replace-owned-keys",
|
|
6764
|
+
ownedKeys: ["private_files"]
|
|
6765
|
+
},
|
|
6474
6766
|
mcp: {
|
|
6475
6767
|
kind: "replace-owned-keys",
|
|
6476
6768
|
ownedKeys: ["context_servers"]
|
|
@@ -6484,6 +6776,10 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6484
6776
|
"AppData/Roaming/Zed/settings.json": {
|
|
6485
6777
|
format: "json",
|
|
6486
6778
|
features: {
|
|
6779
|
+
ignore: {
|
|
6780
|
+
kind: "replace-owned-keys",
|
|
6781
|
+
ownedKeys: ["private_files"]
|
|
6782
|
+
},
|
|
6487
6783
|
mcp: {
|
|
6488
6784
|
kind: "replace-owned-keys",
|
|
6489
6785
|
ownedKeys: ["context_servers"]
|
|
@@ -6740,31 +7036,15 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6740
7036
|
}
|
|
6741
7037
|
}
|
|
6742
7038
|
},
|
|
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
|
-
},
|
|
7039
|
+
[KIMI_CODE_CONFIG_SHARED_FILE_KEY]: KIMI_CODE_CONFIG_DECLARATION,
|
|
7040
|
+
[KIMI_CODE_HOME_CONFIG_SHARED_FILE_KEY]: KIMI_CODE_CONFIG_DECLARATION,
|
|
6765
7041
|
[REASONIX_PROJECT_CONFIG_SHARED_FILE_KEY]: {
|
|
6766
7042
|
format: "toml",
|
|
6767
7043
|
features: {
|
|
7044
|
+
ignore: {
|
|
7045
|
+
kind: "custom",
|
|
7046
|
+
policyFunction: "applyIgnoreReadDenies"
|
|
7047
|
+
},
|
|
6768
7048
|
mcp: {
|
|
6769
7049
|
kind: "replace-owned-keys",
|
|
6770
7050
|
ownedKeys: ["plugins"]
|
|
@@ -6782,6 +7062,10 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6782
7062
|
[REASONIX_GLOBAL_CONFIG_SHARED_FILE_KEY]: {
|
|
6783
7063
|
format: "toml",
|
|
6784
7064
|
features: {
|
|
7065
|
+
ignore: {
|
|
7066
|
+
kind: "custom",
|
|
7067
|
+
policyFunction: "applyIgnoreReadDenies"
|
|
7068
|
+
},
|
|
6785
7069
|
mcp: {
|
|
6786
7070
|
kind: "replace-owned-keys",
|
|
6787
7071
|
ownedKeys: ["plugins"]
|
|
@@ -7226,6 +7510,13 @@ const toolCheckFactories = /* @__PURE__ */ new Map([
|
|
|
7226
7510
|
filePattern: "*.json"
|
|
7227
7511
|
}
|
|
7228
7512
|
}],
|
|
7513
|
+
["rovodev", {
|
|
7514
|
+
class: RovodevCheck,
|
|
7515
|
+
meta: {
|
|
7516
|
+
supportsGlobal: false,
|
|
7517
|
+
filePattern: ROVODEV_REVIEW_AGENT_FILE_NAME
|
|
7518
|
+
}
|
|
7519
|
+
}],
|
|
7229
7520
|
["takt", {
|
|
7230
7521
|
class: TaktCheck,
|
|
7231
7522
|
meta: {
|
|
@@ -7381,19 +7672,65 @@ var ChecksProcessor = class extends FeatureProcessor {
|
|
|
7381
7672
|
}
|
|
7382
7673
|
};
|
|
7383
7674
|
//#endregion
|
|
7675
|
+
//#region src/utils/tool-home.ts
|
|
7676
|
+
/**
|
|
7677
|
+
* Where the rulesync-side source files of a tool with a home override belong.
|
|
7678
|
+
*
|
|
7679
|
+
* A home override redirects the tool's OWN output, but the `.rulesync/` sources
|
|
7680
|
+
* imported back out of it are not part of the tool's profile — they stay under
|
|
7681
|
+
* the rulesync home. When no override is set, the native output root already is
|
|
7682
|
+
* that place.
|
|
7683
|
+
*/
|
|
7684
|
+
function getToolRulesyncOutputRoot({ nativeOutputRoot, global, toolHome }) {
|
|
7685
|
+
return global && toolHome() ? getHomeDirectory() : nativeOutputRoot;
|
|
7686
|
+
}
|
|
7687
|
+
//#endregion
|
|
7384
7688
|
//#region src/utils/hermesagent.ts
|
|
7385
7689
|
function getHermesagentHome() {
|
|
7386
7690
|
const configuredHome = process.env.HERMES_HOME?.trim();
|
|
7387
7691
|
return configuredHome ? (0, node_path.resolve)(configuredHome) : void 0;
|
|
7388
7692
|
}
|
|
7693
|
+
/**
|
|
7694
|
+
* The home-relative Hermes profile directory used when `HERMES_HOME` is unset.
|
|
7695
|
+
*
|
|
7696
|
+
* Upstream `_get_platform_default_hermes_home()` returns `%LOCALAPPDATA%\hermes`
|
|
7697
|
+
* on win32 and `~/.hermes` everywhere else, so the global output directory is
|
|
7698
|
+
* platform-dependent — a global generate on Windows that wrote `~/.hermes`
|
|
7699
|
+
* would land where Hermes never reads.
|
|
7700
|
+
*
|
|
7701
|
+
* @see https://github.com/NousResearch/hermes-agent `hermes_constants.py`
|
|
7702
|
+
*/
|
|
7703
|
+
function getHermesagentGlobalDir() {
|
|
7704
|
+
return process.platform === "win32" ? HERMESAGENT_GLOBAL_WIN32_DIR : HERMESAGENT_GLOBAL_DIR;
|
|
7705
|
+
}
|
|
7389
7706
|
function resolveHermesagentOutputRoot({ outputRoot, global }) {
|
|
7390
7707
|
return global ? getHermesagentHome() ?? outputRoot : outputRoot;
|
|
7391
7708
|
}
|
|
7709
|
+
/**
|
|
7710
|
+
* Map a canonical `.hermes/...` path constant onto the directory rulesync
|
|
7711
|
+
* actually writes in the requested scope.
|
|
7712
|
+
*
|
|
7713
|
+
* Project scope keeps the constant as-is (the project tree is `.hermes/`
|
|
7714
|
+
* everywhere). Global scope strips the `.hermes` prefix and re-anchors it:
|
|
7715
|
+
* `HERMES_HOME` *is* the profile root, so nothing is prepended; otherwise the
|
|
7716
|
+
* platform default directory takes its place.
|
|
7717
|
+
*/
|
|
7392
7718
|
function getHermesagentRelativeDirPath({ global, relativeDirPath }) {
|
|
7393
|
-
if (!global
|
|
7719
|
+
if (!global) return relativeDirPath;
|
|
7394
7720
|
const relativePath = (0, node_path.relative)(HERMESAGENT_GLOBAL_DIR, relativeDirPath);
|
|
7395
|
-
|
|
7396
|
-
|
|
7721
|
+
try {
|
|
7722
|
+
checkPathTraversal({
|
|
7723
|
+
relativePath: relativeDirPath,
|
|
7724
|
+
intendedRootDir: "."
|
|
7725
|
+
});
|
|
7726
|
+
checkPathTraversal({
|
|
7727
|
+
relativePath,
|
|
7728
|
+
intendedRootDir: HERMESAGENT_GLOBAL_DIR
|
|
7729
|
+
});
|
|
7730
|
+
} catch {
|
|
7731
|
+
throw new Error(`Hermes Agent global path must be within ${HERMESAGENT_GLOBAL_DIR}: ${relativeDirPath}`);
|
|
7732
|
+
}
|
|
7733
|
+
return getHermesagentHome() ? relativePath || "." : (0, node_path.join)(getHermesagentGlobalDir(), relativePath);
|
|
7397
7734
|
}
|
|
7398
7735
|
function getHermesagentRelativeFilePath({ global, relativeFilePath }) {
|
|
7399
7736
|
return (0, node_path.join)(getHermesagentRelativeDirPath({
|
|
@@ -7401,8 +7738,53 @@ function getHermesagentRelativeFilePath({ global, relativeFilePath }) {
|
|
|
7401
7738
|
relativeDirPath: (0, node_path.dirname)(relativeFilePath)
|
|
7402
7739
|
}), (0, node_path.basename)(relativeFilePath));
|
|
7403
7740
|
}
|
|
7741
|
+
/**
|
|
7742
|
+
* Every spelling `config.yaml` can take in global scope, so that the
|
|
7743
|
+
* shared-write derivation and the gateway ownership table it is checked against
|
|
7744
|
+
* see the same set of keys on every platform and with or without `HERMES_HOME`.
|
|
7745
|
+
*
|
|
7746
|
+
* `getHermesagentRelativeDirPath` resolves exactly one of these per process,
|
|
7747
|
+
* which would otherwise make the derived shared-file key depend on the ambient
|
|
7748
|
+
* environment — the drift guards would then go blind in precisely the
|
|
7749
|
+
* configuration this feature exists for.
|
|
7750
|
+
*/
|
|
7751
|
+
function getHermesagentSharedConfigWritePaths() {
|
|
7752
|
+
return [
|
|
7753
|
+
{
|
|
7754
|
+
relativeDirPath: HERMESAGENT_GLOBAL_DIR,
|
|
7755
|
+
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
7756
|
+
},
|
|
7757
|
+
{
|
|
7758
|
+
relativeDirPath: HERMESAGENT_GLOBAL_WIN32_DIR,
|
|
7759
|
+
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
7760
|
+
},
|
|
7761
|
+
{
|
|
7762
|
+
relativeDirPath: ".",
|
|
7763
|
+
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
7764
|
+
}
|
|
7765
|
+
];
|
|
7766
|
+
}
|
|
7767
|
+
/**
|
|
7768
|
+
* The `SHARED_CONFIG_OWNERSHIP` key of the `config.yaml` this scope actually
|
|
7769
|
+
* writes. All three spellings carry the same declaration, but passing the key of
|
|
7770
|
+
* the file being written keeps the write path and the drift guards reading the
|
|
7771
|
+
* same entry.
|
|
7772
|
+
*/
|
|
7773
|
+
function getHermesagentConfigSharedFileKey({ global }) {
|
|
7774
|
+
return sharedConfigFileKey({
|
|
7775
|
+
relativeDirPath: getHermesagentRelativeDirPath({
|
|
7776
|
+
global,
|
|
7777
|
+
relativeDirPath: HERMESAGENT_GLOBAL_DIR
|
|
7778
|
+
}),
|
|
7779
|
+
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
7780
|
+
});
|
|
7781
|
+
}
|
|
7404
7782
|
function getHermesagentRulesyncOutputRoot({ nativeOutputRoot, global }) {
|
|
7405
|
-
return
|
|
7783
|
+
return getToolRulesyncOutputRoot({
|
|
7784
|
+
nativeOutputRoot,
|
|
7785
|
+
global,
|
|
7786
|
+
toolHome: getHermesagentHome
|
|
7787
|
+
});
|
|
7406
7788
|
}
|
|
7407
7789
|
//#endregion
|
|
7408
7790
|
//#region src/constants/agentsmd-paths.ts
|
|
@@ -9106,6 +9488,223 @@ var GooseCommand = class GooseCommand extends ToolCommand {
|
|
|
9106
9488
|
}
|
|
9107
9489
|
};
|
|
9108
9490
|
//#endregion
|
|
9491
|
+
//#region src/constants/grokcli-paths.ts
|
|
9492
|
+
/**
|
|
9493
|
+
* Grok Build CLI (xAI) configuration-layout conventions.
|
|
9494
|
+
*
|
|
9495
|
+
* Single source of truth for where Grok Build expects its files. Grok Build
|
|
9496
|
+
* stores MCP servers (and other settings) in a `config.toml` under `.grok/`,
|
|
9497
|
+
* with project/global scopes resolved by the directory the CLI runs in
|
|
9498
|
+
* (`./.grok/config.toml` vs `~/.grok/config.toml`).
|
|
9499
|
+
*
|
|
9500
|
+
* Verified against `grok` 0.2.54 (`grok mcp add --help`, `grok mcp add`):
|
|
9501
|
+
* `-s project` writes `./.grok/config.toml`, `-s user` writes
|
|
9502
|
+
* `~/.grok/config.toml`, both as a TOML `[mcp_servers.<name>]` table.
|
|
9503
|
+
* @see https://docs.x.ai/build/overview
|
|
9504
|
+
*/
|
|
9505
|
+
/** Root directory for Grok Build configuration, relative to the scope root. */
|
|
9506
|
+
const GROKCLI_DIR = ".grok";
|
|
9507
|
+
/** MCP servers and other settings live in `config.toml` under `.grok/`. */
|
|
9508
|
+
const GROKCLI_MCP_FILE_NAME = "config.toml";
|
|
9509
|
+
/**
|
|
9510
|
+
* Shared Grok CLI config file (`config.toml`). MCP servers, the `[ui]`
|
|
9511
|
+
* permission mode, and other settings all live here; permissions reuse the same
|
|
9512
|
+
* file name as MCP since Grok consolidates everything into one config.
|
|
9513
|
+
*/
|
|
9514
|
+
const GROKCLI_CONFIG_FILE_NAME = "config.toml";
|
|
9515
|
+
/** Skills directory under `.grok/` (project: `./.grok/skills`, global: `~/.grok/skills`). */
|
|
9516
|
+
const GROKCLI_SKILLS_DIR_PATH = (0, node_path.join)(GROKCLI_DIR, "skills");
|
|
9517
|
+
/**
|
|
9518
|
+
* Hooks directory under `.grok/`. Grok Build discovers hook config files from
|
|
9519
|
+
* `.grok/hooks/*.json` (project) and `~/.grok/hooks/*.json` (global), each a
|
|
9520
|
+
* standalone JSON file using the Claude-Code-compatible nested `{ hooks: { … } }`
|
|
9521
|
+
* shape. rulesync writes all its hooks into a single `rulesync.json`.
|
|
9522
|
+
* @see https://docs.x.ai/build/features/hooks
|
|
9523
|
+
*/
|
|
9524
|
+
const GROKCLI_HOOKS_DIR_PATH = (0, node_path.join)(GROKCLI_DIR, "hooks");
|
|
9525
|
+
/** rulesync-managed Grok hooks file under `.grok/hooks/`. */
|
|
9526
|
+
const GROKCLI_HOOKS_FILE_NAME = "rulesync.json";
|
|
9527
|
+
/**
|
|
9528
|
+
* Subagents (agent profiles) directory under `.grok/`. Grok Build discovers
|
|
9529
|
+
* agent definitions from `.grok/agents/*.md` (project) and `~/.grok/agents/*.md`
|
|
9530
|
+
* (global), each a Markdown file with YAML frontmatter (verified via
|
|
9531
|
+
* `grok inspect`; format matches the bundled `~/.grok/bundled/agents/*.md`).
|
|
9532
|
+
*/
|
|
9533
|
+
const GROKCLI_AGENTS_DIR_PATH = (0, node_path.join)(GROKCLI_DIR, "agents");
|
|
9534
|
+
/**
|
|
9535
|
+
* Instruction file. Grok reads the AGENTS.md instruction-file family natively,
|
|
9536
|
+
* including the user-level `~/.grok/AGENTS.md` for global rules (verified via
|
|
9537
|
+
* `grok inspect`, consistent with the `.grok/` global discovery used by the
|
|
9538
|
+
* MCP/skills/subagents adapters).
|
|
9539
|
+
*/
|
|
9540
|
+
const GROKCLI_RULE_FILE_NAME = "AGENTS.md";
|
|
9541
|
+
/**
|
|
9542
|
+
* Custom slash commands directory. Grok's `find_command_paths` scans
|
|
9543
|
+
* `commands/*.md` under every discovered config dir — `.grok/commands/`
|
|
9544
|
+
* (project, walked from cwd up to the git root) and `~/.grok/commands/`
|
|
9545
|
+
* (global). The scan is **flat and non-recursive**, so subdirectory
|
|
9546
|
+
* namespacing (`git/commit.md` → `/git:commit`) is not supported the way it is
|
|
9547
|
+
* for Claude Code.
|
|
9548
|
+
*
|
|
9549
|
+
* Skills are collected before commands and win name collisions, so a
|
|
9550
|
+
* `.grok/skills/<name>/` shadows `.grok/commands/<name>.md`.
|
|
9551
|
+
* @see https://docs.x.ai/build/features/skills-plugins-marketplaces
|
|
9552
|
+
*/
|
|
9553
|
+
const GROKCLI_COMMANDS_DIR_PATH = (0, node_path.join)(GROKCLI_DIR, "commands");
|
|
9554
|
+
/**
|
|
9555
|
+
* Non-root rules directory. Grok scans `*.md` here — flat, sorted by name —
|
|
9556
|
+
* alongside the AGENTS.md family: `.grok/rules/` in each project directory it
|
|
9557
|
+
* walks, and `~/.grok/rules/` in the home scope.
|
|
9558
|
+
* @see https://docs.x.ai/build/overview
|
|
9559
|
+
*/
|
|
9560
|
+
const GROKCLI_RULES_DIR_PATH = (0, node_path.join)(GROKCLI_DIR, "rules");
|
|
9561
|
+
//#endregion
|
|
9562
|
+
//#region src/features/commands/grokcli-command.ts
|
|
9563
|
+
/**
|
|
9564
|
+
* Grok CLI custom slash commands are Markdown files under `.grok/commands/`
|
|
9565
|
+
* (project) / `~/.grok/commands/` (global), discovered by the same
|
|
9566
|
+
* Claude-Code-compatible frontmatter parser Grok uses for skills.
|
|
9567
|
+
*
|
|
9568
|
+
* Two upstream constraints shape this adapter:
|
|
9569
|
+
*
|
|
9570
|
+
* - The scan is **flat and non-recursive**, so nested namespacing is not
|
|
9571
|
+
* modelled (`supportsSubdirectory: false` flattens nested rulesync commands
|
|
9572
|
+
* onto their basename).
|
|
9573
|
+
* - Skills are collected before commands and win name collisions, so a
|
|
9574
|
+
* `.grok/skills/<name>/` shadows `.grok/commands/<name>.md`.
|
|
9575
|
+
*
|
|
9576
|
+
* `description` and `argument-hint` describe the command, while
|
|
9577
|
+
* `user-invocable` (default true) and `disable-model-invocation` (default
|
|
9578
|
+
* false) control who may invoke it — the same pair `GrokcliSkill` emits.
|
|
9579
|
+
* @see https://docs.x.ai/build/features/skills-plugins-marketplaces
|
|
9580
|
+
*/
|
|
9581
|
+
const GrokcliCommandFrontmatterSchema = zod_mini.z.looseObject({
|
|
9582
|
+
description: zod_mini.z.optional(zod_mini.z.string()),
|
|
9583
|
+
"argument-hint": zod_mini.z.optional(zod_mini.z.string()),
|
|
9584
|
+
"user-invocable": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
9585
|
+
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean())
|
|
9586
|
+
});
|
|
9587
|
+
var GrokcliCommand = class GrokcliCommand extends ToolCommand {
|
|
9588
|
+
frontmatter;
|
|
9589
|
+
body;
|
|
9590
|
+
constructor({ frontmatter, body, ...rest }) {
|
|
9591
|
+
if (rest.validate) {
|
|
9592
|
+
const result = GrokcliCommandFrontmatterSchema.safeParse(frontmatter);
|
|
9593
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${(0, node_path.join)(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
|
|
9594
|
+
}
|
|
9595
|
+
super({
|
|
9596
|
+
...rest,
|
|
9597
|
+
fileContent: stringifyFrontmatter(body, frontmatter)
|
|
9598
|
+
});
|
|
9599
|
+
this.frontmatter = frontmatter;
|
|
9600
|
+
this.body = body;
|
|
9601
|
+
}
|
|
9602
|
+
static getSettablePaths(_options = {}) {
|
|
9603
|
+
return { relativeDirPath: GROKCLI_COMMANDS_DIR_PATH };
|
|
9604
|
+
}
|
|
9605
|
+
getBody() {
|
|
9606
|
+
return this.body;
|
|
9607
|
+
}
|
|
9608
|
+
getFrontmatter() {
|
|
9609
|
+
return this.frontmatter;
|
|
9610
|
+
}
|
|
9611
|
+
toRulesyncCommand() {
|
|
9612
|
+
const { description, ...restFields } = this.frontmatter;
|
|
9613
|
+
const rulesyncFrontmatter = {
|
|
9614
|
+
targets: ["*"],
|
|
9615
|
+
description,
|
|
9616
|
+
...Object.keys(restFields).length > 0 && { grokcli: restFields }
|
|
9617
|
+
};
|
|
9618
|
+
return new RulesyncCommand({
|
|
9619
|
+
outputRoot: ".",
|
|
9620
|
+
frontmatter: rulesyncFrontmatter,
|
|
9621
|
+
body: this.body,
|
|
9622
|
+
relativeDirPath: RulesyncCommand.getSettablePaths().relativeDirPath,
|
|
9623
|
+
relativeFilePath: this.relativeFilePath,
|
|
9624
|
+
fileContent: stringifyFrontmatter(this.body, rulesyncFrontmatter),
|
|
9625
|
+
validate: true
|
|
9626
|
+
});
|
|
9627
|
+
}
|
|
9628
|
+
static fromRulesyncCommand({ outputRoot = process.cwd(), rulesyncCommand, validate = true, global = false }) {
|
|
9629
|
+
const rulesyncFrontmatter = rulesyncCommand.getFrontmatter();
|
|
9630
|
+
const grokcliFields = rulesyncFrontmatter.grokcli ?? {};
|
|
9631
|
+
const grokcliFrontmatter = {
|
|
9632
|
+
description: rulesyncFrontmatter.description,
|
|
9633
|
+
...grokcliFields
|
|
9634
|
+
};
|
|
9635
|
+
const paths = this.getSettablePaths({ global });
|
|
9636
|
+
return new GrokcliCommand({
|
|
9637
|
+
outputRoot,
|
|
9638
|
+
frontmatter: grokcliFrontmatter,
|
|
9639
|
+
body: rulesyncCommand.getBody(),
|
|
9640
|
+
relativeDirPath: paths.relativeDirPath,
|
|
9641
|
+
relativeFilePath: rulesyncCommand.getRelativeFilePath(),
|
|
9642
|
+
validate
|
|
9643
|
+
});
|
|
9644
|
+
}
|
|
9645
|
+
validate() {
|
|
9646
|
+
if (!this.frontmatter) return {
|
|
9647
|
+
success: true,
|
|
9648
|
+
error: null
|
|
9649
|
+
};
|
|
9650
|
+
const result = GrokcliCommandFrontmatterSchema.safeParse(this.frontmatter);
|
|
9651
|
+
if (result.success) return {
|
|
9652
|
+
success: true,
|
|
9653
|
+
error: null
|
|
9654
|
+
};
|
|
9655
|
+
return {
|
|
9656
|
+
success: false,
|
|
9657
|
+
error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${(0, node_path.join)(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
|
|
9658
|
+
};
|
|
9659
|
+
}
|
|
9660
|
+
static isTargetedByRulesyncCommand(rulesyncCommand) {
|
|
9661
|
+
return this.isTargetedByRulesyncCommandDefault({
|
|
9662
|
+
rulesyncCommand,
|
|
9663
|
+
toolTarget: "grokcli"
|
|
9664
|
+
});
|
|
9665
|
+
}
|
|
9666
|
+
/**
|
|
9667
|
+
* Warn when a rulesync skill would shadow a rulesync command.
|
|
9668
|
+
*
|
|
9669
|
+
* Grok collects skills before commands and lets skills win name collisions,
|
|
9670
|
+
* so `.grok/skills/<name>/` makes `.grok/commands/<name>.md` unreachable.
|
|
9671
|
+
* Both files are still written correctly — nothing is overwritten and no
|
|
9672
|
+
* output is lost — so this warns rather than failing the run the way the
|
|
9673
|
+
* Hermes check does, where the two surfaces really do write the same path.
|
|
9674
|
+
*/
|
|
9675
|
+
static async validateRulesyncCommands({ inputRoot, rulesyncCommands, logger }) {
|
|
9676
|
+
const commandNames = new Set(rulesyncCommands.filter((command) => this.isTargetedByRulesyncCommand(command)).map((command) => (0, node_path.basename)(command.getRelativeFilePath(), ".md")));
|
|
9677
|
+
if (commandNames.size === 0) return;
|
|
9678
|
+
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));
|
|
9679
|
+
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.`);
|
|
9680
|
+
}
|
|
9681
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
9682
|
+
const paths = this.getSettablePaths({ global });
|
|
9683
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
9684
|
+
const { frontmatter, body: content } = parseFrontmatter(await readFileContent(filePath), filePath);
|
|
9685
|
+
const result = GrokcliCommandFrontmatterSchema.safeParse(frontmatter);
|
|
9686
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
9687
|
+
return new GrokcliCommand({
|
|
9688
|
+
outputRoot,
|
|
9689
|
+
relativeDirPath: paths.relativeDirPath,
|
|
9690
|
+
relativeFilePath,
|
|
9691
|
+
frontmatter: result.data,
|
|
9692
|
+
body: content.trim(),
|
|
9693
|
+
validate
|
|
9694
|
+
});
|
|
9695
|
+
}
|
|
9696
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
9697
|
+
return new GrokcliCommand({
|
|
9698
|
+
outputRoot,
|
|
9699
|
+
relativeDirPath,
|
|
9700
|
+
relativeFilePath,
|
|
9701
|
+
frontmatter: { description: "" },
|
|
9702
|
+
body: "",
|
|
9703
|
+
validate: false
|
|
9704
|
+
});
|
|
9705
|
+
}
|
|
9706
|
+
};
|
|
9707
|
+
//#endregion
|
|
9109
9708
|
//#region src/features/skills/tool-skill.ts
|
|
9110
9709
|
/** Ordered skill directory roots: primary first. */
|
|
9111
9710
|
function toolSkillSearchRoots(paths) {
|
|
@@ -9740,7 +10339,7 @@ def register(ctx):
|
|
|
9740
10339
|
_register_command(ctx, command)
|
|
9741
10340
|
`;
|
|
9742
10341
|
}
|
|
9743
|
-
function getEnabledPluginConfigContent$1(currentContent) {
|
|
10342
|
+
function getEnabledPluginConfigContent$1({ currentContent, global }) {
|
|
9744
10343
|
const config = parseSharedConfig({
|
|
9745
10344
|
format: "yaml",
|
|
9746
10345
|
fileContent: currentContent
|
|
@@ -9748,7 +10347,7 @@ function getEnabledPluginConfigContent$1(currentContent) {
|
|
|
9748
10347
|
const plugins = config.plugins && typeof config.plugins === "object" ? config.plugins : {};
|
|
9749
10348
|
const enabled = Array.isArray(plugins.enabled) ? plugins.enabled : [];
|
|
9750
10349
|
return applySharedConfigPatch({
|
|
9751
|
-
fileKey:
|
|
10350
|
+
fileKey: getHermesagentConfigSharedFileKey({ global }),
|
|
9752
10351
|
feature: "commands",
|
|
9753
10352
|
existingContent: currentContent,
|
|
9754
10353
|
patch: { plugins: {
|
|
@@ -9757,7 +10356,7 @@ function getEnabledPluginConfigContent$1(currentContent) {
|
|
|
9757
10356
|
} }
|
|
9758
10357
|
});
|
|
9759
10358
|
}
|
|
9760
|
-
function getDisabledHermesCommandsPluginConfigContent(currentContent) {
|
|
10359
|
+
function getDisabledHermesCommandsPluginConfigContent({ currentContent, global }) {
|
|
9761
10360
|
const config = parseSharedConfig({
|
|
9762
10361
|
format: "yaml",
|
|
9763
10362
|
fileContent: currentContent
|
|
@@ -9765,7 +10364,7 @@ function getDisabledHermesCommandsPluginConfigContent(currentContent) {
|
|
|
9765
10364
|
const plugins = config.plugins && typeof config.plugins === "object" ? config.plugins : {};
|
|
9766
10365
|
const enabled = Array.isArray(plugins.enabled) ? plugins.enabled : [];
|
|
9767
10366
|
return applySharedConfigPatch({
|
|
9768
|
-
fileKey:
|
|
10367
|
+
fileKey: getHermesagentConfigSharedFileKey({ global }),
|
|
9769
10368
|
feature: "commands",
|
|
9770
10369
|
existingContent: currentContent,
|
|
9771
10370
|
patch: { plugins: {
|
|
@@ -9781,35 +10380,36 @@ var HermesagentCommandAuxiliaryFile = class extends ToolFile {
|
|
|
9781
10380
|
error: null
|
|
9782
10381
|
};
|
|
9783
10382
|
}
|
|
9784
|
-
|
|
10383
|
+
/**
|
|
10384
|
+
* Whether this auxiliary file is the one at `relativeFilePath`, comparing
|
|
10385
|
+
* against the scope-resolved location of that canonical `.hermes/...` path.
|
|
10386
|
+
*/
|
|
10387
|
+
matchesPath(relativeFilePath) {
|
|
9785
10388
|
return this.getRelativePathFromCwd() === toPosixPath(getHermesagentRelativeFilePath({
|
|
9786
10389
|
global: this.global,
|
|
9787
|
-
relativeFilePath
|
|
10390
|
+
relativeFilePath
|
|
9788
10391
|
}));
|
|
9789
10392
|
}
|
|
10393
|
+
shouldMergeExistingFileContent() {
|
|
10394
|
+
return this.matchesPath(HERMESAGENT_CONFIG_FILE_PATH);
|
|
10395
|
+
}
|
|
9790
10396
|
setFileContent(newFileContent) {
|
|
9791
|
-
if (this.
|
|
9792
|
-
|
|
9793
|
-
|
|
9794
|
-
|
|
9795
|
-
|
|
10397
|
+
if (this.matchesPath(HERMESAGENT_CONFIG_FILE_PATH)) {
|
|
10398
|
+
super.setFileContent(getEnabledPluginConfigContent$1({
|
|
10399
|
+
currentContent: newFileContent,
|
|
10400
|
+
global: this.global
|
|
10401
|
+
}));
|
|
9796
10402
|
return;
|
|
9797
10403
|
}
|
|
9798
10404
|
super.setFileContent(newFileContent);
|
|
9799
10405
|
}
|
|
9800
10406
|
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());
|
|
10407
|
+
if (this.matchesPath(HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_MANIFEST_PATH)) return getPluginManifestContent$2();
|
|
10408
|
+
if (this.matchesPath(HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_INIT_PATH)) return getPluginInitContent$2();
|
|
10409
|
+
if (this.matchesPath(HERMESAGENT_CONFIG_FILE_PATH)) return getEnabledPluginConfigContent$1({
|
|
10410
|
+
currentContent: super.getFileContent(),
|
|
10411
|
+
global: this.global
|
|
10412
|
+
});
|
|
9813
10413
|
return super.getFileContent();
|
|
9814
10414
|
}
|
|
9815
10415
|
};
|
|
@@ -9826,14 +10426,12 @@ var HermesagentCommand = class HermesagentCommand extends ToolCommand {
|
|
|
9826
10426
|
relativeDirPath: HERMESAGENT_RULESYNC_COMMANDS_DIR_PATH
|
|
9827
10427
|
}) };
|
|
9828
10428
|
}
|
|
9829
|
-
|
|
9830
|
-
|
|
9831
|
-
|
|
9832
|
-
|
|
9833
|
-
|
|
9834
|
-
|
|
9835
|
-
relativeFilePath: (0, node_path.basename)(HERMESAGENT_CONFIG_FILE_PATH)
|
|
9836
|
-
}];
|
|
10429
|
+
/**
|
|
10430
|
+
* `config.yaml` under every spelling the global profile root can take.
|
|
10431
|
+
* @see getHermesagentSharedConfigWritePaths
|
|
10432
|
+
*/
|
|
10433
|
+
static getExtraSharedWritePaths() {
|
|
10434
|
+
return getHermesagentSharedConfigWritePaths();
|
|
9837
10435
|
}
|
|
9838
10436
|
static async validateRulesyncCommands({ inputRoot, rulesyncCommands }) {
|
|
9839
10437
|
const commandSlugs = /* @__PURE__ */ new Set();
|
|
@@ -9858,33 +10456,28 @@ var HermesagentCommand = class HermesagentCommand extends ToolCommand {
|
|
|
9858
10456
|
}
|
|
9859
10457
|
static async getAuxiliaryFiles({ toolCommands, outputRoot, global = false, forDeletion = false }) {
|
|
9860
10458
|
if (toolCommands.length === 0 && !forDeletion) return [];
|
|
10459
|
+
const pluginDirPath = getHermesagentRelativeDirPath({
|
|
10460
|
+
global,
|
|
10461
|
+
relativeDirPath: HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_DIR_PATH
|
|
10462
|
+
});
|
|
9861
10463
|
const pluginFiles = [
|
|
9862
10464
|
new HermesagentCommandAuxiliaryFile({
|
|
9863
10465
|
outputRoot,
|
|
9864
|
-
relativeDirPath:
|
|
9865
|
-
global,
|
|
9866
|
-
relativeDirPath: HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_DIR_PATH
|
|
9867
|
-
}),
|
|
10466
|
+
relativeDirPath: pluginDirPath,
|
|
9868
10467
|
relativeFilePath: (0, node_path.basename)(HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_MANIFEST_PATH),
|
|
9869
10468
|
fileContent: "",
|
|
9870
10469
|
global
|
|
9871
10470
|
}),
|
|
9872
10471
|
new HermesagentCommandAuxiliaryFile({
|
|
9873
10472
|
outputRoot,
|
|
9874
|
-
relativeDirPath:
|
|
9875
|
-
global,
|
|
9876
|
-
relativeDirPath: HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_DIR_PATH
|
|
9877
|
-
}),
|
|
10473
|
+
relativeDirPath: pluginDirPath,
|
|
9878
10474
|
relativeFilePath: (0, node_path.basename)(HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_OWNERSHIP_PATH),
|
|
9879
10475
|
fileContent: "Generated and owned by RuleSync.\n",
|
|
9880
10476
|
global
|
|
9881
10477
|
}),
|
|
9882
10478
|
new HermesagentCommandAuxiliaryFile({
|
|
9883
10479
|
outputRoot,
|
|
9884
|
-
relativeDirPath:
|
|
9885
|
-
global,
|
|
9886
|
-
relativeDirPath: HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_DIR_PATH
|
|
9887
|
-
}),
|
|
10480
|
+
relativeDirPath: pluginDirPath,
|
|
9888
10481
|
relativeFilePath: (0, node_path.basename)(HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_INIT_PATH),
|
|
9889
10482
|
fileContent: "",
|
|
9890
10483
|
global
|
|
@@ -11119,19 +11712,6 @@ var RooCommand = class RooCommand extends ToolCommand {
|
|
|
11119
11712
|
}
|
|
11120
11713
|
};
|
|
11121
11714
|
//#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
11715
|
//#region src/features/commands/rovodev-command.ts
|
|
11136
11716
|
/**
|
|
11137
11717
|
* Rovo Dev CLI "saved prompts": a file-based custom-command surface made of a
|
|
@@ -11715,6 +12295,16 @@ const toolCommandFactories = /* @__PURE__ */ new Map([
|
|
|
11715
12295
|
supportsSubdirectory: false
|
|
11716
12296
|
}
|
|
11717
12297
|
}],
|
|
12298
|
+
["grokcli", {
|
|
12299
|
+
class: GrokcliCommand,
|
|
12300
|
+
meta: {
|
|
12301
|
+
extension: "md",
|
|
12302
|
+
supportsProject: true,
|
|
12303
|
+
supportsGlobal: true,
|
|
12304
|
+
isSimulated: false,
|
|
12305
|
+
supportsSubdirectory: false
|
|
12306
|
+
}
|
|
12307
|
+
}],
|
|
11718
12308
|
["hermesagent", {
|
|
11719
12309
|
class: HermesagentCommand,
|
|
11720
12310
|
meta: {
|
|
@@ -11908,7 +12498,8 @@ var CommandsProcessor = class extends FeatureProcessor {
|
|
|
11908
12498
|
const factory = this.getFactory(this.toolTarget);
|
|
11909
12499
|
await factory.class.validateRulesyncCommands?.({
|
|
11910
12500
|
inputRoot: this.inputRoot,
|
|
11911
|
-
rulesyncCommands
|
|
12501
|
+
rulesyncCommands,
|
|
12502
|
+
logger: this.logger
|
|
11912
12503
|
});
|
|
11913
12504
|
const flattenedPathOrigins = /* @__PURE__ */ new Map();
|
|
11914
12505
|
const toolCommands = rulesyncCommands.map((rulesyncCommand) => {
|
|
@@ -12046,7 +12637,10 @@ var CommandsProcessor = class extends FeatureProcessor {
|
|
|
12046
12637
|
}));
|
|
12047
12638
|
const currentContent = await readFileContentOrNull(configPath);
|
|
12048
12639
|
if (currentContent === null) return changedCount;
|
|
12049
|
-
const nextContent = getDisabledHermesCommandsPluginConfigContent(
|
|
12640
|
+
const nextContent = getDisabledHermesCommandsPluginConfigContent({
|
|
12641
|
+
currentContent,
|
|
12642
|
+
global: this.global
|
|
12643
|
+
});
|
|
12050
12644
|
if (nextContent === currentContent) return changedCount;
|
|
12051
12645
|
if (this.dryRun) this.logger.info(`[DRY RUN] Would write: ${configPath}`);
|
|
12052
12646
|
else await writeFileContent(configPath, nextContent);
|
|
@@ -14508,64 +15102,6 @@ var GooseHooks = class GooseHooks extends ToolHooks {
|
|
|
14508
15102
|
}
|
|
14509
15103
|
};
|
|
14510
15104
|
//#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
15105
|
//#region src/features/hooks/grokcli-hooks.ts
|
|
14570
15106
|
const GROKCLI_CONVERTER_CONFIG = {
|
|
14571
15107
|
supportedEvents: GROKCLI_HOOK_EVENTS,
|
|
@@ -14811,6 +15347,13 @@ var HermesagentHooks = class HermesagentHooks extends ToolHooks {
|
|
|
14811
15347
|
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
14812
15348
|
};
|
|
14813
15349
|
}
|
|
15350
|
+
/**
|
|
15351
|
+
* `config.yaml` under every spelling the global profile root can take.
|
|
15352
|
+
* @see getHermesagentSharedConfigWritePaths
|
|
15353
|
+
*/
|
|
15354
|
+
static getExtraSharedWritePaths() {
|
|
15355
|
+
return getHermesagentSharedConfigWritePaths();
|
|
15356
|
+
}
|
|
14814
15357
|
constructor(params) {
|
|
14815
15358
|
super({
|
|
14816
15359
|
...params,
|
|
@@ -14848,7 +15391,7 @@ var HermesagentHooks = class HermesagentHooks extends ToolHooks {
|
|
|
14848
15391
|
}
|
|
14849
15392
|
setFileContent(fileContent) {
|
|
14850
15393
|
this.fileContent = applySharedConfigPatch({
|
|
14851
|
-
fileKey:
|
|
15394
|
+
fileKey: getHermesagentConfigSharedFileKey({ global: this.global }),
|
|
14852
15395
|
feature: "hooks",
|
|
14853
15396
|
existingContent: fileContent,
|
|
14854
15397
|
patch: parseSharedConfig({
|
|
@@ -15224,8 +15767,38 @@ function getKimiCodeHome() {
|
|
|
15224
15767
|
function getKimiCodeRelativeDirPath({ global, relativeDirPath = "." }) {
|
|
15225
15768
|
return global && getKimiCodeHome() ? relativeDirPath : (0, node_path.join)(KIMI_CODE_DIR, relativeDirPath);
|
|
15226
15769
|
}
|
|
15770
|
+
/**
|
|
15771
|
+
* Both spellings the shared user `config.toml` can take: under `.kimi-code/`,
|
|
15772
|
+
* or at the root of `KIMI_CODE_HOME` when that override names the profile dir.
|
|
15773
|
+
* Declared unconditionally so the derived shared-file keys — and the drift
|
|
15774
|
+
* guards checked against them — do not depend on the ambient environment.
|
|
15775
|
+
*/
|
|
15776
|
+
function getKimiCodeSharedConfigWritePaths() {
|
|
15777
|
+
return [{
|
|
15778
|
+
relativeDirPath: KIMI_CODE_DIR,
|
|
15779
|
+
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
15780
|
+
}, {
|
|
15781
|
+
relativeDirPath: ".",
|
|
15782
|
+
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
15783
|
+
}];
|
|
15784
|
+
}
|
|
15785
|
+
/**
|
|
15786
|
+
* The `SHARED_CONFIG_OWNERSHIP` key of the `config.toml` actually being written.
|
|
15787
|
+
* Both spellings carry the same declaration, but passing the key of the file
|
|
15788
|
+
* being written keeps the write path and the drift guards on the same entry.
|
|
15789
|
+
*/
|
|
15790
|
+
function getKimiCodeConfigSharedFileKey({ global }) {
|
|
15791
|
+
return sharedConfigFileKey({
|
|
15792
|
+
relativeDirPath: getKimiCodeRelativeDirPath({ global }),
|
|
15793
|
+
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
15794
|
+
});
|
|
15795
|
+
}
|
|
15227
15796
|
function getKimiCodeRulesyncOutputRoot({ nativeOutputRoot, global }) {
|
|
15228
|
-
return
|
|
15797
|
+
return getToolRulesyncOutputRoot({
|
|
15798
|
+
nativeOutputRoot,
|
|
15799
|
+
global,
|
|
15800
|
+
toolHome: getKimiCodeHome
|
|
15801
|
+
});
|
|
15229
15802
|
}
|
|
15230
15803
|
//#endregion
|
|
15231
15804
|
//#region src/features/hooks/kimi-code-hooks.ts
|
|
@@ -15323,13 +15896,20 @@ var KimiCodeHooks = class KimiCodeHooks extends ToolHooks {
|
|
|
15323
15896
|
isDeletable() {
|
|
15324
15897
|
return false;
|
|
15325
15898
|
}
|
|
15899
|
+
/**
|
|
15900
|
+
* `config.toml` under both spellings its directory can take.
|
|
15901
|
+
* @see getKimiCodeSharedConfigWritePaths
|
|
15902
|
+
*/
|
|
15903
|
+
static getExtraSharedWritePaths() {
|
|
15904
|
+
return getKimiCodeSharedConfigWritePaths();
|
|
15905
|
+
}
|
|
15326
15906
|
shouldMergeExistingFileContent() {
|
|
15327
15907
|
return true;
|
|
15328
15908
|
}
|
|
15329
15909
|
setFileContent(fileContent) {
|
|
15330
15910
|
const paths = KimiCodeHooks.getSettablePaths({ global: this.global });
|
|
15331
15911
|
this.fileContent = applySharedConfigPatch({
|
|
15332
|
-
fileKey:
|
|
15912
|
+
fileKey: getKimiCodeConfigSharedFileKey({ global: this.global }),
|
|
15333
15913
|
feature: "hooks",
|
|
15334
15914
|
existingContent: fileContent,
|
|
15335
15915
|
patch: parseSharedConfig({
|
|
@@ -18020,6 +18600,151 @@ var QwencodeIgnore = class QwencodeIgnore extends ToolIgnore {
|
|
|
18020
18600
|
}
|
|
18021
18601
|
};
|
|
18022
18602
|
//#endregion
|
|
18603
|
+
//#region src/features/shared/reasonix-config-table.ts
|
|
18604
|
+
/**
|
|
18605
|
+
* Shape-narrowing helpers for the Reasonix TOML config (`reasonix.toml` /
|
|
18606
|
+
* `~/.reasonix/config.toml`), shared by the features that read-modify-write it.
|
|
18607
|
+
*
|
|
18608
|
+
* TOML is only structurally validated on parse, so a hand-edited config can
|
|
18609
|
+
* hold any type under `permissions` or inside `allow`/`ask`/`deny`. Both the
|
|
18610
|
+
* `permissions` and `ignore` adapters have to narrow the same two shapes
|
|
18611
|
+
* before merging, so the narrowing lives here once rather than being re-spelled
|
|
18612
|
+
* (and re-diverging) per feature.
|
|
18613
|
+
*/
|
|
18614
|
+
/** Keep only the string entries of a TOML array; anything else becomes `[]`. */
|
|
18615
|
+
function toReasonixStringArray(value) {
|
|
18616
|
+
if (!Array.isArray(value)) return [];
|
|
18617
|
+
return value.filter((entry) => typeof entry === "string");
|
|
18618
|
+
}
|
|
18619
|
+
/** Copy a TOML table; a non-table (scalar, array, missing) becomes `{}`. */
|
|
18620
|
+
function toReasonixTable(value) {
|
|
18621
|
+
if (!isPlainObject$1(value)) return {};
|
|
18622
|
+
return { ...value };
|
|
18623
|
+
}
|
|
18624
|
+
//#endregion
|
|
18625
|
+
//#region src/features/ignore/reasonix-ignore.ts
|
|
18626
|
+
const permissionsTableOf = (document) => toReasonixTable(document.permissions);
|
|
18627
|
+
/**
|
|
18628
|
+
* Reshape the parsed TOML document into the `permissions.allow/ask/deny` shape
|
|
18629
|
+
* {@link applyIgnoreReadDenies} operates on. Reasonix's `[permissions]` table
|
|
18630
|
+
* is Claude-Code-shaped (SPEC.md §3.7), so the entry-level ownership rule the
|
|
18631
|
+
* gateway already implements applies verbatim; only the surrounding file
|
|
18632
|
+
* format differs. Sibling keys such as `mode` pass through untouched.
|
|
18633
|
+
*/
|
|
18634
|
+
const asClaudeStyleSettings = (document) => {
|
|
18635
|
+
const table = permissionsTableOf(document);
|
|
18636
|
+
return {
|
|
18637
|
+
...document,
|
|
18638
|
+
permissions: {
|
|
18639
|
+
...table,
|
|
18640
|
+
allow: toReasonixStringArray(table.allow),
|
|
18641
|
+
ask: toReasonixStringArray(table.ask),
|
|
18642
|
+
deny: toReasonixStringArray(table.deny)
|
|
18643
|
+
}
|
|
18644
|
+
};
|
|
18645
|
+
};
|
|
18646
|
+
/**
|
|
18647
|
+
* Drop a `[permissions]` table that ended up with nothing in it, so an empty
|
|
18648
|
+
* `.rulesyncignore` does not add a bare table header to a file that never had
|
|
18649
|
+
* one.
|
|
18650
|
+
*/
|
|
18651
|
+
const withoutEmptyPermissions = (settings) => {
|
|
18652
|
+
const document = { ...settings };
|
|
18653
|
+
const permissions = document.permissions;
|
|
18654
|
+
if (isPlainObject$1(permissions) && Object.keys(permissions).length === 0) delete document.permissions;
|
|
18655
|
+
return document;
|
|
18656
|
+
};
|
|
18657
|
+
/**
|
|
18658
|
+
* Writes `.rulesyncignore` patterns as `Read(<pattern>)` entries in the
|
|
18659
|
+
* `[permissions] deny` table of `reasonix.toml` (project) /
|
|
18660
|
+
* `~/.reasonix/config.toml` (global).
|
|
18661
|
+
*
|
|
18662
|
+
* `deny` is the right target rather than `[sandbox] forbid_read`: deny rules
|
|
18663
|
+
* take glob specifiers (`Edit(docs/**)`) and are "a hard block in every mode",
|
|
18664
|
+
* while `forbid_read` is documented as absolute paths with no glob support.
|
|
18665
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/SPEC.md
|
|
18666
|
+
*/
|
|
18667
|
+
var ReasonixIgnore = class ReasonixIgnore extends ToolIgnore {
|
|
18668
|
+
constructor(params) {
|
|
18669
|
+
super(params);
|
|
18670
|
+
const document = parseSharedConfig({
|
|
18671
|
+
format: "toml",
|
|
18672
|
+
fileContent: this.fileContent
|
|
18673
|
+
});
|
|
18674
|
+
this.patterns = toReasonixStringArray(permissionsTableOf(document).deny);
|
|
18675
|
+
}
|
|
18676
|
+
static getSettablePaths({ global = false } = {}) {
|
|
18677
|
+
return {
|
|
18678
|
+
relativeDirPath: global ? REASONIX_GLOBAL_DIR : ".",
|
|
18679
|
+
relativeFilePath: global ? REASONIX_GLOBAL_PERMISSIONS_FILE_NAME : REASONIX_PROJECT_PERMISSIONS_FILE_NAME
|
|
18680
|
+
};
|
|
18681
|
+
}
|
|
18682
|
+
/**
|
|
18683
|
+
* The config file also carries `[[plugins]]`, `[permissions]` rules from the
|
|
18684
|
+
* permissions feature and user-authored tables, so rulesync must never
|
|
18685
|
+
* delete it.
|
|
18686
|
+
*/
|
|
18687
|
+
isDeletable() {
|
|
18688
|
+
return false;
|
|
18689
|
+
}
|
|
18690
|
+
toRulesyncIgnore() {
|
|
18691
|
+
const rulesyncPatterns = this.patterns.filter((pattern) => isReadDenyEntry(pattern)).map((pattern) => pattern.slice(5, -1)).filter((pattern) => pattern.length > 0);
|
|
18692
|
+
return new RulesyncIgnore({
|
|
18693
|
+
outputRoot: this.outputRoot,
|
|
18694
|
+
relativeDirPath: RulesyncIgnore.getSettablePaths().recommended.relativeDirPath,
|
|
18695
|
+
relativeFilePath: RulesyncIgnore.getSettablePaths().recommended.relativeFilePath,
|
|
18696
|
+
fileContent: rulesyncPatterns.join("\n")
|
|
18697
|
+
});
|
|
18698
|
+
}
|
|
18699
|
+
static async fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore, global = false }) {
|
|
18700
|
+
const readDenies = rulesyncIgnore.getFileContent().split(/\r?\n|\r/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).map((pattern) => buildReadDenyEntry(pattern));
|
|
18701
|
+
const paths = this.getSettablePaths({ global });
|
|
18702
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
18703
|
+
const existingDocument = parseSharedConfig({
|
|
18704
|
+
format: "toml",
|
|
18705
|
+
fileContent: await readFileContentOrNull(filePath) ?? "",
|
|
18706
|
+
filePath
|
|
18707
|
+
});
|
|
18708
|
+
const document = withoutEmptyPermissions(applyIgnoreReadDenies({
|
|
18709
|
+
settings: asClaudeStyleSettings(existingDocument),
|
|
18710
|
+
readDenies
|
|
18711
|
+
}));
|
|
18712
|
+
return new ReasonixIgnore({
|
|
18713
|
+
outputRoot,
|
|
18714
|
+
relativeDirPath: paths.relativeDirPath,
|
|
18715
|
+
relativeFilePath: paths.relativeFilePath,
|
|
18716
|
+
fileContent: stringifySharedConfig({
|
|
18717
|
+
format: "toml",
|
|
18718
|
+
document
|
|
18719
|
+
}),
|
|
18720
|
+
validate: true,
|
|
18721
|
+
global
|
|
18722
|
+
});
|
|
18723
|
+
}
|
|
18724
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
18725
|
+
const paths = this.getSettablePaths({ global });
|
|
18726
|
+
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
|
|
18727
|
+
return new ReasonixIgnore({
|
|
18728
|
+
outputRoot,
|
|
18729
|
+
relativeDirPath: paths.relativeDirPath,
|
|
18730
|
+
relativeFilePath: paths.relativeFilePath,
|
|
18731
|
+
fileContent,
|
|
18732
|
+
validate,
|
|
18733
|
+
global
|
|
18734
|
+
});
|
|
18735
|
+
}
|
|
18736
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
18737
|
+
return new ReasonixIgnore({
|
|
18738
|
+
outputRoot,
|
|
18739
|
+
relativeDirPath,
|
|
18740
|
+
relativeFilePath,
|
|
18741
|
+
fileContent: "",
|
|
18742
|
+
validate: false,
|
|
18743
|
+
global
|
|
18744
|
+
});
|
|
18745
|
+
}
|
|
18746
|
+
};
|
|
18747
|
+
//#endregion
|
|
18023
18748
|
//#region src/features/ignore/roo-ignore.ts
|
|
18024
18749
|
/**
|
|
18025
18750
|
* RooIgnore represents ignore patterns for the Roo Code AI coding assistant.
|
|
@@ -18177,6 +18902,14 @@ const ZED_GLOBAL_WIN32_DIR = (0, node_path.join)("AppData", "Roaming", "Zed");
|
|
|
18177
18902
|
function getZedGlobalDir() {
|
|
18178
18903
|
return process.platform === "win32" ? ZED_GLOBAL_WIN32_DIR : ZED_GLOBAL_DIR;
|
|
18179
18904
|
}
|
|
18905
|
+
/**
|
|
18906
|
+
* The global config dir of the OTHER platform. `getZedGlobalDir()` resolves one
|
|
18907
|
+
* spelling per platform, but the shared-write derivation (and the gateway
|
|
18908
|
+
* ownership table it is checked against) must know both on every platform.
|
|
18909
|
+
*/
|
|
18910
|
+
function getZedOtherPlatformGlobalDir() {
|
|
18911
|
+
return process.platform === "win32" ? ZED_GLOBAL_DIR : ZED_GLOBAL_WIN32_DIR;
|
|
18912
|
+
}
|
|
18180
18913
|
const ZED_SETTINGS_FILE_NAME = "settings.json";
|
|
18181
18914
|
const ZED_RULE_FILE_NAME = ".rules";
|
|
18182
18915
|
const ZED_GLOBAL_RULE_FILE_NAME = "AGENTS.md";
|
|
@@ -18189,12 +18922,20 @@ var ZedIgnore = class ZedIgnore extends ToolIgnore {
|
|
|
18189
18922
|
const jsonValue = JSON.parse(this.fileContent);
|
|
18190
18923
|
this.patterns = jsonValue.private_files ?? [];
|
|
18191
18924
|
}
|
|
18192
|
-
static getSettablePaths() {
|
|
18925
|
+
static getSettablePaths({ global = false } = {}) {
|
|
18193
18926
|
return {
|
|
18194
|
-
relativeDirPath: ZED_DIR,
|
|
18927
|
+
relativeDirPath: global ? getZedGlobalDir() : ZED_DIR,
|
|
18195
18928
|
relativeFilePath: ZED_SETTINGS_FILE_NAME
|
|
18196
18929
|
};
|
|
18197
18930
|
}
|
|
18931
|
+
/** @see getZedOtherPlatformGlobalDir */
|
|
18932
|
+
static getExtraSharedWritePaths({ global = false } = {}) {
|
|
18933
|
+
if (!global) return [];
|
|
18934
|
+
return [{
|
|
18935
|
+
relativeDirPath: getZedOtherPlatformGlobalDir(),
|
|
18936
|
+
relativeFilePath: ZED_SETTINGS_FILE_NAME
|
|
18937
|
+
}];
|
|
18938
|
+
}
|
|
18198
18939
|
/**
|
|
18199
18940
|
* ZedIgnore uses settings.json which is a user-managed config file.
|
|
18200
18941
|
* It should not be deleted by rulesync.
|
|
@@ -18205,48 +18946,53 @@ var ZedIgnore = class ZedIgnore extends ToolIgnore {
|
|
|
18205
18946
|
toRulesyncIgnore() {
|
|
18206
18947
|
const fileContent = this.patterns.filter((pattern) => pattern.length > 0).join("\n");
|
|
18207
18948
|
return new RulesyncIgnore({
|
|
18208
|
-
outputRoot:
|
|
18949
|
+
outputRoot: ".",
|
|
18209
18950
|
relativeDirPath: RulesyncIgnore.getSettablePaths().recommended.relativeDirPath,
|
|
18210
18951
|
relativeFilePath: RulesyncIgnore.getSettablePaths().recommended.relativeFilePath,
|
|
18211
18952
|
fileContent
|
|
18212
18953
|
});
|
|
18213
18954
|
}
|
|
18214
|
-
static async fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore }) {
|
|
18955
|
+
static async fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore, global = false }) {
|
|
18215
18956
|
const patterns = rulesyncIgnore.getFileContent().split(/\r?\n|\r/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
18216
|
-
const
|
|
18957
|
+
const paths = this.getSettablePaths({ global });
|
|
18958
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
18217
18959
|
const existingFileContent = await fileExists(filePath) ? await readFileContent(filePath) : "{}";
|
|
18218
|
-
const
|
|
18960
|
+
const managedPatterns = patterns.length > 0 ? [...new Set(patterns)].toSorted() : void 0;
|
|
18219
18961
|
return new ZedIgnore({
|
|
18220
18962
|
outputRoot,
|
|
18221
|
-
relativeDirPath:
|
|
18222
|
-
relativeFilePath:
|
|
18963
|
+
relativeDirPath: paths.relativeDirPath,
|
|
18964
|
+
relativeFilePath: paths.relativeFilePath,
|
|
18223
18965
|
fileContent: applySharedConfigPatch({
|
|
18224
|
-
fileKey: sharedConfigFileKey(
|
|
18966
|
+
fileKey: sharedConfigFileKey(paths),
|
|
18225
18967
|
feature: "ignore",
|
|
18226
18968
|
existingContent: existingFileContent,
|
|
18227
|
-
patch: { private_files:
|
|
18969
|
+
patch: { private_files: managedPatterns },
|
|
18228
18970
|
filePath
|
|
18229
18971
|
}),
|
|
18230
|
-
validate: true
|
|
18972
|
+
validate: true,
|
|
18973
|
+
global
|
|
18231
18974
|
});
|
|
18232
18975
|
}
|
|
18233
|
-
static async fromFile({ outputRoot = process.cwd(), validate = true }) {
|
|
18234
|
-
const
|
|
18976
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
18977
|
+
const paths = this.getSettablePaths({ global });
|
|
18978
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath));
|
|
18235
18979
|
return new ZedIgnore({
|
|
18236
18980
|
outputRoot,
|
|
18237
|
-
relativeDirPath:
|
|
18238
|
-
relativeFilePath:
|
|
18981
|
+
relativeDirPath: paths.relativeDirPath,
|
|
18982
|
+
relativeFilePath: paths.relativeFilePath,
|
|
18239
18983
|
fileContent,
|
|
18240
|
-
validate
|
|
18984
|
+
validate,
|
|
18985
|
+
global
|
|
18241
18986
|
});
|
|
18242
18987
|
}
|
|
18243
|
-
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
18988
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
18244
18989
|
return new ZedIgnore({
|
|
18245
18990
|
outputRoot,
|
|
18246
18991
|
relativeDirPath,
|
|
18247
18992
|
relativeFilePath,
|
|
18248
18993
|
fileContent: "{}",
|
|
18249
|
-
validate: false
|
|
18994
|
+
validate: false,
|
|
18995
|
+
global
|
|
18250
18996
|
});
|
|
18251
18997
|
}
|
|
18252
18998
|
};
|
|
@@ -18268,6 +19014,7 @@ const toolIgnoreFactories = /* @__PURE__ */ new Map([
|
|
|
18268
19014
|
["kiro-cli", { class: KiroIgnore }],
|
|
18269
19015
|
["kiro-ide", { class: KiroIgnore }],
|
|
18270
19016
|
["qwencode", { class: QwencodeIgnore }],
|
|
19017
|
+
["reasonix", { class: ReasonixIgnore }],
|
|
18271
19018
|
["roo", { class: RooIgnore }],
|
|
18272
19019
|
["devin", { class: DevinIgnore }],
|
|
18273
19020
|
["vibe", { class: VibeIgnore }],
|
|
@@ -18278,7 +19025,9 @@ const ignoreProcessorToolTargets = [...toolIgnoreFactories.keys()];
|
|
|
18278
19025
|
const ignoreProcessorGlobalToolTargets = [
|
|
18279
19026
|
"kiro",
|
|
18280
19027
|
"kiro-cli",
|
|
18281
|
-
"kiro-ide"
|
|
19028
|
+
"kiro-ide",
|
|
19029
|
+
"reasonix",
|
|
19030
|
+
"zed"
|
|
18282
19031
|
];
|
|
18283
19032
|
const defaultGetFactory$4 = (target) => {
|
|
18284
19033
|
const factory = toolIgnoreFactories.get(target);
|
|
@@ -20874,7 +21623,7 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
20874
21623
|
}), isRecord(this.config.mcp_servers) ? this.config.mcp_servers : {});
|
|
20875
21624
|
this.config = merged;
|
|
20876
21625
|
super.setFileContent(applySharedConfigPatch({
|
|
20877
|
-
fileKey:
|
|
21626
|
+
fileKey: getHermesagentConfigSharedFileKey({ global: this.global }),
|
|
20878
21627
|
feature: "mcp",
|
|
20879
21628
|
existingContent: fileContent,
|
|
20880
21629
|
patch: { mcp_servers: merged.mcp_servers }
|
|
@@ -20892,6 +21641,13 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
20892
21641
|
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
20893
21642
|
};
|
|
20894
21643
|
}
|
|
21644
|
+
/**
|
|
21645
|
+
* `config.yaml` under every spelling the global profile root can take.
|
|
21646
|
+
* @see getHermesagentSharedConfigWritePaths
|
|
21647
|
+
*/
|
|
21648
|
+
static getExtraSharedWritePaths() {
|
|
21649
|
+
return getHermesagentSharedConfigWritePaths();
|
|
21650
|
+
}
|
|
20895
21651
|
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
20896
21652
|
if (!global) throw new Error(HERMESAGENT_GLOBAL_ONLY_MESSAGE);
|
|
20897
21653
|
const paths = this.getSettablePaths({ global });
|
|
@@ -20918,7 +21674,7 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
20918
21674
|
relativeDirPath: paths.relativeDirPath,
|
|
20919
21675
|
relativeFilePath: paths.relativeFilePath,
|
|
20920
21676
|
fileContent: applySharedConfigPatch({
|
|
20921
|
-
fileKey:
|
|
21677
|
+
fileKey: getHermesagentConfigSharedFileKey({ global }),
|
|
20922
21678
|
feature: "mcp",
|
|
20923
21679
|
existingContent: fileContent,
|
|
20924
21680
|
patch: { mcp_servers: merged.mcp_servers }
|
|
@@ -21379,9 +22135,12 @@ var KiloMcp = class KiloMcp extends ToolMcp {
|
|
|
21379
22135
|
* Merge a list of project rule file globs into the `instructions` array of the
|
|
21380
22136
|
* shared `kilo.jsonc` (or `kilo.json`) config, preserving every existing key
|
|
21381
22137
|
* (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
|
-
*
|
|
22138
|
+
* a *project* `.kilo/rules/` are NOT auto-loaded; they are only picked up
|
|
22139
|
+
* when listed in the `instructions` key. (The home-scope `~/.kilo/rules/` is
|
|
22140
|
+
* different — the rules migrator's `globalRulesDirs()` walks it on every
|
|
22141
|
+
* config load — which is why `KiloRule` registers instructions in project
|
|
22142
|
+
* scope only.) The resulting `instructions` list is deduped and sorted for a
|
|
22143
|
+
* stable output.
|
|
21385
22144
|
*
|
|
21386
22145
|
* @see https://kilo.ai/docs/automate/mcp/using-in-kilo-code
|
|
21387
22146
|
*/
|
|
@@ -21594,7 +22353,7 @@ var KimiCodeMcpConfigToml = class KimiCodeMcpConfigToml extends ToolFile {
|
|
|
21594
22353
|
const existingContent = existing.content;
|
|
21595
22354
|
const existingSection = existing.mcp;
|
|
21596
22355
|
const fileContent = applySharedConfigPatch({
|
|
21597
|
-
fileKey:
|
|
22356
|
+
fileKey: getKimiCodeConfigSharedFileKey({ global: true }),
|
|
21598
22357
|
feature: "mcp",
|
|
21599
22358
|
existingContent,
|
|
21600
22359
|
patch: { mcp: {
|
|
@@ -21681,11 +22440,8 @@ var KimiCodeMcp = class KimiCodeMcp extends ToolMcp {
|
|
|
21681
22440
|
* derivation sees this feature as one of that file's writers — it is not a
|
|
21682
22441
|
* settable path, since the servers themselves live in `mcp.json`.
|
|
21683
22442
|
*/
|
|
21684
|
-
static getExtraSharedWritePaths(
|
|
21685
|
-
return
|
|
21686
|
-
relativeDirPath: getKimiCodeRelativeDirPath({ global: true }),
|
|
21687
|
-
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
21688
|
-
}] : [];
|
|
22443
|
+
static getExtraSharedWritePaths() {
|
|
22444
|
+
return getKimiCodeSharedConfigWritePaths();
|
|
21689
22445
|
}
|
|
21690
22446
|
/**
|
|
21691
22447
|
* The `[mcp]` defaults live in the shared user `config.toml`, not in
|
|
@@ -23321,16 +24077,11 @@ var ZedMcp = class ZedMcp extends ToolMcp {
|
|
|
23321
24077
|
relativeFilePath: ZED_SETTINGS_FILE_NAME
|
|
23322
24078
|
};
|
|
23323
24079
|
}
|
|
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
|
-
*/
|
|
24080
|
+
/** @see getZedOtherPlatformGlobalDir */
|
|
23330
24081
|
static getExtraSharedWritePaths({ global = false } = {}) {
|
|
23331
24082
|
if (!global) return [];
|
|
23332
24083
|
return [{
|
|
23333
|
-
relativeDirPath:
|
|
24084
|
+
relativeDirPath: getZedOtherPlatformGlobalDir(),
|
|
23334
24085
|
relativeFilePath: ZED_SETTINGS_FILE_NAME
|
|
23335
24086
|
}];
|
|
23336
24087
|
}
|
|
@@ -27760,6 +28511,13 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
27760
28511
|
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
27761
28512
|
};
|
|
27762
28513
|
}
|
|
28514
|
+
/**
|
|
28515
|
+
* `config.yaml` under every spelling the global profile root can take.
|
|
28516
|
+
* @see getHermesagentSharedConfigWritePaths
|
|
28517
|
+
*/
|
|
28518
|
+
static getExtraSharedWritePaths() {
|
|
28519
|
+
return getHermesagentSharedConfigWritePaths();
|
|
28520
|
+
}
|
|
27763
28521
|
constructor(params) {
|
|
27764
28522
|
super({
|
|
27765
28523
|
...params,
|
|
@@ -27797,7 +28555,7 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
27797
28555
|
}
|
|
27798
28556
|
setFileContent(fileContent) {
|
|
27799
28557
|
this.fileContent = applySharedConfigPatch({
|
|
27800
|
-
fileKey:
|
|
28558
|
+
fileKey: getHermesagentConfigSharedFileKey({ global: this.global }),
|
|
27801
28559
|
feature: "permissions",
|
|
27802
28560
|
existingContent: fileContent,
|
|
27803
28561
|
patch: parseSharedConfig({
|
|
@@ -28210,7 +28968,10 @@ const KiloPermissionSchema = zod_mini.z.union([zod_mini.z.enum([
|
|
|
28210
28968
|
"ask",
|
|
28211
28969
|
"deny"
|
|
28212
28970
|
]))]);
|
|
28213
|
-
const KiloPermissionsConfigSchema = zod_mini.z.looseObject({
|
|
28971
|
+
const KiloPermissionsConfigSchema = zod_mini.z.looseObject({
|
|
28972
|
+
permission: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), KiloPermissionSchema)),
|
|
28973
|
+
sandbox: zod_mini.z.optional(zod_mini.z.unknown())
|
|
28974
|
+
});
|
|
28214
28975
|
/**
|
|
28215
28976
|
* Kilo permission keys that share a name with a canonical rulesync category and
|
|
28216
28977
|
* therefore stay in the shared `permission` block. Everything else (Kilo-only
|
|
@@ -28267,6 +29028,25 @@ function collectKiloDenyPatterns(value) {
|
|
|
28267
29028
|
}
|
|
28268
29029
|
return [];
|
|
28269
29030
|
}
|
|
29031
|
+
function asKiloRecord(value) {
|
|
29032
|
+
return isPlainObject$1(value) ? { ...value } : {};
|
|
29033
|
+
}
|
|
29034
|
+
/**
|
|
29035
|
+
* The `sandbox` keys a *project* `kilo.jsonc` may state. Kilo honors
|
|
29036
|
+
* `allowed_hosts` and `writable_paths` from the global config only, and lets a
|
|
29037
|
+
* project config merely tighten — so writing the wider keys into a project file
|
|
29038
|
+
* would produce config Kilo ignores.
|
|
29039
|
+
* @see https://kilo.ai/docs/getting-started/settings/sandboxing
|
|
29040
|
+
*/
|
|
29041
|
+
const KILO_PROJECT_SCOPE_SANDBOX_KEYS = /* @__PURE__ */ new Set(["enabled", "network"]);
|
|
29042
|
+
function narrowSandboxToProjectScope({ authored, logger }) {
|
|
29043
|
+
const emitted = {};
|
|
29044
|
+
const dropped = [];
|
|
29045
|
+
for (const [key, value] of Object.entries(authored)) if (KILO_PROJECT_SCOPE_SANDBOX_KEYS.has(key)) emitted[key] = value;
|
|
29046
|
+
else dropped.push(key);
|
|
29047
|
+
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.`);
|
|
29048
|
+
return emitted;
|
|
29049
|
+
}
|
|
28270
29050
|
var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
28271
29051
|
json;
|
|
28272
29052
|
constructor(params) {
|
|
@@ -28346,8 +29126,7 @@ var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
|
28346
29126
|
const basePaths = KiloPermissions.getSettablePaths({ global });
|
|
28347
29127
|
const filePath = (0, node_path.join)(outputRoot, basePaths.relativeDirPath, basePaths.relativeFilePath);
|
|
28348
29128
|
const parsed = parseKiloJsoncStrict(await readFileContentOrNull(filePath) ?? "{}", filePath);
|
|
28349
|
-
const
|
|
28350
|
-
const existingPermission = parsedPermission && typeof parsedPermission === "object" && !Array.isArray(parsedPermission) ? { ...parsedPermission } : {};
|
|
29129
|
+
const existingPermission = asKiloRecord(parsed.permission);
|
|
28351
29130
|
const rulesyncJson = rulesyncPermissions.getJson();
|
|
28352
29131
|
const kiloOverride = rulesyncJson.kilo;
|
|
28353
29132
|
const incomingPermission = {
|
|
@@ -28373,6 +29152,18 @@ var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
|
28373
29152
|
...parsed,
|
|
28374
29153
|
permission: mergedPermission
|
|
28375
29154
|
};
|
|
29155
|
+
if (kiloOverride?.sandbox !== void 0) {
|
|
29156
|
+
const authored = asKiloRecord(kiloOverride.sandbox);
|
|
29157
|
+
const emitted = global ? authored : narrowSandboxToProjectScope({
|
|
29158
|
+
authored,
|
|
29159
|
+
logger
|
|
29160
|
+
});
|
|
29161
|
+
const merged = {
|
|
29162
|
+
...asKiloRecord(parsed.sandbox),
|
|
29163
|
+
...emitted
|
|
29164
|
+
};
|
|
29165
|
+
if (Object.keys(merged).length > 0) nextJson.sandbox = merged;
|
|
29166
|
+
}
|
|
28376
29167
|
return new KiloPermissions({
|
|
28377
29168
|
outputRoot,
|
|
28378
29169
|
relativeDirPath: basePaths.relativeDirPath,
|
|
@@ -28387,9 +29178,14 @@ var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
|
28387
29178
|
const overrideOnly = {};
|
|
28388
29179
|
for (const [key, value] of Object.entries(rawPermission)) if (isSharedKiloCategory(key)) shared[key] = typeof value === "string" ? { "*": value } : value;
|
|
28389
29180
|
else overrideOnly[key] = value;
|
|
28390
|
-
const
|
|
29181
|
+
const sandbox = this.json.sandbox;
|
|
29182
|
+
const override = {
|
|
29183
|
+
...Object.keys(overrideOnly).length > 0 && { permission: overrideOnly },
|
|
29184
|
+
...isPlainObject$1(sandbox) && { sandbox }
|
|
29185
|
+
};
|
|
29186
|
+
const json = Object.keys(override).length > 0 ? {
|
|
28391
29187
|
permission: shared,
|
|
28392
|
-
kilo:
|
|
29188
|
+
kilo: override
|
|
28393
29189
|
} : { permission: shared };
|
|
28394
29190
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(json, null, 2) });
|
|
28395
29191
|
}
|
|
@@ -28630,6 +29426,13 @@ var KimiCodePermissions = class KimiCodePermissions extends ToolPermissions {
|
|
|
28630
29426
|
isDeletable() {
|
|
28631
29427
|
return false;
|
|
28632
29428
|
}
|
|
29429
|
+
/**
|
|
29430
|
+
* `config.toml` under both spellings its directory can take.
|
|
29431
|
+
* @see getKimiCodeSharedConfigWritePaths
|
|
29432
|
+
*/
|
|
29433
|
+
static getExtraSharedWritePaths() {
|
|
29434
|
+
return getKimiCodeSharedConfigWritePaths();
|
|
29435
|
+
}
|
|
28633
29436
|
shouldMergeExistingFileContent() {
|
|
28634
29437
|
return true;
|
|
28635
29438
|
}
|
|
@@ -28644,7 +29447,7 @@ var KimiCodePermissions = class KimiCodePermissions extends ToolPermissions {
|
|
|
28644
29447
|
patch
|
|
28645
29448
|
});
|
|
28646
29449
|
this.fileContent = applySharedConfigPatch({
|
|
28647
|
-
fileKey:
|
|
29450
|
+
fileKey: getKimiCodeConfigSharedFileKey({ global: this.global }),
|
|
28648
29451
|
feature: "permissions",
|
|
28649
29452
|
existingContent: fileContent,
|
|
28650
29453
|
patch: {
|
|
@@ -29568,14 +30371,6 @@ function parseReasonixConfig(fileContent) {
|
|
|
29568
30371
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
29569
30372
|
return { ...parsed };
|
|
29570
30373
|
}
|
|
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
30374
|
const REASONIX_OVERRIDE_AGENT_KEYS = ["plan_mode_read_only_commands"];
|
|
29580
30375
|
/**
|
|
29581
30376
|
* `[agent]` keys an older `reasonix.toml` may carry that left the documented
|
|
@@ -29632,12 +30427,12 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
29632
30427
|
const config = rulesyncPermissions.getJson();
|
|
29633
30428
|
const { allow, ask, deny } = convertRulesyncToReasonixPermissions(config);
|
|
29634
30429
|
const managedToolNames = new Set(Object.keys(config.permission).map((category) => toReasonixToolName(category)));
|
|
29635
|
-
const existingPermissions =
|
|
29636
|
-
const preservedAllow =
|
|
29637
|
-
const preservedAsk =
|
|
29638
|
-
const preservedDeny =
|
|
30430
|
+
const existingPermissions = toReasonixTable(parsed.permissions);
|
|
30431
|
+
const preservedAllow = toReasonixStringArray(existingPermissions.allow).filter((entry) => !managedToolNames.has(parseReasonixPermissionEntry(entry).toolName));
|
|
30432
|
+
const preservedAsk = toReasonixStringArray(existingPermissions.ask).filter((entry) => !managedToolNames.has(parseReasonixPermissionEntry(entry).toolName));
|
|
30433
|
+
const preservedDeny = toReasonixStringArray(existingPermissions.deny).filter((entry) => !managedToolNames.has(parseReasonixPermissionEntry(entry).toolName));
|
|
29639
30434
|
if (logger && managedToolNames.has("Read")) {
|
|
29640
|
-
const droppedReadDenyEntries =
|
|
30435
|
+
const droppedReadDenyEntries = toReasonixStringArray(existingPermissions.deny).filter((entry) => {
|
|
29641
30436
|
const { toolName } = parseReasonixPermissionEntry(entry);
|
|
29642
30437
|
return toolName === "Read";
|
|
29643
30438
|
});
|
|
@@ -29684,11 +30479,11 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
29684
30479
|
});
|
|
29685
30480
|
}
|
|
29686
30481
|
toRulesyncPermissions() {
|
|
29687
|
-
const permissions =
|
|
30482
|
+
const permissions = toReasonixTable(this.toml.permissions);
|
|
29688
30483
|
const config = convertReasonixToRulesyncPermissions({
|
|
29689
|
-
allow:
|
|
29690
|
-
ask:
|
|
29691
|
-
deny:
|
|
30484
|
+
allow: toReasonixStringArray(permissions.allow),
|
|
30485
|
+
ask: toReasonixStringArray(permissions.ask),
|
|
30486
|
+
deny: toReasonixStringArray(permissions.deny)
|
|
29692
30487
|
});
|
|
29693
30488
|
const sandbox = asReasonixRecord(this.toml.sandbox);
|
|
29694
30489
|
const agentPlanMode = pickReasonixKeys(this.toml.agent, [...REASONIX_OVERRIDE_AGENT_KEYS, ...REASONIX_RETIRED_AGENT_KEYS]);
|
|
@@ -29780,19 +30575,31 @@ const CATEGORY_TO_TOOL_KEYS = {
|
|
|
29780
30575
|
"open_files",
|
|
29781
30576
|
"expand_code_chunks",
|
|
29782
30577
|
"expand_folder",
|
|
29783
|
-
"grep"
|
|
30578
|
+
"grep",
|
|
30579
|
+
"getJiraIssue",
|
|
30580
|
+
"getConfluencePage"
|
|
29784
30581
|
],
|
|
29785
30582
|
edit: [
|
|
29786
30583
|
"find_and_replace_code",
|
|
29787
30584
|
"create_file",
|
|
29788
30585
|
"delete_file",
|
|
29789
|
-
"move_file"
|
|
30586
|
+
"move_file",
|
|
30587
|
+
"createTechnicalPlan",
|
|
30588
|
+
"createJiraIssue",
|
|
30589
|
+
"updateJiraIssue",
|
|
30590
|
+
"createConfluencePage",
|
|
30591
|
+
"updateConfluencePage"
|
|
29790
30592
|
],
|
|
29791
30593
|
write: [
|
|
29792
30594
|
"create_file",
|
|
29793
30595
|
"delete_file",
|
|
29794
30596
|
"move_file",
|
|
29795
|
-
"find_and_replace_code"
|
|
30597
|
+
"find_and_replace_code",
|
|
30598
|
+
"createTechnicalPlan",
|
|
30599
|
+
"createJiraIssue",
|
|
30600
|
+
"updateJiraIssue",
|
|
30601
|
+
"createConfluencePage",
|
|
30602
|
+
"updateConfluencePage"
|
|
29796
30603
|
]
|
|
29797
30604
|
};
|
|
29798
30605
|
const TOOL_KEY_TO_CATEGORY = {
|
|
@@ -29800,13 +30607,24 @@ const TOOL_KEY_TO_CATEGORY = {
|
|
|
29800
30607
|
expand_code_chunks: "read",
|
|
29801
30608
|
expand_folder: "read",
|
|
29802
30609
|
grep: "read",
|
|
30610
|
+
getJiraIssue: "read",
|
|
30611
|
+
getConfluencePage: "read",
|
|
29803
30612
|
find_and_replace_code: "edit",
|
|
29804
30613
|
create_file: "edit",
|
|
29805
30614
|
delete_file: "edit",
|
|
29806
|
-
move_file: "edit"
|
|
30615
|
+
move_file: "edit",
|
|
30616
|
+
createTechnicalPlan: "edit",
|
|
30617
|
+
createJiraIssue: "edit",
|
|
30618
|
+
updateJiraIssue: "edit",
|
|
30619
|
+
createConfluencePage: "edit",
|
|
30620
|
+
updateConfluencePage: "edit"
|
|
29807
30621
|
};
|
|
29808
30622
|
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 = [
|
|
30623
|
+
const OWNED_TOOL_PERMISSION_KEYS = [
|
|
30624
|
+
"bash",
|
|
30625
|
+
"allowedExternalPaths",
|
|
30626
|
+
"default"
|
|
30627
|
+
];
|
|
29810
30628
|
/**
|
|
29811
30629
|
* Permissions adapter for Rovo Dev CLI.
|
|
29812
30630
|
*
|
|
@@ -29820,9 +30638,16 @@ const OWNED_TOOL_PERMISSION_KEYS = ["bash", "allowedExternalPaths"];
|
|
|
29820
30638
|
* Mapping decisions (rulesync canonical -> Rovo Dev):
|
|
29821
30639
|
* - `bash`: the catch-all `*` pattern -> `bash.default`; every other pattern ->
|
|
29822
30640
|
* a `bash.commands[]` entry `{ command: <pattern as regex>, permission }`.
|
|
30641
|
+
* - the all-tools category `*`: its catch-all -> `toolPermissions.default`,
|
|
30642
|
+
* the level Rovo Dev falls back to for any tool with no more specific
|
|
30643
|
+
* setting (Rovo Dev's own default is `ask`).
|
|
29823
30644
|
* - `read` -> the inspection tools (`open_files`, `expand_code_chunks`,
|
|
29824
|
-
* `expand_folder`, `grep`
|
|
29825
|
-
*
|
|
30645
|
+
* `expand_folder`, `grep`, `getJiraIssue`, `getConfluencePage`);
|
|
30646
|
+
* `edit`/`write` -> the mutation tools (`find_and_replace_code`,
|
|
30647
|
+
* `create_file`, `delete_file`, `move_file`, `createTechnicalPlan`,
|
|
30648
|
+
* `createJiraIssue`, `updateJiraIssue`, `createConfluencePage`,
|
|
30649
|
+
* `updateConfluencePage`) — so these two categories reach Jira and
|
|
30650
|
+
* Confluence, not just the working tree.
|
|
29826
30651
|
* These Rovo Dev keys hold a single level (no per-pattern rules), so only the
|
|
29827
30652
|
* catch-all `*` of each category sets the level. Non-catch-all `allow` rules
|
|
29828
30653
|
* in those categories are surfaced as `allowedExternalPaths` so explicit path
|
|
@@ -30002,6 +30827,10 @@ function stripPermissiveOwnedValues(toolPermissions) {
|
|
|
30002
30827
|
delete toolPermissions.allowedExternalPaths;
|
|
30003
30828
|
strippedKeys.push("allowedExternalPaths");
|
|
30004
30829
|
}
|
|
30830
|
+
if (toolPermissions.default === "allow") {
|
|
30831
|
+
delete toolPermissions.default;
|
|
30832
|
+
strippedKeys.push("default");
|
|
30833
|
+
}
|
|
30005
30834
|
const bash = toolPermissions.bash;
|
|
30006
30835
|
if (isRecord(bash)) {
|
|
30007
30836
|
const stripped = { ...bash };
|
|
@@ -30026,10 +30855,19 @@ function stripPermissiveOwnedValues(toolPermissions) {
|
|
|
30026
30855
|
function convertRulesyncToRovodevToolPermissions({ config, logger }) {
|
|
30027
30856
|
const toolPermissions = {};
|
|
30028
30857
|
const allowedExternalPaths = [];
|
|
30029
|
-
|
|
30030
|
-
|
|
30031
|
-
|
|
30858
|
+
warnOnEditWriteConflict({
|
|
30859
|
+
config,
|
|
30860
|
+
logger
|
|
30861
|
+
});
|
|
30032
30862
|
for (const [category, rules] of Object.entries(config.permission)) {
|
|
30863
|
+
if (category === CATCH_ALL_PATTERN$1) {
|
|
30864
|
+
const toolWideDefault = convertAllToolsRules({
|
|
30865
|
+
rules,
|
|
30866
|
+
logger
|
|
30867
|
+
});
|
|
30868
|
+
if (toolWideDefault) toolPermissions.default = toolWideDefault;
|
|
30869
|
+
continue;
|
|
30870
|
+
}
|
|
30033
30871
|
if (category === "bash") {
|
|
30034
30872
|
const bash = convertBashRules(rules);
|
|
30035
30873
|
if (bash) toolPermissions.bash = bash;
|
|
@@ -30056,6 +30894,36 @@ function convertRulesyncToRovodevToolPermissions({ config, logger }) {
|
|
|
30056
30894
|
if (allowedExternalPaths.length > 0) toolPermissions.allowedExternalPaths = [...new Set(allowedExternalPaths)].toSorted();
|
|
30057
30895
|
return toolPermissions;
|
|
30058
30896
|
}
|
|
30897
|
+
/**
|
|
30898
|
+
* `edit` and `write` collapse onto the same Rovo Dev file-mutation tools, so a
|
|
30899
|
+
* conflicting catch-all between them cannot be represented. Warn that the loss
|
|
30900
|
+
* is happening; the conversion keeps the stricter of the two — the same
|
|
30901
|
+
* fail-closed rule the import direction uses when those tools disagree, so the
|
|
30902
|
+
* resolution never grants more than the author asked for.
|
|
30903
|
+
*/
|
|
30904
|
+
function warnOnEditWriteConflict({ config, logger }) {
|
|
30905
|
+
const editCatchAll = config.permission.edit?.[CATCH_ALL_PATTERN$1];
|
|
30906
|
+
const writeCatchAll = config.permission.write?.[CATCH_ALL_PATTERN$1];
|
|
30907
|
+
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.`);
|
|
30908
|
+
}
|
|
30909
|
+
/**
|
|
30910
|
+
* The canonical all-tools category. Its catch-all sets the tool-wide
|
|
30911
|
+
* `toolPermissions.default`, the same way `bash`'s catch-all sets
|
|
30912
|
+
* `bash.default` — both are the level Rovo Dev falls back to. Pattern rules
|
|
30913
|
+
* under `*` have no counterpart (the default is a single level), so they are
|
|
30914
|
+
* reported and skipped like any other rule Rovo Dev cannot express.
|
|
30915
|
+
*/
|
|
30916
|
+
function convertAllToolsRules({ rules, logger }) {
|
|
30917
|
+
let toolWideDefault;
|
|
30918
|
+
for (const [pattern, action] of Object.entries(rules)) {
|
|
30919
|
+
if (pattern === CATCH_ALL_PATTERN$1) {
|
|
30920
|
+
toolWideDefault = action;
|
|
30921
|
+
continue;
|
|
30922
|
+
}
|
|
30923
|
+
logger?.warn(`Rovo Dev's tool-wide default is a single level, so it cannot express the pattern "${pattern}" in the "*" category. Skipping it.`);
|
|
30924
|
+
}
|
|
30925
|
+
return toolWideDefault;
|
|
30926
|
+
}
|
|
30059
30927
|
function convertBashRules(rules) {
|
|
30060
30928
|
const bash = {};
|
|
30061
30929
|
const commands = [];
|
|
@@ -30077,6 +30945,7 @@ function convertBashRules(rules) {
|
|
|
30077
30945
|
*/
|
|
30078
30946
|
function convertRovodevToolPermissionsToRulesync(toolPermissions) {
|
|
30079
30947
|
const permission = {};
|
|
30948
|
+
if (isPermissionAction(toolPermissions.default)) permission[CATCH_ALL_PATTERN$1] = { [CATCH_ALL_PATTERN$1]: toolPermissions.default };
|
|
30080
30949
|
const bash = toolPermissions.bash;
|
|
30081
30950
|
if (isRecord(bash)) {
|
|
30082
30951
|
const bashRules = {};
|
|
@@ -30087,12 +30956,15 @@ function convertRovodevToolPermissionsToRulesync(toolPermissions) {
|
|
|
30087
30956
|
if (Object.keys(bashRules).length > 0) permission.bash = bashRules;
|
|
30088
30957
|
}
|
|
30089
30958
|
const nestedTools = isRecord(toolPermissions.tools) ? toolPermissions.tools : {};
|
|
30090
|
-
|
|
30091
|
-
|
|
30092
|
-
|
|
30959
|
+
const implicitLevel = isPermissionAction(toolPermissions.default) ? toolPermissions.default : "ask";
|
|
30960
|
+
for (const category of new Set(Object.values(TOOL_KEY_TO_CATEGORY))) {
|
|
30961
|
+
const levels = Object.entries(TOOL_KEY_TO_CATEGORY).filter(([, mapped]) => mapped === category).map(([toolKey]) => {
|
|
30962
|
+
const value = Object.hasOwn(nestedTools, toolKey) ? nestedTools[toolKey] : toolPermissions[toolKey];
|
|
30963
|
+
return isPermissionAction(value) ? value : void 0;
|
|
30964
|
+
});
|
|
30965
|
+
if (levels.every((level) => level === void 0)) continue;
|
|
30093
30966
|
permission[category] ??= {};
|
|
30094
|
-
|
|
30095
|
-
permission[category][CATCH_ALL_PATTERN$1] = strictestAction(current, value);
|
|
30967
|
+
permission[category][CATCH_ALL_PATTERN$1] = levels.reduce((strictest, level) => strictestAction(strictest, level ?? implicitLevel), permission[category][CATCH_ALL_PATTERN$1]);
|
|
30096
30968
|
}
|
|
30097
30969
|
if (isStringArray$1(toolPermissions.allowedExternalPaths)) for (const path of toolPermissions.allowedExternalPaths) {
|
|
30098
30970
|
permission.read ??= {};
|
|
@@ -30924,16 +31796,11 @@ var ZedPermissions = class ZedPermissions extends ToolPermissions {
|
|
|
30924
31796
|
relativeFilePath: ZED_SETTINGS_FILE_NAME
|
|
30925
31797
|
};
|
|
30926
31798
|
}
|
|
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
|
-
*/
|
|
31799
|
+
/** @see getZedOtherPlatformGlobalDir */
|
|
30933
31800
|
static getExtraSharedWritePaths({ global = false } = {}) {
|
|
30934
31801
|
if (!global) return [];
|
|
30935
31802
|
return [{
|
|
30936
|
-
relativeDirPath:
|
|
31803
|
+
relativeDirPath: getZedOtherPlatformGlobalDir(),
|
|
30937
31804
|
relativeFilePath: ZED_SETTINGS_FILE_NAME
|
|
30938
31805
|
}];
|
|
30939
31806
|
}
|
|
@@ -39505,7 +40372,7 @@ def register(ctx):
|
|
|
39505
40372
|
_register_subagent(ctx, subagent)
|
|
39506
40373
|
`;
|
|
39507
40374
|
}
|
|
39508
|
-
function getEnabledPluginConfigContent(currentContent) {
|
|
40375
|
+
function getEnabledPluginConfigContent({ currentContent, global }) {
|
|
39509
40376
|
const config = parseSharedConfig({
|
|
39510
40377
|
format: "yaml",
|
|
39511
40378
|
fileContent: currentContent
|
|
@@ -39513,7 +40380,7 @@ function getEnabledPluginConfigContent(currentContent) {
|
|
|
39513
40380
|
const plugins = config.plugins && typeof config.plugins === "object" ? config.plugins : {};
|
|
39514
40381
|
const enabled = Array.isArray(plugins.enabled) ? plugins.enabled : [];
|
|
39515
40382
|
return applySharedConfigPatch({
|
|
39516
|
-
fileKey:
|
|
40383
|
+
fileKey: getHermesagentConfigSharedFileKey({ global }),
|
|
39517
40384
|
feature: "subagents",
|
|
39518
40385
|
existingContent: currentContent,
|
|
39519
40386
|
patch: { plugins: {
|
|
@@ -39564,6 +40431,10 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
|
|
|
39564
40431
|
return !targets || targets.includes("*") || targets.includes("hermesagent");
|
|
39565
40432
|
}
|
|
39566
40433
|
static fromRulesyncSubagents({ rulesyncSubagents, outputRoot, global = false }) {
|
|
40434
|
+
const pluginDirPath = getHermesagentRelativeDirPath({
|
|
40435
|
+
global,
|
|
40436
|
+
relativeDirPath: HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_DIR_PATH
|
|
40437
|
+
});
|
|
39567
40438
|
return [
|
|
39568
40439
|
...rulesyncSubagents.map((rulesyncSubagent) => HermesagentSubagent.fromRulesyncSubagent({
|
|
39569
40440
|
relativeDirPath: this.getSettablePaths({ global }).relativeDirPath,
|
|
@@ -39572,20 +40443,14 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
|
|
|
39572
40443
|
global
|
|
39573
40444
|
})),
|
|
39574
40445
|
new HermesagentSubagent({
|
|
39575
|
-
relativeDirPath:
|
|
39576
|
-
global,
|
|
39577
|
-
relativeDirPath: HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_DIR_PATH
|
|
39578
|
-
}),
|
|
40446
|
+
relativeDirPath: pluginDirPath,
|
|
39579
40447
|
relativeFilePath: (0, node_path.basename)(HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_MANIFEST_PATH),
|
|
39580
40448
|
fileContent: "",
|
|
39581
40449
|
outputRoot,
|
|
39582
40450
|
global
|
|
39583
40451
|
}),
|
|
39584
40452
|
new HermesagentSubagent({
|
|
39585
|
-
relativeDirPath:
|
|
39586
|
-
global,
|
|
39587
|
-
relativeDirPath: HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_DIR_PATH
|
|
39588
|
-
}),
|
|
40453
|
+
relativeDirPath: pluginDirPath,
|
|
39589
40454
|
relativeFilePath: (0, node_path.basename)(HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_INIT_PATH),
|
|
39590
40455
|
fileContent: "",
|
|
39591
40456
|
outputRoot,
|
|
@@ -39625,14 +40490,12 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
|
|
|
39625
40490
|
* shared `~/.hermes/config.yaml` (enabling the `rulesync-subagents` plugin),
|
|
39626
40491
|
* so the write must be declared for the shared-file order derivation.
|
|
39627
40492
|
*/
|
|
39628
|
-
|
|
39629
|
-
|
|
39630
|
-
|
|
39631
|
-
|
|
39632
|
-
|
|
39633
|
-
|
|
39634
|
-
relativeFilePath: (0, node_path.basename)(HERMESAGENT_CONFIG_FILE_PATH)
|
|
39635
|
-
}] : [];
|
|
40493
|
+
/**
|
|
40494
|
+
* `config.yaml` under every spelling the global profile root can take.
|
|
40495
|
+
* @see getHermesagentSharedConfigWritePaths
|
|
40496
|
+
*/
|
|
40497
|
+
static getExtraSharedWritePaths() {
|
|
40498
|
+
return getHermesagentSharedConfigWritePaths();
|
|
39636
40499
|
}
|
|
39637
40500
|
static getSettablePathsForRulesyncSubagent(rulesyncSubagent) {
|
|
39638
40501
|
return [(0, node_path.join)(HERMESAGENT_RULESYNC_SUBAGENTS_DIR_PATH, `${subagentSlug(rulesyncSubagent.getRelativePathFromCwd())}.json`)];
|
|
@@ -39665,7 +40528,10 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
|
|
|
39665
40528
|
}
|
|
39666
40529
|
setFileContent(newFileContent) {
|
|
39667
40530
|
if (this.getRelativeFilePath() === (0, node_path.basename)(HERMESAGENT_CONFIG_FILE_PATH)) {
|
|
39668
|
-
super.setFileContent(getEnabledPluginConfigContent(
|
|
40531
|
+
super.setFileContent(getEnabledPluginConfigContent({
|
|
40532
|
+
currentContent: newFileContent,
|
|
40533
|
+
global: this.global
|
|
40534
|
+
}));
|
|
39669
40535
|
return;
|
|
39670
40536
|
}
|
|
39671
40537
|
super.setFileContent(newFileContent);
|
|
@@ -39673,7 +40539,10 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
|
|
|
39673
40539
|
getFileContent() {
|
|
39674
40540
|
if (this.getRelativeFilePath() === (0, node_path.basename)(HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_MANIFEST_PATH)) return getPluginManifestContent();
|
|
39675
40541
|
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(
|
|
40542
|
+
if (this.getRelativeFilePath() === (0, node_path.basename)(HERMESAGENT_CONFIG_FILE_PATH)) return getEnabledPluginConfigContent({
|
|
40543
|
+
currentContent: super.getFileContent(),
|
|
40544
|
+
global: this.global
|
|
40545
|
+
});
|
|
39677
40546
|
return super.getFileContent();
|
|
39678
40547
|
}
|
|
39679
40548
|
};
|
|
@@ -44752,10 +45621,13 @@ var JunieRule = class JunieRule extends ToolRule {
|
|
|
44752
45621
|
//#region src/features/rules/kilo-rule.ts
|
|
44753
45622
|
var KiloRule = class KiloRule extends ToolRule {
|
|
44754
45623
|
static getSettablePaths({ global, excludeToolDir } = {}) {
|
|
44755
|
-
if (global) return {
|
|
44756
|
-
|
|
44757
|
-
|
|
44758
|
-
|
|
45624
|
+
if (global) return {
|
|
45625
|
+
root: {
|
|
45626
|
+
relativeDirPath: buildToolPath(KILO_GLOBAL_DIR, ".", excludeToolDir),
|
|
45627
|
+
relativeFilePath: KILO_RULE_FILE_NAME
|
|
45628
|
+
},
|
|
45629
|
+
nonRoot: { relativeDirPath: buildToolPath(KILO_DIR, KILO_RULES_DIR_NAME, excludeToolDir) }
|
|
45630
|
+
};
|
|
44759
45631
|
return {
|
|
44760
45632
|
root: {
|
|
44761
45633
|
relativeDirPath: ".",
|
|
@@ -46340,6 +47212,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46340
47212
|
extension: "md",
|
|
46341
47213
|
supportsGlobal: false,
|
|
46342
47214
|
ruleDiscoveryMode: "toon",
|
|
47215
|
+
collisionPolicy: "compose",
|
|
46343
47216
|
additionalConventions: {
|
|
46344
47217
|
commands: { commandClass: AgentsmdCommand },
|
|
46345
47218
|
subagents: { subagentClass: AgentsmdSubagent },
|
|
@@ -46360,7 +47233,8 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46360
47233
|
meta: {
|
|
46361
47234
|
extension: "md",
|
|
46362
47235
|
supportsGlobal: true,
|
|
46363
|
-
ruleDiscoveryMode: "toon"
|
|
47236
|
+
ruleDiscoveryMode: "toon",
|
|
47237
|
+
collisionPolicy: "compose"
|
|
46364
47238
|
}
|
|
46365
47239
|
}],
|
|
46366
47240
|
["antigravity-cli", {
|
|
@@ -46437,7 +47311,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46437
47311
|
extension: "md",
|
|
46438
47312
|
supportsGlobal: true,
|
|
46439
47313
|
ruleDiscoveryMode: "auto",
|
|
46440
|
-
|
|
47314
|
+
collisionPolicy: "fold"
|
|
46441
47315
|
}
|
|
46442
47316
|
}],
|
|
46443
47317
|
["copilot", {
|
|
@@ -46470,7 +47344,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46470
47344
|
extension: "md",
|
|
46471
47345
|
supportsGlobal: true,
|
|
46472
47346
|
ruleDiscoveryMode: "auto",
|
|
46473
|
-
|
|
47347
|
+
collisionPolicy: "fold"
|
|
46474
47348
|
}
|
|
46475
47349
|
}],
|
|
46476
47350
|
["factorydroid", {
|
|
@@ -46478,7 +47352,8 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46478
47352
|
meta: {
|
|
46479
47353
|
extension: "md",
|
|
46480
47354
|
supportsGlobal: true,
|
|
46481
|
-
ruleDiscoveryMode: "toon"
|
|
47355
|
+
ruleDiscoveryMode: "toon",
|
|
47356
|
+
collisionPolicy: "compose"
|
|
46482
47357
|
}
|
|
46483
47358
|
}],
|
|
46484
47359
|
["goose", {
|
|
@@ -46487,7 +47362,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46487
47362
|
extension: "md",
|
|
46488
47363
|
supportsGlobal: true,
|
|
46489
47364
|
ruleDiscoveryMode: "auto",
|
|
46490
|
-
|
|
47365
|
+
collisionPolicy: "fold"
|
|
46491
47366
|
}
|
|
46492
47367
|
}],
|
|
46493
47368
|
["hermesagent", {
|
|
@@ -46496,7 +47371,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46496
47371
|
extension: "md",
|
|
46497
47372
|
supportsGlobal: false,
|
|
46498
47373
|
ruleDiscoveryMode: "auto",
|
|
46499
|
-
|
|
47374
|
+
collisionPolicy: "fold"
|
|
46500
47375
|
}
|
|
46501
47376
|
}],
|
|
46502
47377
|
["grokcli", {
|
|
@@ -46513,7 +47388,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46513
47388
|
extension: "md",
|
|
46514
47389
|
supportsGlobal: true,
|
|
46515
47390
|
ruleDiscoveryMode: "auto",
|
|
46516
|
-
|
|
47391
|
+
collisionPolicy: "fold"
|
|
46517
47392
|
}
|
|
46518
47393
|
}],
|
|
46519
47394
|
["kilo", {
|
|
@@ -46522,7 +47397,8 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46522
47397
|
extension: "md",
|
|
46523
47398
|
supportsGlobal: true,
|
|
46524
47399
|
ruleDiscoveryMode: "auto",
|
|
46525
|
-
mcpInstructionsRegistrar: KiloMcp
|
|
47400
|
+
mcpInstructionsRegistrar: KiloMcp,
|
|
47401
|
+
collisionPolicy: "compose"
|
|
46526
47402
|
}
|
|
46527
47403
|
}],
|
|
46528
47404
|
["kimi-code", {
|
|
@@ -46531,7 +47407,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46531
47407
|
extension: "md",
|
|
46532
47408
|
supportsGlobal: true,
|
|
46533
47409
|
ruleDiscoveryMode: "auto",
|
|
46534
|
-
|
|
47410
|
+
collisionPolicy: "fold"
|
|
46535
47411
|
}
|
|
46536
47412
|
}],
|
|
46537
47413
|
["kiro", {
|
|
@@ -46564,7 +47440,8 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46564
47440
|
extension: "md",
|
|
46565
47441
|
supportsGlobal: true,
|
|
46566
47442
|
ruleDiscoveryMode: "toon",
|
|
46567
|
-
mcpInstructionsRegistrar: OpencodeMcp
|
|
47443
|
+
mcpInstructionsRegistrar: OpencodeMcp,
|
|
47444
|
+
collisionPolicy: "compose"
|
|
46568
47445
|
}
|
|
46569
47446
|
}],
|
|
46570
47447
|
["pi", {
|
|
@@ -46573,7 +47450,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46573
47450
|
extension: "md",
|
|
46574
47451
|
supportsGlobal: true,
|
|
46575
47452
|
ruleDiscoveryMode: "auto",
|
|
46576
|
-
|
|
47453
|
+
collisionPolicy: "fold"
|
|
46577
47454
|
}
|
|
46578
47455
|
}],
|
|
46579
47456
|
["qwencode", {
|
|
@@ -46591,7 +47468,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46591
47468
|
extension: "md",
|
|
46592
47469
|
supportsGlobal: true,
|
|
46593
47470
|
ruleDiscoveryMode: "auto",
|
|
46594
|
-
|
|
47471
|
+
collisionPolicy: "fold"
|
|
46595
47472
|
}
|
|
46596
47473
|
}],
|
|
46597
47474
|
["replit", {
|
|
@@ -46648,7 +47525,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46648
47525
|
extension: "md",
|
|
46649
47526
|
supportsGlobal: false,
|
|
46650
47527
|
ruleDiscoveryMode: "toon",
|
|
46651
|
-
|
|
47528
|
+
collisionPolicy: "fold"
|
|
46652
47529
|
}
|
|
46653
47530
|
}],
|
|
46654
47531
|
["devin", {
|
|
@@ -46717,16 +47594,23 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
46717
47594
|
const nonLocalRootRules = rulesyncRules.filter((rule) => !rule.getFrontmatter().localRoot);
|
|
46718
47595
|
const factory = this.getFactory(this.toolTarget);
|
|
46719
47596
|
const { meta } = factory;
|
|
46720
|
-
const
|
|
47597
|
+
const convertedRules = nonLocalRootRules.map((rulesyncRule) => {
|
|
46721
47598
|
if (!factory.class.isTargetedByRulesyncRule(rulesyncRule)) return null;
|
|
46722
|
-
return
|
|
46723
|
-
|
|
46724
|
-
|
|
46725
|
-
|
|
46726
|
-
|
|
46727
|
-
|
|
47599
|
+
return {
|
|
47600
|
+
toolRule: factory.class.fromRulesyncRule({
|
|
47601
|
+
outputRoot: this.outputRoot,
|
|
47602
|
+
rulesyncRule,
|
|
47603
|
+
validate: true,
|
|
47604
|
+
global: this.global
|
|
47605
|
+
}),
|
|
47606
|
+
rulesyncRule
|
|
47607
|
+
};
|
|
46728
47608
|
}).filter((rule) => rule !== null);
|
|
46729
|
-
|
|
47609
|
+
this.mergeRulesByOutputPath({
|
|
47610
|
+
convertedRules,
|
|
47611
|
+
collisionPolicy: meta.collisionPolicy ?? "preserve"
|
|
47612
|
+
});
|
|
47613
|
+
const toolRules = convertedRules.map(({ toolRule }) => toolRule);
|
|
46730
47614
|
this.applyLocalRootRules({
|
|
46731
47615
|
toolRules,
|
|
46732
47616
|
localRootRules,
|
|
@@ -46744,7 +47628,12 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
46744
47628
|
toolRules,
|
|
46745
47629
|
factory
|
|
46746
47630
|
});
|
|
46747
|
-
|
|
47631
|
+
const outputFiles = [...toolRules, ...extraFiles];
|
|
47632
|
+
this.warnForOutputPathCollisions({
|
|
47633
|
+
outputFiles,
|
|
47634
|
+
convertedRules
|
|
47635
|
+
});
|
|
47636
|
+
return outputFiles;
|
|
46748
47637
|
}
|
|
46749
47638
|
/**
|
|
46750
47639
|
* Handle localRoot rules (only in non-global mode and when enabled). Mutates
|
|
@@ -46831,39 +47720,80 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
46831
47720
|
});
|
|
46832
47721
|
}
|
|
46833
47722
|
/**
|
|
46834
|
-
*
|
|
47723
|
+
* Reconcile rules that resolve to the same output path.
|
|
46835
47724
|
*
|
|
46836
|
-
*
|
|
46837
|
-
*
|
|
46838
|
-
*
|
|
46839
|
-
* `.
|
|
46840
|
-
*
|
|
46841
|
-
*
|
|
47725
|
+
* Multiple root fragments are composed for tools that emit a fixed root file.
|
|
47726
|
+
* The `fold` policy is for tools whose rules engine reads only one root file and
|
|
47727
|
+
* neither scans a modular rules directory nor follows references. For example,
|
|
47728
|
+
* dcode reads `.deepagents/AGENTS.md`, while Warp reads root or subdirectory
|
|
47729
|
+
* `AGENTS.md` files but never `.warp/memories/`. Those adapters must fold every
|
|
47730
|
+
* body into one instance because last-writer-wins would silently drop content.
|
|
47731
|
+
* Plain-Markdown adapters can opt into `compose` for colliding modular outputs.
|
|
46842
47732
|
*
|
|
46843
|
-
*
|
|
46844
|
-
*
|
|
46845
|
-
*
|
|
47733
|
+
* A generated root rule becomes the merge target when present. A `fold` group
|
|
47734
|
+
* without one uses its first rule. A group only composes when every rendered
|
|
47735
|
+
* fragment is plain Markdown — a fragment carrying its own frontmatter block
|
|
47736
|
+
* (e.g. Amp's `globs:` gate) would end up mid-body where the tool ignores it.
|
|
47737
|
+
* Root-involved collisions that cannot be composed safely fail; other
|
|
47738
|
+
* collisions remain separate and are reported by the final output-path check.
|
|
47739
|
+
* Mutates `convertedRules` in place.
|
|
46846
47740
|
*/
|
|
46847
|
-
|
|
46848
|
-
if (
|
|
47741
|
+
mergeRulesByOutputPath({ convertedRules, collisionPolicy }) {
|
|
47742
|
+
if (convertedRules.length <= 1) return;
|
|
46849
47743
|
const groups = /* @__PURE__ */ new Map();
|
|
46850
|
-
for (const
|
|
46851
|
-
const path = (0, node_path.join)(
|
|
47744
|
+
for (const conversion of convertedRules) {
|
|
47745
|
+
const path = (0, node_path.join)(conversion.toolRule.getRelativeDirPath(), conversion.toolRule.getRelativeFilePath());
|
|
46852
47746
|
const group = groups.get(path);
|
|
46853
|
-
if (group) group.push(
|
|
46854
|
-
else groups.set(path, [
|
|
47747
|
+
if (group) group.push(conversion);
|
|
47748
|
+
else groups.set(path, [conversion]);
|
|
46855
47749
|
}
|
|
46856
47750
|
const survivors = /* @__PURE__ */ new Set();
|
|
46857
|
-
for (const group of groups
|
|
46858
|
-
|
|
47751
|
+
for (const [path, group] of groups) {
|
|
47752
|
+
if (group.length === 1) {
|
|
47753
|
+
const conversion = group[0];
|
|
47754
|
+
if (conversion) {
|
|
47755
|
+
if (collisionPolicy === "fold") conversion.toolRule.setFileContent(conversion.toolRule.getFileContent().trim());
|
|
47756
|
+
survivors.add(conversion);
|
|
47757
|
+
}
|
|
47758
|
+
continue;
|
|
47759
|
+
}
|
|
47760
|
+
const rootConversion = group.find(({ toolRule }) => toolRule.isRoot());
|
|
47761
|
+
const allGeneratedRulesAreRoots = group.every(({ toolRule }) => toolRule.isRoot());
|
|
47762
|
+
const hasSourceRoot = group.some(({ rulesyncRule }) => rulesyncRule.getFrontmatter().root === true);
|
|
47763
|
+
const allFragmentsArePlain = group.every(({ toolRule }) => !/^---\r?\n/.test(toolRule.getFileContent()));
|
|
47764
|
+
const shouldCompose = (collisionPolicy === "fold" || collisionPolicy === "compose" || allGeneratedRulesAreRoots) && allFragmentsArePlain;
|
|
47765
|
+
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))}`);
|
|
47766
|
+
if (!shouldCompose) {
|
|
47767
|
+
for (const conversion of group) survivors.add(conversion);
|
|
47768
|
+
continue;
|
|
47769
|
+
}
|
|
47770
|
+
const target = rootConversion ?? group[0];
|
|
46859
47771
|
if (!target) continue;
|
|
46860
|
-
const mergedContent = [target, ...group.filter((rule) => rule !== target)].map((
|
|
46861
|
-
target.setFileContent(mergedContent);
|
|
47772
|
+
const mergedContent = [target, ...group.filter((rule) => rule !== target)].map(({ toolRule }) => toolRule.getFileContent().trim()).filter((content) => content.length > 0).join("\n\n");
|
|
47773
|
+
target.toolRule.setFileContent(mergedContent);
|
|
46862
47774
|
survivors.add(target);
|
|
46863
47775
|
}
|
|
46864
|
-
for (let i =
|
|
46865
|
-
const
|
|
46866
|
-
if (
|
|
47776
|
+
for (let i = convertedRules.length - 1; i >= 0; i--) {
|
|
47777
|
+
const conversion = convertedRules[i];
|
|
47778
|
+
if (conversion && !survivors.has(conversion)) convertedRules.splice(i, 1);
|
|
47779
|
+
}
|
|
47780
|
+
}
|
|
47781
|
+
warnForOutputPathCollisions({ outputFiles, convertedRules }) {
|
|
47782
|
+
const seen = /* @__PURE__ */ new Map();
|
|
47783
|
+
const describeSource = (file) => {
|
|
47784
|
+
const source = convertedRules.find(({ toolRule }) => toolRule === file)?.rulesyncRule;
|
|
47785
|
+
return source ? formatRulePaths([source]) : (0, node_path.join)(file.getRelativeDirPath(), file.getRelativeFilePath());
|
|
47786
|
+
};
|
|
47787
|
+
for (const file of outputFiles) {
|
|
47788
|
+
const path = (0, node_path.join)(file.getRelativeDirPath(), file.getRelativeFilePath());
|
|
47789
|
+
const key = path.toLowerCase();
|
|
47790
|
+
const previous = seen.get(key);
|
|
47791
|
+
if (previous) {
|
|
47792
|
+
const previousPath = (0, node_path.join)(previous.getRelativeDirPath(), previous.getRelativeFilePath());
|
|
47793
|
+
const pathDescription = previousPath === path ? `'${path}'` : `'${previousPath}' and '${path}' (compared case-insensitively, as on macOS and Windows)`;
|
|
47794
|
+
this.logger.warn(`Both ${describeSource(previous)} and ${describeSource(file)} generate to ${pathDescription}; the last one wins wherever they collide.`);
|
|
47795
|
+
}
|
|
47796
|
+
seen.set(key, file);
|
|
46867
47797
|
}
|
|
46868
47798
|
}
|
|
46869
47799
|
/**
|
|
@@ -47023,14 +47953,13 @@ As this project's AI coding tool, you must follow the additional conventions bel
|
|
|
47023
47953
|
}));
|
|
47024
47954
|
const factory = this.getFactory(this.toolTarget);
|
|
47025
47955
|
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
47956
|
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
47957
|
const targetedLocalRootRules = rulesyncRules.filter((rule) => rule.getFrontmatter().localRoot).filter((rule) => factory.class.isTargetedByRulesyncRule(rule));
|
|
47029
47958
|
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
47959
|
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
47960
|
if (this.global) {
|
|
47032
47961
|
const globalPaths = factory.class.getSettablePaths({ global: true });
|
|
47033
|
-
const supportsGlobalNonRoot = "nonRoot" in globalPaths && globalPaths.nonRoot !== null || factory.meta.supportsGlobal && factory.meta.
|
|
47962
|
+
const supportsGlobalNonRoot = "nonRoot" in globalPaths && globalPaths.nonRoot !== null || factory.meta.supportsGlobal && factory.meta.collisionPolicy === "fold";
|
|
47034
47963
|
const nonRootRules = rulesyncRules.filter((rule) => !rule.getFrontmatter().root && !rule.getFrontmatter().localRoot && factory.class.isTargetedByRulesyncRule(rule));
|
|
47035
47964
|
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
47965
|
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 +48202,36 @@ async function assertPluginRootSafe(params) {
|
|
|
47273
48202
|
}
|
|
47274
48203
|
//#endregion
|
|
47275
48204
|
//#region src/utils/tool-output-root.ts
|
|
48205
|
+
/** The environment variable each tool reads for its profile root. */
|
|
48206
|
+
const TOOL_HOME_ENV_VARS = {
|
|
48207
|
+
hermesagent: "HERMES_HOME",
|
|
48208
|
+
"kimi-code": "KIMI_CODE_HOME"
|
|
48209
|
+
};
|
|
48210
|
+
/**
|
|
48211
|
+
* Substitute a tool's home override (`HERMES_HOME`, `KIMI_CODE_HOME`) for the
|
|
48212
|
+
* output root in global scope.
|
|
48213
|
+
*
|
|
48214
|
+
* The override wins over `--output-roots`: it names where the tool itself reads
|
|
48215
|
+
* its profile, so writing anywhere else would produce files the tool ignores.
|
|
48216
|
+
*
|
|
48217
|
+
* A substituted value goes through the same `validateOutputRoot` the CLI and
|
|
48218
|
+
* config paths use, so an override of `/` or an unnormalized path is rejected
|
|
48219
|
+
* instead of silently becoming the output root. The rejection is re-thrown
|
|
48220
|
+
* naming the variable, since the user never passed an `--output-roots` flag.
|
|
48221
|
+
*/
|
|
47276
48222
|
function resolveToolOutputRoot({ outputRoot, toolTarget, global }) {
|
|
47277
48223
|
if (!global) return outputRoot;
|
|
47278
|
-
|
|
48224
|
+
const resolved = toolTarget === "hermesagent" ? resolveHermesagentOutputRoot({
|
|
47279
48225
|
outputRoot,
|
|
47280
48226
|
global
|
|
47281
|
-
});
|
|
47282
|
-
if (
|
|
47283
|
-
|
|
48227
|
+
}) : toolTarget === "kimi-code" ? getKimiCodeHome() ?? outputRoot : outputRoot;
|
|
48228
|
+
if (resolved === outputRoot) return resolved;
|
|
48229
|
+
try {
|
|
48230
|
+
validateOutputRoot(resolved);
|
|
48231
|
+
} catch (error) {
|
|
48232
|
+
throw new Error(`${TOOL_HOME_ENV_VARS[toolTarget] ?? "The tool home override"} is not a usable output root: ${formatError(error)}`, { cause: error });
|
|
48233
|
+
}
|
|
48234
|
+
return resolved;
|
|
47284
48235
|
}
|
|
47285
48236
|
//#endregion
|
|
47286
48237
|
//#region src/lib/convert.ts
|
|
@@ -48681,7 +49632,11 @@ async function generateChecksCore(params) {
|
|
|
48681
49632
|
for (const toolTarget of toolTargets) for (const outputRoot of config.getOutputRoots(toolTarget)) {
|
|
48682
49633
|
if (!config.getFeatures(toolTarget).includes("checks")) continue;
|
|
48683
49634
|
const processor = new ChecksProcessor({
|
|
48684
|
-
outputRoot
|
|
49635
|
+
outputRoot: resolveToolOutputRoot({
|
|
49636
|
+
outputRoot,
|
|
49637
|
+
toolTarget,
|
|
49638
|
+
global: config.getGlobal()
|
|
49639
|
+
}),
|
|
48685
49640
|
inputRoot: config.getInputRoot(),
|
|
48686
49641
|
toolTarget,
|
|
48687
49642
|
global: config.getGlobal(),
|
|
@@ -49126,6 +50081,12 @@ Object.defineProperty(exports, "CODEXCLI_DIR", {
|
|
|
49126
50081
|
return CODEXCLI_DIR;
|
|
49127
50082
|
}
|
|
49128
50083
|
});
|
|
50084
|
+
Object.defineProperty(exports, "CONFLICTING_TARGET_PAIRS", {
|
|
50085
|
+
enumerable: true,
|
|
50086
|
+
get: function() {
|
|
50087
|
+
return CONFLICTING_TARGET_PAIRS;
|
|
50088
|
+
}
|
|
50089
|
+
});
|
|
49129
50090
|
Object.defineProperty(exports, "ChecksProcessor", {
|
|
49130
50091
|
enumerable: true,
|
|
49131
50092
|
get: function() {
|
|
@@ -49156,12 +50117,24 @@ Object.defineProperty(exports, "ConsoleLogger", {
|
|
|
49156
50117
|
return ConsoleLogger;
|
|
49157
50118
|
}
|
|
49158
50119
|
});
|
|
50120
|
+
Object.defineProperty(exports, "DEPRECATED_FEATURE_REPLACEMENTS", {
|
|
50121
|
+
enumerable: true,
|
|
50122
|
+
get: function() {
|
|
50123
|
+
return DEPRECATED_FEATURE_REPLACEMENTS;
|
|
50124
|
+
}
|
|
50125
|
+
});
|
|
49159
50126
|
Object.defineProperty(exports, "ErrorCodes", {
|
|
49160
50127
|
enumerable: true,
|
|
49161
50128
|
get: function() {
|
|
49162
50129
|
return ErrorCodes;
|
|
49163
50130
|
}
|
|
49164
50131
|
});
|
|
50132
|
+
Object.defineProperty(exports, "GITIGNORE_DESTINATION_KEY", {
|
|
50133
|
+
enumerable: true,
|
|
50134
|
+
get: function() {
|
|
50135
|
+
return GITIGNORE_DESTINATION_KEY;
|
|
50136
|
+
}
|
|
50137
|
+
});
|
|
49165
50138
|
Object.defineProperty(exports, "HooksProcessor", {
|
|
49166
50139
|
enumerable: true,
|
|
49167
50140
|
get: function() {
|