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
|
@@ -61,6 +61,13 @@ const ALL_FEATURES = [
|
|
|
61
61
|
...ACTIVE_FEATURES_AFTER_IGNORE
|
|
62
62
|
];
|
|
63
63
|
const ALL_FEATURES_WITH_WILDCARD = [...ALL_FEATURES, "*"];
|
|
64
|
+
/**
|
|
65
|
+
* Features that remain accepted for compatibility but are superseded by a
|
|
66
|
+
* newer feature. Maps each deprecated feature to its replacement; consumers
|
|
67
|
+
* (e.g. `rulesync doctor`) derive their deprecation warnings from this map so
|
|
68
|
+
* the set cannot drift from the schema above.
|
|
69
|
+
*/
|
|
70
|
+
const DEPRECATED_FEATURE_REPLACEMENTS = { ignore: "permissions" };
|
|
64
71
|
const ACTIVE_FEATURES = [...ACTIVE_FEATURES_BEFORE_IGNORE, ...ACTIVE_FEATURES_AFTER_IGNORE];
|
|
65
72
|
const DeprecatedIgnoreFeatureSchema = z.literal("ignore").check(meta({
|
|
66
73
|
deprecated: true,
|
|
@@ -192,6 +199,7 @@ const ignoreProcessorToolTargetTuple = [
|
|
|
192
199
|
"kiro-cli",
|
|
193
200
|
"kiro-ide",
|
|
194
201
|
"qwencode",
|
|
202
|
+
"reasonix",
|
|
195
203
|
"roo",
|
|
196
204
|
"devin",
|
|
197
205
|
"vibe",
|
|
@@ -249,6 +257,7 @@ const commandsProcessorToolTargetTuple = [
|
|
|
249
257
|
"cursor",
|
|
250
258
|
"factorydroid",
|
|
251
259
|
"goose",
|
|
260
|
+
"grokcli",
|
|
252
261
|
"hermesagent",
|
|
253
262
|
"junie",
|
|
254
263
|
"kilo",
|
|
@@ -404,6 +413,7 @@ const checksProcessorToolTargetTuple = [
|
|
|
404
413
|
"amp",
|
|
405
414
|
"cursor",
|
|
406
415
|
"hermesagent",
|
|
416
|
+
"rovodev",
|
|
407
417
|
"takt"
|
|
408
418
|
];
|
|
409
419
|
//#endregion
|
|
@@ -807,6 +817,7 @@ const ErrorCodes = {
|
|
|
807
817
|
GITIGNORE_FAILED: "GITIGNORE_FAILED",
|
|
808
818
|
INIT_FAILED: "INIT_FAILED",
|
|
809
819
|
MCP_FAILED: "MCP_FAILED",
|
|
820
|
+
DOCTOR_FAILED: "DOCTOR_FAILED",
|
|
810
821
|
UNKNOWN_ERROR: "UNKNOWN_ERROR"
|
|
811
822
|
};
|
|
812
823
|
/**
|
|
@@ -815,10 +826,12 @@ const ErrorCodes = {
|
|
|
815
826
|
var CLIError = class extends Error {
|
|
816
827
|
code;
|
|
817
828
|
exitCode;
|
|
818
|
-
|
|
829
|
+
details;
|
|
830
|
+
constructor(message, code = ErrorCodes.UNKNOWN_ERROR, exitCode = 1, details) {
|
|
819
831
|
super(message);
|
|
820
832
|
this.code = code;
|
|
821
833
|
this.exitCode = exitCode;
|
|
834
|
+
this.details = details;
|
|
822
835
|
this.name = "CLIError";
|
|
823
836
|
}
|
|
824
837
|
};
|
|
@@ -942,6 +955,7 @@ var JsonLogger = class extends BaseLogger {
|
|
|
942
955
|
message: errorMessage
|
|
943
956
|
};
|
|
944
957
|
if (this._verbose && message instanceof Error && message.stack) errorInfo.stack = message.stack;
|
|
958
|
+
if (message instanceof CLIError && message.details !== void 0) errorInfo.details = message.details;
|
|
945
959
|
this.outputJson(false, errorInfo);
|
|
946
960
|
}
|
|
947
961
|
debug(_message, ..._args) {}
|
|
@@ -1002,6 +1016,10 @@ function hasControlCharacters(value) {
|
|
|
1002
1016
|
}
|
|
1003
1017
|
//#endregion
|
|
1004
1018
|
//#region src/config/config.ts
|
|
1019
|
+
/**
|
|
1020
|
+
* Key accepted alongside feature names in the per-feature object form of
|
|
1021
|
+
* `targets`. Exported so `rulesync doctor` treats the same key as valid.
|
|
1022
|
+
*/
|
|
1005
1023
|
const GITIGNORE_DESTINATION_KEY = "gitignoreDestination";
|
|
1006
1024
|
/**
|
|
1007
1025
|
* Schema for a single source entry in the sources array.
|
|
@@ -1057,7 +1075,20 @@ const ConfigFileSchema = z.object({
|
|
|
1057
1075
|
});
|
|
1058
1076
|
z.required(ConfigParamsSchema);
|
|
1059
1077
|
/**
|
|
1060
|
-
*
|
|
1078
|
+
* Normalizes the configuration file location to an absolute path.
|
|
1079
|
+
*
|
|
1080
|
+
* `ConfigResolver` always supplies the path it actually loaded; the fallback
|
|
1081
|
+
* only covers direct programmatic construction, where the conventional
|
|
1082
|
+
* location next to the input root is the best guess.
|
|
1083
|
+
*/
|
|
1084
|
+
function normalizeConfigFilePath({ configFilePath, inputRoot }) {
|
|
1085
|
+
if (configFilePath === void 0) return join(inputRoot, RULESYNC_CONFIG_RELATIVE_FILE_PATH);
|
|
1086
|
+
return isAbsolute(configFilePath) ? configFilePath : resolve(configFilePath);
|
|
1087
|
+
}
|
|
1088
|
+
/**
|
|
1089
|
+
* Conflicting target pairs that cannot be used together.
|
|
1090
|
+
* Exported so `rulesync doctor` can report the same conflicts as diagnostics
|
|
1091
|
+
* without duplicating the list.
|
|
1061
1092
|
*/
|
|
1062
1093
|
const CONFLICTING_TARGET_PAIRS = [["augmentcode", "augmentcode-legacy"], ["claudecode", "claudecode-legacy"]];
|
|
1063
1094
|
/**
|
|
@@ -1127,8 +1158,9 @@ var Config = class Config {
|
|
|
1127
1158
|
dryRun;
|
|
1128
1159
|
check;
|
|
1129
1160
|
inputRoot;
|
|
1161
|
+
configFilePath;
|
|
1130
1162
|
sources;
|
|
1131
|
-
constructor({ outputRoots, targets, features, verbose, delete: isDelete, global, silent, simulateCommands, simulateSubagents, simulateSkills, flattenedCommandNaming, gitignoreTargetsOnly, gitignoreDestination, dryRun, check, inputRoot, sources, configFileTargets }) {
|
|
1163
|
+
constructor({ outputRoots, targets, features, verbose, delete: isDelete, global, silent, simulateCommands, simulateSubagents, simulateSkills, flattenedCommandNaming, gitignoreTargetsOnly, gitignoreDestination, dryRun, check, inputRoot, configFilePath, sources, configFileTargets }) {
|
|
1132
1164
|
assertTargetsFeaturesExclusive({
|
|
1133
1165
|
targets,
|
|
1134
1166
|
features
|
|
@@ -1161,6 +1193,10 @@ var Config = class Config {
|
|
|
1161
1193
|
this.dryRun = dryRun ?? false;
|
|
1162
1194
|
this.check = check ?? false;
|
|
1163
1195
|
this.inputRoot = inputRoot === void 0 ? process.cwd() : isAbsolute(inputRoot) ? inputRoot : resolve(inputRoot);
|
|
1196
|
+
this.configFilePath = normalizeConfigFilePath({
|
|
1197
|
+
configFilePath,
|
|
1198
|
+
inputRoot: this.inputRoot
|
|
1199
|
+
});
|
|
1164
1200
|
this.sources = sources ?? [];
|
|
1165
1201
|
}
|
|
1166
1202
|
/**
|
|
@@ -1354,6 +1390,14 @@ var Config = class Config {
|
|
|
1354
1390
|
getInputRoot() {
|
|
1355
1391
|
return this.inputRoot;
|
|
1356
1392
|
}
|
|
1393
|
+
/**
|
|
1394
|
+
* Returns the absolute path of the configuration file this config was
|
|
1395
|
+
* resolved from. The file itself may not exist — `rulesync` runs fine
|
|
1396
|
+
* without one — so callers must treat this as a location, not a guarantee.
|
|
1397
|
+
*/
|
|
1398
|
+
getConfigFilePath() {
|
|
1399
|
+
return this.configFilePath;
|
|
1400
|
+
}
|
|
1357
1401
|
getSources() {
|
|
1358
1402
|
return this.sources;
|
|
1359
1403
|
}
|
|
@@ -1576,6 +1620,7 @@ var ConfigResolver = class {
|
|
|
1576
1620
|
fallback: getDefaults().check
|
|
1577
1621
|
}),
|
|
1578
1622
|
inputRoot: resolvedInputRoot !== void 0 ? resolve(resolvedInputRoot) : cwd,
|
|
1623
|
+
configFilePath: validatedConfigPath,
|
|
1579
1624
|
sources: configByFile.sources ?? getDefaults().sources,
|
|
1580
1625
|
flattenedCommandNaming: configByFile.flattenedCommandNaming ?? getDefaults().flattenedCommandNaming,
|
|
1581
1626
|
configFileTargets: extractConfigFileTargets(configByFile.targets)
|
|
@@ -3986,10 +4031,30 @@ const ClinePermissionsOverrideSchema = z.looseObject({
|
|
|
3986
4031
|
* portable and keeps them out of other tools' configs. Mirrors the OpenCode
|
|
3987
4032
|
* override; each value may be a bare action string or a pattern map.
|
|
3988
4033
|
*
|
|
4034
|
+
* `sandbox` is the sibling top-level block that governs the sandbox Kilo runs
|
|
4035
|
+
* commands in: `enabled` (boolean), `network` (`"deny"` and friends),
|
|
4036
|
+
* `allowed_hosts` (a list of `host` / `host:port` destination exceptions) and
|
|
4037
|
+
* `writable_paths`. It has no canonical permission category, so it is authored
|
|
4038
|
+
* here and emitted only for Kilo.
|
|
4039
|
+
*
|
|
4040
|
+
* Upstream restricts what a *project* config may say: `allowed_hosts` and
|
|
4041
|
+
* `writable_paths` are honored from the global config only, and a project
|
|
4042
|
+
* config may merely tighten (`enabled: true`, `network: "deny"`) — a
|
|
4043
|
+
* project-level network denial even clears the global destination exceptions.
|
|
4044
|
+
* rulesync mirrors that: at project scope only `enabled` and `network` are
|
|
4045
|
+
* written, and the rest are dropped with a warning rather than emitted into a
|
|
4046
|
+
* file Kilo would ignore.
|
|
4047
|
+
*
|
|
3989
4048
|
* @example
|
|
3990
4049
|
* { "permission": { "external_directory": "deny", "doom_loop": "ask" } }
|
|
4050
|
+
* @example
|
|
4051
|
+
* { "sandbox": { "enabled": true, "network": "deny" } }
|
|
4052
|
+
* @see https://kilo.ai/docs/getting-started/settings/sandboxing
|
|
3991
4053
|
*/
|
|
3992
|
-
const KiloPermissionsOverrideSchema = z.looseObject({
|
|
4054
|
+
const KiloPermissionsOverrideSchema = z.looseObject({
|
|
4055
|
+
permission: z.optional(z.record(z.string(), OpencodeOverridePermissionValueSchema)),
|
|
4056
|
+
sandbox: z.optional(z.looseObject({}))
|
|
4057
|
+
});
|
|
3993
4058
|
/**
|
|
3994
4059
|
* Tool-scoped override block for Claude Code. Claude Code's `permissions` object
|
|
3995
4060
|
* (in `.claude/settings.json`) carries non-list fields that have no canonical
|
|
@@ -5298,6 +5363,26 @@ const CURSOR_IGNORE_FILE_NAME = ".cursorignore";
|
|
|
5298
5363
|
const CURSOR_PERMISSIONS_FILE_NAME = "cli.json";
|
|
5299
5364
|
const CURSOR_PERMISSIONS_GLOBAL_FILE_NAME = "cli-config.json";
|
|
5300
5365
|
//#endregion
|
|
5366
|
+
//#region src/constants/rovodev-paths.ts
|
|
5367
|
+
const ROVODEV_DIR = ".rovodev";
|
|
5368
|
+
const ROVODEV_SKILLS_DIR_PATH = join(ROVODEV_DIR, "skills");
|
|
5369
|
+
const ROVODEV_SUBAGENTS_DIR_PATH = join(ROVODEV_DIR, "subagents");
|
|
5370
|
+
const ROVODEV_MODULAR_RULES_DIR_PATH = join(ROVODEV_DIR, ".rulesync", "modular-rules");
|
|
5371
|
+
const ROVODEV_RULE_FILE_NAME = "AGENTS.md";
|
|
5372
|
+
const ROVODEV_LEGACY_RULE_FILE_NAME = "AGENTS.local.md";
|
|
5373
|
+
const ROVODEV_MCP_FILE_NAME = "mcp.json";
|
|
5374
|
+
const ROVODEV_CONFIG_FILE_NAME = "config.yml";
|
|
5375
|
+
const ROVODEV_AGENTS_SKILLS_DIR_PATH = join(".agents", "skills");
|
|
5376
|
+
const ROVODEV_PROMPTS_FILE_NAME = "prompts.yml";
|
|
5377
|
+
const ROVODEV_PROMPTS_DIR_PATH = join(ROVODEV_DIR, "prompts");
|
|
5378
|
+
/**
|
|
5379
|
+
* Custom instructions for Rovo Dev's code reviews: a plain-Markdown file (no
|
|
5380
|
+
* frontmatter) in the repository root's `.rovodev/` folder. Note the leading
|
|
5381
|
+
* dot in the file name.
|
|
5382
|
+
* @see https://support.atlassian.com/rovo/docs/set-custom-instructions-for-code-reviews/
|
|
5383
|
+
*/
|
|
5384
|
+
const ROVODEV_REVIEW_AGENT_FILE_NAME = ".review-agent.md";
|
|
5385
|
+
//#endregion
|
|
5301
5386
|
//#region src/constants/takt-paths.ts
|
|
5302
5387
|
const TAKT_DIR = ".takt";
|
|
5303
5388
|
const TAKT_FACETS_SUBDIR = "facets";
|
|
@@ -5759,12 +5844,19 @@ function slugifyCheckName(value) {
|
|
|
5759
5844
|
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48).replace(/-+$/, "");
|
|
5760
5845
|
}
|
|
5761
5846
|
//#endregion
|
|
5762
|
-
//#region src/features/checks/
|
|
5847
|
+
//#region src/features/checks/aggregated-check-file.ts
|
|
5763
5848
|
/**
|
|
5764
|
-
*
|
|
5765
|
-
*
|
|
5766
|
-
*
|
|
5849
|
+
* Shared machinery for the tools whose checks surface is **one aggregated
|
|
5850
|
+
* instruction file** rather than a file per check — Cursor Bugbot's
|
|
5851
|
+
* `.cursor/BUGBOT.md` and Rovo Dev's `.rovodev/.review-agent.md`.
|
|
5852
|
+
*
|
|
5853
|
+
* Both read the file as free prose, so the check identities have to be carried
|
|
5854
|
+
* in something invisible to the reader: an HTML-comment marker per section.
|
|
5855
|
+
* That marker convention, the escaping that keeps a check body from splitting
|
|
5856
|
+
* itself, and the import-side split are the same for both files, so they live
|
|
5857
|
+
* here once.
|
|
5767
5858
|
*/
|
|
5859
|
+
/** Marks where one check starts inside the single instruction file. */
|
|
5768
5860
|
const CHECK_MARKER_PATTERN = /^<!--\s*rulesync:check:(.+?)\s*-->[ \t]*$/gm;
|
|
5769
5861
|
/**
|
|
5770
5862
|
* A marker line a check body wrote itself — a rulesync doc fragment quoted in a
|
|
@@ -5775,17 +5867,16 @@ const CHECK_MARKER_PATTERN = /^<!--\s*rulesync:check:(.+?)\s*-->[ \t]*$/gm;
|
|
|
5775
5867
|
*/
|
|
5776
5868
|
const ESCAPABLE_MARKER_PATTERN = /^(<!--\s*rulesync:)((?:literal-)*check:.+?\s*-->[ \t]*)$/gm;
|
|
5777
5869
|
const ESCAPED_MARKER_PATTERN = /^(<!--\s*rulesync:)literal-((?:literal-)*check:.+?\s*-->[ \t]*)$/gm;
|
|
5778
|
-
|
|
5779
|
-
function renderMarker(name) {
|
|
5870
|
+
function renderCheckMarker(name) {
|
|
5780
5871
|
return `<!-- rulesync:check:${name} -->`;
|
|
5781
5872
|
}
|
|
5782
|
-
function
|
|
5873
|
+
function escapeCheckMarkers(content) {
|
|
5783
5874
|
return content.replace(ESCAPABLE_MARKER_PATTERN, "$1literal-$2");
|
|
5784
5875
|
}
|
|
5785
|
-
function
|
|
5876
|
+
function unescapeCheckMarkers(content) {
|
|
5786
5877
|
return content.replace(ESCAPED_MARKER_PATTERN, "$1$2");
|
|
5787
5878
|
}
|
|
5788
|
-
function
|
|
5879
|
+
function findCheckMarkers(fileContent) {
|
|
5789
5880
|
CHECK_MARKER_PATTERN.lastIndex = 0;
|
|
5790
5881
|
const markers = [];
|
|
5791
5882
|
let match = CHECK_MARKER_PATTERN.exec(fileContent);
|
|
@@ -5800,23 +5891,47 @@ function findMarkers(fileContent) {
|
|
|
5800
5891
|
return markers;
|
|
5801
5892
|
}
|
|
5802
5893
|
/**
|
|
5803
|
-
*
|
|
5804
|
-
*
|
|
5805
|
-
*
|
|
5894
|
+
* Whether the file holds instruction text ahead of the first marker — the
|
|
5895
|
+
* question the "generating replaces this" warning asks. A file with no marker
|
|
5896
|
+
* at all is entirely hand-written, so it qualifies; an empty one does not,
|
|
5897
|
+
* since there is nothing to replace.
|
|
5898
|
+
*/
|
|
5899
|
+
function hasHandWrittenPreamble(fileContent) {
|
|
5900
|
+
const firstMarkerStart = findCheckMarkers(fileContent)[0]?.start ?? fileContent.length;
|
|
5901
|
+
return fileContent.slice(0, firstMarkerStart).trim().length > 0;
|
|
5902
|
+
}
|
|
5903
|
+
/**
|
|
5904
|
+
* Whether the file is nothing but sections rulesync generated — the question
|
|
5905
|
+
* the deletion guard asks, and a stricter one than
|
|
5906
|
+
* {@link hasHandWrittenPreamble}. A file carrying no marker at all is not
|
|
5907
|
+
* rulesync's to remove even when it is empty: rulesync never wrote it, so an
|
|
5908
|
+
* empty one is somebody's placeholder rather than our leftover.
|
|
5909
|
+
*/
|
|
5910
|
+
function isOnlyGeneratedSections(fileContent) {
|
|
5911
|
+
const firstMarkerStart = findCheckMarkers(fileContent)[0]?.start;
|
|
5912
|
+
if (firstMarkerStart === void 0) return false;
|
|
5913
|
+
return fileContent.slice(0, firstMarkerStart).trim().length === 0;
|
|
5914
|
+
}
|
|
5915
|
+
/**
|
|
5916
|
+
* The instruction text one check contributes. Neither file has a field to put a
|
|
5917
|
+
* summary in, so `description` is used only when there is no body.
|
|
5806
5918
|
*/
|
|
5807
5919
|
function toInstruction(rulesyncCheck) {
|
|
5808
5920
|
const body = rulesyncCheck.getBody().trim();
|
|
5809
5921
|
if (body.length > 0) return body;
|
|
5810
5922
|
return rulesyncCheck.getFrontmatter().description?.trim() ?? "";
|
|
5811
5923
|
}
|
|
5812
|
-
function
|
|
5924
|
+
function renderCheckSection(rulesyncCheck) {
|
|
5813
5925
|
const name = basename(rulesyncCheck.getRelativeFilePath(), ".md");
|
|
5814
5926
|
const heading = `## ${name}`;
|
|
5815
5927
|
const instruction = toInstruction(rulesyncCheck);
|
|
5816
|
-
const lines = [
|
|
5817
|
-
if (instruction.length > 0) lines.push("",
|
|
5928
|
+
const lines = [renderCheckMarker(name), heading];
|
|
5929
|
+
if (instruction.length > 0) lines.push("", escapeCheckMarkers(instruction));
|
|
5818
5930
|
return lines.join("\n");
|
|
5819
5931
|
}
|
|
5932
|
+
function renderCheckFile(rulesyncChecks) {
|
|
5933
|
+
return `${rulesyncChecks.map(renderCheckSection).join("\n\n")}\n`;
|
|
5934
|
+
}
|
|
5820
5935
|
/** Drop the heading generate writes, so a round trip does not stack headings. */
|
|
5821
5936
|
function stripGeneratedHeading(section, name) {
|
|
5822
5937
|
const [firstLine, ...rest] = section.split("\n");
|
|
@@ -5824,6 +5939,53 @@ function stripGeneratedHeading(section, name) {
|
|
|
5824
5939
|
return section.trim();
|
|
5825
5940
|
}
|
|
5826
5941
|
/**
|
|
5942
|
+
* Split an aggregated instruction file back into one check per section.
|
|
5943
|
+
*
|
|
5944
|
+
* Content ahead of the first marker — and a hand-written file with no markers
|
|
5945
|
+
* at all — becomes a single check named `fallbackName`, so nothing in the file
|
|
5946
|
+
* is dropped.
|
|
5947
|
+
*/
|
|
5948
|
+
function splitCheckFile({ fileContent, fallbackName }) {
|
|
5949
|
+
const sections = [];
|
|
5950
|
+
const markers = findCheckMarkers(fileContent);
|
|
5951
|
+
const preambleEnd = markers[0]?.start ?? fileContent.length;
|
|
5952
|
+
const preamble = fileContent.slice(0, preambleEnd).trim();
|
|
5953
|
+
if (preamble.length > 0) sections.push({
|
|
5954
|
+
name: fallbackName,
|
|
5955
|
+
content: unescapeCheckMarkers(preamble)
|
|
5956
|
+
});
|
|
5957
|
+
for (const [index, marker] of markers.entries()) {
|
|
5958
|
+
const sectionEnd = markers[index + 1]?.start ?? fileContent.length;
|
|
5959
|
+
const markerName = marker.name.trim();
|
|
5960
|
+
const name = slugifyCheckName(markerName) || fallbackName;
|
|
5961
|
+
const content = stripGeneratedHeading(fileContent.slice(marker.end, sectionEnd).trim(), markerName);
|
|
5962
|
+
sections.push({
|
|
5963
|
+
name,
|
|
5964
|
+
content: unescapeCheckMarkers(content)
|
|
5965
|
+
});
|
|
5966
|
+
}
|
|
5967
|
+
const used = /* @__PURE__ */ new Set();
|
|
5968
|
+
return sections.map(({ name, content }) => {
|
|
5969
|
+
let uniqueName = name;
|
|
5970
|
+
let suffix = 2;
|
|
5971
|
+
while (used.has(uniqueName)) {
|
|
5972
|
+
uniqueName = `${name}-${suffix}`;
|
|
5973
|
+
suffix += 1;
|
|
5974
|
+
}
|
|
5975
|
+
used.add(uniqueName);
|
|
5976
|
+
return new RulesyncCheck({
|
|
5977
|
+
outputRoot: ".",
|
|
5978
|
+
relativeDirPath: RULESYNC_CHECKS_RELATIVE_DIR_PATH,
|
|
5979
|
+
relativeFilePath: `${uniqueName}.md`,
|
|
5980
|
+
frontmatter: { targets: ["*"] },
|
|
5981
|
+
body: content
|
|
5982
|
+
});
|
|
5983
|
+
});
|
|
5984
|
+
}
|
|
5985
|
+
//#endregion
|
|
5986
|
+
//#region src/features/checks/cursor-check.ts
|
|
5987
|
+
const FALLBACK_CHECK_NAME$1 = "bugbot";
|
|
5988
|
+
/**
|
|
5827
5989
|
* Checks adapter for Cursor Bugbot (`.cursor/BUGBOT.md`).
|
|
5828
5990
|
*
|
|
5829
5991
|
* Bugbot takes one aggregated instruction file per directory rather than a file
|
|
@@ -5880,9 +6042,7 @@ var CursorCheck = class CursorCheck extends ToolCheck {
|
|
|
5880
6042
|
const paths = CursorCheck.getSettablePaths();
|
|
5881
6043
|
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath ?? "BUGBOT.md"));
|
|
5882
6044
|
if (fileContent === null) return true;
|
|
5883
|
-
|
|
5884
|
-
if (firstMarkerStart === void 0) return false;
|
|
5885
|
-
return fileContent.slice(0, firstMarkerStart).trim().length === 0;
|
|
6045
|
+
return isOnlyGeneratedSections(fileContent);
|
|
5886
6046
|
}
|
|
5887
6047
|
static fromRulesyncCheck(_params) {
|
|
5888
6048
|
throw new Error("Cursor checks are built from all checks at once; use fromRulesyncChecks.");
|
|
@@ -5892,10 +6052,8 @@ var CursorCheck = class CursorCheck extends ToolCheck {
|
|
|
5892
6052
|
const paths = CursorCheck.getSettablePaths({ global });
|
|
5893
6053
|
const relativeFilePath = paths.relativeFilePath ?? "BUGBOT.md";
|
|
5894
6054
|
const filePath = join(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
5895
|
-
|
|
5896
|
-
const
|
|
5897
|
-
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.`);
|
|
5898
|
-
const fileContent = `${rulesyncChecks.map(renderSection).join("\n\n")}\n`;
|
|
6055
|
+
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.`);
|
|
6056
|
+
const fileContent = renderCheckFile(rulesyncChecks);
|
|
5899
6057
|
return [new CursorCheck({
|
|
5900
6058
|
outputRoot,
|
|
5901
6059
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -5938,41 +6096,9 @@ var CursorCheck = class CursorCheck extends ToolCheck {
|
|
|
5938
6096
|
return first;
|
|
5939
6097
|
}
|
|
5940
6098
|
toRulesyncChecks() {
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
const preambleEnd = markers[0]?.start ?? fileContent.length;
|
|
5945
|
-
const preamble = fileContent.slice(0, preambleEnd).trim();
|
|
5946
|
-
if (preamble.length > 0) sections.push({
|
|
5947
|
-
name: FALLBACK_CHECK_NAME,
|
|
5948
|
-
content: unescapeMarkers(preamble)
|
|
5949
|
-
});
|
|
5950
|
-
for (const [index, marker] of markers.entries()) {
|
|
5951
|
-
const sectionEnd = markers[index + 1]?.start ?? fileContent.length;
|
|
5952
|
-
const markerName = marker.name.trim();
|
|
5953
|
-
const name = slugifyCheckName(markerName) || FALLBACK_CHECK_NAME;
|
|
5954
|
-
const content = stripGeneratedHeading(fileContent.slice(marker.end, sectionEnd).trim(), markerName);
|
|
5955
|
-
sections.push({
|
|
5956
|
-
name,
|
|
5957
|
-
content: unescapeMarkers(content)
|
|
5958
|
-
});
|
|
5959
|
-
}
|
|
5960
|
-
const used = /* @__PURE__ */ new Set();
|
|
5961
|
-
return sections.map(({ name, content }) => {
|
|
5962
|
-
let uniqueName = name;
|
|
5963
|
-
let suffix = 2;
|
|
5964
|
-
while (used.has(uniqueName)) {
|
|
5965
|
-
uniqueName = `${name}-${suffix}`;
|
|
5966
|
-
suffix += 1;
|
|
5967
|
-
}
|
|
5968
|
-
used.add(uniqueName);
|
|
5969
|
-
return new RulesyncCheck({
|
|
5970
|
-
outputRoot: ".",
|
|
5971
|
-
relativeDirPath: RULESYNC_CHECKS_RELATIVE_DIR_PATH,
|
|
5972
|
-
relativeFilePath: `${uniqueName}.md`,
|
|
5973
|
-
frontmatter: { targets: ["*"] },
|
|
5974
|
-
body: content
|
|
5975
|
-
});
|
|
6099
|
+
return splitCheckFile({
|
|
6100
|
+
fileContent: this.getFileContent(),
|
|
6101
|
+
fallbackName: FALLBACK_CHECK_NAME$1
|
|
5976
6102
|
});
|
|
5977
6103
|
}
|
|
5978
6104
|
};
|
|
@@ -5994,8 +6120,22 @@ var CursorCheck = class CursorCheck extends ToolCheck {
|
|
|
5994
6120
|
*/
|
|
5995
6121
|
/** Project-root instruction file auto-injected by Hermes Agent. */
|
|
5996
6122
|
const HERMESAGENT_RULE_FILE_NAME = ".hermes.md";
|
|
5997
|
-
/**
|
|
6123
|
+
/**
|
|
6124
|
+
* Root directory for Hermes Agent global configuration (the HERMES_HOME dir).
|
|
6125
|
+
* Also the project-local plugin tree, which is `.hermes/` on every platform.
|
|
6126
|
+
*/
|
|
5998
6127
|
const HERMESAGENT_GLOBAL_DIR = ".hermes";
|
|
6128
|
+
/**
|
|
6129
|
+
* Home-relative global profile root on Windows: upstream defaults to
|
|
6130
|
+
* `%LOCALAPPDATA%\hermes` there, not `~/.hermes`.
|
|
6131
|
+
* Resolve it through `getHermesagentGlobalDir()` rather than reading it directly.
|
|
6132
|
+
*
|
|
6133
|
+
* Home-relative rather than read from `LOCALAPPDATA`, matching how every other
|
|
6134
|
+
* Windows global path in rulesync is spelled (`ZED_GLOBAL_WIN32_DIR`,
|
|
6135
|
+
* `WARP_WIN32_DIR`). A profile with `LOCALAPPDATA` redirected elsewhere is not
|
|
6136
|
+
* followed; those users should set `HERMES_HOME` explicitly.
|
|
6137
|
+
*/
|
|
6138
|
+
const HERMESAGENT_GLOBAL_WIN32_DIR = join("AppData", "Local", "hermes");
|
|
5999
6139
|
/** MCP servers and other settings live in `config.yaml` under `~/.hermes/`. */
|
|
6000
6140
|
const HERMESAGENT_CONFIG_FILE_NAME = "config.yaml";
|
|
6001
6141
|
const HERMESAGENT_CONFIG_FILE_PATH = join(HERMESAGENT_GLOBAL_DIR, HERMESAGENT_CONFIG_FILE_NAME);
|
|
@@ -6218,6 +6358,114 @@ var HermesagentCheck = class HermesagentCheck extends ToolCheck {
|
|
|
6218
6358
|
}
|
|
6219
6359
|
};
|
|
6220
6360
|
//#endregion
|
|
6361
|
+
//#region src/features/checks/rovodev-check.ts
|
|
6362
|
+
const FALLBACK_CHECK_NAME = "review-agent";
|
|
6363
|
+
/**
|
|
6364
|
+
* Checks adapter for Rovo Dev CLI's code-review custom instructions
|
|
6365
|
+
* (`.rovodev/.review-agent.md`).
|
|
6366
|
+
*
|
|
6367
|
+
* Rovo Dev takes one plain-Markdown instruction file at the repository root's
|
|
6368
|
+
* `.rovodev/` folder — no frontmatter, and note the leading dot in the file
|
|
6369
|
+
* name. Like Cursor Bugbot it is a single aggregated file rather than a file
|
|
6370
|
+
* per check, so every `.rulesync/checks/*.md` targeting Rovo Dev collapses into
|
|
6371
|
+
* it via {@link fromRulesyncChecks}, with each check written as a marked
|
|
6372
|
+
* section (see `aggregated-check-file.ts` for the marker convention the two
|
|
6373
|
+
* adapters share).
|
|
6374
|
+
*
|
|
6375
|
+
* Rovo Dev reads the file as free prose, so a check's `severity` and `tools`
|
|
6376
|
+
* have no equivalent there: they are not written and do not come back on
|
|
6377
|
+
* import. Neither does `description` whenever the check also has a body.
|
|
6378
|
+
*
|
|
6379
|
+
* Project scope only — these are per-repository review instructions, and Rovo
|
|
6380
|
+
* Dev documents no user-level equivalent. (The `permissions` adapter for the
|
|
6381
|
+
* same tool is the opposite: global only.)
|
|
6382
|
+
*
|
|
6383
|
+
* @see https://support.atlassian.com/rovo/docs/set-custom-instructions-for-code-reviews/
|
|
6384
|
+
*/
|
|
6385
|
+
var RovodevCheck = class RovodevCheck extends ToolCheck {
|
|
6386
|
+
static getSettablePaths(_options = {}) {
|
|
6387
|
+
return {
|
|
6388
|
+
relativeDirPath: ROVODEV_DIR,
|
|
6389
|
+
relativeFilePath: ROVODEV_REVIEW_AGENT_FILE_NAME
|
|
6390
|
+
};
|
|
6391
|
+
}
|
|
6392
|
+
static isTargetedByRulesyncCheck(rulesyncCheck) {
|
|
6393
|
+
return this.isTargetedByRulesyncCheckDefault({
|
|
6394
|
+
rulesyncCheck,
|
|
6395
|
+
toolTarget: "rovodev"
|
|
6396
|
+
});
|
|
6397
|
+
}
|
|
6398
|
+
/**
|
|
6399
|
+
* Ownership guard the processor consults before it deletes anything for this
|
|
6400
|
+
* tool. `.review-agent.md` is a file Rovo Dev's own documentation tells users
|
|
6401
|
+
* to hand-write, so anything in it that rulesync did not write is not
|
|
6402
|
+
* rulesync's to remove — dropping the last check targeting Rovo Dev must not
|
|
6403
|
+
* take somebody's hand-written review instructions with it.
|
|
6404
|
+
*/
|
|
6405
|
+
static async canDeleteAuxiliaryFiles({ outputRoot }) {
|
|
6406
|
+
const paths = RovodevCheck.getSettablePaths();
|
|
6407
|
+
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath ?? ".review-agent.md"));
|
|
6408
|
+
if (fileContent === null) return true;
|
|
6409
|
+
return isOnlyGeneratedSections(fileContent);
|
|
6410
|
+
}
|
|
6411
|
+
static fromRulesyncCheck(_params) {
|
|
6412
|
+
throw new Error("Rovo Dev checks are built from all checks at once; use fromRulesyncChecks.");
|
|
6413
|
+
}
|
|
6414
|
+
static async fromRulesyncChecks({ outputRoot = process.cwd(), rulesyncChecks, global = false, logger }) {
|
|
6415
|
+
if (rulesyncChecks.length === 0) return [];
|
|
6416
|
+
const paths = RovodevCheck.getSettablePaths({ global });
|
|
6417
|
+
const relativeFilePath = paths.relativeFilePath ?? ".review-agent.md";
|
|
6418
|
+
const filePath = join(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
6419
|
+
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.`);
|
|
6420
|
+
return [new RovodevCheck({
|
|
6421
|
+
outputRoot,
|
|
6422
|
+
relativeDirPath: paths.relativeDirPath,
|
|
6423
|
+
relativeFilePath,
|
|
6424
|
+
fileContent: renderCheckFile(rulesyncChecks),
|
|
6425
|
+
global
|
|
6426
|
+
})];
|
|
6427
|
+
}
|
|
6428
|
+
static async fromFile({ outputRoot = process.cwd(), global = false }) {
|
|
6429
|
+
const paths = RovodevCheck.getSettablePaths({ global });
|
|
6430
|
+
const relativeFilePath = paths.relativeFilePath ?? ".review-agent.md";
|
|
6431
|
+
const filePath = join(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
6432
|
+
return new RovodevCheck({
|
|
6433
|
+
outputRoot,
|
|
6434
|
+
relativeDirPath: paths.relativeDirPath,
|
|
6435
|
+
relativeFilePath,
|
|
6436
|
+
fileContent: await readFileContentOrNull(filePath) ?? "",
|
|
6437
|
+
global
|
|
6438
|
+
});
|
|
6439
|
+
}
|
|
6440
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
6441
|
+
return new RovodevCheck({
|
|
6442
|
+
outputRoot,
|
|
6443
|
+
relativeDirPath,
|
|
6444
|
+
relativeFilePath,
|
|
6445
|
+
fileContent: "",
|
|
6446
|
+
validate: false,
|
|
6447
|
+
global
|
|
6448
|
+
});
|
|
6449
|
+
}
|
|
6450
|
+
validate() {
|
|
6451
|
+
return {
|
|
6452
|
+
success: true,
|
|
6453
|
+
error: null
|
|
6454
|
+
};
|
|
6455
|
+
}
|
|
6456
|
+
toRulesyncCheck() {
|
|
6457
|
+
const first = this.toRulesyncChecks()[0];
|
|
6458
|
+
if (!first) throw new Error(`No check instructions found in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}.`);
|
|
6459
|
+
return first;
|
|
6460
|
+
}
|
|
6461
|
+
toRulesyncChecks() {
|
|
6462
|
+
return splitCheckFile({
|
|
6463
|
+
fileContent: this.getFileContent(),
|
|
6464
|
+
fallbackName: FALLBACK_CHECK_NAME
|
|
6465
|
+
});
|
|
6466
|
+
}
|
|
6467
|
+
};
|
|
6468
|
+
//#endregion
|
|
6221
6469
|
//#region src/constants/codexcli-paths.ts
|
|
6222
6470
|
const CODEXCLI_DIR = ".codex";
|
|
6223
6471
|
const CODEXCLI_PROMPTS_DIR_PATH = join(CODEXCLI_DIR, "prompts");
|
|
@@ -6330,11 +6578,14 @@ function mergeSharedConfigDeep({ base, patch }) {
|
|
|
6330
6578
|
}
|
|
6331
6579
|
const CLAUDE_SETTINGS_SHARED_FILE_KEY = ".claude/settings.json";
|
|
6332
6580
|
const HERMES_CONFIG_SHARED_FILE_KEY = ".hermes/config.yaml";
|
|
6581
|
+
const HERMES_WIN32_CONFIG_SHARED_FILE_KEY = "AppData/Local/hermes/config.yaml";
|
|
6582
|
+
const HERMES_HOME_CONFIG_SHARED_FILE_KEY = "config.yaml";
|
|
6333
6583
|
const TAKT_CONFIG_SHARED_FILE_KEY = ".takt/config.yaml";
|
|
6334
6584
|
const CODEXCLI_CONFIG_SHARED_FILE_KEY = ".codex/config.toml";
|
|
6335
6585
|
const GROKCLI_CONFIG_SHARED_FILE_KEY = ".grok/config.toml";
|
|
6336
6586
|
const VIBE_CONFIG_SHARED_FILE_KEY = ".vibe/config.toml";
|
|
6337
6587
|
const KIMI_CODE_CONFIG_SHARED_FILE_KEY = ".kimi-code/config.toml";
|
|
6588
|
+
const KIMI_CODE_HOME_CONFIG_SHARED_FILE_KEY = "config.toml";
|
|
6338
6589
|
const REASONIX_PROJECT_CONFIG_SHARED_FILE_KEY = "reasonix.toml";
|
|
6339
6590
|
const REASONIX_GLOBAL_CONFIG_SHARED_FILE_KEY = ".reasonix/config.toml";
|
|
6340
6591
|
/**
|
|
@@ -6358,6 +6609,65 @@ const sharedConfigFileKey = ({ relativeDirPath, relativeFilePath }) => {
|
|
|
6358
6609
|
* lock-step with the writers derived from the processor registry, so an
|
|
6359
6610
|
* undeclared writer fails CI instead of merging by accident.
|
|
6360
6611
|
*/
|
|
6612
|
+
/**
|
|
6613
|
+
* Hermes writes one `config.yaml`, but its global profile root has three
|
|
6614
|
+
* spellings (`~/.hermes`, the win32 `%LOCALAPPDATA%\hermes`, and `HERMES_HOME`
|
|
6615
|
+
* itself). They are the same file with the same owners, so the declaration is
|
|
6616
|
+
* written once and shared — a policy edit cannot land on one spelling only.
|
|
6617
|
+
*/
|
|
6618
|
+
const HERMES_CONFIG_DECLARATION = {
|
|
6619
|
+
format: "yaml",
|
|
6620
|
+
features: {
|
|
6621
|
+
commands: {
|
|
6622
|
+
kind: "replace-owned-keys",
|
|
6623
|
+
ownedKeys: ["plugins"]
|
|
6624
|
+
},
|
|
6625
|
+
subagents: {
|
|
6626
|
+
kind: "replace-owned-keys",
|
|
6627
|
+
ownedKeys: ["plugins"]
|
|
6628
|
+
},
|
|
6629
|
+
mcp: {
|
|
6630
|
+
kind: "replace-owned-keys",
|
|
6631
|
+
ownedKeys: ["mcp_servers"]
|
|
6632
|
+
},
|
|
6633
|
+
hooks: {
|
|
6634
|
+
kind: "replace-owned-keys",
|
|
6635
|
+
ownedKeys: ["hooks"]
|
|
6636
|
+
},
|
|
6637
|
+
permissions: {
|
|
6638
|
+
kind: "deep-merge",
|
|
6639
|
+
replaceKeys: ["permissions"]
|
|
6640
|
+
}
|
|
6641
|
+
}
|
|
6642
|
+
};
|
|
6643
|
+
/**
|
|
6644
|
+
* Kimi Code's user config: hooks owns the flat `hooks` array; permissions owns
|
|
6645
|
+
* the ordered rule list and optional coarse default mode. `KIMI_CODE_HOME` can
|
|
6646
|
+
* name the profile directory itself, so the file has two spellings that share
|
|
6647
|
+
* one declaration — a policy edit cannot land on only one of them.
|
|
6648
|
+
*/
|
|
6649
|
+
const KIMI_CODE_CONFIG_DECLARATION = {
|
|
6650
|
+
format: "toml",
|
|
6651
|
+
invalidRootPolicy: "error",
|
|
6652
|
+
features: {
|
|
6653
|
+
hooks: {
|
|
6654
|
+
kind: "replace-owned-keys",
|
|
6655
|
+
ownedKeys: ["hooks"]
|
|
6656
|
+
},
|
|
6657
|
+
mcp: {
|
|
6658
|
+
kind: "replace-owned-keys",
|
|
6659
|
+
ownedKeys: ["mcp"]
|
|
6660
|
+
},
|
|
6661
|
+
permissions: {
|
|
6662
|
+
kind: "replace-owned-keys",
|
|
6663
|
+
ownedKeys: [
|
|
6664
|
+
"permission",
|
|
6665
|
+
"default_permission_mode",
|
|
6666
|
+
"tools"
|
|
6667
|
+
]
|
|
6668
|
+
}
|
|
6669
|
+
}
|
|
6670
|
+
};
|
|
6361
6671
|
const SHARED_CONFIG_OWNERSHIP = {
|
|
6362
6672
|
[CLAUDE_SETTINGS_SHARED_FILE_KEY]: {
|
|
6363
6673
|
format: "json",
|
|
@@ -6376,31 +6686,9 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6376
6686
|
}
|
|
6377
6687
|
}
|
|
6378
6688
|
},
|
|
6379
|
-
[HERMES_CONFIG_SHARED_FILE_KEY]:
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
commands: {
|
|
6383
|
-
kind: "replace-owned-keys",
|
|
6384
|
-
ownedKeys: ["plugins"]
|
|
6385
|
-
},
|
|
6386
|
-
subagents: {
|
|
6387
|
-
kind: "replace-owned-keys",
|
|
6388
|
-
ownedKeys: ["plugins"]
|
|
6389
|
-
},
|
|
6390
|
-
mcp: {
|
|
6391
|
-
kind: "replace-owned-keys",
|
|
6392
|
-
ownedKeys: ["mcp_servers"]
|
|
6393
|
-
},
|
|
6394
|
-
hooks: {
|
|
6395
|
-
kind: "replace-owned-keys",
|
|
6396
|
-
ownedKeys: ["hooks"]
|
|
6397
|
-
},
|
|
6398
|
-
permissions: {
|
|
6399
|
-
kind: "deep-merge",
|
|
6400
|
-
replaceKeys: ["permissions"]
|
|
6401
|
-
}
|
|
6402
|
-
}
|
|
6403
|
-
},
|
|
6689
|
+
[HERMES_CONFIG_SHARED_FILE_KEY]: HERMES_CONFIG_DECLARATION,
|
|
6690
|
+
[HERMES_WIN32_CONFIG_SHARED_FILE_KEY]: HERMES_CONFIG_DECLARATION,
|
|
6691
|
+
[HERMES_HOME_CONFIG_SHARED_FILE_KEY]: HERMES_CONFIG_DECLARATION,
|
|
6404
6692
|
[TAKT_CONFIG_SHARED_FILE_KEY]: {
|
|
6405
6693
|
format: "yaml",
|
|
6406
6694
|
invalidRootPolicy: "error",
|
|
@@ -6446,6 +6734,10 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6446
6734
|
".config/zed/settings.json": {
|
|
6447
6735
|
format: "json",
|
|
6448
6736
|
features: {
|
|
6737
|
+
ignore: {
|
|
6738
|
+
kind: "replace-owned-keys",
|
|
6739
|
+
ownedKeys: ["private_files"]
|
|
6740
|
+
},
|
|
6449
6741
|
mcp: {
|
|
6450
6742
|
kind: "replace-owned-keys",
|
|
6451
6743
|
ownedKeys: ["context_servers"]
|
|
@@ -6459,6 +6751,10 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6459
6751
|
"AppData/Roaming/Zed/settings.json": {
|
|
6460
6752
|
format: "json",
|
|
6461
6753
|
features: {
|
|
6754
|
+
ignore: {
|
|
6755
|
+
kind: "replace-owned-keys",
|
|
6756
|
+
ownedKeys: ["private_files"]
|
|
6757
|
+
},
|
|
6462
6758
|
mcp: {
|
|
6463
6759
|
kind: "replace-owned-keys",
|
|
6464
6760
|
ownedKeys: ["context_servers"]
|
|
@@ -6715,31 +7011,15 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6715
7011
|
}
|
|
6716
7012
|
}
|
|
6717
7013
|
},
|
|
6718
|
-
[KIMI_CODE_CONFIG_SHARED_FILE_KEY]:
|
|
6719
|
-
|
|
6720
|
-
invalidRootPolicy: "error",
|
|
6721
|
-
features: {
|
|
6722
|
-
hooks: {
|
|
6723
|
-
kind: "replace-owned-keys",
|
|
6724
|
-
ownedKeys: ["hooks"]
|
|
6725
|
-
},
|
|
6726
|
-
mcp: {
|
|
6727
|
-
kind: "replace-owned-keys",
|
|
6728
|
-
ownedKeys: ["mcp"]
|
|
6729
|
-
},
|
|
6730
|
-
permissions: {
|
|
6731
|
-
kind: "replace-owned-keys",
|
|
6732
|
-
ownedKeys: [
|
|
6733
|
-
"permission",
|
|
6734
|
-
"default_permission_mode",
|
|
6735
|
-
"tools"
|
|
6736
|
-
]
|
|
6737
|
-
}
|
|
6738
|
-
}
|
|
6739
|
-
},
|
|
7014
|
+
[KIMI_CODE_CONFIG_SHARED_FILE_KEY]: KIMI_CODE_CONFIG_DECLARATION,
|
|
7015
|
+
[KIMI_CODE_HOME_CONFIG_SHARED_FILE_KEY]: KIMI_CODE_CONFIG_DECLARATION,
|
|
6740
7016
|
[REASONIX_PROJECT_CONFIG_SHARED_FILE_KEY]: {
|
|
6741
7017
|
format: "toml",
|
|
6742
7018
|
features: {
|
|
7019
|
+
ignore: {
|
|
7020
|
+
kind: "custom",
|
|
7021
|
+
policyFunction: "applyIgnoreReadDenies"
|
|
7022
|
+
},
|
|
6743
7023
|
mcp: {
|
|
6744
7024
|
kind: "replace-owned-keys",
|
|
6745
7025
|
ownedKeys: ["plugins"]
|
|
@@ -6757,6 +7037,10 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6757
7037
|
[REASONIX_GLOBAL_CONFIG_SHARED_FILE_KEY]: {
|
|
6758
7038
|
format: "toml",
|
|
6759
7039
|
features: {
|
|
7040
|
+
ignore: {
|
|
7041
|
+
kind: "custom",
|
|
7042
|
+
policyFunction: "applyIgnoreReadDenies"
|
|
7043
|
+
},
|
|
6760
7044
|
mcp: {
|
|
6761
7045
|
kind: "replace-owned-keys",
|
|
6762
7046
|
ownedKeys: ["plugins"]
|
|
@@ -7201,6 +7485,13 @@ const toolCheckFactories = /* @__PURE__ */ new Map([
|
|
|
7201
7485
|
filePattern: "*.json"
|
|
7202
7486
|
}
|
|
7203
7487
|
}],
|
|
7488
|
+
["rovodev", {
|
|
7489
|
+
class: RovodevCheck,
|
|
7490
|
+
meta: {
|
|
7491
|
+
supportsGlobal: false,
|
|
7492
|
+
filePattern: ROVODEV_REVIEW_AGENT_FILE_NAME
|
|
7493
|
+
}
|
|
7494
|
+
}],
|
|
7204
7495
|
["takt", {
|
|
7205
7496
|
class: TaktCheck,
|
|
7206
7497
|
meta: {
|
|
@@ -7356,19 +7647,65 @@ var ChecksProcessor = class extends FeatureProcessor {
|
|
|
7356
7647
|
}
|
|
7357
7648
|
};
|
|
7358
7649
|
//#endregion
|
|
7650
|
+
//#region src/utils/tool-home.ts
|
|
7651
|
+
/**
|
|
7652
|
+
* Where the rulesync-side source files of a tool with a home override belong.
|
|
7653
|
+
*
|
|
7654
|
+
* A home override redirects the tool's OWN output, but the `.rulesync/` sources
|
|
7655
|
+
* imported back out of it are not part of the tool's profile — they stay under
|
|
7656
|
+
* the rulesync home. When no override is set, the native output root already is
|
|
7657
|
+
* that place.
|
|
7658
|
+
*/
|
|
7659
|
+
function getToolRulesyncOutputRoot({ nativeOutputRoot, global, toolHome }) {
|
|
7660
|
+
return global && toolHome() ? getHomeDirectory() : nativeOutputRoot;
|
|
7661
|
+
}
|
|
7662
|
+
//#endregion
|
|
7359
7663
|
//#region src/utils/hermesagent.ts
|
|
7360
7664
|
function getHermesagentHome() {
|
|
7361
7665
|
const configuredHome = process.env.HERMES_HOME?.trim();
|
|
7362
7666
|
return configuredHome ? resolve(configuredHome) : void 0;
|
|
7363
7667
|
}
|
|
7668
|
+
/**
|
|
7669
|
+
* The home-relative Hermes profile directory used when `HERMES_HOME` is unset.
|
|
7670
|
+
*
|
|
7671
|
+
* Upstream `_get_platform_default_hermes_home()` returns `%LOCALAPPDATA%\hermes`
|
|
7672
|
+
* on win32 and `~/.hermes` everywhere else, so the global output directory is
|
|
7673
|
+
* platform-dependent — a global generate on Windows that wrote `~/.hermes`
|
|
7674
|
+
* would land where Hermes never reads.
|
|
7675
|
+
*
|
|
7676
|
+
* @see https://github.com/NousResearch/hermes-agent `hermes_constants.py`
|
|
7677
|
+
*/
|
|
7678
|
+
function getHermesagentGlobalDir() {
|
|
7679
|
+
return process.platform === "win32" ? HERMESAGENT_GLOBAL_WIN32_DIR : HERMESAGENT_GLOBAL_DIR;
|
|
7680
|
+
}
|
|
7364
7681
|
function resolveHermesagentOutputRoot({ outputRoot, global }) {
|
|
7365
7682
|
return global ? getHermesagentHome() ?? outputRoot : outputRoot;
|
|
7366
7683
|
}
|
|
7684
|
+
/**
|
|
7685
|
+
* Map a canonical `.hermes/...` path constant onto the directory rulesync
|
|
7686
|
+
* actually writes in the requested scope.
|
|
7687
|
+
*
|
|
7688
|
+
* Project scope keeps the constant as-is (the project tree is `.hermes/`
|
|
7689
|
+
* everywhere). Global scope strips the `.hermes` prefix and re-anchors it:
|
|
7690
|
+
* `HERMES_HOME` *is* the profile root, so nothing is prepended; otherwise the
|
|
7691
|
+
* platform default directory takes its place.
|
|
7692
|
+
*/
|
|
7367
7693
|
function getHermesagentRelativeDirPath({ global, relativeDirPath }) {
|
|
7368
|
-
if (!global
|
|
7694
|
+
if (!global) return relativeDirPath;
|
|
7369
7695
|
const relativePath = relative(HERMESAGENT_GLOBAL_DIR, relativeDirPath);
|
|
7370
|
-
|
|
7371
|
-
|
|
7696
|
+
try {
|
|
7697
|
+
checkPathTraversal({
|
|
7698
|
+
relativePath: relativeDirPath,
|
|
7699
|
+
intendedRootDir: "."
|
|
7700
|
+
});
|
|
7701
|
+
checkPathTraversal({
|
|
7702
|
+
relativePath,
|
|
7703
|
+
intendedRootDir: HERMESAGENT_GLOBAL_DIR
|
|
7704
|
+
});
|
|
7705
|
+
} catch {
|
|
7706
|
+
throw new Error(`Hermes Agent global path must be within ${HERMESAGENT_GLOBAL_DIR}: ${relativeDirPath}`);
|
|
7707
|
+
}
|
|
7708
|
+
return getHermesagentHome() ? relativePath || "." : join(getHermesagentGlobalDir(), relativePath);
|
|
7372
7709
|
}
|
|
7373
7710
|
function getHermesagentRelativeFilePath({ global, relativeFilePath }) {
|
|
7374
7711
|
return join(getHermesagentRelativeDirPath({
|
|
@@ -7376,8 +7713,53 @@ function getHermesagentRelativeFilePath({ global, relativeFilePath }) {
|
|
|
7376
7713
|
relativeDirPath: dirname(relativeFilePath)
|
|
7377
7714
|
}), basename(relativeFilePath));
|
|
7378
7715
|
}
|
|
7716
|
+
/**
|
|
7717
|
+
* Every spelling `config.yaml` can take in global scope, so that the
|
|
7718
|
+
* shared-write derivation and the gateway ownership table it is checked against
|
|
7719
|
+
* see the same set of keys on every platform and with or without `HERMES_HOME`.
|
|
7720
|
+
*
|
|
7721
|
+
* `getHermesagentRelativeDirPath` resolves exactly one of these per process,
|
|
7722
|
+
* which would otherwise make the derived shared-file key depend on the ambient
|
|
7723
|
+
* environment — the drift guards would then go blind in precisely the
|
|
7724
|
+
* configuration this feature exists for.
|
|
7725
|
+
*/
|
|
7726
|
+
function getHermesagentSharedConfigWritePaths() {
|
|
7727
|
+
return [
|
|
7728
|
+
{
|
|
7729
|
+
relativeDirPath: HERMESAGENT_GLOBAL_DIR,
|
|
7730
|
+
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
7731
|
+
},
|
|
7732
|
+
{
|
|
7733
|
+
relativeDirPath: HERMESAGENT_GLOBAL_WIN32_DIR,
|
|
7734
|
+
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
7735
|
+
},
|
|
7736
|
+
{
|
|
7737
|
+
relativeDirPath: ".",
|
|
7738
|
+
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
7739
|
+
}
|
|
7740
|
+
];
|
|
7741
|
+
}
|
|
7742
|
+
/**
|
|
7743
|
+
* The `SHARED_CONFIG_OWNERSHIP` key of the `config.yaml` this scope actually
|
|
7744
|
+
* writes. All three spellings carry the same declaration, but passing the key of
|
|
7745
|
+
* the file being written keeps the write path and the drift guards reading the
|
|
7746
|
+
* same entry.
|
|
7747
|
+
*/
|
|
7748
|
+
function getHermesagentConfigSharedFileKey({ global }) {
|
|
7749
|
+
return sharedConfigFileKey({
|
|
7750
|
+
relativeDirPath: getHermesagentRelativeDirPath({
|
|
7751
|
+
global,
|
|
7752
|
+
relativeDirPath: HERMESAGENT_GLOBAL_DIR
|
|
7753
|
+
}),
|
|
7754
|
+
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
7755
|
+
});
|
|
7756
|
+
}
|
|
7379
7757
|
function getHermesagentRulesyncOutputRoot({ nativeOutputRoot, global }) {
|
|
7380
|
-
return
|
|
7758
|
+
return getToolRulesyncOutputRoot({
|
|
7759
|
+
nativeOutputRoot,
|
|
7760
|
+
global,
|
|
7761
|
+
toolHome: getHermesagentHome
|
|
7762
|
+
});
|
|
7381
7763
|
}
|
|
7382
7764
|
//#endregion
|
|
7383
7765
|
//#region src/constants/agentsmd-paths.ts
|
|
@@ -9081,6 +9463,223 @@ var GooseCommand = class GooseCommand extends ToolCommand {
|
|
|
9081
9463
|
}
|
|
9082
9464
|
};
|
|
9083
9465
|
//#endregion
|
|
9466
|
+
//#region src/constants/grokcli-paths.ts
|
|
9467
|
+
/**
|
|
9468
|
+
* Grok Build CLI (xAI) configuration-layout conventions.
|
|
9469
|
+
*
|
|
9470
|
+
* Single source of truth for where Grok Build expects its files. Grok Build
|
|
9471
|
+
* stores MCP servers (and other settings) in a `config.toml` under `.grok/`,
|
|
9472
|
+
* with project/global scopes resolved by the directory the CLI runs in
|
|
9473
|
+
* (`./.grok/config.toml` vs `~/.grok/config.toml`).
|
|
9474
|
+
*
|
|
9475
|
+
* Verified against `grok` 0.2.54 (`grok mcp add --help`, `grok mcp add`):
|
|
9476
|
+
* `-s project` writes `./.grok/config.toml`, `-s user` writes
|
|
9477
|
+
* `~/.grok/config.toml`, both as a TOML `[mcp_servers.<name>]` table.
|
|
9478
|
+
* @see https://docs.x.ai/build/overview
|
|
9479
|
+
*/
|
|
9480
|
+
/** Root directory for Grok Build configuration, relative to the scope root. */
|
|
9481
|
+
const GROKCLI_DIR = ".grok";
|
|
9482
|
+
/** MCP servers and other settings live in `config.toml` under `.grok/`. */
|
|
9483
|
+
const GROKCLI_MCP_FILE_NAME = "config.toml";
|
|
9484
|
+
/**
|
|
9485
|
+
* Shared Grok CLI config file (`config.toml`). MCP servers, the `[ui]`
|
|
9486
|
+
* permission mode, and other settings all live here; permissions reuse the same
|
|
9487
|
+
* file name as MCP since Grok consolidates everything into one config.
|
|
9488
|
+
*/
|
|
9489
|
+
const GROKCLI_CONFIG_FILE_NAME = "config.toml";
|
|
9490
|
+
/** Skills directory under `.grok/` (project: `./.grok/skills`, global: `~/.grok/skills`). */
|
|
9491
|
+
const GROKCLI_SKILLS_DIR_PATH = join(GROKCLI_DIR, "skills");
|
|
9492
|
+
/**
|
|
9493
|
+
* Hooks directory under `.grok/`. Grok Build discovers hook config files from
|
|
9494
|
+
* `.grok/hooks/*.json` (project) and `~/.grok/hooks/*.json` (global), each a
|
|
9495
|
+
* standalone JSON file using the Claude-Code-compatible nested `{ hooks: { … } }`
|
|
9496
|
+
* shape. rulesync writes all its hooks into a single `rulesync.json`.
|
|
9497
|
+
* @see https://docs.x.ai/build/features/hooks
|
|
9498
|
+
*/
|
|
9499
|
+
const GROKCLI_HOOKS_DIR_PATH = join(GROKCLI_DIR, "hooks");
|
|
9500
|
+
/** rulesync-managed Grok hooks file under `.grok/hooks/`. */
|
|
9501
|
+
const GROKCLI_HOOKS_FILE_NAME = "rulesync.json";
|
|
9502
|
+
/**
|
|
9503
|
+
* Subagents (agent profiles) directory under `.grok/`. Grok Build discovers
|
|
9504
|
+
* agent definitions from `.grok/agents/*.md` (project) and `~/.grok/agents/*.md`
|
|
9505
|
+
* (global), each a Markdown file with YAML frontmatter (verified via
|
|
9506
|
+
* `grok inspect`; format matches the bundled `~/.grok/bundled/agents/*.md`).
|
|
9507
|
+
*/
|
|
9508
|
+
const GROKCLI_AGENTS_DIR_PATH = join(GROKCLI_DIR, "agents");
|
|
9509
|
+
/**
|
|
9510
|
+
* Instruction file. Grok reads the AGENTS.md instruction-file family natively,
|
|
9511
|
+
* including the user-level `~/.grok/AGENTS.md` for global rules (verified via
|
|
9512
|
+
* `grok inspect`, consistent with the `.grok/` global discovery used by the
|
|
9513
|
+
* MCP/skills/subagents adapters).
|
|
9514
|
+
*/
|
|
9515
|
+
const GROKCLI_RULE_FILE_NAME = "AGENTS.md";
|
|
9516
|
+
/**
|
|
9517
|
+
* Custom slash commands directory. Grok's `find_command_paths` scans
|
|
9518
|
+
* `commands/*.md` under every discovered config dir — `.grok/commands/`
|
|
9519
|
+
* (project, walked from cwd up to the git root) and `~/.grok/commands/`
|
|
9520
|
+
* (global). The scan is **flat and non-recursive**, so subdirectory
|
|
9521
|
+
* namespacing (`git/commit.md` → `/git:commit`) is not supported the way it is
|
|
9522
|
+
* for Claude Code.
|
|
9523
|
+
*
|
|
9524
|
+
* Skills are collected before commands and win name collisions, so a
|
|
9525
|
+
* `.grok/skills/<name>/` shadows `.grok/commands/<name>.md`.
|
|
9526
|
+
* @see https://docs.x.ai/build/features/skills-plugins-marketplaces
|
|
9527
|
+
*/
|
|
9528
|
+
const GROKCLI_COMMANDS_DIR_PATH = join(GROKCLI_DIR, "commands");
|
|
9529
|
+
/**
|
|
9530
|
+
* Non-root rules directory. Grok scans `*.md` here — flat, sorted by name —
|
|
9531
|
+
* alongside the AGENTS.md family: `.grok/rules/` in each project directory it
|
|
9532
|
+
* walks, and `~/.grok/rules/` in the home scope.
|
|
9533
|
+
* @see https://docs.x.ai/build/overview
|
|
9534
|
+
*/
|
|
9535
|
+
const GROKCLI_RULES_DIR_PATH = join(GROKCLI_DIR, "rules");
|
|
9536
|
+
//#endregion
|
|
9537
|
+
//#region src/features/commands/grokcli-command.ts
|
|
9538
|
+
/**
|
|
9539
|
+
* Grok CLI custom slash commands are Markdown files under `.grok/commands/`
|
|
9540
|
+
* (project) / `~/.grok/commands/` (global), discovered by the same
|
|
9541
|
+
* Claude-Code-compatible frontmatter parser Grok uses for skills.
|
|
9542
|
+
*
|
|
9543
|
+
* Two upstream constraints shape this adapter:
|
|
9544
|
+
*
|
|
9545
|
+
* - The scan is **flat and non-recursive**, so nested namespacing is not
|
|
9546
|
+
* modelled (`supportsSubdirectory: false` flattens nested rulesync commands
|
|
9547
|
+
* onto their basename).
|
|
9548
|
+
* - Skills are collected before commands and win name collisions, so a
|
|
9549
|
+
* `.grok/skills/<name>/` shadows `.grok/commands/<name>.md`.
|
|
9550
|
+
*
|
|
9551
|
+
* `description` and `argument-hint` describe the command, while
|
|
9552
|
+
* `user-invocable` (default true) and `disable-model-invocation` (default
|
|
9553
|
+
* false) control who may invoke it — the same pair `GrokcliSkill` emits.
|
|
9554
|
+
* @see https://docs.x.ai/build/features/skills-plugins-marketplaces
|
|
9555
|
+
*/
|
|
9556
|
+
const GrokcliCommandFrontmatterSchema = z.looseObject({
|
|
9557
|
+
description: z.optional(z.string()),
|
|
9558
|
+
"argument-hint": z.optional(z.string()),
|
|
9559
|
+
"user-invocable": z.optional(z.boolean()),
|
|
9560
|
+
"disable-model-invocation": z.optional(z.boolean())
|
|
9561
|
+
});
|
|
9562
|
+
var GrokcliCommand = class GrokcliCommand extends ToolCommand {
|
|
9563
|
+
frontmatter;
|
|
9564
|
+
body;
|
|
9565
|
+
constructor({ frontmatter, body, ...rest }) {
|
|
9566
|
+
if (rest.validate) {
|
|
9567
|
+
const result = GrokcliCommandFrontmatterSchema.safeParse(frontmatter);
|
|
9568
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${join(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
|
|
9569
|
+
}
|
|
9570
|
+
super({
|
|
9571
|
+
...rest,
|
|
9572
|
+
fileContent: stringifyFrontmatter(body, frontmatter)
|
|
9573
|
+
});
|
|
9574
|
+
this.frontmatter = frontmatter;
|
|
9575
|
+
this.body = body;
|
|
9576
|
+
}
|
|
9577
|
+
static getSettablePaths(_options = {}) {
|
|
9578
|
+
return { relativeDirPath: GROKCLI_COMMANDS_DIR_PATH };
|
|
9579
|
+
}
|
|
9580
|
+
getBody() {
|
|
9581
|
+
return this.body;
|
|
9582
|
+
}
|
|
9583
|
+
getFrontmatter() {
|
|
9584
|
+
return this.frontmatter;
|
|
9585
|
+
}
|
|
9586
|
+
toRulesyncCommand() {
|
|
9587
|
+
const { description, ...restFields } = this.frontmatter;
|
|
9588
|
+
const rulesyncFrontmatter = {
|
|
9589
|
+
targets: ["*"],
|
|
9590
|
+
description,
|
|
9591
|
+
...Object.keys(restFields).length > 0 && { grokcli: restFields }
|
|
9592
|
+
};
|
|
9593
|
+
return new RulesyncCommand({
|
|
9594
|
+
outputRoot: ".",
|
|
9595
|
+
frontmatter: rulesyncFrontmatter,
|
|
9596
|
+
body: this.body,
|
|
9597
|
+
relativeDirPath: RulesyncCommand.getSettablePaths().relativeDirPath,
|
|
9598
|
+
relativeFilePath: this.relativeFilePath,
|
|
9599
|
+
fileContent: stringifyFrontmatter(this.body, rulesyncFrontmatter),
|
|
9600
|
+
validate: true
|
|
9601
|
+
});
|
|
9602
|
+
}
|
|
9603
|
+
static fromRulesyncCommand({ outputRoot = process.cwd(), rulesyncCommand, validate = true, global = false }) {
|
|
9604
|
+
const rulesyncFrontmatter = rulesyncCommand.getFrontmatter();
|
|
9605
|
+
const grokcliFields = rulesyncFrontmatter.grokcli ?? {};
|
|
9606
|
+
const grokcliFrontmatter = {
|
|
9607
|
+
description: rulesyncFrontmatter.description,
|
|
9608
|
+
...grokcliFields
|
|
9609
|
+
};
|
|
9610
|
+
const paths = this.getSettablePaths({ global });
|
|
9611
|
+
return new GrokcliCommand({
|
|
9612
|
+
outputRoot,
|
|
9613
|
+
frontmatter: grokcliFrontmatter,
|
|
9614
|
+
body: rulesyncCommand.getBody(),
|
|
9615
|
+
relativeDirPath: paths.relativeDirPath,
|
|
9616
|
+
relativeFilePath: rulesyncCommand.getRelativeFilePath(),
|
|
9617
|
+
validate
|
|
9618
|
+
});
|
|
9619
|
+
}
|
|
9620
|
+
validate() {
|
|
9621
|
+
if (!this.frontmatter) return {
|
|
9622
|
+
success: true,
|
|
9623
|
+
error: null
|
|
9624
|
+
};
|
|
9625
|
+
const result = GrokcliCommandFrontmatterSchema.safeParse(this.frontmatter);
|
|
9626
|
+
if (result.success) return {
|
|
9627
|
+
success: true,
|
|
9628
|
+
error: null
|
|
9629
|
+
};
|
|
9630
|
+
return {
|
|
9631
|
+
success: false,
|
|
9632
|
+
error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${join(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
|
|
9633
|
+
};
|
|
9634
|
+
}
|
|
9635
|
+
static isTargetedByRulesyncCommand(rulesyncCommand) {
|
|
9636
|
+
return this.isTargetedByRulesyncCommandDefault({
|
|
9637
|
+
rulesyncCommand,
|
|
9638
|
+
toolTarget: "grokcli"
|
|
9639
|
+
});
|
|
9640
|
+
}
|
|
9641
|
+
/**
|
|
9642
|
+
* Warn when a rulesync skill would shadow a rulesync command.
|
|
9643
|
+
*
|
|
9644
|
+
* Grok collects skills before commands and lets skills win name collisions,
|
|
9645
|
+
* so `.grok/skills/<name>/` makes `.grok/commands/<name>.md` unreachable.
|
|
9646
|
+
* Both files are still written correctly — nothing is overwritten and no
|
|
9647
|
+
* output is lost — so this warns rather than failing the run the way the
|
|
9648
|
+
* Hermes check does, where the two surfaces really do write the same path.
|
|
9649
|
+
*/
|
|
9650
|
+
static async validateRulesyncCommands({ inputRoot, rulesyncCommands, logger }) {
|
|
9651
|
+
const commandNames = new Set(rulesyncCommands.filter((command) => this.isTargetedByRulesyncCommand(command)).map((command) => basename(command.getRelativeFilePath(), ".md")));
|
|
9652
|
+
if (commandNames.size === 0) return;
|
|
9653
|
+
const shadowed = (await findFilesByGlobs(join(join(inputRoot, RULESYNC_SKILLS_RELATIVE_DIR_PATH), "**", "SKILL.md"))).map((filePath) => basename(dirname(filePath))).filter((skillName) => commandNames.has(skillName));
|
|
9654
|
+
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.`);
|
|
9655
|
+
}
|
|
9656
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
9657
|
+
const paths = this.getSettablePaths({ global });
|
|
9658
|
+
const filePath = join(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
9659
|
+
const { frontmatter, body: content } = parseFrontmatter(await readFileContent(filePath), filePath);
|
|
9660
|
+
const result = GrokcliCommandFrontmatterSchema.safeParse(frontmatter);
|
|
9661
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
9662
|
+
return new GrokcliCommand({
|
|
9663
|
+
outputRoot,
|
|
9664
|
+
relativeDirPath: paths.relativeDirPath,
|
|
9665
|
+
relativeFilePath,
|
|
9666
|
+
frontmatter: result.data,
|
|
9667
|
+
body: content.trim(),
|
|
9668
|
+
validate
|
|
9669
|
+
});
|
|
9670
|
+
}
|
|
9671
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
9672
|
+
return new GrokcliCommand({
|
|
9673
|
+
outputRoot,
|
|
9674
|
+
relativeDirPath,
|
|
9675
|
+
relativeFilePath,
|
|
9676
|
+
frontmatter: { description: "" },
|
|
9677
|
+
body: "",
|
|
9678
|
+
validate: false
|
|
9679
|
+
});
|
|
9680
|
+
}
|
|
9681
|
+
};
|
|
9682
|
+
//#endregion
|
|
9084
9683
|
//#region src/features/skills/tool-skill.ts
|
|
9085
9684
|
/** Ordered skill directory roots: primary first. */
|
|
9086
9685
|
function toolSkillSearchRoots(paths) {
|
|
@@ -9715,7 +10314,7 @@ def register(ctx):
|
|
|
9715
10314
|
_register_command(ctx, command)
|
|
9716
10315
|
`;
|
|
9717
10316
|
}
|
|
9718
|
-
function getEnabledPluginConfigContent$1(currentContent) {
|
|
10317
|
+
function getEnabledPluginConfigContent$1({ currentContent, global }) {
|
|
9719
10318
|
const config = parseSharedConfig({
|
|
9720
10319
|
format: "yaml",
|
|
9721
10320
|
fileContent: currentContent
|
|
@@ -9723,7 +10322,7 @@ function getEnabledPluginConfigContent$1(currentContent) {
|
|
|
9723
10322
|
const plugins = config.plugins && typeof config.plugins === "object" ? config.plugins : {};
|
|
9724
10323
|
const enabled = Array.isArray(plugins.enabled) ? plugins.enabled : [];
|
|
9725
10324
|
return applySharedConfigPatch({
|
|
9726
|
-
fileKey:
|
|
10325
|
+
fileKey: getHermesagentConfigSharedFileKey({ global }),
|
|
9727
10326
|
feature: "commands",
|
|
9728
10327
|
existingContent: currentContent,
|
|
9729
10328
|
patch: { plugins: {
|
|
@@ -9732,7 +10331,7 @@ function getEnabledPluginConfigContent$1(currentContent) {
|
|
|
9732
10331
|
} }
|
|
9733
10332
|
});
|
|
9734
10333
|
}
|
|
9735
|
-
function getDisabledHermesCommandsPluginConfigContent(currentContent) {
|
|
10334
|
+
function getDisabledHermesCommandsPluginConfigContent({ currentContent, global }) {
|
|
9736
10335
|
const config = parseSharedConfig({
|
|
9737
10336
|
format: "yaml",
|
|
9738
10337
|
fileContent: currentContent
|
|
@@ -9740,7 +10339,7 @@ function getDisabledHermesCommandsPluginConfigContent(currentContent) {
|
|
|
9740
10339
|
const plugins = config.plugins && typeof config.plugins === "object" ? config.plugins : {};
|
|
9741
10340
|
const enabled = Array.isArray(plugins.enabled) ? plugins.enabled : [];
|
|
9742
10341
|
return applySharedConfigPatch({
|
|
9743
|
-
fileKey:
|
|
10342
|
+
fileKey: getHermesagentConfigSharedFileKey({ global }),
|
|
9744
10343
|
feature: "commands",
|
|
9745
10344
|
existingContent: currentContent,
|
|
9746
10345
|
patch: { plugins: {
|
|
@@ -9756,35 +10355,36 @@ var HermesagentCommandAuxiliaryFile = class extends ToolFile {
|
|
|
9756
10355
|
error: null
|
|
9757
10356
|
};
|
|
9758
10357
|
}
|
|
9759
|
-
|
|
10358
|
+
/**
|
|
10359
|
+
* Whether this auxiliary file is the one at `relativeFilePath`, comparing
|
|
10360
|
+
* against the scope-resolved location of that canonical `.hermes/...` path.
|
|
10361
|
+
*/
|
|
10362
|
+
matchesPath(relativeFilePath) {
|
|
9760
10363
|
return this.getRelativePathFromCwd() === toPosixPath(getHermesagentRelativeFilePath({
|
|
9761
10364
|
global: this.global,
|
|
9762
|
-
relativeFilePath
|
|
10365
|
+
relativeFilePath
|
|
9763
10366
|
}));
|
|
9764
10367
|
}
|
|
10368
|
+
shouldMergeExistingFileContent() {
|
|
10369
|
+
return this.matchesPath(HERMESAGENT_CONFIG_FILE_PATH);
|
|
10370
|
+
}
|
|
9765
10371
|
setFileContent(newFileContent) {
|
|
9766
|
-
if (this.
|
|
9767
|
-
|
|
9768
|
-
|
|
9769
|
-
|
|
9770
|
-
|
|
10372
|
+
if (this.matchesPath(HERMESAGENT_CONFIG_FILE_PATH)) {
|
|
10373
|
+
super.setFileContent(getEnabledPluginConfigContent$1({
|
|
10374
|
+
currentContent: newFileContent,
|
|
10375
|
+
global: this.global
|
|
10376
|
+
}));
|
|
9771
10377
|
return;
|
|
9772
10378
|
}
|
|
9773
10379
|
super.setFileContent(newFileContent);
|
|
9774
10380
|
}
|
|
9775
10381
|
getFileContent() {
|
|
9776
|
-
if (this.
|
|
9777
|
-
|
|
9778
|
-
|
|
9779
|
-
|
|
9780
|
-
|
|
9781
|
-
|
|
9782
|
-
relativeFilePath: HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_INIT_PATH
|
|
9783
|
-
}))) return getPluginInitContent$2();
|
|
9784
|
-
if (this.getRelativePathFromCwd() === toPosixPath(getHermesagentRelativeFilePath({
|
|
9785
|
-
global: this.global,
|
|
9786
|
-
relativeFilePath: HERMESAGENT_CONFIG_FILE_PATH
|
|
9787
|
-
}))) return getEnabledPluginConfigContent$1(super.getFileContent());
|
|
10382
|
+
if (this.matchesPath(HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_MANIFEST_PATH)) return getPluginManifestContent$2();
|
|
10383
|
+
if (this.matchesPath(HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_INIT_PATH)) return getPluginInitContent$2();
|
|
10384
|
+
if (this.matchesPath(HERMESAGENT_CONFIG_FILE_PATH)) return getEnabledPluginConfigContent$1({
|
|
10385
|
+
currentContent: super.getFileContent(),
|
|
10386
|
+
global: this.global
|
|
10387
|
+
});
|
|
9788
10388
|
return super.getFileContent();
|
|
9789
10389
|
}
|
|
9790
10390
|
};
|
|
@@ -9801,14 +10401,12 @@ var HermesagentCommand = class HermesagentCommand extends ToolCommand {
|
|
|
9801
10401
|
relativeDirPath: HERMESAGENT_RULESYNC_COMMANDS_DIR_PATH
|
|
9802
10402
|
}) };
|
|
9803
10403
|
}
|
|
9804
|
-
|
|
9805
|
-
|
|
9806
|
-
|
|
9807
|
-
|
|
9808
|
-
|
|
9809
|
-
|
|
9810
|
-
relativeFilePath: basename(HERMESAGENT_CONFIG_FILE_PATH)
|
|
9811
|
-
}];
|
|
10404
|
+
/**
|
|
10405
|
+
* `config.yaml` under every spelling the global profile root can take.
|
|
10406
|
+
* @see getHermesagentSharedConfigWritePaths
|
|
10407
|
+
*/
|
|
10408
|
+
static getExtraSharedWritePaths() {
|
|
10409
|
+
return getHermesagentSharedConfigWritePaths();
|
|
9812
10410
|
}
|
|
9813
10411
|
static async validateRulesyncCommands({ inputRoot, rulesyncCommands }) {
|
|
9814
10412
|
const commandSlugs = /* @__PURE__ */ new Set();
|
|
@@ -9833,33 +10431,28 @@ var HermesagentCommand = class HermesagentCommand extends ToolCommand {
|
|
|
9833
10431
|
}
|
|
9834
10432
|
static async getAuxiliaryFiles({ toolCommands, outputRoot, global = false, forDeletion = false }) {
|
|
9835
10433
|
if (toolCommands.length === 0 && !forDeletion) return [];
|
|
10434
|
+
const pluginDirPath = getHermesagentRelativeDirPath({
|
|
10435
|
+
global,
|
|
10436
|
+
relativeDirPath: HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_DIR_PATH
|
|
10437
|
+
});
|
|
9836
10438
|
const pluginFiles = [
|
|
9837
10439
|
new HermesagentCommandAuxiliaryFile({
|
|
9838
10440
|
outputRoot,
|
|
9839
|
-
relativeDirPath:
|
|
9840
|
-
global,
|
|
9841
|
-
relativeDirPath: HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_DIR_PATH
|
|
9842
|
-
}),
|
|
10441
|
+
relativeDirPath: pluginDirPath,
|
|
9843
10442
|
relativeFilePath: basename(HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_MANIFEST_PATH),
|
|
9844
10443
|
fileContent: "",
|
|
9845
10444
|
global
|
|
9846
10445
|
}),
|
|
9847
10446
|
new HermesagentCommandAuxiliaryFile({
|
|
9848
10447
|
outputRoot,
|
|
9849
|
-
relativeDirPath:
|
|
9850
|
-
global,
|
|
9851
|
-
relativeDirPath: HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_DIR_PATH
|
|
9852
|
-
}),
|
|
10448
|
+
relativeDirPath: pluginDirPath,
|
|
9853
10449
|
relativeFilePath: basename(HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_OWNERSHIP_PATH),
|
|
9854
10450
|
fileContent: "Generated and owned by RuleSync.\n",
|
|
9855
10451
|
global
|
|
9856
10452
|
}),
|
|
9857
10453
|
new HermesagentCommandAuxiliaryFile({
|
|
9858
10454
|
outputRoot,
|
|
9859
|
-
relativeDirPath:
|
|
9860
|
-
global,
|
|
9861
|
-
relativeDirPath: HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_DIR_PATH
|
|
9862
|
-
}),
|
|
10455
|
+
relativeDirPath: pluginDirPath,
|
|
9863
10456
|
relativeFilePath: basename(HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_INIT_PATH),
|
|
9864
10457
|
fileContent: "",
|
|
9865
10458
|
global
|
|
@@ -11094,19 +11687,6 @@ var RooCommand = class RooCommand extends ToolCommand {
|
|
|
11094
11687
|
}
|
|
11095
11688
|
};
|
|
11096
11689
|
//#endregion
|
|
11097
|
-
//#region src/constants/rovodev-paths.ts
|
|
11098
|
-
const ROVODEV_DIR = ".rovodev";
|
|
11099
|
-
const ROVODEV_SKILLS_DIR_PATH = join(ROVODEV_DIR, "skills");
|
|
11100
|
-
const ROVODEV_SUBAGENTS_DIR_PATH = join(ROVODEV_DIR, "subagents");
|
|
11101
|
-
const ROVODEV_MODULAR_RULES_DIR_PATH = join(ROVODEV_DIR, ".rulesync", "modular-rules");
|
|
11102
|
-
const ROVODEV_RULE_FILE_NAME = "AGENTS.md";
|
|
11103
|
-
const ROVODEV_LEGACY_RULE_FILE_NAME = "AGENTS.local.md";
|
|
11104
|
-
const ROVODEV_MCP_FILE_NAME = "mcp.json";
|
|
11105
|
-
const ROVODEV_CONFIG_FILE_NAME = "config.yml";
|
|
11106
|
-
const ROVODEV_AGENTS_SKILLS_DIR_PATH = join(".agents", "skills");
|
|
11107
|
-
const ROVODEV_PROMPTS_FILE_NAME = "prompts.yml";
|
|
11108
|
-
const ROVODEV_PROMPTS_DIR_PATH = join(ROVODEV_DIR, "prompts");
|
|
11109
|
-
//#endregion
|
|
11110
11690
|
//#region src/features/commands/rovodev-command.ts
|
|
11111
11691
|
/**
|
|
11112
11692
|
* Rovo Dev CLI "saved prompts": a file-based custom-command surface made of a
|
|
@@ -11690,6 +12270,16 @@ const toolCommandFactories = /* @__PURE__ */ new Map([
|
|
|
11690
12270
|
supportsSubdirectory: false
|
|
11691
12271
|
}
|
|
11692
12272
|
}],
|
|
12273
|
+
["grokcli", {
|
|
12274
|
+
class: GrokcliCommand,
|
|
12275
|
+
meta: {
|
|
12276
|
+
extension: "md",
|
|
12277
|
+
supportsProject: true,
|
|
12278
|
+
supportsGlobal: true,
|
|
12279
|
+
isSimulated: false,
|
|
12280
|
+
supportsSubdirectory: false
|
|
12281
|
+
}
|
|
12282
|
+
}],
|
|
11693
12283
|
["hermesagent", {
|
|
11694
12284
|
class: HermesagentCommand,
|
|
11695
12285
|
meta: {
|
|
@@ -11883,7 +12473,8 @@ var CommandsProcessor = class extends FeatureProcessor {
|
|
|
11883
12473
|
const factory = this.getFactory(this.toolTarget);
|
|
11884
12474
|
await factory.class.validateRulesyncCommands?.({
|
|
11885
12475
|
inputRoot: this.inputRoot,
|
|
11886
|
-
rulesyncCommands
|
|
12476
|
+
rulesyncCommands,
|
|
12477
|
+
logger: this.logger
|
|
11887
12478
|
});
|
|
11888
12479
|
const flattenedPathOrigins = /* @__PURE__ */ new Map();
|
|
11889
12480
|
const toolCommands = rulesyncCommands.map((rulesyncCommand) => {
|
|
@@ -12021,7 +12612,10 @@ var CommandsProcessor = class extends FeatureProcessor {
|
|
|
12021
12612
|
}));
|
|
12022
12613
|
const currentContent = await readFileContentOrNull(configPath);
|
|
12023
12614
|
if (currentContent === null) return changedCount;
|
|
12024
|
-
const nextContent = getDisabledHermesCommandsPluginConfigContent(
|
|
12615
|
+
const nextContent = getDisabledHermesCommandsPluginConfigContent({
|
|
12616
|
+
currentContent,
|
|
12617
|
+
global: this.global
|
|
12618
|
+
});
|
|
12025
12619
|
if (nextContent === currentContent) return changedCount;
|
|
12026
12620
|
if (this.dryRun) this.logger.info(`[DRY RUN] Would write: ${configPath}`);
|
|
12027
12621
|
else await writeFileContent(configPath, nextContent);
|
|
@@ -14483,64 +15077,6 @@ var GooseHooks = class GooseHooks extends ToolHooks {
|
|
|
14483
15077
|
}
|
|
14484
15078
|
};
|
|
14485
15079
|
//#endregion
|
|
14486
|
-
//#region src/constants/grokcli-paths.ts
|
|
14487
|
-
/**
|
|
14488
|
-
* Grok Build CLI (xAI) configuration-layout conventions.
|
|
14489
|
-
*
|
|
14490
|
-
* Single source of truth for where Grok Build expects its files. Grok Build
|
|
14491
|
-
* stores MCP servers (and other settings) in a `config.toml` under `.grok/`,
|
|
14492
|
-
* with project/global scopes resolved by the directory the CLI runs in
|
|
14493
|
-
* (`./.grok/config.toml` vs `~/.grok/config.toml`).
|
|
14494
|
-
*
|
|
14495
|
-
* Verified against `grok` 0.2.54 (`grok mcp add --help`, `grok mcp add`):
|
|
14496
|
-
* `-s project` writes `./.grok/config.toml`, `-s user` writes
|
|
14497
|
-
* `~/.grok/config.toml`, both as a TOML `[mcp_servers.<name>]` table.
|
|
14498
|
-
* @see https://docs.x.ai/build/overview
|
|
14499
|
-
*/
|
|
14500
|
-
/** Root directory for Grok Build configuration, relative to the scope root. */
|
|
14501
|
-
const GROKCLI_DIR = ".grok";
|
|
14502
|
-
/** MCP servers and other settings live in `config.toml` under `.grok/`. */
|
|
14503
|
-
const GROKCLI_MCP_FILE_NAME = "config.toml";
|
|
14504
|
-
/**
|
|
14505
|
-
* Shared Grok CLI config file (`config.toml`). MCP servers, the `[ui]`
|
|
14506
|
-
* permission mode, and other settings all live here; permissions reuse the same
|
|
14507
|
-
* file name as MCP since Grok consolidates everything into one config.
|
|
14508
|
-
*/
|
|
14509
|
-
const GROKCLI_CONFIG_FILE_NAME = "config.toml";
|
|
14510
|
-
/** Skills directory under `.grok/` (project: `./.grok/skills`, global: `~/.grok/skills`). */
|
|
14511
|
-
const GROKCLI_SKILLS_DIR_PATH = join(GROKCLI_DIR, "skills");
|
|
14512
|
-
/**
|
|
14513
|
-
* Hooks directory under `.grok/`. Grok Build discovers hook config files from
|
|
14514
|
-
* `.grok/hooks/*.json` (project) and `~/.grok/hooks/*.json` (global), each a
|
|
14515
|
-
* standalone JSON file using the Claude-Code-compatible nested `{ hooks: { … } }`
|
|
14516
|
-
* shape. rulesync writes all its hooks into a single `rulesync.json`.
|
|
14517
|
-
* @see https://docs.x.ai/build/features/hooks
|
|
14518
|
-
*/
|
|
14519
|
-
const GROKCLI_HOOKS_DIR_PATH = join(GROKCLI_DIR, "hooks");
|
|
14520
|
-
/** rulesync-managed Grok hooks file under `.grok/hooks/`. */
|
|
14521
|
-
const GROKCLI_HOOKS_FILE_NAME = "rulesync.json";
|
|
14522
|
-
/**
|
|
14523
|
-
* Subagents (agent profiles) directory under `.grok/`. Grok Build discovers
|
|
14524
|
-
* agent definitions from `.grok/agents/*.md` (project) and `~/.grok/agents/*.md`
|
|
14525
|
-
* (global), each a Markdown file with YAML frontmatter (verified via
|
|
14526
|
-
* `grok inspect`; format matches the bundled `~/.grok/bundled/agents/*.md`).
|
|
14527
|
-
*/
|
|
14528
|
-
const GROKCLI_AGENTS_DIR_PATH = join(GROKCLI_DIR, "agents");
|
|
14529
|
-
/**
|
|
14530
|
-
* Instruction file. Grok reads the AGENTS.md instruction-file family natively,
|
|
14531
|
-
* including the user-level `~/.grok/AGENTS.md` for global rules (verified via
|
|
14532
|
-
* `grok inspect`, consistent with the `.grok/` global discovery used by the
|
|
14533
|
-
* MCP/skills/subagents adapters).
|
|
14534
|
-
*/
|
|
14535
|
-
const GROKCLI_RULE_FILE_NAME = "AGENTS.md";
|
|
14536
|
-
/**
|
|
14537
|
-
* Non-root rules directory. Grok scans `*.md` here — flat, sorted by name —
|
|
14538
|
-
* alongside the AGENTS.md family: `.grok/rules/` in each project directory it
|
|
14539
|
-
* walks, and `~/.grok/rules/` in the home scope.
|
|
14540
|
-
* @see https://docs.x.ai/build/overview
|
|
14541
|
-
*/
|
|
14542
|
-
const GROKCLI_RULES_DIR_PATH = join(GROKCLI_DIR, "rules");
|
|
14543
|
-
//#endregion
|
|
14544
15080
|
//#region src/features/hooks/grokcli-hooks.ts
|
|
14545
15081
|
const GROKCLI_CONVERTER_CONFIG = {
|
|
14546
15082
|
supportedEvents: GROKCLI_HOOK_EVENTS,
|
|
@@ -14786,6 +15322,13 @@ var HermesagentHooks = class HermesagentHooks extends ToolHooks {
|
|
|
14786
15322
|
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
14787
15323
|
};
|
|
14788
15324
|
}
|
|
15325
|
+
/**
|
|
15326
|
+
* `config.yaml` under every spelling the global profile root can take.
|
|
15327
|
+
* @see getHermesagentSharedConfigWritePaths
|
|
15328
|
+
*/
|
|
15329
|
+
static getExtraSharedWritePaths() {
|
|
15330
|
+
return getHermesagentSharedConfigWritePaths();
|
|
15331
|
+
}
|
|
14789
15332
|
constructor(params) {
|
|
14790
15333
|
super({
|
|
14791
15334
|
...params,
|
|
@@ -14823,7 +15366,7 @@ var HermesagentHooks = class HermesagentHooks extends ToolHooks {
|
|
|
14823
15366
|
}
|
|
14824
15367
|
setFileContent(fileContent) {
|
|
14825
15368
|
this.fileContent = applySharedConfigPatch({
|
|
14826
|
-
fileKey:
|
|
15369
|
+
fileKey: getHermesagentConfigSharedFileKey({ global: this.global }),
|
|
14827
15370
|
feature: "hooks",
|
|
14828
15371
|
existingContent: fileContent,
|
|
14829
15372
|
patch: parseSharedConfig({
|
|
@@ -15199,8 +15742,38 @@ function getKimiCodeHome() {
|
|
|
15199
15742
|
function getKimiCodeRelativeDirPath({ global, relativeDirPath = "." }) {
|
|
15200
15743
|
return global && getKimiCodeHome() ? relativeDirPath : join(KIMI_CODE_DIR, relativeDirPath);
|
|
15201
15744
|
}
|
|
15745
|
+
/**
|
|
15746
|
+
* Both spellings the shared user `config.toml` can take: under `.kimi-code/`,
|
|
15747
|
+
* or at the root of `KIMI_CODE_HOME` when that override names the profile dir.
|
|
15748
|
+
* Declared unconditionally so the derived shared-file keys — and the drift
|
|
15749
|
+
* guards checked against them — do not depend on the ambient environment.
|
|
15750
|
+
*/
|
|
15751
|
+
function getKimiCodeSharedConfigWritePaths() {
|
|
15752
|
+
return [{
|
|
15753
|
+
relativeDirPath: KIMI_CODE_DIR,
|
|
15754
|
+
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
15755
|
+
}, {
|
|
15756
|
+
relativeDirPath: ".",
|
|
15757
|
+
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
15758
|
+
}];
|
|
15759
|
+
}
|
|
15760
|
+
/**
|
|
15761
|
+
* The `SHARED_CONFIG_OWNERSHIP` key of the `config.toml` actually being written.
|
|
15762
|
+
* Both spellings carry the same declaration, but passing the key of the file
|
|
15763
|
+
* being written keeps the write path and the drift guards on the same entry.
|
|
15764
|
+
*/
|
|
15765
|
+
function getKimiCodeConfigSharedFileKey({ global }) {
|
|
15766
|
+
return sharedConfigFileKey({
|
|
15767
|
+
relativeDirPath: getKimiCodeRelativeDirPath({ global }),
|
|
15768
|
+
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
15769
|
+
});
|
|
15770
|
+
}
|
|
15202
15771
|
function getKimiCodeRulesyncOutputRoot({ nativeOutputRoot, global }) {
|
|
15203
|
-
return
|
|
15772
|
+
return getToolRulesyncOutputRoot({
|
|
15773
|
+
nativeOutputRoot,
|
|
15774
|
+
global,
|
|
15775
|
+
toolHome: getKimiCodeHome
|
|
15776
|
+
});
|
|
15204
15777
|
}
|
|
15205
15778
|
//#endregion
|
|
15206
15779
|
//#region src/features/hooks/kimi-code-hooks.ts
|
|
@@ -15298,13 +15871,20 @@ var KimiCodeHooks = class KimiCodeHooks extends ToolHooks {
|
|
|
15298
15871
|
isDeletable() {
|
|
15299
15872
|
return false;
|
|
15300
15873
|
}
|
|
15874
|
+
/**
|
|
15875
|
+
* `config.toml` under both spellings its directory can take.
|
|
15876
|
+
* @see getKimiCodeSharedConfigWritePaths
|
|
15877
|
+
*/
|
|
15878
|
+
static getExtraSharedWritePaths() {
|
|
15879
|
+
return getKimiCodeSharedConfigWritePaths();
|
|
15880
|
+
}
|
|
15301
15881
|
shouldMergeExistingFileContent() {
|
|
15302
15882
|
return true;
|
|
15303
15883
|
}
|
|
15304
15884
|
setFileContent(fileContent) {
|
|
15305
15885
|
const paths = KimiCodeHooks.getSettablePaths({ global: this.global });
|
|
15306
15886
|
this.fileContent = applySharedConfigPatch({
|
|
15307
|
-
fileKey:
|
|
15887
|
+
fileKey: getKimiCodeConfigSharedFileKey({ global: this.global }),
|
|
15308
15888
|
feature: "hooks",
|
|
15309
15889
|
existingContent: fileContent,
|
|
15310
15890
|
patch: parseSharedConfig({
|
|
@@ -17995,6 +18575,151 @@ var QwencodeIgnore = class QwencodeIgnore extends ToolIgnore {
|
|
|
17995
18575
|
}
|
|
17996
18576
|
};
|
|
17997
18577
|
//#endregion
|
|
18578
|
+
//#region src/features/shared/reasonix-config-table.ts
|
|
18579
|
+
/**
|
|
18580
|
+
* Shape-narrowing helpers for the Reasonix TOML config (`reasonix.toml` /
|
|
18581
|
+
* `~/.reasonix/config.toml`), shared by the features that read-modify-write it.
|
|
18582
|
+
*
|
|
18583
|
+
* TOML is only structurally validated on parse, so a hand-edited config can
|
|
18584
|
+
* hold any type under `permissions` or inside `allow`/`ask`/`deny`. Both the
|
|
18585
|
+
* `permissions` and `ignore` adapters have to narrow the same two shapes
|
|
18586
|
+
* before merging, so the narrowing lives here once rather than being re-spelled
|
|
18587
|
+
* (and re-diverging) per feature.
|
|
18588
|
+
*/
|
|
18589
|
+
/** Keep only the string entries of a TOML array; anything else becomes `[]`. */
|
|
18590
|
+
function toReasonixStringArray(value) {
|
|
18591
|
+
if (!Array.isArray(value)) return [];
|
|
18592
|
+
return value.filter((entry) => typeof entry === "string");
|
|
18593
|
+
}
|
|
18594
|
+
/** Copy a TOML table; a non-table (scalar, array, missing) becomes `{}`. */
|
|
18595
|
+
function toReasonixTable(value) {
|
|
18596
|
+
if (!isPlainObject$1(value)) return {};
|
|
18597
|
+
return { ...value };
|
|
18598
|
+
}
|
|
18599
|
+
//#endregion
|
|
18600
|
+
//#region src/features/ignore/reasonix-ignore.ts
|
|
18601
|
+
const permissionsTableOf = (document) => toReasonixTable(document.permissions);
|
|
18602
|
+
/**
|
|
18603
|
+
* Reshape the parsed TOML document into the `permissions.allow/ask/deny` shape
|
|
18604
|
+
* {@link applyIgnoreReadDenies} operates on. Reasonix's `[permissions]` table
|
|
18605
|
+
* is Claude-Code-shaped (SPEC.md §3.7), so the entry-level ownership rule the
|
|
18606
|
+
* gateway already implements applies verbatim; only the surrounding file
|
|
18607
|
+
* format differs. Sibling keys such as `mode` pass through untouched.
|
|
18608
|
+
*/
|
|
18609
|
+
const asClaudeStyleSettings = (document) => {
|
|
18610
|
+
const table = permissionsTableOf(document);
|
|
18611
|
+
return {
|
|
18612
|
+
...document,
|
|
18613
|
+
permissions: {
|
|
18614
|
+
...table,
|
|
18615
|
+
allow: toReasonixStringArray(table.allow),
|
|
18616
|
+
ask: toReasonixStringArray(table.ask),
|
|
18617
|
+
deny: toReasonixStringArray(table.deny)
|
|
18618
|
+
}
|
|
18619
|
+
};
|
|
18620
|
+
};
|
|
18621
|
+
/**
|
|
18622
|
+
* Drop a `[permissions]` table that ended up with nothing in it, so an empty
|
|
18623
|
+
* `.rulesyncignore` does not add a bare table header to a file that never had
|
|
18624
|
+
* one.
|
|
18625
|
+
*/
|
|
18626
|
+
const withoutEmptyPermissions = (settings) => {
|
|
18627
|
+
const document = { ...settings };
|
|
18628
|
+
const permissions = document.permissions;
|
|
18629
|
+
if (isPlainObject$1(permissions) && Object.keys(permissions).length === 0) delete document.permissions;
|
|
18630
|
+
return document;
|
|
18631
|
+
};
|
|
18632
|
+
/**
|
|
18633
|
+
* Writes `.rulesyncignore` patterns as `Read(<pattern>)` entries in the
|
|
18634
|
+
* `[permissions] deny` table of `reasonix.toml` (project) /
|
|
18635
|
+
* `~/.reasonix/config.toml` (global).
|
|
18636
|
+
*
|
|
18637
|
+
* `deny` is the right target rather than `[sandbox] forbid_read`: deny rules
|
|
18638
|
+
* take glob specifiers (`Edit(docs/**)`) and are "a hard block in every mode",
|
|
18639
|
+
* while `forbid_read` is documented as absolute paths with no glob support.
|
|
18640
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/SPEC.md
|
|
18641
|
+
*/
|
|
18642
|
+
var ReasonixIgnore = class ReasonixIgnore extends ToolIgnore {
|
|
18643
|
+
constructor(params) {
|
|
18644
|
+
super(params);
|
|
18645
|
+
const document = parseSharedConfig({
|
|
18646
|
+
format: "toml",
|
|
18647
|
+
fileContent: this.fileContent
|
|
18648
|
+
});
|
|
18649
|
+
this.patterns = toReasonixStringArray(permissionsTableOf(document).deny);
|
|
18650
|
+
}
|
|
18651
|
+
static getSettablePaths({ global = false } = {}) {
|
|
18652
|
+
return {
|
|
18653
|
+
relativeDirPath: global ? REASONIX_GLOBAL_DIR : ".",
|
|
18654
|
+
relativeFilePath: global ? REASONIX_GLOBAL_PERMISSIONS_FILE_NAME : REASONIX_PROJECT_PERMISSIONS_FILE_NAME
|
|
18655
|
+
};
|
|
18656
|
+
}
|
|
18657
|
+
/**
|
|
18658
|
+
* The config file also carries `[[plugins]]`, `[permissions]` rules from the
|
|
18659
|
+
* permissions feature and user-authored tables, so rulesync must never
|
|
18660
|
+
* delete it.
|
|
18661
|
+
*/
|
|
18662
|
+
isDeletable() {
|
|
18663
|
+
return false;
|
|
18664
|
+
}
|
|
18665
|
+
toRulesyncIgnore() {
|
|
18666
|
+
const rulesyncPatterns = this.patterns.filter((pattern) => isReadDenyEntry(pattern)).map((pattern) => pattern.slice(5, -1)).filter((pattern) => pattern.length > 0);
|
|
18667
|
+
return new RulesyncIgnore({
|
|
18668
|
+
outputRoot: this.outputRoot,
|
|
18669
|
+
relativeDirPath: RulesyncIgnore.getSettablePaths().recommended.relativeDirPath,
|
|
18670
|
+
relativeFilePath: RulesyncIgnore.getSettablePaths().recommended.relativeFilePath,
|
|
18671
|
+
fileContent: rulesyncPatterns.join("\n")
|
|
18672
|
+
});
|
|
18673
|
+
}
|
|
18674
|
+
static async fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore, global = false }) {
|
|
18675
|
+
const readDenies = rulesyncIgnore.getFileContent().split(/\r?\n|\r/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).map((pattern) => buildReadDenyEntry(pattern));
|
|
18676
|
+
const paths = this.getSettablePaths({ global });
|
|
18677
|
+
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
18678
|
+
const existingDocument = parseSharedConfig({
|
|
18679
|
+
format: "toml",
|
|
18680
|
+
fileContent: await readFileContentOrNull(filePath) ?? "",
|
|
18681
|
+
filePath
|
|
18682
|
+
});
|
|
18683
|
+
const document = withoutEmptyPermissions(applyIgnoreReadDenies({
|
|
18684
|
+
settings: asClaudeStyleSettings(existingDocument),
|
|
18685
|
+
readDenies
|
|
18686
|
+
}));
|
|
18687
|
+
return new ReasonixIgnore({
|
|
18688
|
+
outputRoot,
|
|
18689
|
+
relativeDirPath: paths.relativeDirPath,
|
|
18690
|
+
relativeFilePath: paths.relativeFilePath,
|
|
18691
|
+
fileContent: stringifySharedConfig({
|
|
18692
|
+
format: "toml",
|
|
18693
|
+
document
|
|
18694
|
+
}),
|
|
18695
|
+
validate: true,
|
|
18696
|
+
global
|
|
18697
|
+
});
|
|
18698
|
+
}
|
|
18699
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
18700
|
+
const paths = this.getSettablePaths({ global });
|
|
18701
|
+
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
|
|
18702
|
+
return new ReasonixIgnore({
|
|
18703
|
+
outputRoot,
|
|
18704
|
+
relativeDirPath: paths.relativeDirPath,
|
|
18705
|
+
relativeFilePath: paths.relativeFilePath,
|
|
18706
|
+
fileContent,
|
|
18707
|
+
validate,
|
|
18708
|
+
global
|
|
18709
|
+
});
|
|
18710
|
+
}
|
|
18711
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
18712
|
+
return new ReasonixIgnore({
|
|
18713
|
+
outputRoot,
|
|
18714
|
+
relativeDirPath,
|
|
18715
|
+
relativeFilePath,
|
|
18716
|
+
fileContent: "",
|
|
18717
|
+
validate: false,
|
|
18718
|
+
global
|
|
18719
|
+
});
|
|
18720
|
+
}
|
|
18721
|
+
};
|
|
18722
|
+
//#endregion
|
|
17998
18723
|
//#region src/features/ignore/roo-ignore.ts
|
|
17999
18724
|
/**
|
|
18000
18725
|
* RooIgnore represents ignore patterns for the Roo Code AI coding assistant.
|
|
@@ -18152,6 +18877,14 @@ const ZED_GLOBAL_WIN32_DIR = join("AppData", "Roaming", "Zed");
|
|
|
18152
18877
|
function getZedGlobalDir() {
|
|
18153
18878
|
return process.platform === "win32" ? ZED_GLOBAL_WIN32_DIR : ZED_GLOBAL_DIR;
|
|
18154
18879
|
}
|
|
18880
|
+
/**
|
|
18881
|
+
* The global config dir of the OTHER platform. `getZedGlobalDir()` resolves one
|
|
18882
|
+
* spelling per platform, but the shared-write derivation (and the gateway
|
|
18883
|
+
* ownership table it is checked against) must know both on every platform.
|
|
18884
|
+
*/
|
|
18885
|
+
function getZedOtherPlatformGlobalDir() {
|
|
18886
|
+
return process.platform === "win32" ? ZED_GLOBAL_DIR : ZED_GLOBAL_WIN32_DIR;
|
|
18887
|
+
}
|
|
18155
18888
|
const ZED_SETTINGS_FILE_NAME = "settings.json";
|
|
18156
18889
|
const ZED_RULE_FILE_NAME = ".rules";
|
|
18157
18890
|
const ZED_GLOBAL_RULE_FILE_NAME = "AGENTS.md";
|
|
@@ -18164,12 +18897,20 @@ var ZedIgnore = class ZedIgnore extends ToolIgnore {
|
|
|
18164
18897
|
const jsonValue = JSON.parse(this.fileContent);
|
|
18165
18898
|
this.patterns = jsonValue.private_files ?? [];
|
|
18166
18899
|
}
|
|
18167
|
-
static getSettablePaths() {
|
|
18900
|
+
static getSettablePaths({ global = false } = {}) {
|
|
18168
18901
|
return {
|
|
18169
|
-
relativeDirPath: ZED_DIR,
|
|
18902
|
+
relativeDirPath: global ? getZedGlobalDir() : ZED_DIR,
|
|
18170
18903
|
relativeFilePath: ZED_SETTINGS_FILE_NAME
|
|
18171
18904
|
};
|
|
18172
18905
|
}
|
|
18906
|
+
/** @see getZedOtherPlatformGlobalDir */
|
|
18907
|
+
static getExtraSharedWritePaths({ global = false } = {}) {
|
|
18908
|
+
if (!global) return [];
|
|
18909
|
+
return [{
|
|
18910
|
+
relativeDirPath: getZedOtherPlatformGlobalDir(),
|
|
18911
|
+
relativeFilePath: ZED_SETTINGS_FILE_NAME
|
|
18912
|
+
}];
|
|
18913
|
+
}
|
|
18173
18914
|
/**
|
|
18174
18915
|
* ZedIgnore uses settings.json which is a user-managed config file.
|
|
18175
18916
|
* It should not be deleted by rulesync.
|
|
@@ -18180,48 +18921,53 @@ var ZedIgnore = class ZedIgnore extends ToolIgnore {
|
|
|
18180
18921
|
toRulesyncIgnore() {
|
|
18181
18922
|
const fileContent = this.patterns.filter((pattern) => pattern.length > 0).join("\n");
|
|
18182
18923
|
return new RulesyncIgnore({
|
|
18183
|
-
outputRoot:
|
|
18924
|
+
outputRoot: ".",
|
|
18184
18925
|
relativeDirPath: RulesyncIgnore.getSettablePaths().recommended.relativeDirPath,
|
|
18185
18926
|
relativeFilePath: RulesyncIgnore.getSettablePaths().recommended.relativeFilePath,
|
|
18186
18927
|
fileContent
|
|
18187
18928
|
});
|
|
18188
18929
|
}
|
|
18189
|
-
static async fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore }) {
|
|
18930
|
+
static async fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore, global = false }) {
|
|
18190
18931
|
const patterns = rulesyncIgnore.getFileContent().split(/\r?\n|\r/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
18191
|
-
const
|
|
18932
|
+
const paths = this.getSettablePaths({ global });
|
|
18933
|
+
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
18192
18934
|
const existingFileContent = await fileExists(filePath) ? await readFileContent(filePath) : "{}";
|
|
18193
|
-
const
|
|
18935
|
+
const managedPatterns = patterns.length > 0 ? [...new Set(patterns)].toSorted() : void 0;
|
|
18194
18936
|
return new ZedIgnore({
|
|
18195
18937
|
outputRoot,
|
|
18196
|
-
relativeDirPath:
|
|
18197
|
-
relativeFilePath:
|
|
18938
|
+
relativeDirPath: paths.relativeDirPath,
|
|
18939
|
+
relativeFilePath: paths.relativeFilePath,
|
|
18198
18940
|
fileContent: applySharedConfigPatch({
|
|
18199
|
-
fileKey: sharedConfigFileKey(
|
|
18941
|
+
fileKey: sharedConfigFileKey(paths),
|
|
18200
18942
|
feature: "ignore",
|
|
18201
18943
|
existingContent: existingFileContent,
|
|
18202
|
-
patch: { private_files:
|
|
18944
|
+
patch: { private_files: managedPatterns },
|
|
18203
18945
|
filePath
|
|
18204
18946
|
}),
|
|
18205
|
-
validate: true
|
|
18947
|
+
validate: true,
|
|
18948
|
+
global
|
|
18206
18949
|
});
|
|
18207
18950
|
}
|
|
18208
|
-
static async fromFile({ outputRoot = process.cwd(), validate = true }) {
|
|
18209
|
-
const
|
|
18951
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
18952
|
+
const paths = this.getSettablePaths({ global });
|
|
18953
|
+
const fileContent = await readFileContent(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath));
|
|
18210
18954
|
return new ZedIgnore({
|
|
18211
18955
|
outputRoot,
|
|
18212
|
-
relativeDirPath:
|
|
18213
|
-
relativeFilePath:
|
|
18956
|
+
relativeDirPath: paths.relativeDirPath,
|
|
18957
|
+
relativeFilePath: paths.relativeFilePath,
|
|
18214
18958
|
fileContent,
|
|
18215
|
-
validate
|
|
18959
|
+
validate,
|
|
18960
|
+
global
|
|
18216
18961
|
});
|
|
18217
18962
|
}
|
|
18218
|
-
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
18963
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
18219
18964
|
return new ZedIgnore({
|
|
18220
18965
|
outputRoot,
|
|
18221
18966
|
relativeDirPath,
|
|
18222
18967
|
relativeFilePath,
|
|
18223
18968
|
fileContent: "{}",
|
|
18224
|
-
validate: false
|
|
18969
|
+
validate: false,
|
|
18970
|
+
global
|
|
18225
18971
|
});
|
|
18226
18972
|
}
|
|
18227
18973
|
};
|
|
@@ -18243,6 +18989,7 @@ const toolIgnoreFactories = /* @__PURE__ */ new Map([
|
|
|
18243
18989
|
["kiro-cli", { class: KiroIgnore }],
|
|
18244
18990
|
["kiro-ide", { class: KiroIgnore }],
|
|
18245
18991
|
["qwencode", { class: QwencodeIgnore }],
|
|
18992
|
+
["reasonix", { class: ReasonixIgnore }],
|
|
18246
18993
|
["roo", { class: RooIgnore }],
|
|
18247
18994
|
["devin", { class: DevinIgnore }],
|
|
18248
18995
|
["vibe", { class: VibeIgnore }],
|
|
@@ -18253,7 +19000,9 @@ const ignoreProcessorToolTargets = [...toolIgnoreFactories.keys()];
|
|
|
18253
19000
|
const ignoreProcessorGlobalToolTargets = [
|
|
18254
19001
|
"kiro",
|
|
18255
19002
|
"kiro-cli",
|
|
18256
|
-
"kiro-ide"
|
|
19003
|
+
"kiro-ide",
|
|
19004
|
+
"reasonix",
|
|
19005
|
+
"zed"
|
|
18257
19006
|
];
|
|
18258
19007
|
const defaultGetFactory$4 = (target) => {
|
|
18259
19008
|
const factory = toolIgnoreFactories.get(target);
|
|
@@ -20849,7 +21598,7 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
20849
21598
|
}), isRecord(this.config.mcp_servers) ? this.config.mcp_servers : {});
|
|
20850
21599
|
this.config = merged;
|
|
20851
21600
|
super.setFileContent(applySharedConfigPatch({
|
|
20852
|
-
fileKey:
|
|
21601
|
+
fileKey: getHermesagentConfigSharedFileKey({ global: this.global }),
|
|
20853
21602
|
feature: "mcp",
|
|
20854
21603
|
existingContent: fileContent,
|
|
20855
21604
|
patch: { mcp_servers: merged.mcp_servers }
|
|
@@ -20867,6 +21616,13 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
20867
21616
|
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
20868
21617
|
};
|
|
20869
21618
|
}
|
|
21619
|
+
/**
|
|
21620
|
+
* `config.yaml` under every spelling the global profile root can take.
|
|
21621
|
+
* @see getHermesagentSharedConfigWritePaths
|
|
21622
|
+
*/
|
|
21623
|
+
static getExtraSharedWritePaths() {
|
|
21624
|
+
return getHermesagentSharedConfigWritePaths();
|
|
21625
|
+
}
|
|
20870
21626
|
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
20871
21627
|
if (!global) throw new Error(HERMESAGENT_GLOBAL_ONLY_MESSAGE);
|
|
20872
21628
|
const paths = this.getSettablePaths({ global });
|
|
@@ -20893,7 +21649,7 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
20893
21649
|
relativeDirPath: paths.relativeDirPath,
|
|
20894
21650
|
relativeFilePath: paths.relativeFilePath,
|
|
20895
21651
|
fileContent: applySharedConfigPatch({
|
|
20896
|
-
fileKey:
|
|
21652
|
+
fileKey: getHermesagentConfigSharedFileKey({ global }),
|
|
20897
21653
|
feature: "mcp",
|
|
20898
21654
|
existingContent: fileContent,
|
|
20899
21655
|
patch: { mcp_servers: merged.mcp_servers }
|
|
@@ -21354,9 +22110,12 @@ var KiloMcp = class KiloMcp extends ToolMcp {
|
|
|
21354
22110
|
* Merge a list of project rule file globs into the `instructions` array of the
|
|
21355
22111
|
* shared `kilo.jsonc` (or `kilo.json`) config, preserving every existing key
|
|
21356
22112
|
* (notably `mcp`/`tools` written by the MCP feature). In Kilo v7, files under
|
|
21357
|
-
* `.kilo/rules/` are NOT auto-loaded; they are only picked up
|
|
21358
|
-
* the `instructions` key. The
|
|
21359
|
-
*
|
|
22113
|
+
* a *project* `.kilo/rules/` are NOT auto-loaded; they are only picked up
|
|
22114
|
+
* when listed in the `instructions` key. (The home-scope `~/.kilo/rules/` is
|
|
22115
|
+
* different — the rules migrator's `globalRulesDirs()` walks it on every
|
|
22116
|
+
* config load — which is why `KiloRule` registers instructions in project
|
|
22117
|
+
* scope only.) The resulting `instructions` list is deduped and sorted for a
|
|
22118
|
+
* stable output.
|
|
21360
22119
|
*
|
|
21361
22120
|
* @see https://kilo.ai/docs/automate/mcp/using-in-kilo-code
|
|
21362
22121
|
*/
|
|
@@ -21569,7 +22328,7 @@ var KimiCodeMcpConfigToml = class KimiCodeMcpConfigToml extends ToolFile {
|
|
|
21569
22328
|
const existingContent = existing.content;
|
|
21570
22329
|
const existingSection = existing.mcp;
|
|
21571
22330
|
const fileContent = applySharedConfigPatch({
|
|
21572
|
-
fileKey:
|
|
22331
|
+
fileKey: getKimiCodeConfigSharedFileKey({ global: true }),
|
|
21573
22332
|
feature: "mcp",
|
|
21574
22333
|
existingContent,
|
|
21575
22334
|
patch: { mcp: {
|
|
@@ -21656,11 +22415,8 @@ var KimiCodeMcp = class KimiCodeMcp extends ToolMcp {
|
|
|
21656
22415
|
* derivation sees this feature as one of that file's writers — it is not a
|
|
21657
22416
|
* settable path, since the servers themselves live in `mcp.json`.
|
|
21658
22417
|
*/
|
|
21659
|
-
static getExtraSharedWritePaths(
|
|
21660
|
-
return
|
|
21661
|
-
relativeDirPath: getKimiCodeRelativeDirPath({ global: true }),
|
|
21662
|
-
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
21663
|
-
}] : [];
|
|
22418
|
+
static getExtraSharedWritePaths() {
|
|
22419
|
+
return getKimiCodeSharedConfigWritePaths();
|
|
21664
22420
|
}
|
|
21665
22421
|
/**
|
|
21666
22422
|
* The `[mcp]` defaults live in the shared user `config.toml`, not in
|
|
@@ -23296,16 +24052,11 @@ var ZedMcp = class ZedMcp extends ToolMcp {
|
|
|
23296
24052
|
relativeFilePath: ZED_SETTINGS_FILE_NAME
|
|
23297
24053
|
};
|
|
23298
24054
|
}
|
|
23299
|
-
/**
|
|
23300
|
-
* The global settings file of the OTHER platform: `getSettablePaths` resolves
|
|
23301
|
-
* `~/.config/zed` vs `%APPDATA%\Zed` per platform, but the shared-write
|
|
23302
|
-
* derivation (and the gateway ownership table it is checked against) must
|
|
23303
|
-
* know both spellings on every platform.
|
|
23304
|
-
*/
|
|
24055
|
+
/** @see getZedOtherPlatformGlobalDir */
|
|
23305
24056
|
static getExtraSharedWritePaths({ global = false } = {}) {
|
|
23306
24057
|
if (!global) return [];
|
|
23307
24058
|
return [{
|
|
23308
|
-
relativeDirPath:
|
|
24059
|
+
relativeDirPath: getZedOtherPlatformGlobalDir(),
|
|
23309
24060
|
relativeFilePath: ZED_SETTINGS_FILE_NAME
|
|
23310
24061
|
}];
|
|
23311
24062
|
}
|
|
@@ -27735,6 +28486,13 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
27735
28486
|
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
27736
28487
|
};
|
|
27737
28488
|
}
|
|
28489
|
+
/**
|
|
28490
|
+
* `config.yaml` under every spelling the global profile root can take.
|
|
28491
|
+
* @see getHermesagentSharedConfigWritePaths
|
|
28492
|
+
*/
|
|
28493
|
+
static getExtraSharedWritePaths() {
|
|
28494
|
+
return getHermesagentSharedConfigWritePaths();
|
|
28495
|
+
}
|
|
27738
28496
|
constructor(params) {
|
|
27739
28497
|
super({
|
|
27740
28498
|
...params,
|
|
@@ -27772,7 +28530,7 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
27772
28530
|
}
|
|
27773
28531
|
setFileContent(fileContent) {
|
|
27774
28532
|
this.fileContent = applySharedConfigPatch({
|
|
27775
|
-
fileKey:
|
|
28533
|
+
fileKey: getHermesagentConfigSharedFileKey({ global: this.global }),
|
|
27776
28534
|
feature: "permissions",
|
|
27777
28535
|
existingContent: fileContent,
|
|
27778
28536
|
patch: parseSharedConfig({
|
|
@@ -28185,7 +28943,10 @@ const KiloPermissionSchema = z.union([z.enum([
|
|
|
28185
28943
|
"ask",
|
|
28186
28944
|
"deny"
|
|
28187
28945
|
]))]);
|
|
28188
|
-
const KiloPermissionsConfigSchema = z.looseObject({
|
|
28946
|
+
const KiloPermissionsConfigSchema = z.looseObject({
|
|
28947
|
+
permission: z.optional(z.record(z.string(), KiloPermissionSchema)),
|
|
28948
|
+
sandbox: z.optional(z.unknown())
|
|
28949
|
+
});
|
|
28189
28950
|
/**
|
|
28190
28951
|
* Kilo permission keys that share a name with a canonical rulesync category and
|
|
28191
28952
|
* therefore stay in the shared `permission` block. Everything else (Kilo-only
|
|
@@ -28242,6 +29003,25 @@ function collectKiloDenyPatterns(value) {
|
|
|
28242
29003
|
}
|
|
28243
29004
|
return [];
|
|
28244
29005
|
}
|
|
29006
|
+
function asKiloRecord(value) {
|
|
29007
|
+
return isPlainObject$1(value) ? { ...value } : {};
|
|
29008
|
+
}
|
|
29009
|
+
/**
|
|
29010
|
+
* The `sandbox` keys a *project* `kilo.jsonc` may state. Kilo honors
|
|
29011
|
+
* `allowed_hosts` and `writable_paths` from the global config only, and lets a
|
|
29012
|
+
* project config merely tighten — so writing the wider keys into a project file
|
|
29013
|
+
* would produce config Kilo ignores.
|
|
29014
|
+
* @see https://kilo.ai/docs/getting-started/settings/sandboxing
|
|
29015
|
+
*/
|
|
29016
|
+
const KILO_PROJECT_SCOPE_SANDBOX_KEYS = /* @__PURE__ */ new Set(["enabled", "network"]);
|
|
29017
|
+
function narrowSandboxToProjectScope({ authored, logger }) {
|
|
29018
|
+
const emitted = {};
|
|
29019
|
+
const dropped = [];
|
|
29020
|
+
for (const [key, value] of Object.entries(authored)) if (KILO_PROJECT_SCOPE_SANDBOX_KEYS.has(key)) emitted[key] = value;
|
|
29021
|
+
else dropped.push(key);
|
|
29022
|
+
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.`);
|
|
29023
|
+
return emitted;
|
|
29024
|
+
}
|
|
28245
29025
|
var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
28246
29026
|
json;
|
|
28247
29027
|
constructor(params) {
|
|
@@ -28321,8 +29101,7 @@ var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
|
28321
29101
|
const basePaths = KiloPermissions.getSettablePaths({ global });
|
|
28322
29102
|
const filePath = join(outputRoot, basePaths.relativeDirPath, basePaths.relativeFilePath);
|
|
28323
29103
|
const parsed = parseKiloJsoncStrict(await readFileContentOrNull(filePath) ?? "{}", filePath);
|
|
28324
|
-
const
|
|
28325
|
-
const existingPermission = parsedPermission && typeof parsedPermission === "object" && !Array.isArray(parsedPermission) ? { ...parsedPermission } : {};
|
|
29104
|
+
const existingPermission = asKiloRecord(parsed.permission);
|
|
28326
29105
|
const rulesyncJson = rulesyncPermissions.getJson();
|
|
28327
29106
|
const kiloOverride = rulesyncJson.kilo;
|
|
28328
29107
|
const incomingPermission = {
|
|
@@ -28348,6 +29127,18 @@ var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
|
28348
29127
|
...parsed,
|
|
28349
29128
|
permission: mergedPermission
|
|
28350
29129
|
};
|
|
29130
|
+
if (kiloOverride?.sandbox !== void 0) {
|
|
29131
|
+
const authored = asKiloRecord(kiloOverride.sandbox);
|
|
29132
|
+
const emitted = global ? authored : narrowSandboxToProjectScope({
|
|
29133
|
+
authored,
|
|
29134
|
+
logger
|
|
29135
|
+
});
|
|
29136
|
+
const merged = {
|
|
29137
|
+
...asKiloRecord(parsed.sandbox),
|
|
29138
|
+
...emitted
|
|
29139
|
+
};
|
|
29140
|
+
if (Object.keys(merged).length > 0) nextJson.sandbox = merged;
|
|
29141
|
+
}
|
|
28351
29142
|
return new KiloPermissions({
|
|
28352
29143
|
outputRoot,
|
|
28353
29144
|
relativeDirPath: basePaths.relativeDirPath,
|
|
@@ -28362,9 +29153,14 @@ var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
|
28362
29153
|
const overrideOnly = {};
|
|
28363
29154
|
for (const [key, value] of Object.entries(rawPermission)) if (isSharedKiloCategory(key)) shared[key] = typeof value === "string" ? { "*": value } : value;
|
|
28364
29155
|
else overrideOnly[key] = value;
|
|
28365
|
-
const
|
|
29156
|
+
const sandbox = this.json.sandbox;
|
|
29157
|
+
const override = {
|
|
29158
|
+
...Object.keys(overrideOnly).length > 0 && { permission: overrideOnly },
|
|
29159
|
+
...isPlainObject$1(sandbox) && { sandbox }
|
|
29160
|
+
};
|
|
29161
|
+
const json = Object.keys(override).length > 0 ? {
|
|
28366
29162
|
permission: shared,
|
|
28367
|
-
kilo:
|
|
29163
|
+
kilo: override
|
|
28368
29164
|
} : { permission: shared };
|
|
28369
29165
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(json, null, 2) });
|
|
28370
29166
|
}
|
|
@@ -28605,6 +29401,13 @@ var KimiCodePermissions = class KimiCodePermissions extends ToolPermissions {
|
|
|
28605
29401
|
isDeletable() {
|
|
28606
29402
|
return false;
|
|
28607
29403
|
}
|
|
29404
|
+
/**
|
|
29405
|
+
* `config.toml` under both spellings its directory can take.
|
|
29406
|
+
* @see getKimiCodeSharedConfigWritePaths
|
|
29407
|
+
*/
|
|
29408
|
+
static getExtraSharedWritePaths() {
|
|
29409
|
+
return getKimiCodeSharedConfigWritePaths();
|
|
29410
|
+
}
|
|
28608
29411
|
shouldMergeExistingFileContent() {
|
|
28609
29412
|
return true;
|
|
28610
29413
|
}
|
|
@@ -28619,7 +29422,7 @@ var KimiCodePermissions = class KimiCodePermissions extends ToolPermissions {
|
|
|
28619
29422
|
patch
|
|
28620
29423
|
});
|
|
28621
29424
|
this.fileContent = applySharedConfigPatch({
|
|
28622
|
-
fileKey:
|
|
29425
|
+
fileKey: getKimiCodeConfigSharedFileKey({ global: this.global }),
|
|
28623
29426
|
feature: "permissions",
|
|
28624
29427
|
existingContent: fileContent,
|
|
28625
29428
|
patch: {
|
|
@@ -29543,14 +30346,6 @@ function parseReasonixConfig(fileContent) {
|
|
|
29543
30346
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
29544
30347
|
return { ...parsed };
|
|
29545
30348
|
}
|
|
29546
|
-
function toStringArray$1(value) {
|
|
29547
|
-
if (!Array.isArray(value)) return [];
|
|
29548
|
-
return value.filter((entry) => typeof entry === "string");
|
|
29549
|
-
}
|
|
29550
|
-
function toPermissionsTable(value) {
|
|
29551
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
29552
|
-
return { ...value };
|
|
29553
|
-
}
|
|
29554
30349
|
const REASONIX_OVERRIDE_AGENT_KEYS = ["plan_mode_read_only_commands"];
|
|
29555
30350
|
/**
|
|
29556
30351
|
* `[agent]` keys an older `reasonix.toml` may carry that left the documented
|
|
@@ -29607,12 +30402,12 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
29607
30402
|
const config = rulesyncPermissions.getJson();
|
|
29608
30403
|
const { allow, ask, deny } = convertRulesyncToReasonixPermissions(config);
|
|
29609
30404
|
const managedToolNames = new Set(Object.keys(config.permission).map((category) => toReasonixToolName(category)));
|
|
29610
|
-
const existingPermissions =
|
|
29611
|
-
const preservedAllow =
|
|
29612
|
-
const preservedAsk =
|
|
29613
|
-
const preservedDeny =
|
|
30405
|
+
const existingPermissions = toReasonixTable(parsed.permissions);
|
|
30406
|
+
const preservedAllow = toReasonixStringArray(existingPermissions.allow).filter((entry) => !managedToolNames.has(parseReasonixPermissionEntry(entry).toolName));
|
|
30407
|
+
const preservedAsk = toReasonixStringArray(existingPermissions.ask).filter((entry) => !managedToolNames.has(parseReasonixPermissionEntry(entry).toolName));
|
|
30408
|
+
const preservedDeny = toReasonixStringArray(existingPermissions.deny).filter((entry) => !managedToolNames.has(parseReasonixPermissionEntry(entry).toolName));
|
|
29614
30409
|
if (logger && managedToolNames.has("Read")) {
|
|
29615
|
-
const droppedReadDenyEntries =
|
|
30410
|
+
const droppedReadDenyEntries = toReasonixStringArray(existingPermissions.deny).filter((entry) => {
|
|
29616
30411
|
const { toolName } = parseReasonixPermissionEntry(entry);
|
|
29617
30412
|
return toolName === "Read";
|
|
29618
30413
|
});
|
|
@@ -29659,11 +30454,11 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
29659
30454
|
});
|
|
29660
30455
|
}
|
|
29661
30456
|
toRulesyncPermissions() {
|
|
29662
|
-
const permissions =
|
|
30457
|
+
const permissions = toReasonixTable(this.toml.permissions);
|
|
29663
30458
|
const config = convertReasonixToRulesyncPermissions({
|
|
29664
|
-
allow:
|
|
29665
|
-
ask:
|
|
29666
|
-
deny:
|
|
30459
|
+
allow: toReasonixStringArray(permissions.allow),
|
|
30460
|
+
ask: toReasonixStringArray(permissions.ask),
|
|
30461
|
+
deny: toReasonixStringArray(permissions.deny)
|
|
29667
30462
|
});
|
|
29668
30463
|
const sandbox = asReasonixRecord(this.toml.sandbox);
|
|
29669
30464
|
const agentPlanMode = pickReasonixKeys(this.toml.agent, [...REASONIX_OVERRIDE_AGENT_KEYS, ...REASONIX_RETIRED_AGENT_KEYS]);
|
|
@@ -29755,19 +30550,31 @@ const CATEGORY_TO_TOOL_KEYS = {
|
|
|
29755
30550
|
"open_files",
|
|
29756
30551
|
"expand_code_chunks",
|
|
29757
30552
|
"expand_folder",
|
|
29758
|
-
"grep"
|
|
30553
|
+
"grep",
|
|
30554
|
+
"getJiraIssue",
|
|
30555
|
+
"getConfluencePage"
|
|
29759
30556
|
],
|
|
29760
30557
|
edit: [
|
|
29761
30558
|
"find_and_replace_code",
|
|
29762
30559
|
"create_file",
|
|
29763
30560
|
"delete_file",
|
|
29764
|
-
"move_file"
|
|
30561
|
+
"move_file",
|
|
30562
|
+
"createTechnicalPlan",
|
|
30563
|
+
"createJiraIssue",
|
|
30564
|
+
"updateJiraIssue",
|
|
30565
|
+
"createConfluencePage",
|
|
30566
|
+
"updateConfluencePage"
|
|
29765
30567
|
],
|
|
29766
30568
|
write: [
|
|
29767
30569
|
"create_file",
|
|
29768
30570
|
"delete_file",
|
|
29769
30571
|
"move_file",
|
|
29770
|
-
"find_and_replace_code"
|
|
30572
|
+
"find_and_replace_code",
|
|
30573
|
+
"createTechnicalPlan",
|
|
30574
|
+
"createJiraIssue",
|
|
30575
|
+
"updateJiraIssue",
|
|
30576
|
+
"createConfluencePage",
|
|
30577
|
+
"updateConfluencePage"
|
|
29771
30578
|
]
|
|
29772
30579
|
};
|
|
29773
30580
|
const TOOL_KEY_TO_CATEGORY = {
|
|
@@ -29775,13 +30582,24 @@ const TOOL_KEY_TO_CATEGORY = {
|
|
|
29775
30582
|
expand_code_chunks: "read",
|
|
29776
30583
|
expand_folder: "read",
|
|
29777
30584
|
grep: "read",
|
|
30585
|
+
getJiraIssue: "read",
|
|
30586
|
+
getConfluencePage: "read",
|
|
29778
30587
|
find_and_replace_code: "edit",
|
|
29779
30588
|
create_file: "edit",
|
|
29780
30589
|
delete_file: "edit",
|
|
29781
|
-
move_file: "edit"
|
|
30590
|
+
move_file: "edit",
|
|
30591
|
+
createTechnicalPlan: "edit",
|
|
30592
|
+
createJiraIssue: "edit",
|
|
30593
|
+
updateJiraIssue: "edit",
|
|
30594
|
+
createConfluencePage: "edit",
|
|
30595
|
+
updateConfluencePage: "edit"
|
|
29782
30596
|
};
|
|
29783
30597
|
const MANAGED_TOOL_KEYS = [.../* @__PURE__ */ new Set([...Object.values(CATEGORY_TO_TOOL_KEYS).flat(), ...Object.keys(TOOL_KEY_TO_CATEGORY)])];
|
|
29784
|
-
const OWNED_TOOL_PERMISSION_KEYS = [
|
|
30598
|
+
const OWNED_TOOL_PERMISSION_KEYS = [
|
|
30599
|
+
"bash",
|
|
30600
|
+
"allowedExternalPaths",
|
|
30601
|
+
"default"
|
|
30602
|
+
];
|
|
29785
30603
|
/**
|
|
29786
30604
|
* Permissions adapter for Rovo Dev CLI.
|
|
29787
30605
|
*
|
|
@@ -29795,9 +30613,16 @@ const OWNED_TOOL_PERMISSION_KEYS = ["bash", "allowedExternalPaths"];
|
|
|
29795
30613
|
* Mapping decisions (rulesync canonical -> Rovo Dev):
|
|
29796
30614
|
* - `bash`: the catch-all `*` pattern -> `bash.default`; every other pattern ->
|
|
29797
30615
|
* a `bash.commands[]` entry `{ command: <pattern as regex>, permission }`.
|
|
30616
|
+
* - the all-tools category `*`: its catch-all -> `toolPermissions.default`,
|
|
30617
|
+
* the level Rovo Dev falls back to for any tool with no more specific
|
|
30618
|
+
* setting (Rovo Dev's own default is `ask`).
|
|
29798
30619
|
* - `read` -> the inspection tools (`open_files`, `expand_code_chunks`,
|
|
29799
|
-
* `expand_folder`, `grep`
|
|
29800
|
-
*
|
|
30620
|
+
* `expand_folder`, `grep`, `getJiraIssue`, `getConfluencePage`);
|
|
30621
|
+
* `edit`/`write` -> the mutation tools (`find_and_replace_code`,
|
|
30622
|
+
* `create_file`, `delete_file`, `move_file`, `createTechnicalPlan`,
|
|
30623
|
+
* `createJiraIssue`, `updateJiraIssue`, `createConfluencePage`,
|
|
30624
|
+
* `updateConfluencePage`) — so these two categories reach Jira and
|
|
30625
|
+
* Confluence, not just the working tree.
|
|
29801
30626
|
* These Rovo Dev keys hold a single level (no per-pattern rules), so only the
|
|
29802
30627
|
* catch-all `*` of each category sets the level. Non-catch-all `allow` rules
|
|
29803
30628
|
* in those categories are surfaced as `allowedExternalPaths` so explicit path
|
|
@@ -29977,6 +30802,10 @@ function stripPermissiveOwnedValues(toolPermissions) {
|
|
|
29977
30802
|
delete toolPermissions.allowedExternalPaths;
|
|
29978
30803
|
strippedKeys.push("allowedExternalPaths");
|
|
29979
30804
|
}
|
|
30805
|
+
if (toolPermissions.default === "allow") {
|
|
30806
|
+
delete toolPermissions.default;
|
|
30807
|
+
strippedKeys.push("default");
|
|
30808
|
+
}
|
|
29980
30809
|
const bash = toolPermissions.bash;
|
|
29981
30810
|
if (isRecord(bash)) {
|
|
29982
30811
|
const stripped = { ...bash };
|
|
@@ -30001,10 +30830,19 @@ function stripPermissiveOwnedValues(toolPermissions) {
|
|
|
30001
30830
|
function convertRulesyncToRovodevToolPermissions({ config, logger }) {
|
|
30002
30831
|
const toolPermissions = {};
|
|
30003
30832
|
const allowedExternalPaths = [];
|
|
30004
|
-
|
|
30005
|
-
|
|
30006
|
-
|
|
30833
|
+
warnOnEditWriteConflict({
|
|
30834
|
+
config,
|
|
30835
|
+
logger
|
|
30836
|
+
});
|
|
30007
30837
|
for (const [category, rules] of Object.entries(config.permission)) {
|
|
30838
|
+
if (category === CATCH_ALL_PATTERN$1) {
|
|
30839
|
+
const toolWideDefault = convertAllToolsRules({
|
|
30840
|
+
rules,
|
|
30841
|
+
logger
|
|
30842
|
+
});
|
|
30843
|
+
if (toolWideDefault) toolPermissions.default = toolWideDefault;
|
|
30844
|
+
continue;
|
|
30845
|
+
}
|
|
30008
30846
|
if (category === "bash") {
|
|
30009
30847
|
const bash = convertBashRules(rules);
|
|
30010
30848
|
if (bash) toolPermissions.bash = bash;
|
|
@@ -30031,6 +30869,36 @@ function convertRulesyncToRovodevToolPermissions({ config, logger }) {
|
|
|
30031
30869
|
if (allowedExternalPaths.length > 0) toolPermissions.allowedExternalPaths = [...new Set(allowedExternalPaths)].toSorted();
|
|
30032
30870
|
return toolPermissions;
|
|
30033
30871
|
}
|
|
30872
|
+
/**
|
|
30873
|
+
* `edit` and `write` collapse onto the same Rovo Dev file-mutation tools, so a
|
|
30874
|
+
* conflicting catch-all between them cannot be represented. Warn that the loss
|
|
30875
|
+
* is happening; the conversion keeps the stricter of the two — the same
|
|
30876
|
+
* fail-closed rule the import direction uses when those tools disagree, so the
|
|
30877
|
+
* resolution never grants more than the author asked for.
|
|
30878
|
+
*/
|
|
30879
|
+
function warnOnEditWriteConflict({ config, logger }) {
|
|
30880
|
+
const editCatchAll = config.permission.edit?.[CATCH_ALL_PATTERN$1];
|
|
30881
|
+
const writeCatchAll = config.permission.write?.[CATCH_ALL_PATTERN$1];
|
|
30882
|
+
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.`);
|
|
30883
|
+
}
|
|
30884
|
+
/**
|
|
30885
|
+
* The canonical all-tools category. Its catch-all sets the tool-wide
|
|
30886
|
+
* `toolPermissions.default`, the same way `bash`'s catch-all sets
|
|
30887
|
+
* `bash.default` — both are the level Rovo Dev falls back to. Pattern rules
|
|
30888
|
+
* under `*` have no counterpart (the default is a single level), so they are
|
|
30889
|
+
* reported and skipped like any other rule Rovo Dev cannot express.
|
|
30890
|
+
*/
|
|
30891
|
+
function convertAllToolsRules({ rules, logger }) {
|
|
30892
|
+
let toolWideDefault;
|
|
30893
|
+
for (const [pattern, action] of Object.entries(rules)) {
|
|
30894
|
+
if (pattern === CATCH_ALL_PATTERN$1) {
|
|
30895
|
+
toolWideDefault = action;
|
|
30896
|
+
continue;
|
|
30897
|
+
}
|
|
30898
|
+
logger?.warn(`Rovo Dev's tool-wide default is a single level, so it cannot express the pattern "${pattern}" in the "*" category. Skipping it.`);
|
|
30899
|
+
}
|
|
30900
|
+
return toolWideDefault;
|
|
30901
|
+
}
|
|
30034
30902
|
function convertBashRules(rules) {
|
|
30035
30903
|
const bash = {};
|
|
30036
30904
|
const commands = [];
|
|
@@ -30052,6 +30920,7 @@ function convertBashRules(rules) {
|
|
|
30052
30920
|
*/
|
|
30053
30921
|
function convertRovodevToolPermissionsToRulesync(toolPermissions) {
|
|
30054
30922
|
const permission = {};
|
|
30923
|
+
if (isPermissionAction(toolPermissions.default)) permission[CATCH_ALL_PATTERN$1] = { [CATCH_ALL_PATTERN$1]: toolPermissions.default };
|
|
30055
30924
|
const bash = toolPermissions.bash;
|
|
30056
30925
|
if (isRecord(bash)) {
|
|
30057
30926
|
const bashRules = {};
|
|
@@ -30062,12 +30931,15 @@ function convertRovodevToolPermissionsToRulesync(toolPermissions) {
|
|
|
30062
30931
|
if (Object.keys(bashRules).length > 0) permission.bash = bashRules;
|
|
30063
30932
|
}
|
|
30064
30933
|
const nestedTools = isRecord(toolPermissions.tools) ? toolPermissions.tools : {};
|
|
30065
|
-
|
|
30066
|
-
|
|
30067
|
-
|
|
30934
|
+
const implicitLevel = isPermissionAction(toolPermissions.default) ? toolPermissions.default : "ask";
|
|
30935
|
+
for (const category of new Set(Object.values(TOOL_KEY_TO_CATEGORY))) {
|
|
30936
|
+
const levels = Object.entries(TOOL_KEY_TO_CATEGORY).filter(([, mapped]) => mapped === category).map(([toolKey]) => {
|
|
30937
|
+
const value = Object.hasOwn(nestedTools, toolKey) ? nestedTools[toolKey] : toolPermissions[toolKey];
|
|
30938
|
+
return isPermissionAction(value) ? value : void 0;
|
|
30939
|
+
});
|
|
30940
|
+
if (levels.every((level) => level === void 0)) continue;
|
|
30068
30941
|
permission[category] ??= {};
|
|
30069
|
-
|
|
30070
|
-
permission[category][CATCH_ALL_PATTERN$1] = strictestAction(current, value);
|
|
30942
|
+
permission[category][CATCH_ALL_PATTERN$1] = levels.reduce((strictest, level) => strictestAction(strictest, level ?? implicitLevel), permission[category][CATCH_ALL_PATTERN$1]);
|
|
30071
30943
|
}
|
|
30072
30944
|
if (isStringArray$1(toolPermissions.allowedExternalPaths)) for (const path of toolPermissions.allowedExternalPaths) {
|
|
30073
30945
|
permission.read ??= {};
|
|
@@ -30899,16 +31771,11 @@ var ZedPermissions = class ZedPermissions extends ToolPermissions {
|
|
|
30899
31771
|
relativeFilePath: ZED_SETTINGS_FILE_NAME
|
|
30900
31772
|
};
|
|
30901
31773
|
}
|
|
30902
|
-
/**
|
|
30903
|
-
* The global settings file of the OTHER platform: `getSettablePaths` resolves
|
|
30904
|
-
* `~/.config/zed` vs `%APPDATA%\Zed` per platform, but the shared-write
|
|
30905
|
-
* derivation (and the gateway ownership table it is checked against) must
|
|
30906
|
-
* know both spellings on every platform.
|
|
30907
|
-
*/
|
|
31774
|
+
/** @see getZedOtherPlatformGlobalDir */
|
|
30908
31775
|
static getExtraSharedWritePaths({ global = false } = {}) {
|
|
30909
31776
|
if (!global) return [];
|
|
30910
31777
|
return [{
|
|
30911
|
-
relativeDirPath:
|
|
31778
|
+
relativeDirPath: getZedOtherPlatformGlobalDir(),
|
|
30912
31779
|
relativeFilePath: ZED_SETTINGS_FILE_NAME
|
|
30913
31780
|
}];
|
|
30914
31781
|
}
|
|
@@ -39480,7 +40347,7 @@ def register(ctx):
|
|
|
39480
40347
|
_register_subagent(ctx, subagent)
|
|
39481
40348
|
`;
|
|
39482
40349
|
}
|
|
39483
|
-
function getEnabledPluginConfigContent(currentContent) {
|
|
40350
|
+
function getEnabledPluginConfigContent({ currentContent, global }) {
|
|
39484
40351
|
const config = parseSharedConfig({
|
|
39485
40352
|
format: "yaml",
|
|
39486
40353
|
fileContent: currentContent
|
|
@@ -39488,7 +40355,7 @@ function getEnabledPluginConfigContent(currentContent) {
|
|
|
39488
40355
|
const plugins = config.plugins && typeof config.plugins === "object" ? config.plugins : {};
|
|
39489
40356
|
const enabled = Array.isArray(plugins.enabled) ? plugins.enabled : [];
|
|
39490
40357
|
return applySharedConfigPatch({
|
|
39491
|
-
fileKey:
|
|
40358
|
+
fileKey: getHermesagentConfigSharedFileKey({ global }),
|
|
39492
40359
|
feature: "subagents",
|
|
39493
40360
|
existingContent: currentContent,
|
|
39494
40361
|
patch: { plugins: {
|
|
@@ -39539,6 +40406,10 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
|
|
|
39539
40406
|
return !targets || targets.includes("*") || targets.includes("hermesagent");
|
|
39540
40407
|
}
|
|
39541
40408
|
static fromRulesyncSubagents({ rulesyncSubagents, outputRoot, global = false }) {
|
|
40409
|
+
const pluginDirPath = getHermesagentRelativeDirPath({
|
|
40410
|
+
global,
|
|
40411
|
+
relativeDirPath: HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_DIR_PATH
|
|
40412
|
+
});
|
|
39542
40413
|
return [
|
|
39543
40414
|
...rulesyncSubagents.map((rulesyncSubagent) => HermesagentSubagent.fromRulesyncSubagent({
|
|
39544
40415
|
relativeDirPath: this.getSettablePaths({ global }).relativeDirPath,
|
|
@@ -39547,20 +40418,14 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
|
|
|
39547
40418
|
global
|
|
39548
40419
|
})),
|
|
39549
40420
|
new HermesagentSubagent({
|
|
39550
|
-
relativeDirPath:
|
|
39551
|
-
global,
|
|
39552
|
-
relativeDirPath: HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_DIR_PATH
|
|
39553
|
-
}),
|
|
40421
|
+
relativeDirPath: pluginDirPath,
|
|
39554
40422
|
relativeFilePath: basename(HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_MANIFEST_PATH),
|
|
39555
40423
|
fileContent: "",
|
|
39556
40424
|
outputRoot,
|
|
39557
40425
|
global
|
|
39558
40426
|
}),
|
|
39559
40427
|
new HermesagentSubagent({
|
|
39560
|
-
relativeDirPath:
|
|
39561
|
-
global,
|
|
39562
|
-
relativeDirPath: HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_DIR_PATH
|
|
39563
|
-
}),
|
|
40428
|
+
relativeDirPath: pluginDirPath,
|
|
39564
40429
|
relativeFilePath: basename(HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_INIT_PATH),
|
|
39565
40430
|
fileContent: "",
|
|
39566
40431
|
outputRoot,
|
|
@@ -39600,14 +40465,12 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
|
|
|
39600
40465
|
* shared `~/.hermes/config.yaml` (enabling the `rulesync-subagents` plugin),
|
|
39601
40466
|
* so the write must be declared for the shared-file order derivation.
|
|
39602
40467
|
*/
|
|
39603
|
-
|
|
39604
|
-
|
|
39605
|
-
|
|
39606
|
-
|
|
39607
|
-
|
|
39608
|
-
|
|
39609
|
-
relativeFilePath: basename(HERMESAGENT_CONFIG_FILE_PATH)
|
|
39610
|
-
}] : [];
|
|
40468
|
+
/**
|
|
40469
|
+
* `config.yaml` under every spelling the global profile root can take.
|
|
40470
|
+
* @see getHermesagentSharedConfigWritePaths
|
|
40471
|
+
*/
|
|
40472
|
+
static getExtraSharedWritePaths() {
|
|
40473
|
+
return getHermesagentSharedConfigWritePaths();
|
|
39611
40474
|
}
|
|
39612
40475
|
static getSettablePathsForRulesyncSubagent(rulesyncSubagent) {
|
|
39613
40476
|
return [join(HERMESAGENT_RULESYNC_SUBAGENTS_DIR_PATH, `${subagentSlug(rulesyncSubagent.getRelativePathFromCwd())}.json`)];
|
|
@@ -39640,7 +40503,10 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
|
|
|
39640
40503
|
}
|
|
39641
40504
|
setFileContent(newFileContent) {
|
|
39642
40505
|
if (this.getRelativeFilePath() === basename(HERMESAGENT_CONFIG_FILE_PATH)) {
|
|
39643
|
-
super.setFileContent(getEnabledPluginConfigContent(
|
|
40506
|
+
super.setFileContent(getEnabledPluginConfigContent({
|
|
40507
|
+
currentContent: newFileContent,
|
|
40508
|
+
global: this.global
|
|
40509
|
+
}));
|
|
39644
40510
|
return;
|
|
39645
40511
|
}
|
|
39646
40512
|
super.setFileContent(newFileContent);
|
|
@@ -39648,7 +40514,10 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
|
|
|
39648
40514
|
getFileContent() {
|
|
39649
40515
|
if (this.getRelativeFilePath() === basename(HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_MANIFEST_PATH)) return getPluginManifestContent();
|
|
39650
40516
|
if (this.getRelativeFilePath() === basename(HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_INIT_PATH)) return getPluginInitContent();
|
|
39651
|
-
if (this.getRelativeFilePath() === basename(HERMESAGENT_CONFIG_FILE_PATH)) return getEnabledPluginConfigContent(
|
|
40517
|
+
if (this.getRelativeFilePath() === basename(HERMESAGENT_CONFIG_FILE_PATH)) return getEnabledPluginConfigContent({
|
|
40518
|
+
currentContent: super.getFileContent(),
|
|
40519
|
+
global: this.global
|
|
40520
|
+
});
|
|
39652
40521
|
return super.getFileContent();
|
|
39653
40522
|
}
|
|
39654
40523
|
};
|
|
@@ -44727,10 +45596,13 @@ var JunieRule = class JunieRule extends ToolRule {
|
|
|
44727
45596
|
//#region src/features/rules/kilo-rule.ts
|
|
44728
45597
|
var KiloRule = class KiloRule extends ToolRule {
|
|
44729
45598
|
static getSettablePaths({ global, excludeToolDir } = {}) {
|
|
44730
|
-
if (global) return {
|
|
44731
|
-
|
|
44732
|
-
|
|
44733
|
-
|
|
45599
|
+
if (global) return {
|
|
45600
|
+
root: {
|
|
45601
|
+
relativeDirPath: buildToolPath(KILO_GLOBAL_DIR, ".", excludeToolDir),
|
|
45602
|
+
relativeFilePath: KILO_RULE_FILE_NAME
|
|
45603
|
+
},
|
|
45604
|
+
nonRoot: { relativeDirPath: buildToolPath(KILO_DIR, KILO_RULES_DIR_NAME, excludeToolDir) }
|
|
45605
|
+
};
|
|
44734
45606
|
return {
|
|
44735
45607
|
root: {
|
|
44736
45608
|
relativeDirPath: ".",
|
|
@@ -46315,6 +47187,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46315
47187
|
extension: "md",
|
|
46316
47188
|
supportsGlobal: false,
|
|
46317
47189
|
ruleDiscoveryMode: "toon",
|
|
47190
|
+
collisionPolicy: "compose",
|
|
46318
47191
|
additionalConventions: {
|
|
46319
47192
|
commands: { commandClass: AgentsmdCommand },
|
|
46320
47193
|
subagents: { subagentClass: AgentsmdSubagent },
|
|
@@ -46335,7 +47208,8 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46335
47208
|
meta: {
|
|
46336
47209
|
extension: "md",
|
|
46337
47210
|
supportsGlobal: true,
|
|
46338
|
-
ruleDiscoveryMode: "toon"
|
|
47211
|
+
ruleDiscoveryMode: "toon",
|
|
47212
|
+
collisionPolicy: "compose"
|
|
46339
47213
|
}
|
|
46340
47214
|
}],
|
|
46341
47215
|
["antigravity-cli", {
|
|
@@ -46412,7 +47286,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46412
47286
|
extension: "md",
|
|
46413
47287
|
supportsGlobal: true,
|
|
46414
47288
|
ruleDiscoveryMode: "auto",
|
|
46415
|
-
|
|
47289
|
+
collisionPolicy: "fold"
|
|
46416
47290
|
}
|
|
46417
47291
|
}],
|
|
46418
47292
|
["copilot", {
|
|
@@ -46445,7 +47319,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46445
47319
|
extension: "md",
|
|
46446
47320
|
supportsGlobal: true,
|
|
46447
47321
|
ruleDiscoveryMode: "auto",
|
|
46448
|
-
|
|
47322
|
+
collisionPolicy: "fold"
|
|
46449
47323
|
}
|
|
46450
47324
|
}],
|
|
46451
47325
|
["factorydroid", {
|
|
@@ -46453,7 +47327,8 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46453
47327
|
meta: {
|
|
46454
47328
|
extension: "md",
|
|
46455
47329
|
supportsGlobal: true,
|
|
46456
|
-
ruleDiscoveryMode: "toon"
|
|
47330
|
+
ruleDiscoveryMode: "toon",
|
|
47331
|
+
collisionPolicy: "compose"
|
|
46457
47332
|
}
|
|
46458
47333
|
}],
|
|
46459
47334
|
["goose", {
|
|
@@ -46462,7 +47337,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46462
47337
|
extension: "md",
|
|
46463
47338
|
supportsGlobal: true,
|
|
46464
47339
|
ruleDiscoveryMode: "auto",
|
|
46465
|
-
|
|
47340
|
+
collisionPolicy: "fold"
|
|
46466
47341
|
}
|
|
46467
47342
|
}],
|
|
46468
47343
|
["hermesagent", {
|
|
@@ -46471,7 +47346,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46471
47346
|
extension: "md",
|
|
46472
47347
|
supportsGlobal: false,
|
|
46473
47348
|
ruleDiscoveryMode: "auto",
|
|
46474
|
-
|
|
47349
|
+
collisionPolicy: "fold"
|
|
46475
47350
|
}
|
|
46476
47351
|
}],
|
|
46477
47352
|
["grokcli", {
|
|
@@ -46488,7 +47363,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46488
47363
|
extension: "md",
|
|
46489
47364
|
supportsGlobal: true,
|
|
46490
47365
|
ruleDiscoveryMode: "auto",
|
|
46491
|
-
|
|
47366
|
+
collisionPolicy: "fold"
|
|
46492
47367
|
}
|
|
46493
47368
|
}],
|
|
46494
47369
|
["kilo", {
|
|
@@ -46497,7 +47372,8 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46497
47372
|
extension: "md",
|
|
46498
47373
|
supportsGlobal: true,
|
|
46499
47374
|
ruleDiscoveryMode: "auto",
|
|
46500
|
-
mcpInstructionsRegistrar: KiloMcp
|
|
47375
|
+
mcpInstructionsRegistrar: KiloMcp,
|
|
47376
|
+
collisionPolicy: "compose"
|
|
46501
47377
|
}
|
|
46502
47378
|
}],
|
|
46503
47379
|
["kimi-code", {
|
|
@@ -46506,7 +47382,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46506
47382
|
extension: "md",
|
|
46507
47383
|
supportsGlobal: true,
|
|
46508
47384
|
ruleDiscoveryMode: "auto",
|
|
46509
|
-
|
|
47385
|
+
collisionPolicy: "fold"
|
|
46510
47386
|
}
|
|
46511
47387
|
}],
|
|
46512
47388
|
["kiro", {
|
|
@@ -46539,7 +47415,8 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46539
47415
|
extension: "md",
|
|
46540
47416
|
supportsGlobal: true,
|
|
46541
47417
|
ruleDiscoveryMode: "toon",
|
|
46542
|
-
mcpInstructionsRegistrar: OpencodeMcp
|
|
47418
|
+
mcpInstructionsRegistrar: OpencodeMcp,
|
|
47419
|
+
collisionPolicy: "compose"
|
|
46543
47420
|
}
|
|
46544
47421
|
}],
|
|
46545
47422
|
["pi", {
|
|
@@ -46548,7 +47425,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46548
47425
|
extension: "md",
|
|
46549
47426
|
supportsGlobal: true,
|
|
46550
47427
|
ruleDiscoveryMode: "auto",
|
|
46551
|
-
|
|
47428
|
+
collisionPolicy: "fold"
|
|
46552
47429
|
}
|
|
46553
47430
|
}],
|
|
46554
47431
|
["qwencode", {
|
|
@@ -46566,7 +47443,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46566
47443
|
extension: "md",
|
|
46567
47444
|
supportsGlobal: true,
|
|
46568
47445
|
ruleDiscoveryMode: "auto",
|
|
46569
|
-
|
|
47446
|
+
collisionPolicy: "fold"
|
|
46570
47447
|
}
|
|
46571
47448
|
}],
|
|
46572
47449
|
["replit", {
|
|
@@ -46623,7 +47500,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46623
47500
|
extension: "md",
|
|
46624
47501
|
supportsGlobal: false,
|
|
46625
47502
|
ruleDiscoveryMode: "toon",
|
|
46626
|
-
|
|
47503
|
+
collisionPolicy: "fold"
|
|
46627
47504
|
}
|
|
46628
47505
|
}],
|
|
46629
47506
|
["devin", {
|
|
@@ -46692,16 +47569,23 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
46692
47569
|
const nonLocalRootRules = rulesyncRules.filter((rule) => !rule.getFrontmatter().localRoot);
|
|
46693
47570
|
const factory = this.getFactory(this.toolTarget);
|
|
46694
47571
|
const { meta } = factory;
|
|
46695
|
-
const
|
|
47572
|
+
const convertedRules = nonLocalRootRules.map((rulesyncRule) => {
|
|
46696
47573
|
if (!factory.class.isTargetedByRulesyncRule(rulesyncRule)) return null;
|
|
46697
|
-
return
|
|
46698
|
-
|
|
46699
|
-
|
|
46700
|
-
|
|
46701
|
-
|
|
46702
|
-
|
|
47574
|
+
return {
|
|
47575
|
+
toolRule: factory.class.fromRulesyncRule({
|
|
47576
|
+
outputRoot: this.outputRoot,
|
|
47577
|
+
rulesyncRule,
|
|
47578
|
+
validate: true,
|
|
47579
|
+
global: this.global
|
|
47580
|
+
}),
|
|
47581
|
+
rulesyncRule
|
|
47582
|
+
};
|
|
46703
47583
|
}).filter((rule) => rule !== null);
|
|
46704
|
-
|
|
47584
|
+
this.mergeRulesByOutputPath({
|
|
47585
|
+
convertedRules,
|
|
47586
|
+
collisionPolicy: meta.collisionPolicy ?? "preserve"
|
|
47587
|
+
});
|
|
47588
|
+
const toolRules = convertedRules.map(({ toolRule }) => toolRule);
|
|
46705
47589
|
this.applyLocalRootRules({
|
|
46706
47590
|
toolRules,
|
|
46707
47591
|
localRootRules,
|
|
@@ -46719,7 +47603,12 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
46719
47603
|
toolRules,
|
|
46720
47604
|
factory
|
|
46721
47605
|
});
|
|
46722
|
-
|
|
47606
|
+
const outputFiles = [...toolRules, ...extraFiles];
|
|
47607
|
+
this.warnForOutputPathCollisions({
|
|
47608
|
+
outputFiles,
|
|
47609
|
+
convertedRules
|
|
47610
|
+
});
|
|
47611
|
+
return outputFiles;
|
|
46723
47612
|
}
|
|
46724
47613
|
/**
|
|
46725
47614
|
* Handle localRoot rules (only in non-global mode and when enabled). Mutates
|
|
@@ -46806,39 +47695,80 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
46806
47695
|
});
|
|
46807
47696
|
}
|
|
46808
47697
|
/**
|
|
46809
|
-
*
|
|
47698
|
+
* Reconcile rules that resolve to the same output path.
|
|
46810
47699
|
*
|
|
46811
|
-
*
|
|
46812
|
-
*
|
|
46813
|
-
*
|
|
46814
|
-
* `.
|
|
46815
|
-
*
|
|
46816
|
-
*
|
|
47700
|
+
* Multiple root fragments are composed for tools that emit a fixed root file.
|
|
47701
|
+
* The `fold` policy is for tools whose rules engine reads only one root file and
|
|
47702
|
+
* neither scans a modular rules directory nor follows references. For example,
|
|
47703
|
+
* dcode reads `.deepagents/AGENTS.md`, while Warp reads root or subdirectory
|
|
47704
|
+
* `AGENTS.md` files but never `.warp/memories/`. Those adapters must fold every
|
|
47705
|
+
* body into one instance because last-writer-wins would silently drop content.
|
|
47706
|
+
* Plain-Markdown adapters can opt into `compose` for colliding modular outputs.
|
|
46817
47707
|
*
|
|
46818
|
-
*
|
|
46819
|
-
*
|
|
46820
|
-
*
|
|
47708
|
+
* A generated root rule becomes the merge target when present. A `fold` group
|
|
47709
|
+
* without one uses its first rule. A group only composes when every rendered
|
|
47710
|
+
* fragment is plain Markdown — a fragment carrying its own frontmatter block
|
|
47711
|
+
* (e.g. Amp's `globs:` gate) would end up mid-body where the tool ignores it.
|
|
47712
|
+
* Root-involved collisions that cannot be composed safely fail; other
|
|
47713
|
+
* collisions remain separate and are reported by the final output-path check.
|
|
47714
|
+
* Mutates `convertedRules` in place.
|
|
46821
47715
|
*/
|
|
46822
|
-
|
|
46823
|
-
if (
|
|
47716
|
+
mergeRulesByOutputPath({ convertedRules, collisionPolicy }) {
|
|
47717
|
+
if (convertedRules.length <= 1) return;
|
|
46824
47718
|
const groups = /* @__PURE__ */ new Map();
|
|
46825
|
-
for (const
|
|
46826
|
-
const path = join(
|
|
47719
|
+
for (const conversion of convertedRules) {
|
|
47720
|
+
const path = join(conversion.toolRule.getRelativeDirPath(), conversion.toolRule.getRelativeFilePath());
|
|
46827
47721
|
const group = groups.get(path);
|
|
46828
|
-
if (group) group.push(
|
|
46829
|
-
else groups.set(path, [
|
|
47722
|
+
if (group) group.push(conversion);
|
|
47723
|
+
else groups.set(path, [conversion]);
|
|
46830
47724
|
}
|
|
46831
47725
|
const survivors = /* @__PURE__ */ new Set();
|
|
46832
|
-
for (const group of groups
|
|
46833
|
-
|
|
47726
|
+
for (const [path, group] of groups) {
|
|
47727
|
+
if (group.length === 1) {
|
|
47728
|
+
const conversion = group[0];
|
|
47729
|
+
if (conversion) {
|
|
47730
|
+
if (collisionPolicy === "fold") conversion.toolRule.setFileContent(conversion.toolRule.getFileContent().trim());
|
|
47731
|
+
survivors.add(conversion);
|
|
47732
|
+
}
|
|
47733
|
+
continue;
|
|
47734
|
+
}
|
|
47735
|
+
const rootConversion = group.find(({ toolRule }) => toolRule.isRoot());
|
|
47736
|
+
const allGeneratedRulesAreRoots = group.every(({ toolRule }) => toolRule.isRoot());
|
|
47737
|
+
const hasSourceRoot = group.some(({ rulesyncRule }) => rulesyncRule.getFrontmatter().root === true);
|
|
47738
|
+
const allFragmentsArePlain = group.every(({ toolRule }) => !/^---\r?\n/.test(toolRule.getFileContent()));
|
|
47739
|
+
const shouldCompose = (collisionPolicy === "fold" || collisionPolicy === "compose" || allGeneratedRulesAreRoots) && allFragmentsArePlain;
|
|
47740
|
+
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))}`);
|
|
47741
|
+
if (!shouldCompose) {
|
|
47742
|
+
for (const conversion of group) survivors.add(conversion);
|
|
47743
|
+
continue;
|
|
47744
|
+
}
|
|
47745
|
+
const target = rootConversion ?? group[0];
|
|
46834
47746
|
if (!target) continue;
|
|
46835
|
-
const mergedContent = [target, ...group.filter((rule) => rule !== target)].map((
|
|
46836
|
-
target.setFileContent(mergedContent);
|
|
47747
|
+
const mergedContent = [target, ...group.filter((rule) => rule !== target)].map(({ toolRule }) => toolRule.getFileContent().trim()).filter((content) => content.length > 0).join("\n\n");
|
|
47748
|
+
target.toolRule.setFileContent(mergedContent);
|
|
46837
47749
|
survivors.add(target);
|
|
46838
47750
|
}
|
|
46839
|
-
for (let i =
|
|
46840
|
-
const
|
|
46841
|
-
if (
|
|
47751
|
+
for (let i = convertedRules.length - 1; i >= 0; i--) {
|
|
47752
|
+
const conversion = convertedRules[i];
|
|
47753
|
+
if (conversion && !survivors.has(conversion)) convertedRules.splice(i, 1);
|
|
47754
|
+
}
|
|
47755
|
+
}
|
|
47756
|
+
warnForOutputPathCollisions({ outputFiles, convertedRules }) {
|
|
47757
|
+
const seen = /* @__PURE__ */ new Map();
|
|
47758
|
+
const describeSource = (file) => {
|
|
47759
|
+
const source = convertedRules.find(({ toolRule }) => toolRule === file)?.rulesyncRule;
|
|
47760
|
+
return source ? formatRulePaths([source]) : join(file.getRelativeDirPath(), file.getRelativeFilePath());
|
|
47761
|
+
};
|
|
47762
|
+
for (const file of outputFiles) {
|
|
47763
|
+
const path = join(file.getRelativeDirPath(), file.getRelativeFilePath());
|
|
47764
|
+
const key = path.toLowerCase();
|
|
47765
|
+
const previous = seen.get(key);
|
|
47766
|
+
if (previous) {
|
|
47767
|
+
const previousPath = join(previous.getRelativeDirPath(), previous.getRelativeFilePath());
|
|
47768
|
+
const pathDescription = previousPath === path ? `'${path}'` : `'${previousPath}' and '${path}' (compared case-insensitively, as on macOS and Windows)`;
|
|
47769
|
+
this.logger.warn(`Both ${describeSource(previous)} and ${describeSource(file)} generate to ${pathDescription}; the last one wins wherever they collide.`);
|
|
47770
|
+
}
|
|
47771
|
+
seen.set(key, file);
|
|
46842
47772
|
}
|
|
46843
47773
|
}
|
|
46844
47774
|
/**
|
|
@@ -46998,14 +47928,13 @@ As this project's AI coding tool, you must follow the additional conventions bel
|
|
|
46998
47928
|
}));
|
|
46999
47929
|
const factory = this.getFactory(this.toolTarget);
|
|
47000
47930
|
const targetedRootRules = rulesyncRules.filter((rule) => rule.getFrontmatter().root).filter((rule) => factory.class.isTargetedByRulesyncRule(rule));
|
|
47001
|
-
if (targetedRootRules.length > 1) throw new Error(`Multiple root rulesync rules found for target '${this.toolTarget}': ${formatRulePaths(targetedRootRules)}`);
|
|
47002
47931
|
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}.`);
|
|
47003
47932
|
const targetedLocalRootRules = rulesyncRules.filter((rule) => rule.getFrontmatter().localRoot).filter((rule) => factory.class.isTargetedByRulesyncRule(rule));
|
|
47004
47933
|
if (targetedLocalRootRules.length > 1) throw new Error(`Multiple localRoot rules found for target '${this.toolTarget}': ${formatRulePaths(targetedLocalRootRules)}. Only one rule can have localRoot: true`);
|
|
47005
47934
|
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)})`);
|
|
47006
47935
|
if (this.global) {
|
|
47007
47936
|
const globalPaths = factory.class.getSettablePaths({ global: true });
|
|
47008
|
-
const supportsGlobalNonRoot = "nonRoot" in globalPaths && globalPaths.nonRoot !== null || factory.meta.supportsGlobal && factory.meta.
|
|
47937
|
+
const supportsGlobalNonRoot = "nonRoot" in globalPaths && globalPaths.nonRoot !== null || factory.meta.supportsGlobal && factory.meta.collisionPolicy === "fold";
|
|
47009
47938
|
const nonRootRules = rulesyncRules.filter((rule) => !rule.getFrontmatter().root && !rule.getFrontmatter().localRoot && factory.class.isTargetedByRulesyncRule(rule));
|
|
47010
47939
|
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)}`);
|
|
47011
47940
|
if (targetedLocalRootRules.length > 0) this.logger.warn(`${targetedLocalRootRules.length} localRoot rules found, but localRoot is not supported in global mode, ignoring them: ${formatRulePaths(targetedLocalRootRules)}`);
|
|
@@ -47248,14 +48177,36 @@ async function assertPluginRootSafe(params) {
|
|
|
47248
48177
|
}
|
|
47249
48178
|
//#endregion
|
|
47250
48179
|
//#region src/utils/tool-output-root.ts
|
|
48180
|
+
/** The environment variable each tool reads for its profile root. */
|
|
48181
|
+
const TOOL_HOME_ENV_VARS = {
|
|
48182
|
+
hermesagent: "HERMES_HOME",
|
|
48183
|
+
"kimi-code": "KIMI_CODE_HOME"
|
|
48184
|
+
};
|
|
48185
|
+
/**
|
|
48186
|
+
* Substitute a tool's home override (`HERMES_HOME`, `KIMI_CODE_HOME`) for the
|
|
48187
|
+
* output root in global scope.
|
|
48188
|
+
*
|
|
48189
|
+
* The override wins over `--output-roots`: it names where the tool itself reads
|
|
48190
|
+
* its profile, so writing anywhere else would produce files the tool ignores.
|
|
48191
|
+
*
|
|
48192
|
+
* A substituted value goes through the same `validateOutputRoot` the CLI and
|
|
48193
|
+
* config paths use, so an override of `/` or an unnormalized path is rejected
|
|
48194
|
+
* instead of silently becoming the output root. The rejection is re-thrown
|
|
48195
|
+
* naming the variable, since the user never passed an `--output-roots` flag.
|
|
48196
|
+
*/
|
|
47251
48197
|
function resolveToolOutputRoot({ outputRoot, toolTarget, global }) {
|
|
47252
48198
|
if (!global) return outputRoot;
|
|
47253
|
-
|
|
48199
|
+
const resolved = toolTarget === "hermesagent" ? resolveHermesagentOutputRoot({
|
|
47254
48200
|
outputRoot,
|
|
47255
48201
|
global
|
|
47256
|
-
});
|
|
47257
|
-
if (
|
|
47258
|
-
|
|
48202
|
+
}) : toolTarget === "kimi-code" ? getKimiCodeHome() ?? outputRoot : outputRoot;
|
|
48203
|
+
if (resolved === outputRoot) return resolved;
|
|
48204
|
+
try {
|
|
48205
|
+
validateOutputRoot(resolved);
|
|
48206
|
+
} catch (error) {
|
|
48207
|
+
throw new Error(`${TOOL_HOME_ENV_VARS[toolTarget] ?? "The tool home override"} is not a usable output root: ${formatError(error)}`, { cause: error });
|
|
48208
|
+
}
|
|
48209
|
+
return resolved;
|
|
47259
48210
|
}
|
|
47260
48211
|
//#endregion
|
|
47261
48212
|
//#region src/lib/convert.ts
|
|
@@ -48656,7 +49607,11 @@ async function generateChecksCore(params) {
|
|
|
48656
49607
|
for (const toolTarget of toolTargets) for (const outputRoot of config.getOutputRoots(toolTarget)) {
|
|
48657
49608
|
if (!config.getFeatures(toolTarget).includes("checks")) continue;
|
|
48658
49609
|
const processor = new ChecksProcessor({
|
|
48659
|
-
outputRoot
|
|
49610
|
+
outputRoot: resolveToolOutputRoot({
|
|
49611
|
+
outputRoot,
|
|
49612
|
+
toolTarget,
|
|
49613
|
+
global: config.getGlobal()
|
|
49614
|
+
}),
|
|
48660
49615
|
inputRoot: config.getInputRoot(),
|
|
48661
49616
|
toolTarget,
|
|
48662
49617
|
global: config.getGlobal(),
|
|
@@ -49029,6 +49984,6 @@ async function importChecksCore(params) {
|
|
|
49029
49984
|
return writtenCount;
|
|
49030
49985
|
}
|
|
49031
49986
|
//#endregion
|
|
49032
|
-
export {
|
|
49987
|
+
export { warnOnConflictingFlags as $, RULESYNC_RELATIVE_DIR_PATH as $t, RulesyncMcp as A, MAX_FILE_SIZE as At, stringifyFrontmatter as B, RULESYNC_HOOKS_LEGACY_FILE_NAME as Bt, RulesyncSubagent as C, runWithDirectoryRollback as Ct, RulesyncRule as D, ALL_TOOL_TARGETS_WITH_WILDCARD as Dt, RulesyncSkillFrontmatterSchema as E, ALL_TOOL_TARGETS as Et, parseJsonc as F, RULESYNC_CONFIG_RELATIVE_FILE_PATH as Ft, CONFLICTING_TARGET_PAIRS as G, RULESYNC_MCP_LEGACY_FILE_NAME as Gt, SHARED_USER_MANAGED_CONFIG_PATHS as H, RULESYNC_IGNORE_RELATIVE_FILE_PATH as Ht, RulesyncCommand as I, RULESYNC_CONFIG_SCHEMA_URL as It, SourceEntrySchema as J, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as Jt, ConfigFileSchema as K, RULESYNC_MCP_RELATIVE_FILE_PATH as Kt, RulesyncCommandFrontmatterSchema as L, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as Lt, RulesyncHooks as M, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as Mt, getRulesyncSourceCandidates as N, RULESYNC_CHECKS_RELATIVE_DIR_PATH as Nt, RulesyncRuleFrontmatterSchema as O, PACKAGING_TOOL_TARGETS as Ot, resolveRulesyncSourceWritePath as P, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as Pt, fallbackLogger as Q, RULESYNC_PERMISSIONS_SCHEMA_URL as Qt, RulesyncCheck as R, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as Rt, getLocalSkillDirNames as S, resolvePath as St, RulesyncSkill as T, writeFileContent as Tt, SKILL_FILE_NAME as U, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as Ut, loadYaml as V, RULESYNC_HOOKS_RELATIVE_FILE_PATH as Vt, ConfigResolver as W, RULESYNC_MCP_FILE_NAME as Wt, ConsoleLogger as X, RULESYNC_PERMISSIONS_LEGACY_FILE_NAME as Xt, findControlCharacter as Y, RULESYNC_PERMISSIONS_FILE_NAME as Yt, JsonLogger as Z, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as Zt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as _, removeDirectory as _t, convertFromTool as a, ALL_FEATURES_WITH_WILDCARD as an, checkPathTraversal as at, CODEXCLI_BASH_RULES_FILE_NAME as b, removeFileStrict as bt, SubagentsProcessor as c, ensureDir as ct, IgnoreProcessor as d, getFileSize as dt, RULESYNC_RULES_RELATIVE_DIR_PATH as en, CLIError as et, HooksProcessor as f, getHomeDirectory as ft, CLAUDECODE_MEMORIES_DIR_NAME as g, readFileContentOrNull as gt, CLAUDECODE_LOCAL_RULE_FILE_NAME as h, readFileContent as ht, getProcessorRegistryEntry as i, ALL_FEATURES as in, assertWritablePathInsideRoot as it, RulesyncIgnore as j, RULESYNC_AIIGNORE_FILE_NAME as jt, RulesyncPermissions as k, ToolTargetSchema as kt, SkillsProcessor as l, fileExists as lt, CLAUDECODE_DIR as m, listDirectoryFiles as mt, checkRulesyncDirExists as n, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as nn, assertDirectoryIfExists as nt, isPackagingToolTarget as o, DEPRECATED_FEATURE_REPLACEMENTS as on, createTempDirectory as ot, CommandsProcessor as p, isSymlink as pt, GITIGNORE_DESTINATION_KEY as q, RULESYNC_MCP_SCHEMA_URL as qt, generate as r, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as rn, assertTreeContainsNoSymlinks as rt, RulesProcessor as s, formatError as sn, directoryExists as st, importFromTool as t, RULESYNC_SKILLS_RELATIVE_DIR_PATH as tn, ErrorCodes as tt, McpProcessor as u, findFilesByGlobs as ut, CLAUDECODE_SKILLS_DIR_PATH as v, removeDirectoryStrict as vt, RulesyncSubagentFrontmatterSchema as w, toPosixPath as wt, CODEXCLI_DIR as x, removeTempDirectory as xt, ChecksProcessor as y, removeFile as yt, RulesyncCheckFrontmatterSchema as z, RULESYNC_HOOKS_FILE_NAME as zt };
|
|
49033
49988
|
|
|
49034
|
-
//# sourceMappingURL=import-
|
|
49989
|
+
//# sourceMappingURL=import-Bj5HxY9p.js.map
|