rulesync 16.1.0 → 16.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/cli/index.cjs +298 -5
- package/dist/cli/index.js +299 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-BmXT7mRS.js → import-CArKOPG_.js} +1328 -390
- package/dist/import-CArKOPG_.js.map +1 -0
- package/dist/{import-eG7fZPXI.cjs → import-DNfxS6QZ.cjs} +1342 -404
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/import-BmXT7mRS.js.map +0 -1
|
@@ -192,6 +192,7 @@ const ignoreProcessorToolTargetTuple = [
|
|
|
192
192
|
"kiro-cli",
|
|
193
193
|
"kiro-ide",
|
|
194
194
|
"qwencode",
|
|
195
|
+
"reasonix",
|
|
195
196
|
"roo",
|
|
196
197
|
"devin",
|
|
197
198
|
"vibe",
|
|
@@ -249,6 +250,7 @@ const commandsProcessorToolTargetTuple = [
|
|
|
249
250
|
"cursor",
|
|
250
251
|
"factorydroid",
|
|
251
252
|
"goose",
|
|
253
|
+
"grokcli",
|
|
252
254
|
"hermesagent",
|
|
253
255
|
"junie",
|
|
254
256
|
"kilo",
|
|
@@ -404,6 +406,7 @@ const checksProcessorToolTargetTuple = [
|
|
|
404
406
|
"amp",
|
|
405
407
|
"cursor",
|
|
406
408
|
"hermesagent",
|
|
409
|
+
"rovodev",
|
|
407
410
|
"takt"
|
|
408
411
|
];
|
|
409
412
|
//#endregion
|
|
@@ -1057,6 +1060,17 @@ const ConfigFileSchema = z.object({
|
|
|
1057
1060
|
});
|
|
1058
1061
|
z.required(ConfigParamsSchema);
|
|
1059
1062
|
/**
|
|
1063
|
+
* Normalizes the configuration file location to an absolute path.
|
|
1064
|
+
*
|
|
1065
|
+
* `ConfigResolver` always supplies the path it actually loaded; the fallback
|
|
1066
|
+
* only covers direct programmatic construction, where the conventional
|
|
1067
|
+
* location next to the input root is the best guess.
|
|
1068
|
+
*/
|
|
1069
|
+
function normalizeConfigFilePath({ configFilePath, inputRoot }) {
|
|
1070
|
+
if (configFilePath === void 0) return join(inputRoot, RULESYNC_CONFIG_RELATIVE_FILE_PATH);
|
|
1071
|
+
return isAbsolute(configFilePath) ? configFilePath : resolve(configFilePath);
|
|
1072
|
+
}
|
|
1073
|
+
/**
|
|
1060
1074
|
* Conflicting target pairs that cannot be used together
|
|
1061
1075
|
*/
|
|
1062
1076
|
const CONFLICTING_TARGET_PAIRS = [["augmentcode", "augmentcode-legacy"], ["claudecode", "claudecode-legacy"]];
|
|
@@ -1127,8 +1141,9 @@ var Config = class Config {
|
|
|
1127
1141
|
dryRun;
|
|
1128
1142
|
check;
|
|
1129
1143
|
inputRoot;
|
|
1144
|
+
configFilePath;
|
|
1130
1145
|
sources;
|
|
1131
|
-
constructor({ outputRoots, targets, features, verbose, delete: isDelete, global, silent, simulateCommands, simulateSubagents, simulateSkills, flattenedCommandNaming, gitignoreTargetsOnly, gitignoreDestination, dryRun, check, inputRoot, sources, configFileTargets }) {
|
|
1146
|
+
constructor({ outputRoots, targets, features, verbose, delete: isDelete, global, silent, simulateCommands, simulateSubagents, simulateSkills, flattenedCommandNaming, gitignoreTargetsOnly, gitignoreDestination, dryRun, check, inputRoot, configFilePath, sources, configFileTargets }) {
|
|
1132
1147
|
assertTargetsFeaturesExclusive({
|
|
1133
1148
|
targets,
|
|
1134
1149
|
features
|
|
@@ -1161,6 +1176,10 @@ var Config = class Config {
|
|
|
1161
1176
|
this.dryRun = dryRun ?? false;
|
|
1162
1177
|
this.check = check ?? false;
|
|
1163
1178
|
this.inputRoot = inputRoot === void 0 ? process.cwd() : isAbsolute(inputRoot) ? inputRoot : resolve(inputRoot);
|
|
1179
|
+
this.configFilePath = normalizeConfigFilePath({
|
|
1180
|
+
configFilePath,
|
|
1181
|
+
inputRoot: this.inputRoot
|
|
1182
|
+
});
|
|
1164
1183
|
this.sources = sources ?? [];
|
|
1165
1184
|
}
|
|
1166
1185
|
/**
|
|
@@ -1354,6 +1373,14 @@ var Config = class Config {
|
|
|
1354
1373
|
getInputRoot() {
|
|
1355
1374
|
return this.inputRoot;
|
|
1356
1375
|
}
|
|
1376
|
+
/**
|
|
1377
|
+
* Returns the absolute path of the configuration file this config was
|
|
1378
|
+
* resolved from. The file itself may not exist — `rulesync` runs fine
|
|
1379
|
+
* without one — so callers must treat this as a location, not a guarantee.
|
|
1380
|
+
*/
|
|
1381
|
+
getConfigFilePath() {
|
|
1382
|
+
return this.configFilePath;
|
|
1383
|
+
}
|
|
1357
1384
|
getSources() {
|
|
1358
1385
|
return this.sources;
|
|
1359
1386
|
}
|
|
@@ -1576,6 +1603,7 @@ var ConfigResolver = class {
|
|
|
1576
1603
|
fallback: getDefaults().check
|
|
1577
1604
|
}),
|
|
1578
1605
|
inputRoot: resolvedInputRoot !== void 0 ? resolve(resolvedInputRoot) : cwd,
|
|
1606
|
+
configFilePath: validatedConfigPath,
|
|
1579
1607
|
sources: configByFile.sources ?? getDefaults().sources,
|
|
1580
1608
|
flattenedCommandNaming: configByFile.flattenedCommandNaming ?? getDefaults().flattenedCommandNaming,
|
|
1581
1609
|
configFileTargets: extractConfigFileTargets(configByFile.targets)
|
|
@@ -3986,10 +4014,30 @@ const ClinePermissionsOverrideSchema = z.looseObject({
|
|
|
3986
4014
|
* portable and keeps them out of other tools' configs. Mirrors the OpenCode
|
|
3987
4015
|
* override; each value may be a bare action string or a pattern map.
|
|
3988
4016
|
*
|
|
4017
|
+
* `sandbox` is the sibling top-level block that governs the sandbox Kilo runs
|
|
4018
|
+
* commands in: `enabled` (boolean), `network` (`"deny"` and friends),
|
|
4019
|
+
* `allowed_hosts` (a list of `host` / `host:port` destination exceptions) and
|
|
4020
|
+
* `writable_paths`. It has no canonical permission category, so it is authored
|
|
4021
|
+
* here and emitted only for Kilo.
|
|
4022
|
+
*
|
|
4023
|
+
* Upstream restricts what a *project* config may say: `allowed_hosts` and
|
|
4024
|
+
* `writable_paths` are honored from the global config only, and a project
|
|
4025
|
+
* config may merely tighten (`enabled: true`, `network: "deny"`) — a
|
|
4026
|
+
* project-level network denial even clears the global destination exceptions.
|
|
4027
|
+
* rulesync mirrors that: at project scope only `enabled` and `network` are
|
|
4028
|
+
* written, and the rest are dropped with a warning rather than emitted into a
|
|
4029
|
+
* file Kilo would ignore.
|
|
4030
|
+
*
|
|
3989
4031
|
* @example
|
|
3990
4032
|
* { "permission": { "external_directory": "deny", "doom_loop": "ask" } }
|
|
4033
|
+
* @example
|
|
4034
|
+
* { "sandbox": { "enabled": true, "network": "deny" } }
|
|
4035
|
+
* @see https://kilo.ai/docs/getting-started/settings/sandboxing
|
|
3991
4036
|
*/
|
|
3992
|
-
const KiloPermissionsOverrideSchema = z.looseObject({
|
|
4037
|
+
const KiloPermissionsOverrideSchema = z.looseObject({
|
|
4038
|
+
permission: z.optional(z.record(z.string(), OpencodeOverridePermissionValueSchema)),
|
|
4039
|
+
sandbox: z.optional(z.looseObject({}))
|
|
4040
|
+
});
|
|
3993
4041
|
/**
|
|
3994
4042
|
* Tool-scoped override block for Claude Code. Claude Code's `permissions` object
|
|
3995
4043
|
* (in `.claude/settings.json`) carries non-list fields that have no canonical
|
|
@@ -5298,6 +5346,26 @@ const CURSOR_IGNORE_FILE_NAME = ".cursorignore";
|
|
|
5298
5346
|
const CURSOR_PERMISSIONS_FILE_NAME = "cli.json";
|
|
5299
5347
|
const CURSOR_PERMISSIONS_GLOBAL_FILE_NAME = "cli-config.json";
|
|
5300
5348
|
//#endregion
|
|
5349
|
+
//#region src/constants/rovodev-paths.ts
|
|
5350
|
+
const ROVODEV_DIR = ".rovodev";
|
|
5351
|
+
const ROVODEV_SKILLS_DIR_PATH = join(ROVODEV_DIR, "skills");
|
|
5352
|
+
const ROVODEV_SUBAGENTS_DIR_PATH = join(ROVODEV_DIR, "subagents");
|
|
5353
|
+
const ROVODEV_MODULAR_RULES_DIR_PATH = join(ROVODEV_DIR, ".rulesync", "modular-rules");
|
|
5354
|
+
const ROVODEV_RULE_FILE_NAME = "AGENTS.md";
|
|
5355
|
+
const ROVODEV_LEGACY_RULE_FILE_NAME = "AGENTS.local.md";
|
|
5356
|
+
const ROVODEV_MCP_FILE_NAME = "mcp.json";
|
|
5357
|
+
const ROVODEV_CONFIG_FILE_NAME = "config.yml";
|
|
5358
|
+
const ROVODEV_AGENTS_SKILLS_DIR_PATH = join(".agents", "skills");
|
|
5359
|
+
const ROVODEV_PROMPTS_FILE_NAME = "prompts.yml";
|
|
5360
|
+
const ROVODEV_PROMPTS_DIR_PATH = join(ROVODEV_DIR, "prompts");
|
|
5361
|
+
/**
|
|
5362
|
+
* Custom instructions for Rovo Dev's code reviews: a plain-Markdown file (no
|
|
5363
|
+
* frontmatter) in the repository root's `.rovodev/` folder. Note the leading
|
|
5364
|
+
* dot in the file name.
|
|
5365
|
+
* @see https://support.atlassian.com/rovo/docs/set-custom-instructions-for-code-reviews/
|
|
5366
|
+
*/
|
|
5367
|
+
const ROVODEV_REVIEW_AGENT_FILE_NAME = ".review-agent.md";
|
|
5368
|
+
//#endregion
|
|
5301
5369
|
//#region src/constants/takt-paths.ts
|
|
5302
5370
|
const TAKT_DIR = ".takt";
|
|
5303
5371
|
const TAKT_FACETS_SUBDIR = "facets";
|
|
@@ -5759,12 +5827,19 @@ function slugifyCheckName(value) {
|
|
|
5759
5827
|
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48).replace(/-+$/, "");
|
|
5760
5828
|
}
|
|
5761
5829
|
//#endregion
|
|
5762
|
-
//#region src/features/checks/
|
|
5830
|
+
//#region src/features/checks/aggregated-check-file.ts
|
|
5763
5831
|
/**
|
|
5764
|
-
*
|
|
5765
|
-
*
|
|
5766
|
-
*
|
|
5832
|
+
* Shared machinery for the tools whose checks surface is **one aggregated
|
|
5833
|
+
* instruction file** rather than a file per check — Cursor Bugbot's
|
|
5834
|
+
* `.cursor/BUGBOT.md` and Rovo Dev's `.rovodev/.review-agent.md`.
|
|
5835
|
+
*
|
|
5836
|
+
* Both read the file as free prose, so the check identities have to be carried
|
|
5837
|
+
* in something invisible to the reader: an HTML-comment marker per section.
|
|
5838
|
+
* That marker convention, the escaping that keeps a check body from splitting
|
|
5839
|
+
* itself, and the import-side split are the same for both files, so they live
|
|
5840
|
+
* here once.
|
|
5767
5841
|
*/
|
|
5842
|
+
/** Marks where one check starts inside the single instruction file. */
|
|
5768
5843
|
const CHECK_MARKER_PATTERN = /^<!--\s*rulesync:check:(.+?)\s*-->[ \t]*$/gm;
|
|
5769
5844
|
/**
|
|
5770
5845
|
* A marker line a check body wrote itself — a rulesync doc fragment quoted in a
|
|
@@ -5775,17 +5850,16 @@ const CHECK_MARKER_PATTERN = /^<!--\s*rulesync:check:(.+?)\s*-->[ \t]*$/gm;
|
|
|
5775
5850
|
*/
|
|
5776
5851
|
const ESCAPABLE_MARKER_PATTERN = /^(<!--\s*rulesync:)((?:literal-)*check:.+?\s*-->[ \t]*)$/gm;
|
|
5777
5852
|
const ESCAPED_MARKER_PATTERN = /^(<!--\s*rulesync:)literal-((?:literal-)*check:.+?\s*-->[ \t]*)$/gm;
|
|
5778
|
-
|
|
5779
|
-
function renderMarker(name) {
|
|
5853
|
+
function renderCheckMarker(name) {
|
|
5780
5854
|
return `<!-- rulesync:check:${name} -->`;
|
|
5781
5855
|
}
|
|
5782
|
-
function
|
|
5856
|
+
function escapeCheckMarkers(content) {
|
|
5783
5857
|
return content.replace(ESCAPABLE_MARKER_PATTERN, "$1literal-$2");
|
|
5784
5858
|
}
|
|
5785
|
-
function
|
|
5859
|
+
function unescapeCheckMarkers(content) {
|
|
5786
5860
|
return content.replace(ESCAPED_MARKER_PATTERN, "$1$2");
|
|
5787
5861
|
}
|
|
5788
|
-
function
|
|
5862
|
+
function findCheckMarkers(fileContent) {
|
|
5789
5863
|
CHECK_MARKER_PATTERN.lastIndex = 0;
|
|
5790
5864
|
const markers = [];
|
|
5791
5865
|
let match = CHECK_MARKER_PATTERN.exec(fileContent);
|
|
@@ -5800,23 +5874,47 @@ function findMarkers(fileContent) {
|
|
|
5800
5874
|
return markers;
|
|
5801
5875
|
}
|
|
5802
5876
|
/**
|
|
5803
|
-
*
|
|
5804
|
-
*
|
|
5805
|
-
*
|
|
5877
|
+
* Whether the file holds instruction text ahead of the first marker — the
|
|
5878
|
+
* question the "generating replaces this" warning asks. A file with no marker
|
|
5879
|
+
* at all is entirely hand-written, so it qualifies; an empty one does not,
|
|
5880
|
+
* since there is nothing to replace.
|
|
5881
|
+
*/
|
|
5882
|
+
function hasHandWrittenPreamble(fileContent) {
|
|
5883
|
+
const firstMarkerStart = findCheckMarkers(fileContent)[0]?.start ?? fileContent.length;
|
|
5884
|
+
return fileContent.slice(0, firstMarkerStart).trim().length > 0;
|
|
5885
|
+
}
|
|
5886
|
+
/**
|
|
5887
|
+
* Whether the file is nothing but sections rulesync generated — the question
|
|
5888
|
+
* the deletion guard asks, and a stricter one than
|
|
5889
|
+
* {@link hasHandWrittenPreamble}. A file carrying no marker at all is not
|
|
5890
|
+
* rulesync's to remove even when it is empty: rulesync never wrote it, so an
|
|
5891
|
+
* empty one is somebody's placeholder rather than our leftover.
|
|
5892
|
+
*/
|
|
5893
|
+
function isOnlyGeneratedSections(fileContent) {
|
|
5894
|
+
const firstMarkerStart = findCheckMarkers(fileContent)[0]?.start;
|
|
5895
|
+
if (firstMarkerStart === void 0) return false;
|
|
5896
|
+
return fileContent.slice(0, firstMarkerStart).trim().length === 0;
|
|
5897
|
+
}
|
|
5898
|
+
/**
|
|
5899
|
+
* The instruction text one check contributes. Neither file has a field to put a
|
|
5900
|
+
* summary in, so `description` is used only when there is no body.
|
|
5806
5901
|
*/
|
|
5807
5902
|
function toInstruction(rulesyncCheck) {
|
|
5808
5903
|
const body = rulesyncCheck.getBody().trim();
|
|
5809
5904
|
if (body.length > 0) return body;
|
|
5810
5905
|
return rulesyncCheck.getFrontmatter().description?.trim() ?? "";
|
|
5811
5906
|
}
|
|
5812
|
-
function
|
|
5907
|
+
function renderCheckSection(rulesyncCheck) {
|
|
5813
5908
|
const name = basename(rulesyncCheck.getRelativeFilePath(), ".md");
|
|
5814
5909
|
const heading = `## ${name}`;
|
|
5815
5910
|
const instruction = toInstruction(rulesyncCheck);
|
|
5816
|
-
const lines = [
|
|
5817
|
-
if (instruction.length > 0) lines.push("",
|
|
5911
|
+
const lines = [renderCheckMarker(name), heading];
|
|
5912
|
+
if (instruction.length > 0) lines.push("", escapeCheckMarkers(instruction));
|
|
5818
5913
|
return lines.join("\n");
|
|
5819
5914
|
}
|
|
5915
|
+
function renderCheckFile(rulesyncChecks) {
|
|
5916
|
+
return `${rulesyncChecks.map(renderCheckSection).join("\n\n")}\n`;
|
|
5917
|
+
}
|
|
5820
5918
|
/** Drop the heading generate writes, so a round trip does not stack headings. */
|
|
5821
5919
|
function stripGeneratedHeading(section, name) {
|
|
5822
5920
|
const [firstLine, ...rest] = section.split("\n");
|
|
@@ -5824,6 +5922,53 @@ function stripGeneratedHeading(section, name) {
|
|
|
5824
5922
|
return section.trim();
|
|
5825
5923
|
}
|
|
5826
5924
|
/**
|
|
5925
|
+
* Split an aggregated instruction file back into one check per section.
|
|
5926
|
+
*
|
|
5927
|
+
* Content ahead of the first marker — and a hand-written file with no markers
|
|
5928
|
+
* at all — becomes a single check named `fallbackName`, so nothing in the file
|
|
5929
|
+
* is dropped.
|
|
5930
|
+
*/
|
|
5931
|
+
function splitCheckFile({ fileContent, fallbackName }) {
|
|
5932
|
+
const sections = [];
|
|
5933
|
+
const markers = findCheckMarkers(fileContent);
|
|
5934
|
+
const preambleEnd = markers[0]?.start ?? fileContent.length;
|
|
5935
|
+
const preamble = fileContent.slice(0, preambleEnd).trim();
|
|
5936
|
+
if (preamble.length > 0) sections.push({
|
|
5937
|
+
name: fallbackName,
|
|
5938
|
+
content: unescapeCheckMarkers(preamble)
|
|
5939
|
+
});
|
|
5940
|
+
for (const [index, marker] of markers.entries()) {
|
|
5941
|
+
const sectionEnd = markers[index + 1]?.start ?? fileContent.length;
|
|
5942
|
+
const markerName = marker.name.trim();
|
|
5943
|
+
const name = slugifyCheckName(markerName) || fallbackName;
|
|
5944
|
+
const content = stripGeneratedHeading(fileContent.slice(marker.end, sectionEnd).trim(), markerName);
|
|
5945
|
+
sections.push({
|
|
5946
|
+
name,
|
|
5947
|
+
content: unescapeCheckMarkers(content)
|
|
5948
|
+
});
|
|
5949
|
+
}
|
|
5950
|
+
const used = /* @__PURE__ */ new Set();
|
|
5951
|
+
return sections.map(({ name, content }) => {
|
|
5952
|
+
let uniqueName = name;
|
|
5953
|
+
let suffix = 2;
|
|
5954
|
+
while (used.has(uniqueName)) {
|
|
5955
|
+
uniqueName = `${name}-${suffix}`;
|
|
5956
|
+
suffix += 1;
|
|
5957
|
+
}
|
|
5958
|
+
used.add(uniqueName);
|
|
5959
|
+
return new RulesyncCheck({
|
|
5960
|
+
outputRoot: ".",
|
|
5961
|
+
relativeDirPath: RULESYNC_CHECKS_RELATIVE_DIR_PATH,
|
|
5962
|
+
relativeFilePath: `${uniqueName}.md`,
|
|
5963
|
+
frontmatter: { targets: ["*"] },
|
|
5964
|
+
body: content
|
|
5965
|
+
});
|
|
5966
|
+
});
|
|
5967
|
+
}
|
|
5968
|
+
//#endregion
|
|
5969
|
+
//#region src/features/checks/cursor-check.ts
|
|
5970
|
+
const FALLBACK_CHECK_NAME$1 = "bugbot";
|
|
5971
|
+
/**
|
|
5827
5972
|
* Checks adapter for Cursor Bugbot (`.cursor/BUGBOT.md`).
|
|
5828
5973
|
*
|
|
5829
5974
|
* Bugbot takes one aggregated instruction file per directory rather than a file
|
|
@@ -5880,9 +6025,7 @@ var CursorCheck = class CursorCheck extends ToolCheck {
|
|
|
5880
6025
|
const paths = CursorCheck.getSettablePaths();
|
|
5881
6026
|
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath ?? "BUGBOT.md"));
|
|
5882
6027
|
if (fileContent === null) return true;
|
|
5883
|
-
|
|
5884
|
-
if (firstMarkerStart === void 0) return false;
|
|
5885
|
-
return fileContent.slice(0, firstMarkerStart).trim().length === 0;
|
|
6028
|
+
return isOnlyGeneratedSections(fileContent);
|
|
5886
6029
|
}
|
|
5887
6030
|
static fromRulesyncCheck(_params) {
|
|
5888
6031
|
throw new Error("Cursor checks are built from all checks at once; use fromRulesyncChecks.");
|
|
@@ -5892,10 +6035,8 @@ var CursorCheck = class CursorCheck extends ToolCheck {
|
|
|
5892
6035
|
const paths = CursorCheck.getSettablePaths({ global });
|
|
5893
6036
|
const relativeFilePath = paths.relativeFilePath ?? "BUGBOT.md";
|
|
5894
6037
|
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`;
|
|
6038
|
+
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.`);
|
|
6039
|
+
const fileContent = renderCheckFile(rulesyncChecks);
|
|
5899
6040
|
return [new CursorCheck({
|
|
5900
6041
|
outputRoot,
|
|
5901
6042
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -5938,41 +6079,9 @@ var CursorCheck = class CursorCheck extends ToolCheck {
|
|
|
5938
6079
|
return first;
|
|
5939
6080
|
}
|
|
5940
6081
|
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
|
-
});
|
|
6082
|
+
return splitCheckFile({
|
|
6083
|
+
fileContent: this.getFileContent(),
|
|
6084
|
+
fallbackName: FALLBACK_CHECK_NAME$1
|
|
5976
6085
|
});
|
|
5977
6086
|
}
|
|
5978
6087
|
};
|
|
@@ -5994,8 +6103,22 @@ var CursorCheck = class CursorCheck extends ToolCheck {
|
|
|
5994
6103
|
*/
|
|
5995
6104
|
/** Project-root instruction file auto-injected by Hermes Agent. */
|
|
5996
6105
|
const HERMESAGENT_RULE_FILE_NAME = ".hermes.md";
|
|
5997
|
-
/**
|
|
6106
|
+
/**
|
|
6107
|
+
* Root directory for Hermes Agent global configuration (the HERMES_HOME dir).
|
|
6108
|
+
* Also the project-local plugin tree, which is `.hermes/` on every platform.
|
|
6109
|
+
*/
|
|
5998
6110
|
const HERMESAGENT_GLOBAL_DIR = ".hermes";
|
|
6111
|
+
/**
|
|
6112
|
+
* Home-relative global profile root on Windows: upstream defaults to
|
|
6113
|
+
* `%LOCALAPPDATA%\hermes` there, not `~/.hermes`.
|
|
6114
|
+
* Resolve it through `getHermesagentGlobalDir()` rather than reading it directly.
|
|
6115
|
+
*
|
|
6116
|
+
* Home-relative rather than read from `LOCALAPPDATA`, matching how every other
|
|
6117
|
+
* Windows global path in rulesync is spelled (`ZED_GLOBAL_WIN32_DIR`,
|
|
6118
|
+
* `WARP_WIN32_DIR`). A profile with `LOCALAPPDATA` redirected elsewhere is not
|
|
6119
|
+
* followed; those users should set `HERMES_HOME` explicitly.
|
|
6120
|
+
*/
|
|
6121
|
+
const HERMESAGENT_GLOBAL_WIN32_DIR = join("AppData", "Local", "hermes");
|
|
5999
6122
|
/** MCP servers and other settings live in `config.yaml` under `~/.hermes/`. */
|
|
6000
6123
|
const HERMESAGENT_CONFIG_FILE_NAME = "config.yaml";
|
|
6001
6124
|
const HERMESAGENT_CONFIG_FILE_PATH = join(HERMESAGENT_GLOBAL_DIR, HERMESAGENT_CONFIG_FILE_NAME);
|
|
@@ -6218,6 +6341,114 @@ var HermesagentCheck = class HermesagentCheck extends ToolCheck {
|
|
|
6218
6341
|
}
|
|
6219
6342
|
};
|
|
6220
6343
|
//#endregion
|
|
6344
|
+
//#region src/features/checks/rovodev-check.ts
|
|
6345
|
+
const FALLBACK_CHECK_NAME = "review-agent";
|
|
6346
|
+
/**
|
|
6347
|
+
* Checks adapter for Rovo Dev CLI's code-review custom instructions
|
|
6348
|
+
* (`.rovodev/.review-agent.md`).
|
|
6349
|
+
*
|
|
6350
|
+
* Rovo Dev takes one plain-Markdown instruction file at the repository root's
|
|
6351
|
+
* `.rovodev/` folder — no frontmatter, and note the leading dot in the file
|
|
6352
|
+
* name. Like Cursor Bugbot it is a single aggregated file rather than a file
|
|
6353
|
+
* per check, so every `.rulesync/checks/*.md` targeting Rovo Dev collapses into
|
|
6354
|
+
* it via {@link fromRulesyncChecks}, with each check written as a marked
|
|
6355
|
+
* section (see `aggregated-check-file.ts` for the marker convention the two
|
|
6356
|
+
* adapters share).
|
|
6357
|
+
*
|
|
6358
|
+
* Rovo Dev reads the file as free prose, so a check's `severity` and `tools`
|
|
6359
|
+
* have no equivalent there: they are not written and do not come back on
|
|
6360
|
+
* import. Neither does `description` whenever the check also has a body.
|
|
6361
|
+
*
|
|
6362
|
+
* Project scope only — these are per-repository review instructions, and Rovo
|
|
6363
|
+
* Dev documents no user-level equivalent. (The `permissions` adapter for the
|
|
6364
|
+
* same tool is the opposite: global only.)
|
|
6365
|
+
*
|
|
6366
|
+
* @see https://support.atlassian.com/rovo/docs/set-custom-instructions-for-code-reviews/
|
|
6367
|
+
*/
|
|
6368
|
+
var RovodevCheck = class RovodevCheck extends ToolCheck {
|
|
6369
|
+
static getSettablePaths(_options = {}) {
|
|
6370
|
+
return {
|
|
6371
|
+
relativeDirPath: ROVODEV_DIR,
|
|
6372
|
+
relativeFilePath: ROVODEV_REVIEW_AGENT_FILE_NAME
|
|
6373
|
+
};
|
|
6374
|
+
}
|
|
6375
|
+
static isTargetedByRulesyncCheck(rulesyncCheck) {
|
|
6376
|
+
return this.isTargetedByRulesyncCheckDefault({
|
|
6377
|
+
rulesyncCheck,
|
|
6378
|
+
toolTarget: "rovodev"
|
|
6379
|
+
});
|
|
6380
|
+
}
|
|
6381
|
+
/**
|
|
6382
|
+
* Ownership guard the processor consults before it deletes anything for this
|
|
6383
|
+
* tool. `.review-agent.md` is a file Rovo Dev's own documentation tells users
|
|
6384
|
+
* to hand-write, so anything in it that rulesync did not write is not
|
|
6385
|
+
* rulesync's to remove — dropping the last check targeting Rovo Dev must not
|
|
6386
|
+
* take somebody's hand-written review instructions with it.
|
|
6387
|
+
*/
|
|
6388
|
+
static async canDeleteAuxiliaryFiles({ outputRoot }) {
|
|
6389
|
+
const paths = RovodevCheck.getSettablePaths();
|
|
6390
|
+
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath ?? ".review-agent.md"));
|
|
6391
|
+
if (fileContent === null) return true;
|
|
6392
|
+
return isOnlyGeneratedSections(fileContent);
|
|
6393
|
+
}
|
|
6394
|
+
static fromRulesyncCheck(_params) {
|
|
6395
|
+
throw new Error("Rovo Dev checks are built from all checks at once; use fromRulesyncChecks.");
|
|
6396
|
+
}
|
|
6397
|
+
static async fromRulesyncChecks({ outputRoot = process.cwd(), rulesyncChecks, global = false, logger }) {
|
|
6398
|
+
if (rulesyncChecks.length === 0) return [];
|
|
6399
|
+
const paths = RovodevCheck.getSettablePaths({ global });
|
|
6400
|
+
const relativeFilePath = paths.relativeFilePath ?? ".review-agent.md";
|
|
6401
|
+
const filePath = join(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
6402
|
+
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.`);
|
|
6403
|
+
return [new RovodevCheck({
|
|
6404
|
+
outputRoot,
|
|
6405
|
+
relativeDirPath: paths.relativeDirPath,
|
|
6406
|
+
relativeFilePath,
|
|
6407
|
+
fileContent: renderCheckFile(rulesyncChecks),
|
|
6408
|
+
global
|
|
6409
|
+
})];
|
|
6410
|
+
}
|
|
6411
|
+
static async fromFile({ outputRoot = process.cwd(), global = false }) {
|
|
6412
|
+
const paths = RovodevCheck.getSettablePaths({ global });
|
|
6413
|
+
const relativeFilePath = paths.relativeFilePath ?? ".review-agent.md";
|
|
6414
|
+
const filePath = join(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
6415
|
+
return new RovodevCheck({
|
|
6416
|
+
outputRoot,
|
|
6417
|
+
relativeDirPath: paths.relativeDirPath,
|
|
6418
|
+
relativeFilePath,
|
|
6419
|
+
fileContent: await readFileContentOrNull(filePath) ?? "",
|
|
6420
|
+
global
|
|
6421
|
+
});
|
|
6422
|
+
}
|
|
6423
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
6424
|
+
return new RovodevCheck({
|
|
6425
|
+
outputRoot,
|
|
6426
|
+
relativeDirPath,
|
|
6427
|
+
relativeFilePath,
|
|
6428
|
+
fileContent: "",
|
|
6429
|
+
validate: false,
|
|
6430
|
+
global
|
|
6431
|
+
});
|
|
6432
|
+
}
|
|
6433
|
+
validate() {
|
|
6434
|
+
return {
|
|
6435
|
+
success: true,
|
|
6436
|
+
error: null
|
|
6437
|
+
};
|
|
6438
|
+
}
|
|
6439
|
+
toRulesyncCheck() {
|
|
6440
|
+
const first = this.toRulesyncChecks()[0];
|
|
6441
|
+
if (!first) throw new Error(`No check instructions found in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}.`);
|
|
6442
|
+
return first;
|
|
6443
|
+
}
|
|
6444
|
+
toRulesyncChecks() {
|
|
6445
|
+
return splitCheckFile({
|
|
6446
|
+
fileContent: this.getFileContent(),
|
|
6447
|
+
fallbackName: FALLBACK_CHECK_NAME
|
|
6448
|
+
});
|
|
6449
|
+
}
|
|
6450
|
+
};
|
|
6451
|
+
//#endregion
|
|
6221
6452
|
//#region src/constants/codexcli-paths.ts
|
|
6222
6453
|
const CODEXCLI_DIR = ".codex";
|
|
6223
6454
|
const CODEXCLI_PROMPTS_DIR_PATH = join(CODEXCLI_DIR, "prompts");
|
|
@@ -6330,11 +6561,14 @@ function mergeSharedConfigDeep({ base, patch }) {
|
|
|
6330
6561
|
}
|
|
6331
6562
|
const CLAUDE_SETTINGS_SHARED_FILE_KEY = ".claude/settings.json";
|
|
6332
6563
|
const HERMES_CONFIG_SHARED_FILE_KEY = ".hermes/config.yaml";
|
|
6564
|
+
const HERMES_WIN32_CONFIG_SHARED_FILE_KEY = "AppData/Local/hermes/config.yaml";
|
|
6565
|
+
const HERMES_HOME_CONFIG_SHARED_FILE_KEY = "config.yaml";
|
|
6333
6566
|
const TAKT_CONFIG_SHARED_FILE_KEY = ".takt/config.yaml";
|
|
6334
6567
|
const CODEXCLI_CONFIG_SHARED_FILE_KEY = ".codex/config.toml";
|
|
6335
6568
|
const GROKCLI_CONFIG_SHARED_FILE_KEY = ".grok/config.toml";
|
|
6336
6569
|
const VIBE_CONFIG_SHARED_FILE_KEY = ".vibe/config.toml";
|
|
6337
6570
|
const KIMI_CODE_CONFIG_SHARED_FILE_KEY = ".kimi-code/config.toml";
|
|
6571
|
+
const KIMI_CODE_HOME_CONFIG_SHARED_FILE_KEY = "config.toml";
|
|
6338
6572
|
const REASONIX_PROJECT_CONFIG_SHARED_FILE_KEY = "reasonix.toml";
|
|
6339
6573
|
const REASONIX_GLOBAL_CONFIG_SHARED_FILE_KEY = ".reasonix/config.toml";
|
|
6340
6574
|
/**
|
|
@@ -6358,6 +6592,65 @@ const sharedConfigFileKey = ({ relativeDirPath, relativeFilePath }) => {
|
|
|
6358
6592
|
* lock-step with the writers derived from the processor registry, so an
|
|
6359
6593
|
* undeclared writer fails CI instead of merging by accident.
|
|
6360
6594
|
*/
|
|
6595
|
+
/**
|
|
6596
|
+
* Hermes writes one `config.yaml`, but its global profile root has three
|
|
6597
|
+
* spellings (`~/.hermes`, the win32 `%LOCALAPPDATA%\hermes`, and `HERMES_HOME`
|
|
6598
|
+
* itself). They are the same file with the same owners, so the declaration is
|
|
6599
|
+
* written once and shared — a policy edit cannot land on one spelling only.
|
|
6600
|
+
*/
|
|
6601
|
+
const HERMES_CONFIG_DECLARATION = {
|
|
6602
|
+
format: "yaml",
|
|
6603
|
+
features: {
|
|
6604
|
+
commands: {
|
|
6605
|
+
kind: "replace-owned-keys",
|
|
6606
|
+
ownedKeys: ["plugins"]
|
|
6607
|
+
},
|
|
6608
|
+
subagents: {
|
|
6609
|
+
kind: "replace-owned-keys",
|
|
6610
|
+
ownedKeys: ["plugins"]
|
|
6611
|
+
},
|
|
6612
|
+
mcp: {
|
|
6613
|
+
kind: "replace-owned-keys",
|
|
6614
|
+
ownedKeys: ["mcp_servers"]
|
|
6615
|
+
},
|
|
6616
|
+
hooks: {
|
|
6617
|
+
kind: "replace-owned-keys",
|
|
6618
|
+
ownedKeys: ["hooks"]
|
|
6619
|
+
},
|
|
6620
|
+
permissions: {
|
|
6621
|
+
kind: "deep-merge",
|
|
6622
|
+
replaceKeys: ["permissions"]
|
|
6623
|
+
}
|
|
6624
|
+
}
|
|
6625
|
+
};
|
|
6626
|
+
/**
|
|
6627
|
+
* Kimi Code's user config: hooks owns the flat `hooks` array; permissions owns
|
|
6628
|
+
* the ordered rule list and optional coarse default mode. `KIMI_CODE_HOME` can
|
|
6629
|
+
* name the profile directory itself, so the file has two spellings that share
|
|
6630
|
+
* one declaration — a policy edit cannot land on only one of them.
|
|
6631
|
+
*/
|
|
6632
|
+
const KIMI_CODE_CONFIG_DECLARATION = {
|
|
6633
|
+
format: "toml",
|
|
6634
|
+
invalidRootPolicy: "error",
|
|
6635
|
+
features: {
|
|
6636
|
+
hooks: {
|
|
6637
|
+
kind: "replace-owned-keys",
|
|
6638
|
+
ownedKeys: ["hooks"]
|
|
6639
|
+
},
|
|
6640
|
+
mcp: {
|
|
6641
|
+
kind: "replace-owned-keys",
|
|
6642
|
+
ownedKeys: ["mcp"]
|
|
6643
|
+
},
|
|
6644
|
+
permissions: {
|
|
6645
|
+
kind: "replace-owned-keys",
|
|
6646
|
+
ownedKeys: [
|
|
6647
|
+
"permission",
|
|
6648
|
+
"default_permission_mode",
|
|
6649
|
+
"tools"
|
|
6650
|
+
]
|
|
6651
|
+
}
|
|
6652
|
+
}
|
|
6653
|
+
};
|
|
6361
6654
|
const SHARED_CONFIG_OWNERSHIP = {
|
|
6362
6655
|
[CLAUDE_SETTINGS_SHARED_FILE_KEY]: {
|
|
6363
6656
|
format: "json",
|
|
@@ -6376,31 +6669,9 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6376
6669
|
}
|
|
6377
6670
|
}
|
|
6378
6671
|
},
|
|
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
|
-
},
|
|
6672
|
+
[HERMES_CONFIG_SHARED_FILE_KEY]: HERMES_CONFIG_DECLARATION,
|
|
6673
|
+
[HERMES_WIN32_CONFIG_SHARED_FILE_KEY]: HERMES_CONFIG_DECLARATION,
|
|
6674
|
+
[HERMES_HOME_CONFIG_SHARED_FILE_KEY]: HERMES_CONFIG_DECLARATION,
|
|
6404
6675
|
[TAKT_CONFIG_SHARED_FILE_KEY]: {
|
|
6405
6676
|
format: "yaml",
|
|
6406
6677
|
invalidRootPolicy: "error",
|
|
@@ -6446,6 +6717,10 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6446
6717
|
".config/zed/settings.json": {
|
|
6447
6718
|
format: "json",
|
|
6448
6719
|
features: {
|
|
6720
|
+
ignore: {
|
|
6721
|
+
kind: "replace-owned-keys",
|
|
6722
|
+
ownedKeys: ["private_files"]
|
|
6723
|
+
},
|
|
6449
6724
|
mcp: {
|
|
6450
6725
|
kind: "replace-owned-keys",
|
|
6451
6726
|
ownedKeys: ["context_servers"]
|
|
@@ -6459,6 +6734,10 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6459
6734
|
"AppData/Roaming/Zed/settings.json": {
|
|
6460
6735
|
format: "json",
|
|
6461
6736
|
features: {
|
|
6737
|
+
ignore: {
|
|
6738
|
+
kind: "replace-owned-keys",
|
|
6739
|
+
ownedKeys: ["private_files"]
|
|
6740
|
+
},
|
|
6462
6741
|
mcp: {
|
|
6463
6742
|
kind: "replace-owned-keys",
|
|
6464
6743
|
ownedKeys: ["context_servers"]
|
|
@@ -6715,31 +6994,15 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6715
6994
|
}
|
|
6716
6995
|
}
|
|
6717
6996
|
},
|
|
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
|
-
},
|
|
6997
|
+
[KIMI_CODE_CONFIG_SHARED_FILE_KEY]: KIMI_CODE_CONFIG_DECLARATION,
|
|
6998
|
+
[KIMI_CODE_HOME_CONFIG_SHARED_FILE_KEY]: KIMI_CODE_CONFIG_DECLARATION,
|
|
6740
6999
|
[REASONIX_PROJECT_CONFIG_SHARED_FILE_KEY]: {
|
|
6741
7000
|
format: "toml",
|
|
6742
7001
|
features: {
|
|
7002
|
+
ignore: {
|
|
7003
|
+
kind: "custom",
|
|
7004
|
+
policyFunction: "applyIgnoreReadDenies"
|
|
7005
|
+
},
|
|
6743
7006
|
mcp: {
|
|
6744
7007
|
kind: "replace-owned-keys",
|
|
6745
7008
|
ownedKeys: ["plugins"]
|
|
@@ -6757,6 +7020,10 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6757
7020
|
[REASONIX_GLOBAL_CONFIG_SHARED_FILE_KEY]: {
|
|
6758
7021
|
format: "toml",
|
|
6759
7022
|
features: {
|
|
7023
|
+
ignore: {
|
|
7024
|
+
kind: "custom",
|
|
7025
|
+
policyFunction: "applyIgnoreReadDenies"
|
|
7026
|
+
},
|
|
6760
7027
|
mcp: {
|
|
6761
7028
|
kind: "replace-owned-keys",
|
|
6762
7029
|
ownedKeys: ["plugins"]
|
|
@@ -7201,6 +7468,13 @@ const toolCheckFactories = /* @__PURE__ */ new Map([
|
|
|
7201
7468
|
filePattern: "*.json"
|
|
7202
7469
|
}
|
|
7203
7470
|
}],
|
|
7471
|
+
["rovodev", {
|
|
7472
|
+
class: RovodevCheck,
|
|
7473
|
+
meta: {
|
|
7474
|
+
supportsGlobal: false,
|
|
7475
|
+
filePattern: ROVODEV_REVIEW_AGENT_FILE_NAME
|
|
7476
|
+
}
|
|
7477
|
+
}],
|
|
7204
7478
|
["takt", {
|
|
7205
7479
|
class: TaktCheck,
|
|
7206
7480
|
meta: {
|
|
@@ -7356,19 +7630,65 @@ var ChecksProcessor = class extends FeatureProcessor {
|
|
|
7356
7630
|
}
|
|
7357
7631
|
};
|
|
7358
7632
|
//#endregion
|
|
7633
|
+
//#region src/utils/tool-home.ts
|
|
7634
|
+
/**
|
|
7635
|
+
* Where the rulesync-side source files of a tool with a home override belong.
|
|
7636
|
+
*
|
|
7637
|
+
* A home override redirects the tool's OWN output, but the `.rulesync/` sources
|
|
7638
|
+
* imported back out of it are not part of the tool's profile — they stay under
|
|
7639
|
+
* the rulesync home. When no override is set, the native output root already is
|
|
7640
|
+
* that place.
|
|
7641
|
+
*/
|
|
7642
|
+
function getToolRulesyncOutputRoot({ nativeOutputRoot, global, toolHome }) {
|
|
7643
|
+
return global && toolHome() ? getHomeDirectory() : nativeOutputRoot;
|
|
7644
|
+
}
|
|
7645
|
+
//#endregion
|
|
7359
7646
|
//#region src/utils/hermesagent.ts
|
|
7360
7647
|
function getHermesagentHome() {
|
|
7361
7648
|
const configuredHome = process.env.HERMES_HOME?.trim();
|
|
7362
7649
|
return configuredHome ? resolve(configuredHome) : void 0;
|
|
7363
7650
|
}
|
|
7651
|
+
/**
|
|
7652
|
+
* The home-relative Hermes profile directory used when `HERMES_HOME` is unset.
|
|
7653
|
+
*
|
|
7654
|
+
* Upstream `_get_platform_default_hermes_home()` returns `%LOCALAPPDATA%\hermes`
|
|
7655
|
+
* on win32 and `~/.hermes` everywhere else, so the global output directory is
|
|
7656
|
+
* platform-dependent — a global generate on Windows that wrote `~/.hermes`
|
|
7657
|
+
* would land where Hermes never reads.
|
|
7658
|
+
*
|
|
7659
|
+
* @see https://github.com/NousResearch/hermes-agent `hermes_constants.py`
|
|
7660
|
+
*/
|
|
7661
|
+
function getHermesagentGlobalDir() {
|
|
7662
|
+
return process.platform === "win32" ? HERMESAGENT_GLOBAL_WIN32_DIR : HERMESAGENT_GLOBAL_DIR;
|
|
7663
|
+
}
|
|
7364
7664
|
function resolveHermesagentOutputRoot({ outputRoot, global }) {
|
|
7365
7665
|
return global ? getHermesagentHome() ?? outputRoot : outputRoot;
|
|
7366
7666
|
}
|
|
7667
|
+
/**
|
|
7668
|
+
* Map a canonical `.hermes/...` path constant onto the directory rulesync
|
|
7669
|
+
* actually writes in the requested scope.
|
|
7670
|
+
*
|
|
7671
|
+
* Project scope keeps the constant as-is (the project tree is `.hermes/`
|
|
7672
|
+
* everywhere). Global scope strips the `.hermes` prefix and re-anchors it:
|
|
7673
|
+
* `HERMES_HOME` *is* the profile root, so nothing is prepended; otherwise the
|
|
7674
|
+
* platform default directory takes its place.
|
|
7675
|
+
*/
|
|
7367
7676
|
function getHermesagentRelativeDirPath({ global, relativeDirPath }) {
|
|
7368
|
-
if (!global
|
|
7677
|
+
if (!global) return relativeDirPath;
|
|
7369
7678
|
const relativePath = relative(HERMESAGENT_GLOBAL_DIR, relativeDirPath);
|
|
7370
|
-
|
|
7371
|
-
|
|
7679
|
+
try {
|
|
7680
|
+
checkPathTraversal({
|
|
7681
|
+
relativePath: relativeDirPath,
|
|
7682
|
+
intendedRootDir: "."
|
|
7683
|
+
});
|
|
7684
|
+
checkPathTraversal({
|
|
7685
|
+
relativePath,
|
|
7686
|
+
intendedRootDir: HERMESAGENT_GLOBAL_DIR
|
|
7687
|
+
});
|
|
7688
|
+
} catch {
|
|
7689
|
+
throw new Error(`Hermes Agent global path must be within ${HERMESAGENT_GLOBAL_DIR}: ${relativeDirPath}`);
|
|
7690
|
+
}
|
|
7691
|
+
return getHermesagentHome() ? relativePath || "." : join(getHermesagentGlobalDir(), relativePath);
|
|
7372
7692
|
}
|
|
7373
7693
|
function getHermesagentRelativeFilePath({ global, relativeFilePath }) {
|
|
7374
7694
|
return join(getHermesagentRelativeDirPath({
|
|
@@ -7376,8 +7696,53 @@ function getHermesagentRelativeFilePath({ global, relativeFilePath }) {
|
|
|
7376
7696
|
relativeDirPath: dirname(relativeFilePath)
|
|
7377
7697
|
}), basename(relativeFilePath));
|
|
7378
7698
|
}
|
|
7699
|
+
/**
|
|
7700
|
+
* Every spelling `config.yaml` can take in global scope, so that the
|
|
7701
|
+
* shared-write derivation and the gateway ownership table it is checked against
|
|
7702
|
+
* see the same set of keys on every platform and with or without `HERMES_HOME`.
|
|
7703
|
+
*
|
|
7704
|
+
* `getHermesagentRelativeDirPath` resolves exactly one of these per process,
|
|
7705
|
+
* which would otherwise make the derived shared-file key depend on the ambient
|
|
7706
|
+
* environment — the drift guards would then go blind in precisely the
|
|
7707
|
+
* configuration this feature exists for.
|
|
7708
|
+
*/
|
|
7709
|
+
function getHermesagentSharedConfigWritePaths() {
|
|
7710
|
+
return [
|
|
7711
|
+
{
|
|
7712
|
+
relativeDirPath: HERMESAGENT_GLOBAL_DIR,
|
|
7713
|
+
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
7714
|
+
},
|
|
7715
|
+
{
|
|
7716
|
+
relativeDirPath: HERMESAGENT_GLOBAL_WIN32_DIR,
|
|
7717
|
+
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
7718
|
+
},
|
|
7719
|
+
{
|
|
7720
|
+
relativeDirPath: ".",
|
|
7721
|
+
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
7722
|
+
}
|
|
7723
|
+
];
|
|
7724
|
+
}
|
|
7725
|
+
/**
|
|
7726
|
+
* The `SHARED_CONFIG_OWNERSHIP` key of the `config.yaml` this scope actually
|
|
7727
|
+
* writes. All three spellings carry the same declaration, but passing the key of
|
|
7728
|
+
* the file being written keeps the write path and the drift guards reading the
|
|
7729
|
+
* same entry.
|
|
7730
|
+
*/
|
|
7731
|
+
function getHermesagentConfigSharedFileKey({ global }) {
|
|
7732
|
+
return sharedConfigFileKey({
|
|
7733
|
+
relativeDirPath: getHermesagentRelativeDirPath({
|
|
7734
|
+
global,
|
|
7735
|
+
relativeDirPath: HERMESAGENT_GLOBAL_DIR
|
|
7736
|
+
}),
|
|
7737
|
+
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
7738
|
+
});
|
|
7739
|
+
}
|
|
7379
7740
|
function getHermesagentRulesyncOutputRoot({ nativeOutputRoot, global }) {
|
|
7380
|
-
return
|
|
7741
|
+
return getToolRulesyncOutputRoot({
|
|
7742
|
+
nativeOutputRoot,
|
|
7743
|
+
global,
|
|
7744
|
+
toolHome: getHermesagentHome
|
|
7745
|
+
});
|
|
7381
7746
|
}
|
|
7382
7747
|
//#endregion
|
|
7383
7748
|
//#region src/constants/agentsmd-paths.ts
|
|
@@ -9081,6 +9446,223 @@ var GooseCommand = class GooseCommand extends ToolCommand {
|
|
|
9081
9446
|
}
|
|
9082
9447
|
};
|
|
9083
9448
|
//#endregion
|
|
9449
|
+
//#region src/constants/grokcli-paths.ts
|
|
9450
|
+
/**
|
|
9451
|
+
* Grok Build CLI (xAI) configuration-layout conventions.
|
|
9452
|
+
*
|
|
9453
|
+
* Single source of truth for where Grok Build expects its files. Grok Build
|
|
9454
|
+
* stores MCP servers (and other settings) in a `config.toml` under `.grok/`,
|
|
9455
|
+
* with project/global scopes resolved by the directory the CLI runs in
|
|
9456
|
+
* (`./.grok/config.toml` vs `~/.grok/config.toml`).
|
|
9457
|
+
*
|
|
9458
|
+
* Verified against `grok` 0.2.54 (`grok mcp add --help`, `grok mcp add`):
|
|
9459
|
+
* `-s project` writes `./.grok/config.toml`, `-s user` writes
|
|
9460
|
+
* `~/.grok/config.toml`, both as a TOML `[mcp_servers.<name>]` table.
|
|
9461
|
+
* @see https://docs.x.ai/build/overview
|
|
9462
|
+
*/
|
|
9463
|
+
/** Root directory for Grok Build configuration, relative to the scope root. */
|
|
9464
|
+
const GROKCLI_DIR = ".grok";
|
|
9465
|
+
/** MCP servers and other settings live in `config.toml` under `.grok/`. */
|
|
9466
|
+
const GROKCLI_MCP_FILE_NAME = "config.toml";
|
|
9467
|
+
/**
|
|
9468
|
+
* Shared Grok CLI config file (`config.toml`). MCP servers, the `[ui]`
|
|
9469
|
+
* permission mode, and other settings all live here; permissions reuse the same
|
|
9470
|
+
* file name as MCP since Grok consolidates everything into one config.
|
|
9471
|
+
*/
|
|
9472
|
+
const GROKCLI_CONFIG_FILE_NAME = "config.toml";
|
|
9473
|
+
/** Skills directory under `.grok/` (project: `./.grok/skills`, global: `~/.grok/skills`). */
|
|
9474
|
+
const GROKCLI_SKILLS_DIR_PATH = join(GROKCLI_DIR, "skills");
|
|
9475
|
+
/**
|
|
9476
|
+
* Hooks directory under `.grok/`. Grok Build discovers hook config files from
|
|
9477
|
+
* `.grok/hooks/*.json` (project) and `~/.grok/hooks/*.json` (global), each a
|
|
9478
|
+
* standalone JSON file using the Claude-Code-compatible nested `{ hooks: { … } }`
|
|
9479
|
+
* shape. rulesync writes all its hooks into a single `rulesync.json`.
|
|
9480
|
+
* @see https://docs.x.ai/build/features/hooks
|
|
9481
|
+
*/
|
|
9482
|
+
const GROKCLI_HOOKS_DIR_PATH = join(GROKCLI_DIR, "hooks");
|
|
9483
|
+
/** rulesync-managed Grok hooks file under `.grok/hooks/`. */
|
|
9484
|
+
const GROKCLI_HOOKS_FILE_NAME = "rulesync.json";
|
|
9485
|
+
/**
|
|
9486
|
+
* Subagents (agent profiles) directory under `.grok/`. Grok Build discovers
|
|
9487
|
+
* agent definitions from `.grok/agents/*.md` (project) and `~/.grok/agents/*.md`
|
|
9488
|
+
* (global), each a Markdown file with YAML frontmatter (verified via
|
|
9489
|
+
* `grok inspect`; format matches the bundled `~/.grok/bundled/agents/*.md`).
|
|
9490
|
+
*/
|
|
9491
|
+
const GROKCLI_AGENTS_DIR_PATH = join(GROKCLI_DIR, "agents");
|
|
9492
|
+
/**
|
|
9493
|
+
* Instruction file. Grok reads the AGENTS.md instruction-file family natively,
|
|
9494
|
+
* including the user-level `~/.grok/AGENTS.md` for global rules (verified via
|
|
9495
|
+
* `grok inspect`, consistent with the `.grok/` global discovery used by the
|
|
9496
|
+
* MCP/skills/subagents adapters).
|
|
9497
|
+
*/
|
|
9498
|
+
const GROKCLI_RULE_FILE_NAME = "AGENTS.md";
|
|
9499
|
+
/**
|
|
9500
|
+
* Custom slash commands directory. Grok's `find_command_paths` scans
|
|
9501
|
+
* `commands/*.md` under every discovered config dir — `.grok/commands/`
|
|
9502
|
+
* (project, walked from cwd up to the git root) and `~/.grok/commands/`
|
|
9503
|
+
* (global). The scan is **flat and non-recursive**, so subdirectory
|
|
9504
|
+
* namespacing (`git/commit.md` → `/git:commit`) is not supported the way it is
|
|
9505
|
+
* for Claude Code.
|
|
9506
|
+
*
|
|
9507
|
+
* Skills are collected before commands and win name collisions, so a
|
|
9508
|
+
* `.grok/skills/<name>/` shadows `.grok/commands/<name>.md`.
|
|
9509
|
+
* @see https://docs.x.ai/build/features/skills-plugins-marketplaces
|
|
9510
|
+
*/
|
|
9511
|
+
const GROKCLI_COMMANDS_DIR_PATH = join(GROKCLI_DIR, "commands");
|
|
9512
|
+
/**
|
|
9513
|
+
* Non-root rules directory. Grok scans `*.md` here — flat, sorted by name —
|
|
9514
|
+
* alongside the AGENTS.md family: `.grok/rules/` in each project directory it
|
|
9515
|
+
* walks, and `~/.grok/rules/` in the home scope.
|
|
9516
|
+
* @see https://docs.x.ai/build/overview
|
|
9517
|
+
*/
|
|
9518
|
+
const GROKCLI_RULES_DIR_PATH = join(GROKCLI_DIR, "rules");
|
|
9519
|
+
//#endregion
|
|
9520
|
+
//#region src/features/commands/grokcli-command.ts
|
|
9521
|
+
/**
|
|
9522
|
+
* Grok CLI custom slash commands are Markdown files under `.grok/commands/`
|
|
9523
|
+
* (project) / `~/.grok/commands/` (global), discovered by the same
|
|
9524
|
+
* Claude-Code-compatible frontmatter parser Grok uses for skills.
|
|
9525
|
+
*
|
|
9526
|
+
* Two upstream constraints shape this adapter:
|
|
9527
|
+
*
|
|
9528
|
+
* - The scan is **flat and non-recursive**, so nested namespacing is not
|
|
9529
|
+
* modelled (`supportsSubdirectory: false` flattens nested rulesync commands
|
|
9530
|
+
* onto their basename).
|
|
9531
|
+
* - Skills are collected before commands and win name collisions, so a
|
|
9532
|
+
* `.grok/skills/<name>/` shadows `.grok/commands/<name>.md`.
|
|
9533
|
+
*
|
|
9534
|
+
* `description` and `argument-hint` describe the command, while
|
|
9535
|
+
* `user-invocable` (default true) and `disable-model-invocation` (default
|
|
9536
|
+
* false) control who may invoke it — the same pair `GrokcliSkill` emits.
|
|
9537
|
+
* @see https://docs.x.ai/build/features/skills-plugins-marketplaces
|
|
9538
|
+
*/
|
|
9539
|
+
const GrokcliCommandFrontmatterSchema = z.looseObject({
|
|
9540
|
+
description: z.optional(z.string()),
|
|
9541
|
+
"argument-hint": z.optional(z.string()),
|
|
9542
|
+
"user-invocable": z.optional(z.boolean()),
|
|
9543
|
+
"disable-model-invocation": z.optional(z.boolean())
|
|
9544
|
+
});
|
|
9545
|
+
var GrokcliCommand = class GrokcliCommand extends ToolCommand {
|
|
9546
|
+
frontmatter;
|
|
9547
|
+
body;
|
|
9548
|
+
constructor({ frontmatter, body, ...rest }) {
|
|
9549
|
+
if (rest.validate) {
|
|
9550
|
+
const result = GrokcliCommandFrontmatterSchema.safeParse(frontmatter);
|
|
9551
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${join(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
|
|
9552
|
+
}
|
|
9553
|
+
super({
|
|
9554
|
+
...rest,
|
|
9555
|
+
fileContent: stringifyFrontmatter(body, frontmatter)
|
|
9556
|
+
});
|
|
9557
|
+
this.frontmatter = frontmatter;
|
|
9558
|
+
this.body = body;
|
|
9559
|
+
}
|
|
9560
|
+
static getSettablePaths(_options = {}) {
|
|
9561
|
+
return { relativeDirPath: GROKCLI_COMMANDS_DIR_PATH };
|
|
9562
|
+
}
|
|
9563
|
+
getBody() {
|
|
9564
|
+
return this.body;
|
|
9565
|
+
}
|
|
9566
|
+
getFrontmatter() {
|
|
9567
|
+
return this.frontmatter;
|
|
9568
|
+
}
|
|
9569
|
+
toRulesyncCommand() {
|
|
9570
|
+
const { description, ...restFields } = this.frontmatter;
|
|
9571
|
+
const rulesyncFrontmatter = {
|
|
9572
|
+
targets: ["*"],
|
|
9573
|
+
description,
|
|
9574
|
+
...Object.keys(restFields).length > 0 && { grokcli: restFields }
|
|
9575
|
+
};
|
|
9576
|
+
return new RulesyncCommand({
|
|
9577
|
+
outputRoot: ".",
|
|
9578
|
+
frontmatter: rulesyncFrontmatter,
|
|
9579
|
+
body: this.body,
|
|
9580
|
+
relativeDirPath: RulesyncCommand.getSettablePaths().relativeDirPath,
|
|
9581
|
+
relativeFilePath: this.relativeFilePath,
|
|
9582
|
+
fileContent: stringifyFrontmatter(this.body, rulesyncFrontmatter),
|
|
9583
|
+
validate: true
|
|
9584
|
+
});
|
|
9585
|
+
}
|
|
9586
|
+
static fromRulesyncCommand({ outputRoot = process.cwd(), rulesyncCommand, validate = true, global = false }) {
|
|
9587
|
+
const rulesyncFrontmatter = rulesyncCommand.getFrontmatter();
|
|
9588
|
+
const grokcliFields = rulesyncFrontmatter.grokcli ?? {};
|
|
9589
|
+
const grokcliFrontmatter = {
|
|
9590
|
+
description: rulesyncFrontmatter.description,
|
|
9591
|
+
...grokcliFields
|
|
9592
|
+
};
|
|
9593
|
+
const paths = this.getSettablePaths({ global });
|
|
9594
|
+
return new GrokcliCommand({
|
|
9595
|
+
outputRoot,
|
|
9596
|
+
frontmatter: grokcliFrontmatter,
|
|
9597
|
+
body: rulesyncCommand.getBody(),
|
|
9598
|
+
relativeDirPath: paths.relativeDirPath,
|
|
9599
|
+
relativeFilePath: rulesyncCommand.getRelativeFilePath(),
|
|
9600
|
+
validate
|
|
9601
|
+
});
|
|
9602
|
+
}
|
|
9603
|
+
validate() {
|
|
9604
|
+
if (!this.frontmatter) return {
|
|
9605
|
+
success: true,
|
|
9606
|
+
error: null
|
|
9607
|
+
};
|
|
9608
|
+
const result = GrokcliCommandFrontmatterSchema.safeParse(this.frontmatter);
|
|
9609
|
+
if (result.success) return {
|
|
9610
|
+
success: true,
|
|
9611
|
+
error: null
|
|
9612
|
+
};
|
|
9613
|
+
return {
|
|
9614
|
+
success: false,
|
|
9615
|
+
error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${join(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
|
|
9616
|
+
};
|
|
9617
|
+
}
|
|
9618
|
+
static isTargetedByRulesyncCommand(rulesyncCommand) {
|
|
9619
|
+
return this.isTargetedByRulesyncCommandDefault({
|
|
9620
|
+
rulesyncCommand,
|
|
9621
|
+
toolTarget: "grokcli"
|
|
9622
|
+
});
|
|
9623
|
+
}
|
|
9624
|
+
/**
|
|
9625
|
+
* Warn when a rulesync skill would shadow a rulesync command.
|
|
9626
|
+
*
|
|
9627
|
+
* Grok collects skills before commands and lets skills win name collisions,
|
|
9628
|
+
* so `.grok/skills/<name>/` makes `.grok/commands/<name>.md` unreachable.
|
|
9629
|
+
* Both files are still written correctly — nothing is overwritten and no
|
|
9630
|
+
* output is lost — so this warns rather than failing the run the way the
|
|
9631
|
+
* Hermes check does, where the two surfaces really do write the same path.
|
|
9632
|
+
*/
|
|
9633
|
+
static async validateRulesyncCommands({ inputRoot, rulesyncCommands, logger }) {
|
|
9634
|
+
const commandNames = new Set(rulesyncCommands.filter((command) => this.isTargetedByRulesyncCommand(command)).map((command) => basename(command.getRelativeFilePath(), ".md")));
|
|
9635
|
+
if (commandNames.size === 0) return;
|
|
9636
|
+
const shadowed = (await findFilesByGlobs(join(join(inputRoot, RULESYNC_SKILLS_RELATIVE_DIR_PATH), "**", "SKILL.md"))).map((filePath) => basename(dirname(filePath))).filter((skillName) => commandNames.has(skillName));
|
|
9637
|
+
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.`);
|
|
9638
|
+
}
|
|
9639
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
9640
|
+
const paths = this.getSettablePaths({ global });
|
|
9641
|
+
const filePath = join(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
9642
|
+
const { frontmatter, body: content } = parseFrontmatter(await readFileContent(filePath), filePath);
|
|
9643
|
+
const result = GrokcliCommandFrontmatterSchema.safeParse(frontmatter);
|
|
9644
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
9645
|
+
return new GrokcliCommand({
|
|
9646
|
+
outputRoot,
|
|
9647
|
+
relativeDirPath: paths.relativeDirPath,
|
|
9648
|
+
relativeFilePath,
|
|
9649
|
+
frontmatter: result.data,
|
|
9650
|
+
body: content.trim(),
|
|
9651
|
+
validate
|
|
9652
|
+
});
|
|
9653
|
+
}
|
|
9654
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
9655
|
+
return new GrokcliCommand({
|
|
9656
|
+
outputRoot,
|
|
9657
|
+
relativeDirPath,
|
|
9658
|
+
relativeFilePath,
|
|
9659
|
+
frontmatter: { description: "" },
|
|
9660
|
+
body: "",
|
|
9661
|
+
validate: false
|
|
9662
|
+
});
|
|
9663
|
+
}
|
|
9664
|
+
};
|
|
9665
|
+
//#endregion
|
|
9084
9666
|
//#region src/features/skills/tool-skill.ts
|
|
9085
9667
|
/** Ordered skill directory roots: primary first. */
|
|
9086
9668
|
function toolSkillSearchRoots(paths) {
|
|
@@ -9715,7 +10297,7 @@ def register(ctx):
|
|
|
9715
10297
|
_register_command(ctx, command)
|
|
9716
10298
|
`;
|
|
9717
10299
|
}
|
|
9718
|
-
function getEnabledPluginConfigContent$1(currentContent) {
|
|
10300
|
+
function getEnabledPluginConfigContent$1({ currentContent, global }) {
|
|
9719
10301
|
const config = parseSharedConfig({
|
|
9720
10302
|
format: "yaml",
|
|
9721
10303
|
fileContent: currentContent
|
|
@@ -9723,7 +10305,7 @@ function getEnabledPluginConfigContent$1(currentContent) {
|
|
|
9723
10305
|
const plugins = config.plugins && typeof config.plugins === "object" ? config.plugins : {};
|
|
9724
10306
|
const enabled = Array.isArray(plugins.enabled) ? plugins.enabled : [];
|
|
9725
10307
|
return applySharedConfigPatch({
|
|
9726
|
-
fileKey:
|
|
10308
|
+
fileKey: getHermesagentConfigSharedFileKey({ global }),
|
|
9727
10309
|
feature: "commands",
|
|
9728
10310
|
existingContent: currentContent,
|
|
9729
10311
|
patch: { plugins: {
|
|
@@ -9732,7 +10314,7 @@ function getEnabledPluginConfigContent$1(currentContent) {
|
|
|
9732
10314
|
} }
|
|
9733
10315
|
});
|
|
9734
10316
|
}
|
|
9735
|
-
function getDisabledHermesCommandsPluginConfigContent(currentContent) {
|
|
10317
|
+
function getDisabledHermesCommandsPluginConfigContent({ currentContent, global }) {
|
|
9736
10318
|
const config = parseSharedConfig({
|
|
9737
10319
|
format: "yaml",
|
|
9738
10320
|
fileContent: currentContent
|
|
@@ -9740,7 +10322,7 @@ function getDisabledHermesCommandsPluginConfigContent(currentContent) {
|
|
|
9740
10322
|
const plugins = config.plugins && typeof config.plugins === "object" ? config.plugins : {};
|
|
9741
10323
|
const enabled = Array.isArray(plugins.enabled) ? plugins.enabled : [];
|
|
9742
10324
|
return applySharedConfigPatch({
|
|
9743
|
-
fileKey:
|
|
10325
|
+
fileKey: getHermesagentConfigSharedFileKey({ global }),
|
|
9744
10326
|
feature: "commands",
|
|
9745
10327
|
existingContent: currentContent,
|
|
9746
10328
|
patch: { plugins: {
|
|
@@ -9756,35 +10338,36 @@ var HermesagentCommandAuxiliaryFile = class extends ToolFile {
|
|
|
9756
10338
|
error: null
|
|
9757
10339
|
};
|
|
9758
10340
|
}
|
|
9759
|
-
|
|
10341
|
+
/**
|
|
10342
|
+
* Whether this auxiliary file is the one at `relativeFilePath`, comparing
|
|
10343
|
+
* against the scope-resolved location of that canonical `.hermes/...` path.
|
|
10344
|
+
*/
|
|
10345
|
+
matchesPath(relativeFilePath) {
|
|
9760
10346
|
return this.getRelativePathFromCwd() === toPosixPath(getHermesagentRelativeFilePath({
|
|
9761
10347
|
global: this.global,
|
|
9762
|
-
relativeFilePath
|
|
10348
|
+
relativeFilePath
|
|
9763
10349
|
}));
|
|
9764
10350
|
}
|
|
10351
|
+
shouldMergeExistingFileContent() {
|
|
10352
|
+
return this.matchesPath(HERMESAGENT_CONFIG_FILE_PATH);
|
|
10353
|
+
}
|
|
9765
10354
|
setFileContent(newFileContent) {
|
|
9766
|
-
if (this.
|
|
9767
|
-
|
|
9768
|
-
|
|
9769
|
-
|
|
9770
|
-
|
|
10355
|
+
if (this.matchesPath(HERMESAGENT_CONFIG_FILE_PATH)) {
|
|
10356
|
+
super.setFileContent(getEnabledPluginConfigContent$1({
|
|
10357
|
+
currentContent: newFileContent,
|
|
10358
|
+
global: this.global
|
|
10359
|
+
}));
|
|
9771
10360
|
return;
|
|
9772
10361
|
}
|
|
9773
10362
|
super.setFileContent(newFileContent);
|
|
9774
10363
|
}
|
|
9775
10364
|
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());
|
|
10365
|
+
if (this.matchesPath(HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_MANIFEST_PATH)) return getPluginManifestContent$2();
|
|
10366
|
+
if (this.matchesPath(HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_INIT_PATH)) return getPluginInitContent$2();
|
|
10367
|
+
if (this.matchesPath(HERMESAGENT_CONFIG_FILE_PATH)) return getEnabledPluginConfigContent$1({
|
|
10368
|
+
currentContent: super.getFileContent(),
|
|
10369
|
+
global: this.global
|
|
10370
|
+
});
|
|
9788
10371
|
return super.getFileContent();
|
|
9789
10372
|
}
|
|
9790
10373
|
};
|
|
@@ -9801,14 +10384,12 @@ var HermesagentCommand = class HermesagentCommand extends ToolCommand {
|
|
|
9801
10384
|
relativeDirPath: HERMESAGENT_RULESYNC_COMMANDS_DIR_PATH
|
|
9802
10385
|
}) };
|
|
9803
10386
|
}
|
|
9804
|
-
|
|
9805
|
-
|
|
9806
|
-
|
|
9807
|
-
|
|
9808
|
-
|
|
9809
|
-
|
|
9810
|
-
relativeFilePath: basename(HERMESAGENT_CONFIG_FILE_PATH)
|
|
9811
|
-
}];
|
|
10387
|
+
/**
|
|
10388
|
+
* `config.yaml` under every spelling the global profile root can take.
|
|
10389
|
+
* @see getHermesagentSharedConfigWritePaths
|
|
10390
|
+
*/
|
|
10391
|
+
static getExtraSharedWritePaths() {
|
|
10392
|
+
return getHermesagentSharedConfigWritePaths();
|
|
9812
10393
|
}
|
|
9813
10394
|
static async validateRulesyncCommands({ inputRoot, rulesyncCommands }) {
|
|
9814
10395
|
const commandSlugs = /* @__PURE__ */ new Set();
|
|
@@ -9833,33 +10414,28 @@ var HermesagentCommand = class HermesagentCommand extends ToolCommand {
|
|
|
9833
10414
|
}
|
|
9834
10415
|
static async getAuxiliaryFiles({ toolCommands, outputRoot, global = false, forDeletion = false }) {
|
|
9835
10416
|
if (toolCommands.length === 0 && !forDeletion) return [];
|
|
10417
|
+
const pluginDirPath = getHermesagentRelativeDirPath({
|
|
10418
|
+
global,
|
|
10419
|
+
relativeDirPath: HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_DIR_PATH
|
|
10420
|
+
});
|
|
9836
10421
|
const pluginFiles = [
|
|
9837
10422
|
new HermesagentCommandAuxiliaryFile({
|
|
9838
10423
|
outputRoot,
|
|
9839
|
-
relativeDirPath:
|
|
9840
|
-
global,
|
|
9841
|
-
relativeDirPath: HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_DIR_PATH
|
|
9842
|
-
}),
|
|
10424
|
+
relativeDirPath: pluginDirPath,
|
|
9843
10425
|
relativeFilePath: basename(HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_MANIFEST_PATH),
|
|
9844
10426
|
fileContent: "",
|
|
9845
10427
|
global
|
|
9846
10428
|
}),
|
|
9847
10429
|
new HermesagentCommandAuxiliaryFile({
|
|
9848
10430
|
outputRoot,
|
|
9849
|
-
relativeDirPath:
|
|
9850
|
-
global,
|
|
9851
|
-
relativeDirPath: HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_DIR_PATH
|
|
9852
|
-
}),
|
|
10431
|
+
relativeDirPath: pluginDirPath,
|
|
9853
10432
|
relativeFilePath: basename(HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_OWNERSHIP_PATH),
|
|
9854
10433
|
fileContent: "Generated and owned by RuleSync.\n",
|
|
9855
10434
|
global
|
|
9856
10435
|
}),
|
|
9857
10436
|
new HermesagentCommandAuxiliaryFile({
|
|
9858
10437
|
outputRoot,
|
|
9859
|
-
relativeDirPath:
|
|
9860
|
-
global,
|
|
9861
|
-
relativeDirPath: HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_DIR_PATH
|
|
9862
|
-
}),
|
|
10438
|
+
relativeDirPath: pluginDirPath,
|
|
9863
10439
|
relativeFilePath: basename(HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_INIT_PATH),
|
|
9864
10440
|
fileContent: "",
|
|
9865
10441
|
global
|
|
@@ -11094,19 +11670,6 @@ var RooCommand = class RooCommand extends ToolCommand {
|
|
|
11094
11670
|
}
|
|
11095
11671
|
};
|
|
11096
11672
|
//#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
11673
|
//#region src/features/commands/rovodev-command.ts
|
|
11111
11674
|
/**
|
|
11112
11675
|
* Rovo Dev CLI "saved prompts": a file-based custom-command surface made of a
|
|
@@ -11690,6 +12253,16 @@ const toolCommandFactories = /* @__PURE__ */ new Map([
|
|
|
11690
12253
|
supportsSubdirectory: false
|
|
11691
12254
|
}
|
|
11692
12255
|
}],
|
|
12256
|
+
["grokcli", {
|
|
12257
|
+
class: GrokcliCommand,
|
|
12258
|
+
meta: {
|
|
12259
|
+
extension: "md",
|
|
12260
|
+
supportsProject: true,
|
|
12261
|
+
supportsGlobal: true,
|
|
12262
|
+
isSimulated: false,
|
|
12263
|
+
supportsSubdirectory: false
|
|
12264
|
+
}
|
|
12265
|
+
}],
|
|
11693
12266
|
["hermesagent", {
|
|
11694
12267
|
class: HermesagentCommand,
|
|
11695
12268
|
meta: {
|
|
@@ -11883,7 +12456,8 @@ var CommandsProcessor = class extends FeatureProcessor {
|
|
|
11883
12456
|
const factory = this.getFactory(this.toolTarget);
|
|
11884
12457
|
await factory.class.validateRulesyncCommands?.({
|
|
11885
12458
|
inputRoot: this.inputRoot,
|
|
11886
|
-
rulesyncCommands
|
|
12459
|
+
rulesyncCommands,
|
|
12460
|
+
logger: this.logger
|
|
11887
12461
|
});
|
|
11888
12462
|
const flattenedPathOrigins = /* @__PURE__ */ new Map();
|
|
11889
12463
|
const toolCommands = rulesyncCommands.map((rulesyncCommand) => {
|
|
@@ -12021,7 +12595,10 @@ var CommandsProcessor = class extends FeatureProcessor {
|
|
|
12021
12595
|
}));
|
|
12022
12596
|
const currentContent = await readFileContentOrNull(configPath);
|
|
12023
12597
|
if (currentContent === null) return changedCount;
|
|
12024
|
-
const nextContent = getDisabledHermesCommandsPluginConfigContent(
|
|
12598
|
+
const nextContent = getDisabledHermesCommandsPluginConfigContent({
|
|
12599
|
+
currentContent,
|
|
12600
|
+
global: this.global
|
|
12601
|
+
});
|
|
12025
12602
|
if (nextContent === currentContent) return changedCount;
|
|
12026
12603
|
if (this.dryRun) this.logger.info(`[DRY RUN] Would write: ${configPath}`);
|
|
12027
12604
|
else await writeFileContent(configPath, nextContent);
|
|
@@ -14483,64 +15060,6 @@ var GooseHooks = class GooseHooks extends ToolHooks {
|
|
|
14483
15060
|
}
|
|
14484
15061
|
};
|
|
14485
15062
|
//#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
15063
|
//#region src/features/hooks/grokcli-hooks.ts
|
|
14545
15064
|
const GROKCLI_CONVERTER_CONFIG = {
|
|
14546
15065
|
supportedEvents: GROKCLI_HOOK_EVENTS,
|
|
@@ -14786,6 +15305,13 @@ var HermesagentHooks = class HermesagentHooks extends ToolHooks {
|
|
|
14786
15305
|
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
14787
15306
|
};
|
|
14788
15307
|
}
|
|
15308
|
+
/**
|
|
15309
|
+
* `config.yaml` under every spelling the global profile root can take.
|
|
15310
|
+
* @see getHermesagentSharedConfigWritePaths
|
|
15311
|
+
*/
|
|
15312
|
+
static getExtraSharedWritePaths() {
|
|
15313
|
+
return getHermesagentSharedConfigWritePaths();
|
|
15314
|
+
}
|
|
14789
15315
|
constructor(params) {
|
|
14790
15316
|
super({
|
|
14791
15317
|
...params,
|
|
@@ -14823,7 +15349,7 @@ var HermesagentHooks = class HermesagentHooks extends ToolHooks {
|
|
|
14823
15349
|
}
|
|
14824
15350
|
setFileContent(fileContent) {
|
|
14825
15351
|
this.fileContent = applySharedConfigPatch({
|
|
14826
|
-
fileKey:
|
|
15352
|
+
fileKey: getHermesagentConfigSharedFileKey({ global: this.global }),
|
|
14827
15353
|
feature: "hooks",
|
|
14828
15354
|
existingContent: fileContent,
|
|
14829
15355
|
patch: parseSharedConfig({
|
|
@@ -15199,8 +15725,38 @@ function getKimiCodeHome() {
|
|
|
15199
15725
|
function getKimiCodeRelativeDirPath({ global, relativeDirPath = "." }) {
|
|
15200
15726
|
return global && getKimiCodeHome() ? relativeDirPath : join(KIMI_CODE_DIR, relativeDirPath);
|
|
15201
15727
|
}
|
|
15728
|
+
/**
|
|
15729
|
+
* Both spellings the shared user `config.toml` can take: under `.kimi-code/`,
|
|
15730
|
+
* or at the root of `KIMI_CODE_HOME` when that override names the profile dir.
|
|
15731
|
+
* Declared unconditionally so the derived shared-file keys — and the drift
|
|
15732
|
+
* guards checked against them — do not depend on the ambient environment.
|
|
15733
|
+
*/
|
|
15734
|
+
function getKimiCodeSharedConfigWritePaths() {
|
|
15735
|
+
return [{
|
|
15736
|
+
relativeDirPath: KIMI_CODE_DIR,
|
|
15737
|
+
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
15738
|
+
}, {
|
|
15739
|
+
relativeDirPath: ".",
|
|
15740
|
+
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
15741
|
+
}];
|
|
15742
|
+
}
|
|
15743
|
+
/**
|
|
15744
|
+
* The `SHARED_CONFIG_OWNERSHIP` key of the `config.toml` actually being written.
|
|
15745
|
+
* Both spellings carry the same declaration, but passing the key of the file
|
|
15746
|
+
* being written keeps the write path and the drift guards on the same entry.
|
|
15747
|
+
*/
|
|
15748
|
+
function getKimiCodeConfigSharedFileKey({ global }) {
|
|
15749
|
+
return sharedConfigFileKey({
|
|
15750
|
+
relativeDirPath: getKimiCodeRelativeDirPath({ global }),
|
|
15751
|
+
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
15752
|
+
});
|
|
15753
|
+
}
|
|
15202
15754
|
function getKimiCodeRulesyncOutputRoot({ nativeOutputRoot, global }) {
|
|
15203
|
-
return
|
|
15755
|
+
return getToolRulesyncOutputRoot({
|
|
15756
|
+
nativeOutputRoot,
|
|
15757
|
+
global,
|
|
15758
|
+
toolHome: getKimiCodeHome
|
|
15759
|
+
});
|
|
15204
15760
|
}
|
|
15205
15761
|
//#endregion
|
|
15206
15762
|
//#region src/features/hooks/kimi-code-hooks.ts
|
|
@@ -15298,13 +15854,20 @@ var KimiCodeHooks = class KimiCodeHooks extends ToolHooks {
|
|
|
15298
15854
|
isDeletable() {
|
|
15299
15855
|
return false;
|
|
15300
15856
|
}
|
|
15857
|
+
/**
|
|
15858
|
+
* `config.toml` under both spellings its directory can take.
|
|
15859
|
+
* @see getKimiCodeSharedConfigWritePaths
|
|
15860
|
+
*/
|
|
15861
|
+
static getExtraSharedWritePaths() {
|
|
15862
|
+
return getKimiCodeSharedConfigWritePaths();
|
|
15863
|
+
}
|
|
15301
15864
|
shouldMergeExistingFileContent() {
|
|
15302
15865
|
return true;
|
|
15303
15866
|
}
|
|
15304
15867
|
setFileContent(fileContent) {
|
|
15305
15868
|
const paths = KimiCodeHooks.getSettablePaths({ global: this.global });
|
|
15306
15869
|
this.fileContent = applySharedConfigPatch({
|
|
15307
|
-
fileKey:
|
|
15870
|
+
fileKey: getKimiCodeConfigSharedFileKey({ global: this.global }),
|
|
15308
15871
|
feature: "hooks",
|
|
15309
15872
|
existingContent: fileContent,
|
|
15310
15873
|
patch: parseSharedConfig({
|
|
@@ -17995,6 +18558,151 @@ var QwencodeIgnore = class QwencodeIgnore extends ToolIgnore {
|
|
|
17995
18558
|
}
|
|
17996
18559
|
};
|
|
17997
18560
|
//#endregion
|
|
18561
|
+
//#region src/features/shared/reasonix-config-table.ts
|
|
18562
|
+
/**
|
|
18563
|
+
* Shape-narrowing helpers for the Reasonix TOML config (`reasonix.toml` /
|
|
18564
|
+
* `~/.reasonix/config.toml`), shared by the features that read-modify-write it.
|
|
18565
|
+
*
|
|
18566
|
+
* TOML is only structurally validated on parse, so a hand-edited config can
|
|
18567
|
+
* hold any type under `permissions` or inside `allow`/`ask`/`deny`. Both the
|
|
18568
|
+
* `permissions` and `ignore` adapters have to narrow the same two shapes
|
|
18569
|
+
* before merging, so the narrowing lives here once rather than being re-spelled
|
|
18570
|
+
* (and re-diverging) per feature.
|
|
18571
|
+
*/
|
|
18572
|
+
/** Keep only the string entries of a TOML array; anything else becomes `[]`. */
|
|
18573
|
+
function toReasonixStringArray(value) {
|
|
18574
|
+
if (!Array.isArray(value)) return [];
|
|
18575
|
+
return value.filter((entry) => typeof entry === "string");
|
|
18576
|
+
}
|
|
18577
|
+
/** Copy a TOML table; a non-table (scalar, array, missing) becomes `{}`. */
|
|
18578
|
+
function toReasonixTable(value) {
|
|
18579
|
+
if (!isPlainObject$1(value)) return {};
|
|
18580
|
+
return { ...value };
|
|
18581
|
+
}
|
|
18582
|
+
//#endregion
|
|
18583
|
+
//#region src/features/ignore/reasonix-ignore.ts
|
|
18584
|
+
const permissionsTableOf = (document) => toReasonixTable(document.permissions);
|
|
18585
|
+
/**
|
|
18586
|
+
* Reshape the parsed TOML document into the `permissions.allow/ask/deny` shape
|
|
18587
|
+
* {@link applyIgnoreReadDenies} operates on. Reasonix's `[permissions]` table
|
|
18588
|
+
* is Claude-Code-shaped (SPEC.md §3.7), so the entry-level ownership rule the
|
|
18589
|
+
* gateway already implements applies verbatim; only the surrounding file
|
|
18590
|
+
* format differs. Sibling keys such as `mode` pass through untouched.
|
|
18591
|
+
*/
|
|
18592
|
+
const asClaudeStyleSettings = (document) => {
|
|
18593
|
+
const table = permissionsTableOf(document);
|
|
18594
|
+
return {
|
|
18595
|
+
...document,
|
|
18596
|
+
permissions: {
|
|
18597
|
+
...table,
|
|
18598
|
+
allow: toReasonixStringArray(table.allow),
|
|
18599
|
+
ask: toReasonixStringArray(table.ask),
|
|
18600
|
+
deny: toReasonixStringArray(table.deny)
|
|
18601
|
+
}
|
|
18602
|
+
};
|
|
18603
|
+
};
|
|
18604
|
+
/**
|
|
18605
|
+
* Drop a `[permissions]` table that ended up with nothing in it, so an empty
|
|
18606
|
+
* `.rulesyncignore` does not add a bare table header to a file that never had
|
|
18607
|
+
* one.
|
|
18608
|
+
*/
|
|
18609
|
+
const withoutEmptyPermissions = (settings) => {
|
|
18610
|
+
const document = { ...settings };
|
|
18611
|
+
const permissions = document.permissions;
|
|
18612
|
+
if (isPlainObject$1(permissions) && Object.keys(permissions).length === 0) delete document.permissions;
|
|
18613
|
+
return document;
|
|
18614
|
+
};
|
|
18615
|
+
/**
|
|
18616
|
+
* Writes `.rulesyncignore` patterns as `Read(<pattern>)` entries in the
|
|
18617
|
+
* `[permissions] deny` table of `reasonix.toml` (project) /
|
|
18618
|
+
* `~/.reasonix/config.toml` (global).
|
|
18619
|
+
*
|
|
18620
|
+
* `deny` is the right target rather than `[sandbox] forbid_read`: deny rules
|
|
18621
|
+
* take glob specifiers (`Edit(docs/**)`) and are "a hard block in every mode",
|
|
18622
|
+
* while `forbid_read` is documented as absolute paths with no glob support.
|
|
18623
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/SPEC.md
|
|
18624
|
+
*/
|
|
18625
|
+
var ReasonixIgnore = class ReasonixIgnore extends ToolIgnore {
|
|
18626
|
+
constructor(params) {
|
|
18627
|
+
super(params);
|
|
18628
|
+
const document = parseSharedConfig({
|
|
18629
|
+
format: "toml",
|
|
18630
|
+
fileContent: this.fileContent
|
|
18631
|
+
});
|
|
18632
|
+
this.patterns = toReasonixStringArray(permissionsTableOf(document).deny);
|
|
18633
|
+
}
|
|
18634
|
+
static getSettablePaths({ global = false } = {}) {
|
|
18635
|
+
return {
|
|
18636
|
+
relativeDirPath: global ? REASONIX_GLOBAL_DIR : ".",
|
|
18637
|
+
relativeFilePath: global ? REASONIX_GLOBAL_PERMISSIONS_FILE_NAME : REASONIX_PROJECT_PERMISSIONS_FILE_NAME
|
|
18638
|
+
};
|
|
18639
|
+
}
|
|
18640
|
+
/**
|
|
18641
|
+
* The config file also carries `[[plugins]]`, `[permissions]` rules from the
|
|
18642
|
+
* permissions feature and user-authored tables, so rulesync must never
|
|
18643
|
+
* delete it.
|
|
18644
|
+
*/
|
|
18645
|
+
isDeletable() {
|
|
18646
|
+
return false;
|
|
18647
|
+
}
|
|
18648
|
+
toRulesyncIgnore() {
|
|
18649
|
+
const rulesyncPatterns = this.patterns.filter((pattern) => isReadDenyEntry(pattern)).map((pattern) => pattern.slice(5, -1)).filter((pattern) => pattern.length > 0);
|
|
18650
|
+
return new RulesyncIgnore({
|
|
18651
|
+
outputRoot: this.outputRoot,
|
|
18652
|
+
relativeDirPath: RulesyncIgnore.getSettablePaths().recommended.relativeDirPath,
|
|
18653
|
+
relativeFilePath: RulesyncIgnore.getSettablePaths().recommended.relativeFilePath,
|
|
18654
|
+
fileContent: rulesyncPatterns.join("\n")
|
|
18655
|
+
});
|
|
18656
|
+
}
|
|
18657
|
+
static async fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore, global = false }) {
|
|
18658
|
+
const readDenies = rulesyncIgnore.getFileContent().split(/\r?\n|\r/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).map((pattern) => buildReadDenyEntry(pattern));
|
|
18659
|
+
const paths = this.getSettablePaths({ global });
|
|
18660
|
+
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
18661
|
+
const existingDocument = parseSharedConfig({
|
|
18662
|
+
format: "toml",
|
|
18663
|
+
fileContent: await readFileContentOrNull(filePath) ?? "",
|
|
18664
|
+
filePath
|
|
18665
|
+
});
|
|
18666
|
+
const document = withoutEmptyPermissions(applyIgnoreReadDenies({
|
|
18667
|
+
settings: asClaudeStyleSettings(existingDocument),
|
|
18668
|
+
readDenies
|
|
18669
|
+
}));
|
|
18670
|
+
return new ReasonixIgnore({
|
|
18671
|
+
outputRoot,
|
|
18672
|
+
relativeDirPath: paths.relativeDirPath,
|
|
18673
|
+
relativeFilePath: paths.relativeFilePath,
|
|
18674
|
+
fileContent: stringifySharedConfig({
|
|
18675
|
+
format: "toml",
|
|
18676
|
+
document
|
|
18677
|
+
}),
|
|
18678
|
+
validate: true,
|
|
18679
|
+
global
|
|
18680
|
+
});
|
|
18681
|
+
}
|
|
18682
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
18683
|
+
const paths = this.getSettablePaths({ global });
|
|
18684
|
+
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
|
|
18685
|
+
return new ReasonixIgnore({
|
|
18686
|
+
outputRoot,
|
|
18687
|
+
relativeDirPath: paths.relativeDirPath,
|
|
18688
|
+
relativeFilePath: paths.relativeFilePath,
|
|
18689
|
+
fileContent,
|
|
18690
|
+
validate,
|
|
18691
|
+
global
|
|
18692
|
+
});
|
|
18693
|
+
}
|
|
18694
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
18695
|
+
return new ReasonixIgnore({
|
|
18696
|
+
outputRoot,
|
|
18697
|
+
relativeDirPath,
|
|
18698
|
+
relativeFilePath,
|
|
18699
|
+
fileContent: "",
|
|
18700
|
+
validate: false,
|
|
18701
|
+
global
|
|
18702
|
+
});
|
|
18703
|
+
}
|
|
18704
|
+
};
|
|
18705
|
+
//#endregion
|
|
17998
18706
|
//#region src/features/ignore/roo-ignore.ts
|
|
17999
18707
|
/**
|
|
18000
18708
|
* RooIgnore represents ignore patterns for the Roo Code AI coding assistant.
|
|
@@ -18152,6 +18860,14 @@ const ZED_GLOBAL_WIN32_DIR = join("AppData", "Roaming", "Zed");
|
|
|
18152
18860
|
function getZedGlobalDir() {
|
|
18153
18861
|
return process.platform === "win32" ? ZED_GLOBAL_WIN32_DIR : ZED_GLOBAL_DIR;
|
|
18154
18862
|
}
|
|
18863
|
+
/**
|
|
18864
|
+
* The global config dir of the OTHER platform. `getZedGlobalDir()` resolves one
|
|
18865
|
+
* spelling per platform, but the shared-write derivation (and the gateway
|
|
18866
|
+
* ownership table it is checked against) must know both on every platform.
|
|
18867
|
+
*/
|
|
18868
|
+
function getZedOtherPlatformGlobalDir() {
|
|
18869
|
+
return process.platform === "win32" ? ZED_GLOBAL_DIR : ZED_GLOBAL_WIN32_DIR;
|
|
18870
|
+
}
|
|
18155
18871
|
const ZED_SETTINGS_FILE_NAME = "settings.json";
|
|
18156
18872
|
const ZED_RULE_FILE_NAME = ".rules";
|
|
18157
18873
|
const ZED_GLOBAL_RULE_FILE_NAME = "AGENTS.md";
|
|
@@ -18164,12 +18880,20 @@ var ZedIgnore = class ZedIgnore extends ToolIgnore {
|
|
|
18164
18880
|
const jsonValue = JSON.parse(this.fileContent);
|
|
18165
18881
|
this.patterns = jsonValue.private_files ?? [];
|
|
18166
18882
|
}
|
|
18167
|
-
static getSettablePaths() {
|
|
18883
|
+
static getSettablePaths({ global = false } = {}) {
|
|
18168
18884
|
return {
|
|
18169
|
-
relativeDirPath: ZED_DIR,
|
|
18885
|
+
relativeDirPath: global ? getZedGlobalDir() : ZED_DIR,
|
|
18170
18886
|
relativeFilePath: ZED_SETTINGS_FILE_NAME
|
|
18171
18887
|
};
|
|
18172
18888
|
}
|
|
18889
|
+
/** @see getZedOtherPlatformGlobalDir */
|
|
18890
|
+
static getExtraSharedWritePaths({ global = false } = {}) {
|
|
18891
|
+
if (!global) return [];
|
|
18892
|
+
return [{
|
|
18893
|
+
relativeDirPath: getZedOtherPlatformGlobalDir(),
|
|
18894
|
+
relativeFilePath: ZED_SETTINGS_FILE_NAME
|
|
18895
|
+
}];
|
|
18896
|
+
}
|
|
18173
18897
|
/**
|
|
18174
18898
|
* ZedIgnore uses settings.json which is a user-managed config file.
|
|
18175
18899
|
* It should not be deleted by rulesync.
|
|
@@ -18180,48 +18904,53 @@ var ZedIgnore = class ZedIgnore extends ToolIgnore {
|
|
|
18180
18904
|
toRulesyncIgnore() {
|
|
18181
18905
|
const fileContent = this.patterns.filter((pattern) => pattern.length > 0).join("\n");
|
|
18182
18906
|
return new RulesyncIgnore({
|
|
18183
|
-
outputRoot:
|
|
18907
|
+
outputRoot: ".",
|
|
18184
18908
|
relativeDirPath: RulesyncIgnore.getSettablePaths().recommended.relativeDirPath,
|
|
18185
18909
|
relativeFilePath: RulesyncIgnore.getSettablePaths().recommended.relativeFilePath,
|
|
18186
18910
|
fileContent
|
|
18187
18911
|
});
|
|
18188
18912
|
}
|
|
18189
|
-
static async fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore }) {
|
|
18913
|
+
static async fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore, global = false }) {
|
|
18190
18914
|
const patterns = rulesyncIgnore.getFileContent().split(/\r?\n|\r/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
18191
|
-
const
|
|
18915
|
+
const paths = this.getSettablePaths({ global });
|
|
18916
|
+
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
18192
18917
|
const existingFileContent = await fileExists(filePath) ? await readFileContent(filePath) : "{}";
|
|
18193
|
-
const
|
|
18918
|
+
const managedPatterns = patterns.length > 0 ? [...new Set(patterns)].toSorted() : void 0;
|
|
18194
18919
|
return new ZedIgnore({
|
|
18195
18920
|
outputRoot,
|
|
18196
|
-
relativeDirPath:
|
|
18197
|
-
relativeFilePath:
|
|
18921
|
+
relativeDirPath: paths.relativeDirPath,
|
|
18922
|
+
relativeFilePath: paths.relativeFilePath,
|
|
18198
18923
|
fileContent: applySharedConfigPatch({
|
|
18199
|
-
fileKey: sharedConfigFileKey(
|
|
18924
|
+
fileKey: sharedConfigFileKey(paths),
|
|
18200
18925
|
feature: "ignore",
|
|
18201
18926
|
existingContent: existingFileContent,
|
|
18202
|
-
patch: { private_files:
|
|
18927
|
+
patch: { private_files: managedPatterns },
|
|
18203
18928
|
filePath
|
|
18204
18929
|
}),
|
|
18205
|
-
validate: true
|
|
18930
|
+
validate: true,
|
|
18931
|
+
global
|
|
18206
18932
|
});
|
|
18207
18933
|
}
|
|
18208
|
-
static async fromFile({ outputRoot = process.cwd(), validate = true }) {
|
|
18209
|
-
const
|
|
18934
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
18935
|
+
const paths = this.getSettablePaths({ global });
|
|
18936
|
+
const fileContent = await readFileContent(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath));
|
|
18210
18937
|
return new ZedIgnore({
|
|
18211
18938
|
outputRoot,
|
|
18212
|
-
relativeDirPath:
|
|
18213
|
-
relativeFilePath:
|
|
18939
|
+
relativeDirPath: paths.relativeDirPath,
|
|
18940
|
+
relativeFilePath: paths.relativeFilePath,
|
|
18214
18941
|
fileContent,
|
|
18215
|
-
validate
|
|
18942
|
+
validate,
|
|
18943
|
+
global
|
|
18216
18944
|
});
|
|
18217
18945
|
}
|
|
18218
|
-
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
18946
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
18219
18947
|
return new ZedIgnore({
|
|
18220
18948
|
outputRoot,
|
|
18221
18949
|
relativeDirPath,
|
|
18222
18950
|
relativeFilePath,
|
|
18223
18951
|
fileContent: "{}",
|
|
18224
|
-
validate: false
|
|
18952
|
+
validate: false,
|
|
18953
|
+
global
|
|
18225
18954
|
});
|
|
18226
18955
|
}
|
|
18227
18956
|
};
|
|
@@ -18243,6 +18972,7 @@ const toolIgnoreFactories = /* @__PURE__ */ new Map([
|
|
|
18243
18972
|
["kiro-cli", { class: KiroIgnore }],
|
|
18244
18973
|
["kiro-ide", { class: KiroIgnore }],
|
|
18245
18974
|
["qwencode", { class: QwencodeIgnore }],
|
|
18975
|
+
["reasonix", { class: ReasonixIgnore }],
|
|
18246
18976
|
["roo", { class: RooIgnore }],
|
|
18247
18977
|
["devin", { class: DevinIgnore }],
|
|
18248
18978
|
["vibe", { class: VibeIgnore }],
|
|
@@ -18253,7 +18983,9 @@ const ignoreProcessorToolTargets = [...toolIgnoreFactories.keys()];
|
|
|
18253
18983
|
const ignoreProcessorGlobalToolTargets = [
|
|
18254
18984
|
"kiro",
|
|
18255
18985
|
"kiro-cli",
|
|
18256
|
-
"kiro-ide"
|
|
18986
|
+
"kiro-ide",
|
|
18987
|
+
"reasonix",
|
|
18988
|
+
"zed"
|
|
18257
18989
|
];
|
|
18258
18990
|
const defaultGetFactory$4 = (target) => {
|
|
18259
18991
|
const factory = toolIgnoreFactories.get(target);
|
|
@@ -20849,7 +21581,7 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
20849
21581
|
}), isRecord(this.config.mcp_servers) ? this.config.mcp_servers : {});
|
|
20850
21582
|
this.config = merged;
|
|
20851
21583
|
super.setFileContent(applySharedConfigPatch({
|
|
20852
|
-
fileKey:
|
|
21584
|
+
fileKey: getHermesagentConfigSharedFileKey({ global: this.global }),
|
|
20853
21585
|
feature: "mcp",
|
|
20854
21586
|
existingContent: fileContent,
|
|
20855
21587
|
patch: { mcp_servers: merged.mcp_servers }
|
|
@@ -20867,6 +21599,13 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
20867
21599
|
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
20868
21600
|
};
|
|
20869
21601
|
}
|
|
21602
|
+
/**
|
|
21603
|
+
* `config.yaml` under every spelling the global profile root can take.
|
|
21604
|
+
* @see getHermesagentSharedConfigWritePaths
|
|
21605
|
+
*/
|
|
21606
|
+
static getExtraSharedWritePaths() {
|
|
21607
|
+
return getHermesagentSharedConfigWritePaths();
|
|
21608
|
+
}
|
|
20870
21609
|
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
20871
21610
|
if (!global) throw new Error(HERMESAGENT_GLOBAL_ONLY_MESSAGE);
|
|
20872
21611
|
const paths = this.getSettablePaths({ global });
|
|
@@ -20893,7 +21632,7 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
20893
21632
|
relativeDirPath: paths.relativeDirPath,
|
|
20894
21633
|
relativeFilePath: paths.relativeFilePath,
|
|
20895
21634
|
fileContent: applySharedConfigPatch({
|
|
20896
|
-
fileKey:
|
|
21635
|
+
fileKey: getHermesagentConfigSharedFileKey({ global }),
|
|
20897
21636
|
feature: "mcp",
|
|
20898
21637
|
existingContent: fileContent,
|
|
20899
21638
|
patch: { mcp_servers: merged.mcp_servers }
|
|
@@ -21354,9 +22093,12 @@ var KiloMcp = class KiloMcp extends ToolMcp {
|
|
|
21354
22093
|
* Merge a list of project rule file globs into the `instructions` array of the
|
|
21355
22094
|
* shared `kilo.jsonc` (or `kilo.json`) config, preserving every existing key
|
|
21356
22095
|
* (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
|
-
*
|
|
22096
|
+
* a *project* `.kilo/rules/` are NOT auto-loaded; they are only picked up
|
|
22097
|
+
* when listed in the `instructions` key. (The home-scope `~/.kilo/rules/` is
|
|
22098
|
+
* different — the rules migrator's `globalRulesDirs()` walks it on every
|
|
22099
|
+
* config load — which is why `KiloRule` registers instructions in project
|
|
22100
|
+
* scope only.) The resulting `instructions` list is deduped and sorted for a
|
|
22101
|
+
* stable output.
|
|
21360
22102
|
*
|
|
21361
22103
|
* @see https://kilo.ai/docs/automate/mcp/using-in-kilo-code
|
|
21362
22104
|
*/
|
|
@@ -21569,7 +22311,7 @@ var KimiCodeMcpConfigToml = class KimiCodeMcpConfigToml extends ToolFile {
|
|
|
21569
22311
|
const existingContent = existing.content;
|
|
21570
22312
|
const existingSection = existing.mcp;
|
|
21571
22313
|
const fileContent = applySharedConfigPatch({
|
|
21572
|
-
fileKey:
|
|
22314
|
+
fileKey: getKimiCodeConfigSharedFileKey({ global: true }),
|
|
21573
22315
|
feature: "mcp",
|
|
21574
22316
|
existingContent,
|
|
21575
22317
|
patch: { mcp: {
|
|
@@ -21656,11 +22398,8 @@ var KimiCodeMcp = class KimiCodeMcp extends ToolMcp {
|
|
|
21656
22398
|
* derivation sees this feature as one of that file's writers — it is not a
|
|
21657
22399
|
* settable path, since the servers themselves live in `mcp.json`.
|
|
21658
22400
|
*/
|
|
21659
|
-
static getExtraSharedWritePaths(
|
|
21660
|
-
return
|
|
21661
|
-
relativeDirPath: getKimiCodeRelativeDirPath({ global: true }),
|
|
21662
|
-
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
21663
|
-
}] : [];
|
|
22401
|
+
static getExtraSharedWritePaths() {
|
|
22402
|
+
return getKimiCodeSharedConfigWritePaths();
|
|
21664
22403
|
}
|
|
21665
22404
|
/**
|
|
21666
22405
|
* The `[mcp]` defaults live in the shared user `config.toml`, not in
|
|
@@ -23296,16 +24035,11 @@ var ZedMcp = class ZedMcp extends ToolMcp {
|
|
|
23296
24035
|
relativeFilePath: ZED_SETTINGS_FILE_NAME
|
|
23297
24036
|
};
|
|
23298
24037
|
}
|
|
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
|
-
*/
|
|
24038
|
+
/** @see getZedOtherPlatformGlobalDir */
|
|
23305
24039
|
static getExtraSharedWritePaths({ global = false } = {}) {
|
|
23306
24040
|
if (!global) return [];
|
|
23307
24041
|
return [{
|
|
23308
|
-
relativeDirPath:
|
|
24042
|
+
relativeDirPath: getZedOtherPlatformGlobalDir(),
|
|
23309
24043
|
relativeFilePath: ZED_SETTINGS_FILE_NAME
|
|
23310
24044
|
}];
|
|
23311
24045
|
}
|
|
@@ -27735,6 +28469,13 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
27735
28469
|
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
27736
28470
|
};
|
|
27737
28471
|
}
|
|
28472
|
+
/**
|
|
28473
|
+
* `config.yaml` under every spelling the global profile root can take.
|
|
28474
|
+
* @see getHermesagentSharedConfigWritePaths
|
|
28475
|
+
*/
|
|
28476
|
+
static getExtraSharedWritePaths() {
|
|
28477
|
+
return getHermesagentSharedConfigWritePaths();
|
|
28478
|
+
}
|
|
27738
28479
|
constructor(params) {
|
|
27739
28480
|
super({
|
|
27740
28481
|
...params,
|
|
@@ -27772,7 +28513,7 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
27772
28513
|
}
|
|
27773
28514
|
setFileContent(fileContent) {
|
|
27774
28515
|
this.fileContent = applySharedConfigPatch({
|
|
27775
|
-
fileKey:
|
|
28516
|
+
fileKey: getHermesagentConfigSharedFileKey({ global: this.global }),
|
|
27776
28517
|
feature: "permissions",
|
|
27777
28518
|
existingContent: fileContent,
|
|
27778
28519
|
patch: parseSharedConfig({
|
|
@@ -28185,7 +28926,10 @@ const KiloPermissionSchema = z.union([z.enum([
|
|
|
28185
28926
|
"ask",
|
|
28186
28927
|
"deny"
|
|
28187
28928
|
]))]);
|
|
28188
|
-
const KiloPermissionsConfigSchema = z.looseObject({
|
|
28929
|
+
const KiloPermissionsConfigSchema = z.looseObject({
|
|
28930
|
+
permission: z.optional(z.record(z.string(), KiloPermissionSchema)),
|
|
28931
|
+
sandbox: z.optional(z.unknown())
|
|
28932
|
+
});
|
|
28189
28933
|
/**
|
|
28190
28934
|
* Kilo permission keys that share a name with a canonical rulesync category and
|
|
28191
28935
|
* therefore stay in the shared `permission` block. Everything else (Kilo-only
|
|
@@ -28242,6 +28986,25 @@ function collectKiloDenyPatterns(value) {
|
|
|
28242
28986
|
}
|
|
28243
28987
|
return [];
|
|
28244
28988
|
}
|
|
28989
|
+
function asKiloRecord(value) {
|
|
28990
|
+
return isPlainObject$1(value) ? { ...value } : {};
|
|
28991
|
+
}
|
|
28992
|
+
/**
|
|
28993
|
+
* The `sandbox` keys a *project* `kilo.jsonc` may state. Kilo honors
|
|
28994
|
+
* `allowed_hosts` and `writable_paths` from the global config only, and lets a
|
|
28995
|
+
* project config merely tighten — so writing the wider keys into a project file
|
|
28996
|
+
* would produce config Kilo ignores.
|
|
28997
|
+
* @see https://kilo.ai/docs/getting-started/settings/sandboxing
|
|
28998
|
+
*/
|
|
28999
|
+
const KILO_PROJECT_SCOPE_SANDBOX_KEYS = /* @__PURE__ */ new Set(["enabled", "network"]);
|
|
29000
|
+
function narrowSandboxToProjectScope({ authored, logger }) {
|
|
29001
|
+
const emitted = {};
|
|
29002
|
+
const dropped = [];
|
|
29003
|
+
for (const [key, value] of Object.entries(authored)) if (KILO_PROJECT_SCOPE_SANDBOX_KEYS.has(key)) emitted[key] = value;
|
|
29004
|
+
else dropped.push(key);
|
|
29005
|
+
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.`);
|
|
29006
|
+
return emitted;
|
|
29007
|
+
}
|
|
28245
29008
|
var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
28246
29009
|
json;
|
|
28247
29010
|
constructor(params) {
|
|
@@ -28321,8 +29084,7 @@ var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
|
28321
29084
|
const basePaths = KiloPermissions.getSettablePaths({ global });
|
|
28322
29085
|
const filePath = join(outputRoot, basePaths.relativeDirPath, basePaths.relativeFilePath);
|
|
28323
29086
|
const parsed = parseKiloJsoncStrict(await readFileContentOrNull(filePath) ?? "{}", filePath);
|
|
28324
|
-
const
|
|
28325
|
-
const existingPermission = parsedPermission && typeof parsedPermission === "object" && !Array.isArray(parsedPermission) ? { ...parsedPermission } : {};
|
|
29087
|
+
const existingPermission = asKiloRecord(parsed.permission);
|
|
28326
29088
|
const rulesyncJson = rulesyncPermissions.getJson();
|
|
28327
29089
|
const kiloOverride = rulesyncJson.kilo;
|
|
28328
29090
|
const incomingPermission = {
|
|
@@ -28348,6 +29110,18 @@ var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
|
28348
29110
|
...parsed,
|
|
28349
29111
|
permission: mergedPermission
|
|
28350
29112
|
};
|
|
29113
|
+
if (kiloOverride?.sandbox !== void 0) {
|
|
29114
|
+
const authored = asKiloRecord(kiloOverride.sandbox);
|
|
29115
|
+
const emitted = global ? authored : narrowSandboxToProjectScope({
|
|
29116
|
+
authored,
|
|
29117
|
+
logger
|
|
29118
|
+
});
|
|
29119
|
+
const merged = {
|
|
29120
|
+
...asKiloRecord(parsed.sandbox),
|
|
29121
|
+
...emitted
|
|
29122
|
+
};
|
|
29123
|
+
if (Object.keys(merged).length > 0) nextJson.sandbox = merged;
|
|
29124
|
+
}
|
|
28351
29125
|
return new KiloPermissions({
|
|
28352
29126
|
outputRoot,
|
|
28353
29127
|
relativeDirPath: basePaths.relativeDirPath,
|
|
@@ -28362,9 +29136,14 @@ var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
|
28362
29136
|
const overrideOnly = {};
|
|
28363
29137
|
for (const [key, value] of Object.entries(rawPermission)) if (isSharedKiloCategory(key)) shared[key] = typeof value === "string" ? { "*": value } : value;
|
|
28364
29138
|
else overrideOnly[key] = value;
|
|
28365
|
-
const
|
|
29139
|
+
const sandbox = this.json.sandbox;
|
|
29140
|
+
const override = {
|
|
29141
|
+
...Object.keys(overrideOnly).length > 0 && { permission: overrideOnly },
|
|
29142
|
+
...isPlainObject$1(sandbox) && { sandbox }
|
|
29143
|
+
};
|
|
29144
|
+
const json = Object.keys(override).length > 0 ? {
|
|
28366
29145
|
permission: shared,
|
|
28367
|
-
kilo:
|
|
29146
|
+
kilo: override
|
|
28368
29147
|
} : { permission: shared };
|
|
28369
29148
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(json, null, 2) });
|
|
28370
29149
|
}
|
|
@@ -28605,6 +29384,13 @@ var KimiCodePermissions = class KimiCodePermissions extends ToolPermissions {
|
|
|
28605
29384
|
isDeletable() {
|
|
28606
29385
|
return false;
|
|
28607
29386
|
}
|
|
29387
|
+
/**
|
|
29388
|
+
* `config.toml` under both spellings its directory can take.
|
|
29389
|
+
* @see getKimiCodeSharedConfigWritePaths
|
|
29390
|
+
*/
|
|
29391
|
+
static getExtraSharedWritePaths() {
|
|
29392
|
+
return getKimiCodeSharedConfigWritePaths();
|
|
29393
|
+
}
|
|
28608
29394
|
shouldMergeExistingFileContent() {
|
|
28609
29395
|
return true;
|
|
28610
29396
|
}
|
|
@@ -28619,7 +29405,7 @@ var KimiCodePermissions = class KimiCodePermissions extends ToolPermissions {
|
|
|
28619
29405
|
patch
|
|
28620
29406
|
});
|
|
28621
29407
|
this.fileContent = applySharedConfigPatch({
|
|
28622
|
-
fileKey:
|
|
29408
|
+
fileKey: getKimiCodeConfigSharedFileKey({ global: this.global }),
|
|
28623
29409
|
feature: "permissions",
|
|
28624
29410
|
existingContent: fileContent,
|
|
28625
29411
|
patch: {
|
|
@@ -29543,14 +30329,6 @@ function parseReasonixConfig(fileContent) {
|
|
|
29543
30329
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
29544
30330
|
return { ...parsed };
|
|
29545
30331
|
}
|
|
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
30332
|
const REASONIX_OVERRIDE_AGENT_KEYS = ["plan_mode_read_only_commands"];
|
|
29555
30333
|
/**
|
|
29556
30334
|
* `[agent]` keys an older `reasonix.toml` may carry that left the documented
|
|
@@ -29607,12 +30385,12 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
29607
30385
|
const config = rulesyncPermissions.getJson();
|
|
29608
30386
|
const { allow, ask, deny } = convertRulesyncToReasonixPermissions(config);
|
|
29609
30387
|
const managedToolNames = new Set(Object.keys(config.permission).map((category) => toReasonixToolName(category)));
|
|
29610
|
-
const existingPermissions =
|
|
29611
|
-
const preservedAllow =
|
|
29612
|
-
const preservedAsk =
|
|
29613
|
-
const preservedDeny =
|
|
30388
|
+
const existingPermissions = toReasonixTable(parsed.permissions);
|
|
30389
|
+
const preservedAllow = toReasonixStringArray(existingPermissions.allow).filter((entry) => !managedToolNames.has(parseReasonixPermissionEntry(entry).toolName));
|
|
30390
|
+
const preservedAsk = toReasonixStringArray(existingPermissions.ask).filter((entry) => !managedToolNames.has(parseReasonixPermissionEntry(entry).toolName));
|
|
30391
|
+
const preservedDeny = toReasonixStringArray(existingPermissions.deny).filter((entry) => !managedToolNames.has(parseReasonixPermissionEntry(entry).toolName));
|
|
29614
30392
|
if (logger && managedToolNames.has("Read")) {
|
|
29615
|
-
const droppedReadDenyEntries =
|
|
30393
|
+
const droppedReadDenyEntries = toReasonixStringArray(existingPermissions.deny).filter((entry) => {
|
|
29616
30394
|
const { toolName } = parseReasonixPermissionEntry(entry);
|
|
29617
30395
|
return toolName === "Read";
|
|
29618
30396
|
});
|
|
@@ -29659,11 +30437,11 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
29659
30437
|
});
|
|
29660
30438
|
}
|
|
29661
30439
|
toRulesyncPermissions() {
|
|
29662
|
-
const permissions =
|
|
30440
|
+
const permissions = toReasonixTable(this.toml.permissions);
|
|
29663
30441
|
const config = convertReasonixToRulesyncPermissions({
|
|
29664
|
-
allow:
|
|
29665
|
-
ask:
|
|
29666
|
-
deny:
|
|
30442
|
+
allow: toReasonixStringArray(permissions.allow),
|
|
30443
|
+
ask: toReasonixStringArray(permissions.ask),
|
|
30444
|
+
deny: toReasonixStringArray(permissions.deny)
|
|
29667
30445
|
});
|
|
29668
30446
|
const sandbox = asReasonixRecord(this.toml.sandbox);
|
|
29669
30447
|
const agentPlanMode = pickReasonixKeys(this.toml.agent, [...REASONIX_OVERRIDE_AGENT_KEYS, ...REASONIX_RETIRED_AGENT_KEYS]);
|
|
@@ -29755,19 +30533,31 @@ const CATEGORY_TO_TOOL_KEYS = {
|
|
|
29755
30533
|
"open_files",
|
|
29756
30534
|
"expand_code_chunks",
|
|
29757
30535
|
"expand_folder",
|
|
29758
|
-
"grep"
|
|
30536
|
+
"grep",
|
|
30537
|
+
"getJiraIssue",
|
|
30538
|
+
"getConfluencePage"
|
|
29759
30539
|
],
|
|
29760
30540
|
edit: [
|
|
29761
30541
|
"find_and_replace_code",
|
|
29762
30542
|
"create_file",
|
|
29763
30543
|
"delete_file",
|
|
29764
|
-
"move_file"
|
|
30544
|
+
"move_file",
|
|
30545
|
+
"createTechnicalPlan",
|
|
30546
|
+
"createJiraIssue",
|
|
30547
|
+
"updateJiraIssue",
|
|
30548
|
+
"createConfluencePage",
|
|
30549
|
+
"updateConfluencePage"
|
|
29765
30550
|
],
|
|
29766
30551
|
write: [
|
|
29767
30552
|
"create_file",
|
|
29768
30553
|
"delete_file",
|
|
29769
30554
|
"move_file",
|
|
29770
|
-
"find_and_replace_code"
|
|
30555
|
+
"find_and_replace_code",
|
|
30556
|
+
"createTechnicalPlan",
|
|
30557
|
+
"createJiraIssue",
|
|
30558
|
+
"updateJiraIssue",
|
|
30559
|
+
"createConfluencePage",
|
|
30560
|
+
"updateConfluencePage"
|
|
29771
30561
|
]
|
|
29772
30562
|
};
|
|
29773
30563
|
const TOOL_KEY_TO_CATEGORY = {
|
|
@@ -29775,13 +30565,24 @@ const TOOL_KEY_TO_CATEGORY = {
|
|
|
29775
30565
|
expand_code_chunks: "read",
|
|
29776
30566
|
expand_folder: "read",
|
|
29777
30567
|
grep: "read",
|
|
30568
|
+
getJiraIssue: "read",
|
|
30569
|
+
getConfluencePage: "read",
|
|
29778
30570
|
find_and_replace_code: "edit",
|
|
29779
30571
|
create_file: "edit",
|
|
29780
30572
|
delete_file: "edit",
|
|
29781
|
-
move_file: "edit"
|
|
30573
|
+
move_file: "edit",
|
|
30574
|
+
createTechnicalPlan: "edit",
|
|
30575
|
+
createJiraIssue: "edit",
|
|
30576
|
+
updateJiraIssue: "edit",
|
|
30577
|
+
createConfluencePage: "edit",
|
|
30578
|
+
updateConfluencePage: "edit"
|
|
29782
30579
|
};
|
|
29783
30580
|
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 = [
|
|
30581
|
+
const OWNED_TOOL_PERMISSION_KEYS = [
|
|
30582
|
+
"bash",
|
|
30583
|
+
"allowedExternalPaths",
|
|
30584
|
+
"default"
|
|
30585
|
+
];
|
|
29785
30586
|
/**
|
|
29786
30587
|
* Permissions adapter for Rovo Dev CLI.
|
|
29787
30588
|
*
|
|
@@ -29795,9 +30596,16 @@ const OWNED_TOOL_PERMISSION_KEYS = ["bash", "allowedExternalPaths"];
|
|
|
29795
30596
|
* Mapping decisions (rulesync canonical -> Rovo Dev):
|
|
29796
30597
|
* - `bash`: the catch-all `*` pattern -> `bash.default`; every other pattern ->
|
|
29797
30598
|
* a `bash.commands[]` entry `{ command: <pattern as regex>, permission }`.
|
|
30599
|
+
* - the all-tools category `*`: its catch-all -> `toolPermissions.default`,
|
|
30600
|
+
* the level Rovo Dev falls back to for any tool with no more specific
|
|
30601
|
+
* setting (Rovo Dev's own default is `ask`).
|
|
29798
30602
|
* - `read` -> the inspection tools (`open_files`, `expand_code_chunks`,
|
|
29799
|
-
* `expand_folder`, `grep`
|
|
29800
|
-
*
|
|
30603
|
+
* `expand_folder`, `grep`, `getJiraIssue`, `getConfluencePage`);
|
|
30604
|
+
* `edit`/`write` -> the mutation tools (`find_and_replace_code`,
|
|
30605
|
+
* `create_file`, `delete_file`, `move_file`, `createTechnicalPlan`,
|
|
30606
|
+
* `createJiraIssue`, `updateJiraIssue`, `createConfluencePage`,
|
|
30607
|
+
* `updateConfluencePage`) — so these two categories reach Jira and
|
|
30608
|
+
* Confluence, not just the working tree.
|
|
29801
30609
|
* These Rovo Dev keys hold a single level (no per-pattern rules), so only the
|
|
29802
30610
|
* catch-all `*` of each category sets the level. Non-catch-all `allow` rules
|
|
29803
30611
|
* in those categories are surfaced as `allowedExternalPaths` so explicit path
|
|
@@ -29977,6 +30785,10 @@ function stripPermissiveOwnedValues(toolPermissions) {
|
|
|
29977
30785
|
delete toolPermissions.allowedExternalPaths;
|
|
29978
30786
|
strippedKeys.push("allowedExternalPaths");
|
|
29979
30787
|
}
|
|
30788
|
+
if (toolPermissions.default === "allow") {
|
|
30789
|
+
delete toolPermissions.default;
|
|
30790
|
+
strippedKeys.push("default");
|
|
30791
|
+
}
|
|
29980
30792
|
const bash = toolPermissions.bash;
|
|
29981
30793
|
if (isRecord(bash)) {
|
|
29982
30794
|
const stripped = { ...bash };
|
|
@@ -30001,10 +30813,19 @@ function stripPermissiveOwnedValues(toolPermissions) {
|
|
|
30001
30813
|
function convertRulesyncToRovodevToolPermissions({ config, logger }) {
|
|
30002
30814
|
const toolPermissions = {};
|
|
30003
30815
|
const allowedExternalPaths = [];
|
|
30004
|
-
|
|
30005
|
-
|
|
30006
|
-
|
|
30816
|
+
warnOnEditWriteConflict({
|
|
30817
|
+
config,
|
|
30818
|
+
logger
|
|
30819
|
+
});
|
|
30007
30820
|
for (const [category, rules] of Object.entries(config.permission)) {
|
|
30821
|
+
if (category === CATCH_ALL_PATTERN$1) {
|
|
30822
|
+
const toolWideDefault = convertAllToolsRules({
|
|
30823
|
+
rules,
|
|
30824
|
+
logger
|
|
30825
|
+
});
|
|
30826
|
+
if (toolWideDefault) toolPermissions.default = toolWideDefault;
|
|
30827
|
+
continue;
|
|
30828
|
+
}
|
|
30008
30829
|
if (category === "bash") {
|
|
30009
30830
|
const bash = convertBashRules(rules);
|
|
30010
30831
|
if (bash) toolPermissions.bash = bash;
|
|
@@ -30031,6 +30852,36 @@ function convertRulesyncToRovodevToolPermissions({ config, logger }) {
|
|
|
30031
30852
|
if (allowedExternalPaths.length > 0) toolPermissions.allowedExternalPaths = [...new Set(allowedExternalPaths)].toSorted();
|
|
30032
30853
|
return toolPermissions;
|
|
30033
30854
|
}
|
|
30855
|
+
/**
|
|
30856
|
+
* `edit` and `write` collapse onto the same Rovo Dev file-mutation tools, so a
|
|
30857
|
+
* conflicting catch-all between them cannot be represented. Warn that the loss
|
|
30858
|
+
* is happening; the conversion keeps the stricter of the two — the same
|
|
30859
|
+
* fail-closed rule the import direction uses when those tools disagree, so the
|
|
30860
|
+
* resolution never grants more than the author asked for.
|
|
30861
|
+
*/
|
|
30862
|
+
function warnOnEditWriteConflict({ config, logger }) {
|
|
30863
|
+
const editCatchAll = config.permission.edit?.[CATCH_ALL_PATTERN$1];
|
|
30864
|
+
const writeCatchAll = config.permission.write?.[CATCH_ALL_PATTERN$1];
|
|
30865
|
+
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.`);
|
|
30866
|
+
}
|
|
30867
|
+
/**
|
|
30868
|
+
* The canonical all-tools category. Its catch-all sets the tool-wide
|
|
30869
|
+
* `toolPermissions.default`, the same way `bash`'s catch-all sets
|
|
30870
|
+
* `bash.default` — both are the level Rovo Dev falls back to. Pattern rules
|
|
30871
|
+
* under `*` have no counterpart (the default is a single level), so they are
|
|
30872
|
+
* reported and skipped like any other rule Rovo Dev cannot express.
|
|
30873
|
+
*/
|
|
30874
|
+
function convertAllToolsRules({ rules, logger }) {
|
|
30875
|
+
let toolWideDefault;
|
|
30876
|
+
for (const [pattern, action] of Object.entries(rules)) {
|
|
30877
|
+
if (pattern === CATCH_ALL_PATTERN$1) {
|
|
30878
|
+
toolWideDefault = action;
|
|
30879
|
+
continue;
|
|
30880
|
+
}
|
|
30881
|
+
logger?.warn(`Rovo Dev's tool-wide default is a single level, so it cannot express the pattern "${pattern}" in the "*" category. Skipping it.`);
|
|
30882
|
+
}
|
|
30883
|
+
return toolWideDefault;
|
|
30884
|
+
}
|
|
30034
30885
|
function convertBashRules(rules) {
|
|
30035
30886
|
const bash = {};
|
|
30036
30887
|
const commands = [];
|
|
@@ -30052,6 +30903,7 @@ function convertBashRules(rules) {
|
|
|
30052
30903
|
*/
|
|
30053
30904
|
function convertRovodevToolPermissionsToRulesync(toolPermissions) {
|
|
30054
30905
|
const permission = {};
|
|
30906
|
+
if (isPermissionAction(toolPermissions.default)) permission[CATCH_ALL_PATTERN$1] = { [CATCH_ALL_PATTERN$1]: toolPermissions.default };
|
|
30055
30907
|
const bash = toolPermissions.bash;
|
|
30056
30908
|
if (isRecord(bash)) {
|
|
30057
30909
|
const bashRules = {};
|
|
@@ -30062,12 +30914,15 @@ function convertRovodevToolPermissionsToRulesync(toolPermissions) {
|
|
|
30062
30914
|
if (Object.keys(bashRules).length > 0) permission.bash = bashRules;
|
|
30063
30915
|
}
|
|
30064
30916
|
const nestedTools = isRecord(toolPermissions.tools) ? toolPermissions.tools : {};
|
|
30065
|
-
|
|
30066
|
-
|
|
30067
|
-
|
|
30917
|
+
const implicitLevel = isPermissionAction(toolPermissions.default) ? toolPermissions.default : "ask";
|
|
30918
|
+
for (const category of new Set(Object.values(TOOL_KEY_TO_CATEGORY))) {
|
|
30919
|
+
const levels = Object.entries(TOOL_KEY_TO_CATEGORY).filter(([, mapped]) => mapped === category).map(([toolKey]) => {
|
|
30920
|
+
const value = Object.hasOwn(nestedTools, toolKey) ? nestedTools[toolKey] : toolPermissions[toolKey];
|
|
30921
|
+
return isPermissionAction(value) ? value : void 0;
|
|
30922
|
+
});
|
|
30923
|
+
if (levels.every((level) => level === void 0)) continue;
|
|
30068
30924
|
permission[category] ??= {};
|
|
30069
|
-
|
|
30070
|
-
permission[category][CATCH_ALL_PATTERN$1] = strictestAction(current, value);
|
|
30925
|
+
permission[category][CATCH_ALL_PATTERN$1] = levels.reduce((strictest, level) => strictestAction(strictest, level ?? implicitLevel), permission[category][CATCH_ALL_PATTERN$1]);
|
|
30071
30926
|
}
|
|
30072
30927
|
if (isStringArray$1(toolPermissions.allowedExternalPaths)) for (const path of toolPermissions.allowedExternalPaths) {
|
|
30073
30928
|
permission.read ??= {};
|
|
@@ -30899,16 +31754,11 @@ var ZedPermissions = class ZedPermissions extends ToolPermissions {
|
|
|
30899
31754
|
relativeFilePath: ZED_SETTINGS_FILE_NAME
|
|
30900
31755
|
};
|
|
30901
31756
|
}
|
|
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
|
-
*/
|
|
31757
|
+
/** @see getZedOtherPlatformGlobalDir */
|
|
30908
31758
|
static getExtraSharedWritePaths({ global = false } = {}) {
|
|
30909
31759
|
if (!global) return [];
|
|
30910
31760
|
return [{
|
|
30911
|
-
relativeDirPath:
|
|
31761
|
+
relativeDirPath: getZedOtherPlatformGlobalDir(),
|
|
30912
31762
|
relativeFilePath: ZED_SETTINGS_FILE_NAME
|
|
30913
31763
|
}];
|
|
30914
31764
|
}
|
|
@@ -39480,7 +40330,7 @@ def register(ctx):
|
|
|
39480
40330
|
_register_subagent(ctx, subagent)
|
|
39481
40331
|
`;
|
|
39482
40332
|
}
|
|
39483
|
-
function getEnabledPluginConfigContent(currentContent) {
|
|
40333
|
+
function getEnabledPluginConfigContent({ currentContent, global }) {
|
|
39484
40334
|
const config = parseSharedConfig({
|
|
39485
40335
|
format: "yaml",
|
|
39486
40336
|
fileContent: currentContent
|
|
@@ -39488,7 +40338,7 @@ function getEnabledPluginConfigContent(currentContent) {
|
|
|
39488
40338
|
const plugins = config.plugins && typeof config.plugins === "object" ? config.plugins : {};
|
|
39489
40339
|
const enabled = Array.isArray(plugins.enabled) ? plugins.enabled : [];
|
|
39490
40340
|
return applySharedConfigPatch({
|
|
39491
|
-
fileKey:
|
|
40341
|
+
fileKey: getHermesagentConfigSharedFileKey({ global }),
|
|
39492
40342
|
feature: "subagents",
|
|
39493
40343
|
existingContent: currentContent,
|
|
39494
40344
|
patch: { plugins: {
|
|
@@ -39539,6 +40389,10 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
|
|
|
39539
40389
|
return !targets || targets.includes("*") || targets.includes("hermesagent");
|
|
39540
40390
|
}
|
|
39541
40391
|
static fromRulesyncSubagents({ rulesyncSubagents, outputRoot, global = false }) {
|
|
40392
|
+
const pluginDirPath = getHermesagentRelativeDirPath({
|
|
40393
|
+
global,
|
|
40394
|
+
relativeDirPath: HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_DIR_PATH
|
|
40395
|
+
});
|
|
39542
40396
|
return [
|
|
39543
40397
|
...rulesyncSubagents.map((rulesyncSubagent) => HermesagentSubagent.fromRulesyncSubagent({
|
|
39544
40398
|
relativeDirPath: this.getSettablePaths({ global }).relativeDirPath,
|
|
@@ -39547,20 +40401,14 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
|
|
|
39547
40401
|
global
|
|
39548
40402
|
})),
|
|
39549
40403
|
new HermesagentSubagent({
|
|
39550
|
-
relativeDirPath:
|
|
39551
|
-
global,
|
|
39552
|
-
relativeDirPath: HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_DIR_PATH
|
|
39553
|
-
}),
|
|
40404
|
+
relativeDirPath: pluginDirPath,
|
|
39554
40405
|
relativeFilePath: basename(HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_MANIFEST_PATH),
|
|
39555
40406
|
fileContent: "",
|
|
39556
40407
|
outputRoot,
|
|
39557
40408
|
global
|
|
39558
40409
|
}),
|
|
39559
40410
|
new HermesagentSubagent({
|
|
39560
|
-
relativeDirPath:
|
|
39561
|
-
global,
|
|
39562
|
-
relativeDirPath: HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_DIR_PATH
|
|
39563
|
-
}),
|
|
40411
|
+
relativeDirPath: pluginDirPath,
|
|
39564
40412
|
relativeFilePath: basename(HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_INIT_PATH),
|
|
39565
40413
|
fileContent: "",
|
|
39566
40414
|
outputRoot,
|
|
@@ -39600,14 +40448,12 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
|
|
|
39600
40448
|
* shared `~/.hermes/config.yaml` (enabling the `rulesync-subagents` plugin),
|
|
39601
40449
|
* so the write must be declared for the shared-file order derivation.
|
|
39602
40450
|
*/
|
|
39603
|
-
|
|
39604
|
-
|
|
39605
|
-
|
|
39606
|
-
|
|
39607
|
-
|
|
39608
|
-
|
|
39609
|
-
relativeFilePath: basename(HERMESAGENT_CONFIG_FILE_PATH)
|
|
39610
|
-
}] : [];
|
|
40451
|
+
/**
|
|
40452
|
+
* `config.yaml` under every spelling the global profile root can take.
|
|
40453
|
+
* @see getHermesagentSharedConfigWritePaths
|
|
40454
|
+
*/
|
|
40455
|
+
static getExtraSharedWritePaths() {
|
|
40456
|
+
return getHermesagentSharedConfigWritePaths();
|
|
39611
40457
|
}
|
|
39612
40458
|
static getSettablePathsForRulesyncSubagent(rulesyncSubagent) {
|
|
39613
40459
|
return [join(HERMESAGENT_RULESYNC_SUBAGENTS_DIR_PATH, `${subagentSlug(rulesyncSubagent.getRelativePathFromCwd())}.json`)];
|
|
@@ -39640,7 +40486,10 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
|
|
|
39640
40486
|
}
|
|
39641
40487
|
setFileContent(newFileContent) {
|
|
39642
40488
|
if (this.getRelativeFilePath() === basename(HERMESAGENT_CONFIG_FILE_PATH)) {
|
|
39643
|
-
super.setFileContent(getEnabledPluginConfigContent(
|
|
40489
|
+
super.setFileContent(getEnabledPluginConfigContent({
|
|
40490
|
+
currentContent: newFileContent,
|
|
40491
|
+
global: this.global
|
|
40492
|
+
}));
|
|
39644
40493
|
return;
|
|
39645
40494
|
}
|
|
39646
40495
|
super.setFileContent(newFileContent);
|
|
@@ -39648,7 +40497,10 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
|
|
|
39648
40497
|
getFileContent() {
|
|
39649
40498
|
if (this.getRelativeFilePath() === basename(HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_MANIFEST_PATH)) return getPluginManifestContent();
|
|
39650
40499
|
if (this.getRelativeFilePath() === basename(HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_INIT_PATH)) return getPluginInitContent();
|
|
39651
|
-
if (this.getRelativeFilePath() === basename(HERMESAGENT_CONFIG_FILE_PATH)) return getEnabledPluginConfigContent(
|
|
40500
|
+
if (this.getRelativeFilePath() === basename(HERMESAGENT_CONFIG_FILE_PATH)) return getEnabledPluginConfigContent({
|
|
40501
|
+
currentContent: super.getFileContent(),
|
|
40502
|
+
global: this.global
|
|
40503
|
+
});
|
|
39652
40504
|
return super.getFileContent();
|
|
39653
40505
|
}
|
|
39654
40506
|
};
|
|
@@ -44727,10 +45579,13 @@ var JunieRule = class JunieRule extends ToolRule {
|
|
|
44727
45579
|
//#region src/features/rules/kilo-rule.ts
|
|
44728
45580
|
var KiloRule = class KiloRule extends ToolRule {
|
|
44729
45581
|
static getSettablePaths({ global, excludeToolDir } = {}) {
|
|
44730
|
-
if (global) return {
|
|
44731
|
-
|
|
44732
|
-
|
|
44733
|
-
|
|
45582
|
+
if (global) return {
|
|
45583
|
+
root: {
|
|
45584
|
+
relativeDirPath: buildToolPath(KILO_GLOBAL_DIR, ".", excludeToolDir),
|
|
45585
|
+
relativeFilePath: KILO_RULE_FILE_NAME
|
|
45586
|
+
},
|
|
45587
|
+
nonRoot: { relativeDirPath: buildToolPath(KILO_DIR, KILO_RULES_DIR_NAME, excludeToolDir) }
|
|
45588
|
+
};
|
|
44734
45589
|
return {
|
|
44735
45590
|
root: {
|
|
44736
45591
|
relativeDirPath: ".",
|
|
@@ -46315,6 +47170,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46315
47170
|
extension: "md",
|
|
46316
47171
|
supportsGlobal: false,
|
|
46317
47172
|
ruleDiscoveryMode: "toon",
|
|
47173
|
+
collisionPolicy: "compose",
|
|
46318
47174
|
additionalConventions: {
|
|
46319
47175
|
commands: { commandClass: AgentsmdCommand },
|
|
46320
47176
|
subagents: { subagentClass: AgentsmdSubagent },
|
|
@@ -46335,7 +47191,8 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46335
47191
|
meta: {
|
|
46336
47192
|
extension: "md",
|
|
46337
47193
|
supportsGlobal: true,
|
|
46338
|
-
ruleDiscoveryMode: "toon"
|
|
47194
|
+
ruleDiscoveryMode: "toon",
|
|
47195
|
+
collisionPolicy: "compose"
|
|
46339
47196
|
}
|
|
46340
47197
|
}],
|
|
46341
47198
|
["antigravity-cli", {
|
|
@@ -46412,7 +47269,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46412
47269
|
extension: "md",
|
|
46413
47270
|
supportsGlobal: true,
|
|
46414
47271
|
ruleDiscoveryMode: "auto",
|
|
46415
|
-
|
|
47272
|
+
collisionPolicy: "fold"
|
|
46416
47273
|
}
|
|
46417
47274
|
}],
|
|
46418
47275
|
["copilot", {
|
|
@@ -46445,7 +47302,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46445
47302
|
extension: "md",
|
|
46446
47303
|
supportsGlobal: true,
|
|
46447
47304
|
ruleDiscoveryMode: "auto",
|
|
46448
|
-
|
|
47305
|
+
collisionPolicy: "fold"
|
|
46449
47306
|
}
|
|
46450
47307
|
}],
|
|
46451
47308
|
["factorydroid", {
|
|
@@ -46453,7 +47310,8 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46453
47310
|
meta: {
|
|
46454
47311
|
extension: "md",
|
|
46455
47312
|
supportsGlobal: true,
|
|
46456
|
-
ruleDiscoveryMode: "toon"
|
|
47313
|
+
ruleDiscoveryMode: "toon",
|
|
47314
|
+
collisionPolicy: "compose"
|
|
46457
47315
|
}
|
|
46458
47316
|
}],
|
|
46459
47317
|
["goose", {
|
|
@@ -46462,7 +47320,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46462
47320
|
extension: "md",
|
|
46463
47321
|
supportsGlobal: true,
|
|
46464
47322
|
ruleDiscoveryMode: "auto",
|
|
46465
|
-
|
|
47323
|
+
collisionPolicy: "fold"
|
|
46466
47324
|
}
|
|
46467
47325
|
}],
|
|
46468
47326
|
["hermesagent", {
|
|
@@ -46471,7 +47329,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46471
47329
|
extension: "md",
|
|
46472
47330
|
supportsGlobal: false,
|
|
46473
47331
|
ruleDiscoveryMode: "auto",
|
|
46474
|
-
|
|
47332
|
+
collisionPolicy: "fold"
|
|
46475
47333
|
}
|
|
46476
47334
|
}],
|
|
46477
47335
|
["grokcli", {
|
|
@@ -46488,7 +47346,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46488
47346
|
extension: "md",
|
|
46489
47347
|
supportsGlobal: true,
|
|
46490
47348
|
ruleDiscoveryMode: "auto",
|
|
46491
|
-
|
|
47349
|
+
collisionPolicy: "fold"
|
|
46492
47350
|
}
|
|
46493
47351
|
}],
|
|
46494
47352
|
["kilo", {
|
|
@@ -46497,7 +47355,8 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46497
47355
|
extension: "md",
|
|
46498
47356
|
supportsGlobal: true,
|
|
46499
47357
|
ruleDiscoveryMode: "auto",
|
|
46500
|
-
mcpInstructionsRegistrar: KiloMcp
|
|
47358
|
+
mcpInstructionsRegistrar: KiloMcp,
|
|
47359
|
+
collisionPolicy: "compose"
|
|
46501
47360
|
}
|
|
46502
47361
|
}],
|
|
46503
47362
|
["kimi-code", {
|
|
@@ -46506,7 +47365,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46506
47365
|
extension: "md",
|
|
46507
47366
|
supportsGlobal: true,
|
|
46508
47367
|
ruleDiscoveryMode: "auto",
|
|
46509
|
-
|
|
47368
|
+
collisionPolicy: "fold"
|
|
46510
47369
|
}
|
|
46511
47370
|
}],
|
|
46512
47371
|
["kiro", {
|
|
@@ -46539,7 +47398,8 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46539
47398
|
extension: "md",
|
|
46540
47399
|
supportsGlobal: true,
|
|
46541
47400
|
ruleDiscoveryMode: "toon",
|
|
46542
|
-
mcpInstructionsRegistrar: OpencodeMcp
|
|
47401
|
+
mcpInstructionsRegistrar: OpencodeMcp,
|
|
47402
|
+
collisionPolicy: "compose"
|
|
46543
47403
|
}
|
|
46544
47404
|
}],
|
|
46545
47405
|
["pi", {
|
|
@@ -46548,7 +47408,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46548
47408
|
extension: "md",
|
|
46549
47409
|
supportsGlobal: true,
|
|
46550
47410
|
ruleDiscoveryMode: "auto",
|
|
46551
|
-
|
|
47411
|
+
collisionPolicy: "fold"
|
|
46552
47412
|
}
|
|
46553
47413
|
}],
|
|
46554
47414
|
["qwencode", {
|
|
@@ -46566,7 +47426,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46566
47426
|
extension: "md",
|
|
46567
47427
|
supportsGlobal: true,
|
|
46568
47428
|
ruleDiscoveryMode: "auto",
|
|
46569
|
-
|
|
47429
|
+
collisionPolicy: "fold"
|
|
46570
47430
|
}
|
|
46571
47431
|
}],
|
|
46572
47432
|
["replit", {
|
|
@@ -46623,7 +47483,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
46623
47483
|
extension: "md",
|
|
46624
47484
|
supportsGlobal: false,
|
|
46625
47485
|
ruleDiscoveryMode: "toon",
|
|
46626
|
-
|
|
47486
|
+
collisionPolicy: "fold"
|
|
46627
47487
|
}
|
|
46628
47488
|
}],
|
|
46629
47489
|
["devin", {
|
|
@@ -46692,16 +47552,23 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
46692
47552
|
const nonLocalRootRules = rulesyncRules.filter((rule) => !rule.getFrontmatter().localRoot);
|
|
46693
47553
|
const factory = this.getFactory(this.toolTarget);
|
|
46694
47554
|
const { meta } = factory;
|
|
46695
|
-
const
|
|
47555
|
+
const convertedRules = nonLocalRootRules.map((rulesyncRule) => {
|
|
46696
47556
|
if (!factory.class.isTargetedByRulesyncRule(rulesyncRule)) return null;
|
|
46697
|
-
return
|
|
46698
|
-
|
|
46699
|
-
|
|
46700
|
-
|
|
46701
|
-
|
|
46702
|
-
|
|
47557
|
+
return {
|
|
47558
|
+
toolRule: factory.class.fromRulesyncRule({
|
|
47559
|
+
outputRoot: this.outputRoot,
|
|
47560
|
+
rulesyncRule,
|
|
47561
|
+
validate: true,
|
|
47562
|
+
global: this.global
|
|
47563
|
+
}),
|
|
47564
|
+
rulesyncRule
|
|
47565
|
+
};
|
|
46703
47566
|
}).filter((rule) => rule !== null);
|
|
46704
|
-
|
|
47567
|
+
this.mergeRulesByOutputPath({
|
|
47568
|
+
convertedRules,
|
|
47569
|
+
collisionPolicy: meta.collisionPolicy ?? "preserve"
|
|
47570
|
+
});
|
|
47571
|
+
const toolRules = convertedRules.map(({ toolRule }) => toolRule);
|
|
46705
47572
|
this.applyLocalRootRules({
|
|
46706
47573
|
toolRules,
|
|
46707
47574
|
localRootRules,
|
|
@@ -46719,7 +47586,12 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
46719
47586
|
toolRules,
|
|
46720
47587
|
factory
|
|
46721
47588
|
});
|
|
46722
|
-
|
|
47589
|
+
const outputFiles = [...toolRules, ...extraFiles];
|
|
47590
|
+
this.warnForOutputPathCollisions({
|
|
47591
|
+
outputFiles,
|
|
47592
|
+
convertedRules
|
|
47593
|
+
});
|
|
47594
|
+
return outputFiles;
|
|
46723
47595
|
}
|
|
46724
47596
|
/**
|
|
46725
47597
|
* Handle localRoot rules (only in non-global mode and when enabled). Mutates
|
|
@@ -46806,39 +47678,80 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
46806
47678
|
});
|
|
46807
47679
|
}
|
|
46808
47680
|
/**
|
|
46809
|
-
*
|
|
47681
|
+
* Reconcile rules that resolve to the same output path.
|
|
46810
47682
|
*
|
|
46811
|
-
*
|
|
46812
|
-
*
|
|
46813
|
-
*
|
|
46814
|
-
* `.
|
|
46815
|
-
*
|
|
46816
|
-
*
|
|
47683
|
+
* Multiple root fragments are composed for tools that emit a fixed root file.
|
|
47684
|
+
* The `fold` policy is for tools whose rules engine reads only one root file and
|
|
47685
|
+
* neither scans a modular rules directory nor follows references. For example,
|
|
47686
|
+
* dcode reads `.deepagents/AGENTS.md`, while Warp reads root or subdirectory
|
|
47687
|
+
* `AGENTS.md` files but never `.warp/memories/`. Those adapters must fold every
|
|
47688
|
+
* body into one instance because last-writer-wins would silently drop content.
|
|
47689
|
+
* Plain-Markdown adapters can opt into `compose` for colliding modular outputs.
|
|
46817
47690
|
*
|
|
46818
|
-
*
|
|
46819
|
-
*
|
|
46820
|
-
*
|
|
47691
|
+
* A generated root rule becomes the merge target when present. A `fold` group
|
|
47692
|
+
* without one uses its first rule. A group only composes when every rendered
|
|
47693
|
+
* fragment is plain Markdown — a fragment carrying its own frontmatter block
|
|
47694
|
+
* (e.g. Amp's `globs:` gate) would end up mid-body where the tool ignores it.
|
|
47695
|
+
* Root-involved collisions that cannot be composed safely fail; other
|
|
47696
|
+
* collisions remain separate and are reported by the final output-path check.
|
|
47697
|
+
* Mutates `convertedRules` in place.
|
|
46821
47698
|
*/
|
|
46822
|
-
|
|
46823
|
-
if (
|
|
47699
|
+
mergeRulesByOutputPath({ convertedRules, collisionPolicy }) {
|
|
47700
|
+
if (convertedRules.length <= 1) return;
|
|
46824
47701
|
const groups = /* @__PURE__ */ new Map();
|
|
46825
|
-
for (const
|
|
46826
|
-
const path = join(
|
|
47702
|
+
for (const conversion of convertedRules) {
|
|
47703
|
+
const path = join(conversion.toolRule.getRelativeDirPath(), conversion.toolRule.getRelativeFilePath());
|
|
46827
47704
|
const group = groups.get(path);
|
|
46828
|
-
if (group) group.push(
|
|
46829
|
-
else groups.set(path, [
|
|
47705
|
+
if (group) group.push(conversion);
|
|
47706
|
+
else groups.set(path, [conversion]);
|
|
46830
47707
|
}
|
|
46831
47708
|
const survivors = /* @__PURE__ */ new Set();
|
|
46832
|
-
for (const group of groups
|
|
46833
|
-
|
|
47709
|
+
for (const [path, group] of groups) {
|
|
47710
|
+
if (group.length === 1) {
|
|
47711
|
+
const conversion = group[0];
|
|
47712
|
+
if (conversion) {
|
|
47713
|
+
if (collisionPolicy === "fold") conversion.toolRule.setFileContent(conversion.toolRule.getFileContent().trim());
|
|
47714
|
+
survivors.add(conversion);
|
|
47715
|
+
}
|
|
47716
|
+
continue;
|
|
47717
|
+
}
|
|
47718
|
+
const rootConversion = group.find(({ toolRule }) => toolRule.isRoot());
|
|
47719
|
+
const allGeneratedRulesAreRoots = group.every(({ toolRule }) => toolRule.isRoot());
|
|
47720
|
+
const hasSourceRoot = group.some(({ rulesyncRule }) => rulesyncRule.getFrontmatter().root === true);
|
|
47721
|
+
const allFragmentsArePlain = group.every(({ toolRule }) => !/^---\r?\n/.test(toolRule.getFileContent()));
|
|
47722
|
+
const shouldCompose = (collisionPolicy === "fold" || collisionPolicy === "compose" || allGeneratedRulesAreRoots) && allFragmentsArePlain;
|
|
47723
|
+
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))}`);
|
|
47724
|
+
if (!shouldCompose) {
|
|
47725
|
+
for (const conversion of group) survivors.add(conversion);
|
|
47726
|
+
continue;
|
|
47727
|
+
}
|
|
47728
|
+
const target = rootConversion ?? group[0];
|
|
46834
47729
|
if (!target) continue;
|
|
46835
|
-
const mergedContent = [target, ...group.filter((rule) => rule !== target)].map((
|
|
46836
|
-
target.setFileContent(mergedContent);
|
|
47730
|
+
const mergedContent = [target, ...group.filter((rule) => rule !== target)].map(({ toolRule }) => toolRule.getFileContent().trim()).filter((content) => content.length > 0).join("\n\n");
|
|
47731
|
+
target.toolRule.setFileContent(mergedContent);
|
|
46837
47732
|
survivors.add(target);
|
|
46838
47733
|
}
|
|
46839
|
-
for (let i =
|
|
46840
|
-
const
|
|
46841
|
-
if (
|
|
47734
|
+
for (let i = convertedRules.length - 1; i >= 0; i--) {
|
|
47735
|
+
const conversion = convertedRules[i];
|
|
47736
|
+
if (conversion && !survivors.has(conversion)) convertedRules.splice(i, 1);
|
|
47737
|
+
}
|
|
47738
|
+
}
|
|
47739
|
+
warnForOutputPathCollisions({ outputFiles, convertedRules }) {
|
|
47740
|
+
const seen = /* @__PURE__ */ new Map();
|
|
47741
|
+
const describeSource = (file) => {
|
|
47742
|
+
const source = convertedRules.find(({ toolRule }) => toolRule === file)?.rulesyncRule;
|
|
47743
|
+
return source ? formatRulePaths([source]) : join(file.getRelativeDirPath(), file.getRelativeFilePath());
|
|
47744
|
+
};
|
|
47745
|
+
for (const file of outputFiles) {
|
|
47746
|
+
const path = join(file.getRelativeDirPath(), file.getRelativeFilePath());
|
|
47747
|
+
const key = path.toLowerCase();
|
|
47748
|
+
const previous = seen.get(key);
|
|
47749
|
+
if (previous) {
|
|
47750
|
+
const previousPath = join(previous.getRelativeDirPath(), previous.getRelativeFilePath());
|
|
47751
|
+
const pathDescription = previousPath === path ? `'${path}'` : `'${previousPath}' and '${path}' (compared case-insensitively, as on macOS and Windows)`;
|
|
47752
|
+
this.logger.warn(`Both ${describeSource(previous)} and ${describeSource(file)} generate to ${pathDescription}; the last one wins wherever they collide.`);
|
|
47753
|
+
}
|
|
47754
|
+
seen.set(key, file);
|
|
46842
47755
|
}
|
|
46843
47756
|
}
|
|
46844
47757
|
/**
|
|
@@ -46998,14 +47911,13 @@ As this project's AI coding tool, you must follow the additional conventions bel
|
|
|
46998
47911
|
}));
|
|
46999
47912
|
const factory = this.getFactory(this.toolTarget);
|
|
47000
47913
|
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
47914
|
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
47915
|
const targetedLocalRootRules = rulesyncRules.filter((rule) => rule.getFrontmatter().localRoot).filter((rule) => factory.class.isTargetedByRulesyncRule(rule));
|
|
47004
47916
|
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
47917
|
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
47918
|
if (this.global) {
|
|
47007
47919
|
const globalPaths = factory.class.getSettablePaths({ global: true });
|
|
47008
|
-
const supportsGlobalNonRoot = "nonRoot" in globalPaths && globalPaths.nonRoot !== null || factory.meta.supportsGlobal && factory.meta.
|
|
47920
|
+
const supportsGlobalNonRoot = "nonRoot" in globalPaths && globalPaths.nonRoot !== null || factory.meta.supportsGlobal && factory.meta.collisionPolicy === "fold";
|
|
47009
47921
|
const nonRootRules = rulesyncRules.filter((rule) => !rule.getFrontmatter().root && !rule.getFrontmatter().localRoot && factory.class.isTargetedByRulesyncRule(rule));
|
|
47010
47922
|
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
47923
|
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 +48160,36 @@ async function assertPluginRootSafe(params) {
|
|
|
47248
48160
|
}
|
|
47249
48161
|
//#endregion
|
|
47250
48162
|
//#region src/utils/tool-output-root.ts
|
|
48163
|
+
/** The environment variable each tool reads for its profile root. */
|
|
48164
|
+
const TOOL_HOME_ENV_VARS = {
|
|
48165
|
+
hermesagent: "HERMES_HOME",
|
|
48166
|
+
"kimi-code": "KIMI_CODE_HOME"
|
|
48167
|
+
};
|
|
48168
|
+
/**
|
|
48169
|
+
* Substitute a tool's home override (`HERMES_HOME`, `KIMI_CODE_HOME`) for the
|
|
48170
|
+
* output root in global scope.
|
|
48171
|
+
*
|
|
48172
|
+
* The override wins over `--output-roots`: it names where the tool itself reads
|
|
48173
|
+
* its profile, so writing anywhere else would produce files the tool ignores.
|
|
48174
|
+
*
|
|
48175
|
+
* A substituted value goes through the same `validateOutputRoot` the CLI and
|
|
48176
|
+
* config paths use, so an override of `/` or an unnormalized path is rejected
|
|
48177
|
+
* instead of silently becoming the output root. The rejection is re-thrown
|
|
48178
|
+
* naming the variable, since the user never passed an `--output-roots` flag.
|
|
48179
|
+
*/
|
|
47251
48180
|
function resolveToolOutputRoot({ outputRoot, toolTarget, global }) {
|
|
47252
48181
|
if (!global) return outputRoot;
|
|
47253
|
-
|
|
48182
|
+
const resolved = toolTarget === "hermesagent" ? resolveHermesagentOutputRoot({
|
|
47254
48183
|
outputRoot,
|
|
47255
48184
|
global
|
|
47256
|
-
});
|
|
47257
|
-
if (
|
|
47258
|
-
|
|
48185
|
+
}) : toolTarget === "kimi-code" ? getKimiCodeHome() ?? outputRoot : outputRoot;
|
|
48186
|
+
if (resolved === outputRoot) return resolved;
|
|
48187
|
+
try {
|
|
48188
|
+
validateOutputRoot(resolved);
|
|
48189
|
+
} catch (error) {
|
|
48190
|
+
throw new Error(`${TOOL_HOME_ENV_VARS[toolTarget] ?? "The tool home override"} is not a usable output root: ${formatError(error)}`, { cause: error });
|
|
48191
|
+
}
|
|
48192
|
+
return resolved;
|
|
47259
48193
|
}
|
|
47260
48194
|
//#endregion
|
|
47261
48195
|
//#region src/lib/convert.ts
|
|
@@ -48656,7 +49590,11 @@ async function generateChecksCore(params) {
|
|
|
48656
49590
|
for (const toolTarget of toolTargets) for (const outputRoot of config.getOutputRoots(toolTarget)) {
|
|
48657
49591
|
if (!config.getFeatures(toolTarget).includes("checks")) continue;
|
|
48658
49592
|
const processor = new ChecksProcessor({
|
|
48659
|
-
outputRoot
|
|
49593
|
+
outputRoot: resolveToolOutputRoot({
|
|
49594
|
+
outputRoot,
|
|
49595
|
+
toolTarget,
|
|
49596
|
+
global: config.getGlobal()
|
|
49597
|
+
}),
|
|
48660
49598
|
inputRoot: config.getInputRoot(),
|
|
48661
49599
|
toolTarget,
|
|
48662
49600
|
global: config.getGlobal(),
|
|
@@ -49031,4 +49969,4 @@ async function importChecksCore(params) {
|
|
|
49031
49969
|
//#endregion
|
|
49032
49970
|
export { ErrorCodes as $, RULESYNC_SKILLS_RELATIVE_DIR_PATH as $t, RulesyncMcp as A, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as At, stringifyFrontmatter as B, RULESYNC_IGNORE_RELATIVE_FILE_PATH as Bt, RulesyncSubagent as C, writeFileContent as Ct, RulesyncRule as D, ToolTargetSchema as Dt, RulesyncSkillFrontmatterSchema as E, PACKAGING_TOOL_TARGETS as Et, parseJsonc as F, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as Ft, ConfigFileSchema as G, RULESYNC_MCP_SCHEMA_URL as Gt, SHARED_USER_MANAGED_CONFIG_PATHS as H, RULESYNC_MCP_FILE_NAME as Ht, RulesyncCommand as I, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as It, ConsoleLogger as J, RULESYNC_PERMISSIONS_LEGACY_FILE_NAME as Jt, SourceEntrySchema as K, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as Kt, RulesyncCommandFrontmatterSchema as L, RULESYNC_HOOKS_FILE_NAME as Lt, RulesyncHooks as M, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as Mt, getRulesyncSourceCandidates as N, RULESYNC_CONFIG_RELATIVE_FILE_PATH as Nt, RulesyncRuleFrontmatterSchema as O, MAX_FILE_SIZE as Ot, resolveRulesyncSourceWritePath as P, RULESYNC_CONFIG_SCHEMA_URL as Pt, CLIError as Q, RULESYNC_RULES_RELATIVE_DIR_PATH as Qt, RulesyncCheck as R, RULESYNC_HOOKS_LEGACY_FILE_NAME as Rt, getLocalSkillDirNames as S, toPosixPath as St, RulesyncSkill as T, ALL_TOOL_TARGETS_WITH_WILDCARD as Tt, SKILL_FILE_NAME as U, RULESYNC_MCP_LEGACY_FILE_NAME as Ut, loadYaml as V, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as Vt, ConfigResolver as W, RULESYNC_MCP_RELATIVE_FILE_PATH as Wt, fallbackLogger as X, RULESYNC_PERMISSIONS_SCHEMA_URL as Xt, JsonLogger as Y, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as Yt, warnOnConflictingFlags as Z, RULESYNC_RELATIVE_DIR_PATH as Zt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as _, removeFile as _t, convertFromTool as a, directoryExists as at, CODEXCLI_BASH_RULES_FILE_NAME as b, resolvePath as bt, SubagentsProcessor as c, findFilesByGlobs as ct, IgnoreProcessor as d, isSymlink as dt, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as en, assertDirectoryIfExists as et, HooksProcessor as f, listDirectoryFiles as ft, CLAUDECODE_MEMORIES_DIR_NAME as g, removeDirectoryStrict as gt, CLAUDECODE_LOCAL_RULE_FILE_NAME as h, removeDirectory as ht, getProcessorRegistryEntry as i, formatError as in, createTempDirectory as it, RulesyncIgnore as j, RULESYNC_CHECKS_RELATIVE_DIR_PATH as jt, RulesyncPermissions as k, RULESYNC_AIIGNORE_FILE_NAME as kt, SkillsProcessor as l, getFileSize as lt, CLAUDECODE_DIR as m, readFileContentOrNull as mt, checkRulesyncDirExists as n, ALL_FEATURES as nn, assertWritablePathInsideRoot as nt, isPackagingToolTarget as o, ensureDir as ot, CommandsProcessor as p, readFileContent as pt, findControlCharacter as q, RULESYNC_PERMISSIONS_FILE_NAME as qt, generate as r, ALL_FEATURES_WITH_WILDCARD as rn, checkPathTraversal as rt, RulesProcessor as s, fileExists as st, importFromTool as t, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as tn, assertTreeContainsNoSymlinks as tt, McpProcessor as u, getHomeDirectory as ut, CLAUDECODE_SKILLS_DIR_PATH as v, removeFileStrict as vt, RulesyncSubagentFrontmatterSchema as w, ALL_TOOL_TARGETS as wt, CODEXCLI_DIR as x, runWithDirectoryRollback as xt, ChecksProcessor as y, removeTempDirectory as yt, RulesyncCheckFrontmatterSchema as z, RULESYNC_HOOKS_RELATIVE_FILE_PATH as zt };
|
|
49033
49971
|
|
|
49034
|
-
//# sourceMappingURL=import-
|
|
49972
|
+
//# sourceMappingURL=import-CArKOPG_.js.map
|