rulesync 43.5.0 → 43.6.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 +1 -1
- package/dist/cli/index.cjs +5 -5
- package/dist/cli/index.js +5 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-C_XK2L62.js → import--Y_A56qx.js} +469 -136
- package/dist/{import-C_XK2L62.js.map → import--Y_A56qx.js.map} +1 -1
- package/dist/{import-CAePeHTT.cjs → import-C-oT_Hdp.cjs} +485 -152
- 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
|
@@ -873,6 +873,7 @@ const hooksProcessorToolTargetTuple = [
|
|
|
873
873
|
"qwencode",
|
|
874
874
|
"reasonix",
|
|
875
875
|
"tabnine",
|
|
876
|
+
"trae",
|
|
876
877
|
"grokcli",
|
|
877
878
|
"gitlabduo",
|
|
878
879
|
"lettacode",
|
|
@@ -3923,6 +3924,7 @@ const SHARED_USER_MANAGED_CONFIG_PATHS = [
|
|
|
3923
3924
|
".poolside/settings.yaml",
|
|
3924
3925
|
".config/poolside/settings.yaml",
|
|
3925
3926
|
".tabnine/agent/settings.json",
|
|
3927
|
+
".trae/hooks.json",
|
|
3926
3928
|
".vibe/config.toml",
|
|
3927
3929
|
".zcode/config.json",
|
|
3928
3930
|
".zcode/cli/config.json",
|
|
@@ -6005,6 +6007,34 @@ const CANONICAL_TO_CORTEXCODE_EVENT_NAMES = {
|
|
|
6005
6007
|
};
|
|
6006
6008
|
const CORTEXCODE_TO_CANONICAL_EVENT_NAMES = invertEventNames(CANONICAL_TO_CORTEXCODE_EVENT_NAMES);
|
|
6007
6009
|
/**
|
|
6010
|
+
* Hook events supported by TRAE.
|
|
6011
|
+
*
|
|
6012
|
+
* TRAE reads `.trae/hooks.json` (project) and `~/.trae/hooks.json` (user):
|
|
6013
|
+
* `{ "version"?: 1, "hooks": { "<Event>": [{ "matcher"?, "hooks": [{ "type":
|
|
6014
|
+
* "command", "command", "timeout"? }] }] } }` with Claude-Code-style
|
|
6015
|
+
* PascalCase event names. `$TRAE_PROJECT_DIR` resolves to the workspace root
|
|
6016
|
+
* inside commands.
|
|
6017
|
+
*
|
|
6018
|
+
* @see https://docs.trae.ai/ide/hook-configuration-reference?_lang=en
|
|
6019
|
+
*/
|
|
6020
|
+
const TRAE_HOOK_EVENTS = [
|
|
6021
|
+
"sessionStart",
|
|
6022
|
+
"beforeSubmitPrompt",
|
|
6023
|
+
"preToolUse",
|
|
6024
|
+
"postToolUse",
|
|
6025
|
+
"stop",
|
|
6026
|
+
"notification"
|
|
6027
|
+
];
|
|
6028
|
+
const CANONICAL_TO_TRAE_EVENT_NAMES = {
|
|
6029
|
+
sessionStart: "SessionStart",
|
|
6030
|
+
beforeSubmitPrompt: "UserPromptSubmit",
|
|
6031
|
+
preToolUse: "PreToolUse",
|
|
6032
|
+
postToolUse: "PostToolUse",
|
|
6033
|
+
stop: "Stop",
|
|
6034
|
+
notification: "Notification"
|
|
6035
|
+
};
|
|
6036
|
+
const TRAE_TO_CANONICAL_EVENT_NAMES = invertEventNames(CANONICAL_TO_TRAE_EVENT_NAMES);
|
|
6037
|
+
/**
|
|
6008
6038
|
* Hook events supported by CodeBuddy Code.
|
|
6009
6039
|
*
|
|
6010
6040
|
* CodeBuddy Code reads hooks from the `hooks` key of `.codebuddy/settings.json`
|
|
@@ -6477,6 +6507,7 @@ const HooksConfigSchema = z.looseObject({
|
|
|
6477
6507
|
bob: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
|
|
6478
6508
|
codebuddy: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
|
|
6479
6509
|
cortexcode: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
|
|
6510
|
+
trae: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
|
|
6480
6511
|
commandcode: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
|
|
6481
6512
|
lettacode: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
|
|
6482
6513
|
continue: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
|
|
@@ -14159,6 +14190,14 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
14159
14190
|
}
|
|
14160
14191
|
}
|
|
14161
14192
|
},
|
|
14193
|
+
".trae/hooks.json": {
|
|
14194
|
+
format: "json",
|
|
14195
|
+
invalidRootPolicy: "error",
|
|
14196
|
+
features: { hooks: {
|
|
14197
|
+
kind: "replace-owned-keys",
|
|
14198
|
+
ownedKeys: ["hooks"]
|
|
14199
|
+
} }
|
|
14200
|
+
},
|
|
14162
14201
|
".trae/permission/global.json": {
|
|
14163
14202
|
format: "json",
|
|
14164
14203
|
invalidRootPolicy: "error",
|
|
@@ -23284,10 +23323,13 @@ var TaktCommand = class TaktCommand extends ToolCommand {
|
|
|
23284
23323
|
* @see https://docs.trae.ai/ide/slash-commands?_lang=en
|
|
23285
23324
|
* @see https://docs.trae.ai/ide/ignore-files?_lang=en
|
|
23286
23325
|
* @see https://docs.trae.ai/ide/permission-and-approval?_lang=en
|
|
23326
|
+
* @see https://docs.trae.ai/ide/hook-configuration-reference?_lang=en
|
|
23287
23327
|
*/
|
|
23288
23328
|
const TRAE_DIR = ".trae";
|
|
23289
23329
|
/** Project MCP servers (`mcpServers` object), project scope only. */
|
|
23290
23330
|
const TRAE_MCP_FILE_NAME = "mcp.json";
|
|
23331
|
+
/** Hooks: `.trae/hooks.json` (project) and `~/.trae/hooks.json` (global). */
|
|
23332
|
+
const TRAE_HOOKS_FILE_NAME = "hooks.json";
|
|
23291
23333
|
/** Skills: `.trae/skills/<name>/SKILL.md` (project) and `~/.trae/skills/` (global). */
|
|
23292
23334
|
const TRAE_SKILLS_DIR_PATH = join(TRAE_DIR, "skills");
|
|
23293
23335
|
/**
|
|
@@ -25212,12 +25254,80 @@ var AiassistantSkill = class AiassistantSkill extends ToolSkill {
|
|
|
25212
25254
|
});
|
|
25213
25255
|
}
|
|
25214
25256
|
};
|
|
25257
|
+
// src/features/shared/amp-settings.ts
|
|
25258
|
+
/**
|
|
25259
|
+
* Parse an Amp `settings.json` / `settings.jsonc` document.
|
|
25260
|
+
*
|
|
25261
|
+
* Shared by the MCP and permissions adapters because both read the very same
|
|
25262
|
+
* file. Two separately maintained copies drifted once already: the permissions
|
|
25263
|
+
* side was hardened while the MCP side kept calling `jsonc-parser` directly,
|
|
25264
|
+
* which left it importing MCP servers reachable only through an injected
|
|
25265
|
+
* `__proto__`.
|
|
25266
|
+
*
|
|
25267
|
+
* {@link parseJsonc} is the strict, sanitizing parser: it throws on any syntax
|
|
25268
|
+
* error instead of returning `jsonc-parser`'s best-effort value, and it
|
|
25269
|
+
* rebuilds every object from its own enumerable entries, so a root `__proto__`
|
|
25270
|
+
* cannot swap the returned object's prototype and `constructor` / `prototype`
|
|
25271
|
+
* never survive as own keys, at any depth.
|
|
25272
|
+
*
|
|
25273
|
+
* Removal is silent, matching every other tool-side adapter (opencode, kilo,
|
|
25274
|
+
* copilot). Reporting the removed keys through `droppedPollutionKeysError` is
|
|
25275
|
+
* reserved for the three files a user authors under `.rulesync/`, whose whole
|
|
25276
|
+
* purpose is to be turned into tool config — a key that vanishes there needs
|
|
25277
|
+
* explaining, whereas here the surrounding settings are the user's own file
|
|
25278
|
+
* and are left alone.
|
|
25279
|
+
*
|
|
25280
|
+
* Note the consequence for a root `__proto__`: sanitizing runs before the
|
|
25281
|
+
* plain-object check, so such a document parses successfully with the key
|
|
25282
|
+
* stripped rather than failing that check. That is deliberate — dropping the
|
|
25283
|
+
* one poisoned key keeps the user's unrelated settings — and matches the
|
|
25284
|
+
* sanitize-before-the-root-check design the shared config gateway documents.
|
|
25285
|
+
*/
|
|
25286
|
+
function parseAmpSettings({ fileContent }) {
|
|
25287
|
+
let parsed;
|
|
25288
|
+
try {
|
|
25289
|
+
parsed = parseJsonc(fileContent || "{}");
|
|
25290
|
+
} catch (error) {
|
|
25291
|
+
throw new Error(`Failed to parse Amp settings: ${formatError(error)}`, { cause: error });
|
|
25292
|
+
}
|
|
25293
|
+
if (!isPlainObject(parsed)) throw new Error("Amp settings must be a JSON object");
|
|
25294
|
+
return parsed;
|
|
25295
|
+
}
|
|
25296
|
+
/**
|
|
25297
|
+
* Probe `<jsonDir>/settings.jsonc` first, falling back to `settings.json`, so
|
|
25298
|
+
* an existing user file is read-modified-written in place instead of a fresh
|
|
25299
|
+
* `.json` sibling being created next to a hand-authored `.jsonc`. Defaults to
|
|
25300
|
+
* `settings.json` when neither file exists: Amp's canonically documented
|
|
25301
|
+
* settings file (`.amp/settings.json`), shared by the MCP and permissions
|
|
25302
|
+
* adapters, so generating both on a clean repo writes a single settings file.
|
|
25303
|
+
*/
|
|
25304
|
+
async function resolveAmpSettingsFile(jsonDir) {
|
|
25305
|
+
const jsoncContent = await readFileContentOrNull(join(jsonDir, AMP_SETTINGS_JSONC_FILE_NAME));
|
|
25306
|
+
if (jsoncContent !== null) return {
|
|
25307
|
+
fileContent: jsoncContent,
|
|
25308
|
+
relativeFilePath: AMP_SETTINGS_JSONC_FILE_NAME
|
|
25309
|
+
};
|
|
25310
|
+
const jsonContent = await readFileContentOrNull(join(jsonDir, AMP_SETTINGS_FILE_NAME));
|
|
25311
|
+
if (jsonContent !== null) return {
|
|
25312
|
+
fileContent: jsonContent,
|
|
25313
|
+
relativeFilePath: AMP_SETTINGS_FILE_NAME
|
|
25314
|
+
};
|
|
25315
|
+
return {
|
|
25316
|
+
fileContent: null,
|
|
25317
|
+
relativeFilePath: AMP_SETTINGS_FILE_NAME
|
|
25318
|
+
};
|
|
25319
|
+
}
|
|
25215
25320
|
// src/features/skills/amp-skill.ts
|
|
25216
25321
|
const AmpSkillFrontmatterSchema = z.looseObject({
|
|
25217
25322
|
name: z.string(),
|
|
25218
25323
|
description: z.string()
|
|
25219
25324
|
});
|
|
25220
25325
|
/**
|
|
25326
|
+
* Amp setting that makes it skip `~/.config/agents/skills/` and
|
|
25327
|
+
* `~/.agents/skills/`. https://ampcode.com/docs/cli/settings
|
|
25328
|
+
*/
|
|
25329
|
+
const AMP_DISABLE_GLOBAL_AGENTS_SKILLS_KEY = "amp.skills.disableGlobalAgentsSkills";
|
|
25330
|
+
/**
|
|
25221
25331
|
* Represents an Amp Agent Skill directory.
|
|
25222
25332
|
*
|
|
25223
25333
|
* Amp natively reads Agent Skills following the open standard: a directory with a
|
|
@@ -25252,6 +25362,27 @@ var AmpSkill = class AmpSkill extends ToolSkill {
|
|
|
25252
25362
|
...global && { importOnlySkillRoots: [AGENTSMD_SKILLS_DIR_PATH, AMP_SKILLS_NATIVE_GLOBAL_DIR] }
|
|
25253
25363
|
};
|
|
25254
25364
|
}
|
|
25365
|
+
/**
|
|
25366
|
+
* Warn when the user's Amp settings disable the global agent-agnostic skill
|
|
25367
|
+
* roots, because the global skills rulesync writes to
|
|
25368
|
+
* `~/.config/agents/skills/` would then never load. Only the user settings
|
|
25369
|
+
* file is checked: a workspace `.amp/settings.json(c)` depends on where Amp
|
|
25370
|
+
* is started, which global generation cannot know.
|
|
25371
|
+
*/
|
|
25372
|
+
static async getGenerationWarnings({ outputRoot, global, logger }) {
|
|
25373
|
+
if (!global) return [];
|
|
25374
|
+
const { fileContent, relativeFilePath } = await resolveAmpSettingsFile(join(outputRoot, AMP_GLOBAL_DIR));
|
|
25375
|
+
if (fileContent === null) return [];
|
|
25376
|
+
let settings;
|
|
25377
|
+
try {
|
|
25378
|
+
settings = parseAmpSettings({ fileContent });
|
|
25379
|
+
} catch (error) {
|
|
25380
|
+
logger.debug(`Skipping the Amp skills settings check for ${join(AMP_GLOBAL_DIR, relativeFilePath)}: ${formatError(error)}`);
|
|
25381
|
+
return [];
|
|
25382
|
+
}
|
|
25383
|
+
if (settings[AMP_DISABLE_GLOBAL_AGENTS_SKILLS_KEY] !== true) return [];
|
|
25384
|
+
return [`Amp will not load the global skills rulesync writes to ~/${AMP_SKILLS_GLOBAL_DIR}/: '${AMP_DISABLE_GLOBAL_AGENTS_SKILLS_KEY}' is true in ~/${join(AMP_GLOBAL_DIR, relativeFilePath)}.`];
|
|
25385
|
+
}
|
|
25255
25386
|
getFrontmatter() {
|
|
25256
25387
|
return AmpSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
|
|
25257
25388
|
}
|
|
@@ -33228,7 +33359,7 @@ var SkillsProcessor = class extends DirFeatureProcessor {
|
|
|
33228
33359
|
async convertRulesyncDirsToToolDirs(rulesyncDirs) {
|
|
33229
33360
|
const rulesyncSkills = rulesyncDirs.filter((dir) => dir instanceof RulesyncSkill);
|
|
33230
33361
|
const factory = this.getFactory(this.toolTarget);
|
|
33231
|
-
|
|
33362
|
+
const toolSkills = (await Promise.all(rulesyncSkills.map(async (rulesyncSkill) => {
|
|
33232
33363
|
const isClaudecodeScheduledTask = rulesyncSkill.getFrontmatter().claudecode?.["scheduled-task"] === true;
|
|
33233
33364
|
if (isClaudecodeScheduledTask && this.toolTarget !== "claudecode" && this.toolTarget !== "claudecode-legacy") return null;
|
|
33234
33365
|
if (!factory.class.isTargetedByRulesyncSkill(rulesyncSkill)) return null;
|
|
@@ -33255,6 +33386,15 @@ var SkillsProcessor = class extends DirFeatureProcessor {
|
|
|
33255
33386
|
logger: this.logger
|
|
33256
33387
|
});
|
|
33257
33388
|
}))).filter((skill) => skill !== null);
|
|
33389
|
+
if (toolSkills.length > 0 && factory.class.getGenerationWarnings) {
|
|
33390
|
+
const warnings = await factory.class.getGenerationWarnings({
|
|
33391
|
+
outputRoot: this.outputRoot,
|
|
33392
|
+
global: this.global,
|
|
33393
|
+
logger: this.logger
|
|
33394
|
+
});
|
|
33395
|
+
for (const warning of warnings) this.logger.warn(warning);
|
|
33396
|
+
}
|
|
33397
|
+
return toolSkills;
|
|
33258
33398
|
}
|
|
33259
33399
|
async convertToolDirsToRulesyncDirs(toolDirs) {
|
|
33260
33400
|
const toolSkills = toolDirs.filter((dir) => dir instanceof ToolSkill);
|
|
@@ -47460,6 +47600,45 @@ var DshRule = class DshRule extends NestedAgentsmdRule {
|
|
|
47460
47600
|
};
|
|
47461
47601
|
}
|
|
47462
47602
|
};
|
|
47603
|
+
// src/features/rules/output-style-paths.ts
|
|
47604
|
+
/**
|
|
47605
|
+
* Shared helpers for tool rule adapters that write opted-in rules as custom
|
|
47606
|
+
* output styles (`<tool dir>/output-styles/<name>.md`), e.g. Qoder and
|
|
47607
|
+
* Factory Droid. Both tools read only the direct `.md` children of their
|
|
47608
|
+
* output-styles directory, with optional `name` / `description` frontmatter.
|
|
47609
|
+
*/
|
|
47610
|
+
/**
|
|
47611
|
+
* Whether the given path is a direct `.md` child of `outputStylesDirPath`.
|
|
47612
|
+
*/
|
|
47613
|
+
function isDirectMarkdownChild({ outputStylesDirPath, relativeDirPath, relativeFilePath }) {
|
|
47614
|
+
return relativeDirPath === outputStylesDirPath && extname(relativeFilePath) === ".md" && basename(relativeFilePath) === relativeFilePath;
|
|
47615
|
+
}
|
|
47616
|
+
/**
|
|
47617
|
+
* Whether a rule opts into the `output-style` channel: only a non-root rule
|
|
47618
|
+
* can, since an output style is a file of its own.
|
|
47619
|
+
*/
|
|
47620
|
+
function isOutputStyleChannel({ root, channel }) {
|
|
47621
|
+
return !root && channel === "output-style";
|
|
47622
|
+
}
|
|
47623
|
+
/**
|
|
47624
|
+
* Return the file stem of an output-style source rule, throwing `message`
|
|
47625
|
+
* when the rule is nested in a subdirectory of the rules directory: the
|
|
47626
|
+
* output file is named after the rule, and the tools read only the top level
|
|
47627
|
+
* of their output-styles directory.
|
|
47628
|
+
*/
|
|
47629
|
+
function resolveTopLevelOutputStyleStem({ sourcePath, message }) {
|
|
47630
|
+
if (basename(sourcePath) !== sourcePath) throw new Error(message);
|
|
47631
|
+
return basename(sourcePath, extname(sourcePath));
|
|
47632
|
+
}
|
|
47633
|
+
/**
|
|
47634
|
+
* The output style's frontmatter: only the `name` and `description` that are set.
|
|
47635
|
+
*/
|
|
47636
|
+
function buildOutputStyleFrontmatter({ name, description }) {
|
|
47637
|
+
return {
|
|
47638
|
+
...name !== void 0 && { name },
|
|
47639
|
+
...description !== void 0 && { description }
|
|
47640
|
+
};
|
|
47641
|
+
}
|
|
47463
47642
|
// src/features/rules/factorydroid-rule.ts
|
|
47464
47643
|
/**
|
|
47465
47644
|
* The file-name glob `getExtraFixedFiles` returns for the output-styles
|
|
@@ -47564,7 +47743,11 @@ var FactorydroidRule = class FactorydroidRule extends ToolRule {
|
|
|
47564
47743
|
* child of the output-styles directory, at either scope.
|
|
47565
47744
|
*/
|
|
47566
47745
|
static isOutputStylePath({ relativeDirPath, relativeFilePath }) {
|
|
47567
|
-
return
|
|
47746
|
+
return isDirectMarkdownChild({
|
|
47747
|
+
outputStylesDirPath: this.getOutputStylesDirPath(),
|
|
47748
|
+
relativeDirPath,
|
|
47749
|
+
relativeFilePath
|
|
47750
|
+
});
|
|
47568
47751
|
}
|
|
47569
47752
|
/**
|
|
47570
47753
|
* Which fixed-file channel, if any, owns the given output path. Matching on
|
|
@@ -47604,7 +47787,10 @@ var FactorydroidRule = class FactorydroidRule extends ToolRule {
|
|
|
47604
47787
|
/** Whether a rule opts into the `output-style` channel (non-root only). */
|
|
47605
47788
|
static isOutputStyleRule(rulesyncRule) {
|
|
47606
47789
|
const frontmatter = rulesyncRule.getFrontmatter();
|
|
47607
|
-
return
|
|
47790
|
+
return isOutputStyleChannel({
|
|
47791
|
+
root: frontmatter.root,
|
|
47792
|
+
channel: frontmatter.factorydroid?.channel
|
|
47793
|
+
});
|
|
47608
47794
|
}
|
|
47609
47795
|
/**
|
|
47610
47796
|
* Factory Droid loads the channel files itself, so listing one in the root
|
|
@@ -47736,15 +47922,17 @@ var FactorydroidRule = class FactorydroidRule extends ToolRule {
|
|
|
47736
47922
|
static buildOutputStyle({ outputRoot, rulesyncRule, validate }) {
|
|
47737
47923
|
const frontmatter = rulesyncRule.getFrontmatter();
|
|
47738
47924
|
const sourcePath = rulesyncRule.getRelativeFilePath();
|
|
47739
|
-
|
|
47740
|
-
|
|
47925
|
+
const stem = resolveTopLevelOutputStyleStem({
|
|
47926
|
+
sourcePath,
|
|
47927
|
+
message: `Rule '${sourcePath}' sets factorydroid.channel: output-style but is nested in a subdirectory: Factory Droid loads only direct .md children of .factory/output-styles/, so move it to the top of ${RULESYNC_RULES_RELATIVE_DIR_PATH}.`
|
|
47928
|
+
});
|
|
47741
47929
|
const name = frontmatter.factorydroid?.name;
|
|
47742
47930
|
const styleName = name ?? stem;
|
|
47743
47931
|
if (FACTORYDROID_RESERVED_OUTPUT_STYLE_NAMES.includes(styleName.trim().toLowerCase())) throw new Error(`Rule '${sourcePath}' sets factorydroid.channel: output-style with the style name '${styleName}', which is reserved: Factory Droid's built-in Default and Concise styles cannot be replaced. Rename the rule file or set factorydroid.name.`);
|
|
47744
|
-
const styleFrontmatter = {
|
|
47745
|
-
|
|
47746
|
-
|
|
47747
|
-
};
|
|
47932
|
+
const styleFrontmatter = buildOutputStyleFrontmatter({
|
|
47933
|
+
name,
|
|
47934
|
+
description: frontmatter.description
|
|
47935
|
+
});
|
|
47748
47936
|
const body = rulesyncRule.getBody();
|
|
47749
47937
|
return new FactorydroidRule({
|
|
47750
47938
|
outputRoot,
|
|
@@ -49380,12 +49568,19 @@ var QoderRule = class QoderRule extends ToolRule {
|
|
|
49380
49568
|
* `.qoder/output-styles/`, at either scope.
|
|
49381
49569
|
*/
|
|
49382
49570
|
static isOutputStylePath({ relativeDirPath, relativeFilePath }) {
|
|
49383
|
-
return
|
|
49571
|
+
return isDirectMarkdownChild({
|
|
49572
|
+
outputStylesDirPath: QODER_OUTPUT_STYLES_DIR_PATH,
|
|
49573
|
+
relativeDirPath,
|
|
49574
|
+
relativeFilePath
|
|
49575
|
+
});
|
|
49384
49576
|
}
|
|
49385
49577
|
/** Whether a rule opts into the `output-style` channel (non-root only). */
|
|
49386
49578
|
static isOutputStyleRule(rulesyncRule) {
|
|
49387
49579
|
const frontmatter = rulesyncRule.getFrontmatter();
|
|
49388
|
-
return
|
|
49580
|
+
return isOutputStyleChannel({
|
|
49581
|
+
root: frontmatter.root,
|
|
49582
|
+
channel: frontmatter.qoder?.channel
|
|
49583
|
+
});
|
|
49389
49584
|
}
|
|
49390
49585
|
/**
|
|
49391
49586
|
* The output-styles directory, enumerated for import only: it also holds
|
|
@@ -49496,16 +49691,18 @@ var QoderRule = class QoderRule extends ToolRule {
|
|
|
49496
49691
|
static buildOutputStyle({ outputRoot, rulesyncRule, validate }) {
|
|
49497
49692
|
const { description, qoder } = rulesyncRule.getFrontmatter();
|
|
49498
49693
|
const sourcePath = rulesyncRule.getRelativeFilePath();
|
|
49499
|
-
|
|
49500
|
-
|
|
49694
|
+
const stem = resolveTopLevelOutputStyleStem({
|
|
49695
|
+
sourcePath,
|
|
49696
|
+
message: `Rule '${sourcePath}' sets qoder.channel: output-style but is nested in a subdirectory: Qoder reads only the files directly in .qoder/output-styles/, so move it to the top of ${RULESYNC_RULES_RELATIVE_DIR_PATH}.`
|
|
49697
|
+
});
|
|
49501
49698
|
return new QoderRule({
|
|
49502
49699
|
outputRoot,
|
|
49503
49700
|
relativeDirPath: QODER_OUTPUT_STYLES_DIR_PATH,
|
|
49504
|
-
relativeFilePath: `${
|
|
49505
|
-
frontmatter: {
|
|
49506
|
-
|
|
49507
|
-
|
|
49508
|
-
},
|
|
49701
|
+
relativeFilePath: `${stem}.md`,
|
|
49702
|
+
frontmatter: buildOutputStyleFrontmatter({
|
|
49703
|
+
name: qoder?.name,
|
|
49704
|
+
description
|
|
49705
|
+
}),
|
|
49509
49706
|
body: rulesyncRule.getBody(),
|
|
49510
49707
|
validate,
|
|
49511
49708
|
root: false,
|
|
@@ -56183,9 +56380,9 @@ function groupDefinitionsByMatcher({ definitions, eventName, converterConfig, wa
|
|
|
56183
56380
|
});
|
|
56184
56381
|
translatedMatchers.set(normalizedMatcher, matcher);
|
|
56185
56382
|
}
|
|
56186
|
-
const key = [matcher, ...subdividingFields.map(({ canonical, valueType }) => {
|
|
56383
|
+
const key = [matcher, ...subdividingFields.map(({ canonical, valueType, events }) => {
|
|
56187
56384
|
const value = def[canonical];
|
|
56188
|
-
return isGroupPassthroughValue(value, valueType) ? stableJson(value) : "";
|
|
56385
|
+
return isGroupFieldOnEvent(events, eventName) && isGroupPassthroughValue(value, valueType) ? stableJson(value) : "";
|
|
56189
56386
|
})].join("\0");
|
|
56190
56387
|
const group = byMatcher.get(key);
|
|
56191
56388
|
if (group) group.defs.push(def);
|
|
@@ -56226,6 +56423,7 @@ function runsInPosixShell({ shell, converterConfig }) {
|
|
|
56226
56423
|
* Apply the optional project directory variable prefix to a command string.
|
|
56227
56424
|
*/
|
|
56228
56425
|
function applyCommandPrefix({ def, converterConfig }) {
|
|
56426
|
+
if (converterConfig.stripsProjectDirVarOnImportOnly) return def.command;
|
|
56229
56427
|
const commandText = def.command;
|
|
56230
56428
|
const trimmedCommand = typeof commandText === "string" ? commandText.trimStart() : void 0;
|
|
56231
56429
|
const unquotedCommand = trimmedCommand?.replace(/^["']/, "");
|
|
@@ -56434,12 +56632,16 @@ const describeInvalidObject = ({ tool, canonical, value }) => isPlainObject(valu
|
|
|
56434
56632
|
value
|
|
56435
56633
|
})} Importing it would fail validation on the next run.` : `Dropping "${tool}" while importing a hook: it must be an object.`;
|
|
56436
56634
|
const describeInvalidRecord = ({ tool }) => `Dropping "${tool}" while importing a hook: it must be a map of strings whose keys are non-empty and free of "=", and whose keys and values carry no newline, carriage return or NUL characters.`;
|
|
56635
|
+
function isGroupFieldOnEvent(events, eventName) {
|
|
56636
|
+
return events === void 0 || events.has(eventName);
|
|
56637
|
+
}
|
|
56437
56638
|
/**
|
|
56438
56639
|
* Check a value against the shape its field documents. A string field also
|
|
56439
56640
|
* rejects control characters, matching the canonical `safeString` so an
|
|
56440
56641
|
* imported value cannot fail validation on the next generate.
|
|
56441
56642
|
*/
|
|
56442
56643
|
function isGroupPassthroughValue(value, valueType = "object") {
|
|
56644
|
+
if (valueType === "number") return typeof value === "number" && Number.isFinite(value);
|
|
56443
56645
|
if (valueType === "string") return typeof value === "string" && !CONTROL_CHARS.some((char) => value.includes(char));
|
|
56444
56646
|
return isPlainObject(value);
|
|
56445
56647
|
}
|
|
@@ -56449,10 +56651,14 @@ function isGroupPassthroughValue(value, valueType = "object") {
|
|
|
56449
56651
|
*/
|
|
56450
56652
|
function emitGroupPassthroughFields({ defs, eventName, converterConfig, logger }) {
|
|
56451
56653
|
const emitted = {};
|
|
56452
|
-
for (const { canonical, tool, valueType } of converterConfig.groupPassthroughFields ?? []) {
|
|
56654
|
+
for (const { canonical, tool, valueType, events } of converterConfig.groupPassthroughFields ?? []) {
|
|
56453
56655
|
const carried = defs.map((def) => def[canonical]);
|
|
56454
56656
|
const first = carried.find((value) => isGroupPassthroughValue(value, valueType));
|
|
56455
56657
|
if (first === void 0) continue;
|
|
56658
|
+
if (!isGroupFieldOnEvent(events, eventName)) {
|
|
56659
|
+
logger?.warn(`"${canonical}" on "${eventName}" hooks is ignored — the tool supports "${tool}" only on ${[...events ?? []].map((event) => `"${event}"`).join(", ")} hooks.`);
|
|
56660
|
+
continue;
|
|
56661
|
+
}
|
|
56456
56662
|
const firstStable = stableJson(first);
|
|
56457
56663
|
const agrees = (value) => isGroupPassthroughValue(value, valueType) && stableJson(value) === firstStable;
|
|
56458
56664
|
if (!carried.every(agrees)) logger?.warn(`"${tool}" belongs to the whole matcher group on "${eventName}" hooks, so every hook in this group gets ${quoteValueForWarning(first)} — including any that asked for something else, or for nothing.`);
|
|
@@ -56464,9 +56670,9 @@ function emitGroupPassthroughFields({ defs, eventName, converterConfig, logger }
|
|
|
56464
56670
|
* Import the configured group-level passthrough fields onto a definition,
|
|
56465
56671
|
* reversing {@link emitGroupPassthroughFields}.
|
|
56466
56672
|
*/
|
|
56467
|
-
function importGroupPassthroughFields({ rawEntry, converterConfig }) {
|
|
56673
|
+
function importGroupPassthroughFields({ rawEntry, eventName, converterConfig }) {
|
|
56468
56674
|
const entry = rawEntry;
|
|
56469
|
-
return Object.fromEntries((converterConfig.groupPassthroughFields ?? []).filter(({ tool, valueType }) => isGroupPassthroughValue(entry[tool], valueType)).map(({ canonical, tool }) => [canonical, entry[tool]]));
|
|
56675
|
+
return Object.fromEntries((converterConfig.groupPassthroughFields ?? []).filter(({ tool, valueType, events }) => isGroupFieldOnEvent(events, eventName) && isGroupPassthroughValue(entry[tool], valueType)).map(({ canonical, tool }) => [canonical, entry[tool]]));
|
|
56470
56676
|
}
|
|
56471
56677
|
/**
|
|
56472
56678
|
* Emit the payload fields specific to a hook type — `url`/`headers`/
|
|
@@ -56792,19 +56998,23 @@ function stripCommandPrefix({ command, args, shell, converterConfig, warn }) {
|
|
|
56792
56998
|
const cmd = typeof command === "string" ? command : void 0;
|
|
56793
56999
|
const { projectDirVar } = converterConfig;
|
|
56794
57000
|
if (projectDirVar === "" || typeof cmd !== "string") return cmd;
|
|
56795
|
-
|
|
56796
|
-
|
|
56797
|
-
|
|
56798
|
-
|
|
56799
|
-
|
|
56800
|
-
|
|
56801
|
-
|
|
56802
|
-
|
|
56803
|
-
|
|
56804
|
-
|
|
56805
|
-
|
|
56806
|
-
|
|
56807
|
-
|
|
57001
|
+
let stripped = cmd;
|
|
57002
|
+
for (const variable of [projectDirVar, ...converterConfig.importedProjectDirVarAliases ?? []]) {
|
|
57003
|
+
const result = importProjectDirVariable({
|
|
57004
|
+
command: stripped,
|
|
57005
|
+
projectDirVar: variable,
|
|
57006
|
+
firstWordOnly: isExecFormHook({
|
|
57007
|
+
args,
|
|
57008
|
+
converterConfig
|
|
57009
|
+
}) || !runsInPosixShell({
|
|
57010
|
+
shell,
|
|
57011
|
+
converterConfig
|
|
57012
|
+
})
|
|
57013
|
+
});
|
|
57014
|
+
stripped = result.command;
|
|
57015
|
+
if (warn && result.unrestored && !converterConfig.stripsProjectDirVarOnImportOnly) warn(`Hook command ${quoteValueForWarning(cmd)} was imported as ${quoteValueForWarning(stripped)}: a ${variable} path that is not a script the command runs becomes relative to the hook's working directory and is not restored on generate. Put the exact command in a tool-specific hooks override (such as "claudecode.hooks") to keep the variable.`);
|
|
57016
|
+
if (warn && result.retained) warn(`Hook command ${quoteValueForWarning(cmd)} keeps ${variable} in an assignment or option value, or in a later word of a command whose later words are not normalized, so the imported command still refers to it and other tools that do not define it receive it verbatim. Put the exact command in a tool-specific hooks override (such as "claudecode.hooks") if other tools should not see it.`);
|
|
57017
|
+
}
|
|
56808
57018
|
return stripped;
|
|
56809
57019
|
}
|
|
56810
57020
|
/**
|
|
@@ -56845,7 +57055,7 @@ function isStringRecord(value) {
|
|
|
56845
57055
|
}
|
|
56846
57056
|
/** Compare object values without letting key order decide the answer. */
|
|
56847
57057
|
function stableJson(value) {
|
|
56848
|
-
if (typeof value
|
|
57058
|
+
if (typeof value !== "object") return JSON.stringify(value);
|
|
56849
57059
|
return JSON.stringify(Object.fromEntries(Object.entries(value).toSorted(([a], [b]) => a.localeCompare(b))));
|
|
56850
57060
|
}
|
|
56851
57061
|
/**
|
|
@@ -57085,6 +57295,7 @@ function toolHookToCanonical({ h, rawEntry, eventName, converterConfig, warn })
|
|
|
57085
57295
|
}),
|
|
57086
57296
|
...importGroupPassthroughFields({
|
|
57087
57297
|
rawEntry,
|
|
57298
|
+
eventName,
|
|
57088
57299
|
converterConfig
|
|
57089
57300
|
}),
|
|
57090
57301
|
...matcher !== void 0 && matcher !== null && matcher !== "" && { matcher }
|
|
@@ -57137,15 +57348,26 @@ function definingFields({ h, rawEntry, hookType, converterConfig }) {
|
|
|
57137
57348
|
* is the group-level twin of `matcher`: importing the group without it would
|
|
57138
57349
|
* widen every hook in it, so the group is skipped instead.
|
|
57139
57350
|
*/
|
|
57140
|
-
function describeGroupSkipReason({ rawEntry, converterConfig }) {
|
|
57351
|
+
function describeGroupSkipReason({ rawEntry, eventName, converterConfig }) {
|
|
57141
57352
|
const entry = rawEntry;
|
|
57142
|
-
for (const { tool, valueType, subdividesGroup } of converterConfig.groupPassthroughFields ?? []) {
|
|
57353
|
+
for (const { tool, valueType, subdividesGroup, events, onInvalidImport } of converterConfig.groupPassthroughFields ?? []) {
|
|
57143
57354
|
const value = entry[tool];
|
|
57144
|
-
if (subdividesGroup !== true || value === void 0) continue;
|
|
57355
|
+
if (subdividesGroup !== true || onInvalidImport === "drop-field" || value === void 0 || !isGroupFieldOnEvent(events, eventName)) continue;
|
|
57145
57356
|
if (!isGroupPassthroughValue(value, valueType)) return `Skipping the hooks of a matcher group while importing: its "${tool}" (${quoteValueForWarning(value)}) is unusable, and these hooks run only where it matches. Importing them without it would widen when they fire, so they are skipped.`;
|
|
57146
57357
|
}
|
|
57147
57358
|
}
|
|
57148
57359
|
/**
|
|
57360
|
+
* Warn about each `"drop-field"` group value that is present but unusable;
|
|
57361
|
+
* {@link importGroupPassthroughFields} leaves it off the imported hooks.
|
|
57362
|
+
*/
|
|
57363
|
+
function warnDroppedGroupFields({ rawEntry, eventName, converterConfig, warn }) {
|
|
57364
|
+
const entry = rawEntry;
|
|
57365
|
+
for (const { tool, valueType, events, onInvalidImport } of converterConfig.groupPassthroughFields ?? []) {
|
|
57366
|
+
const value = entry[tool];
|
|
57367
|
+
if (onInvalidImport === "drop-field" && value !== void 0 && isGroupFieldOnEvent(events, eventName) && !isGroupPassthroughValue(value, valueType)) warn?.(`Dropping the unusable "${tool}" (${quoteValueForWarning(value)}) of a "${eventName}" matcher group while importing; its hooks are imported without it.`);
|
|
57368
|
+
}
|
|
57369
|
+
}
|
|
57370
|
+
/**
|
|
57149
57371
|
* Why this hook cannot be imported, or `undefined` when it can.
|
|
57150
57372
|
*/
|
|
57151
57373
|
function describeHookSkipReason({ h, rawEntry, hookType, converterConfig }) {
|
|
@@ -57172,12 +57394,19 @@ function toolMatcherEntryToCanonical({ rawEntry, eventName, converterConfig, war
|
|
|
57172
57394
|
const hookDefs = rawEntry.hooks ?? [];
|
|
57173
57395
|
const groupSkipReason = describeGroupSkipReason({
|
|
57174
57396
|
rawEntry,
|
|
57397
|
+
eventName,
|
|
57175
57398
|
converterConfig
|
|
57176
57399
|
});
|
|
57177
57400
|
if (groupSkipReason !== void 0) {
|
|
57178
57401
|
warn?.(groupSkipReason);
|
|
57179
57402
|
return [];
|
|
57180
57403
|
}
|
|
57404
|
+
warnDroppedGroupFields({
|
|
57405
|
+
rawEntry,
|
|
57406
|
+
eventName,
|
|
57407
|
+
converterConfig,
|
|
57408
|
+
warn
|
|
57409
|
+
});
|
|
57181
57410
|
const definitions = [];
|
|
57182
57411
|
for (const h of hookDefs) {
|
|
57183
57412
|
const skipReason = describeHookSkipReason({
|
|
@@ -60516,6 +60745,23 @@ var CopilotcliHooks = class CopilotcliHooks extends ToolHooks {
|
|
|
60516
60745
|
});
|
|
60517
60746
|
}
|
|
60518
60747
|
};
|
|
60748
|
+
// src/features/hooks/hook-matcher-tool-names.ts
|
|
60749
|
+
const MATCHER_TOKEN_PATTERN = /\\.|\[(?:\\.|[^\]\\])*\]|(?<=^|[^\w\\]|\\\w)[A-Za-z_]\w*(?!\w)/gs;
|
|
60750
|
+
function isMatcherName(token) {
|
|
60751
|
+
return !token.startsWith("\\") && !token.startsWith("[");
|
|
60752
|
+
}
|
|
60753
|
+
/**
|
|
60754
|
+
* Rewrites the whole tool names inside a hook `matcher` regex through `names`,
|
|
60755
|
+
* leaving escapes, character classes and unknown names as written. Used by
|
|
60756
|
+
* adapters whose runtime tool names differ from the canonical (Claude Code)
|
|
60757
|
+
* spelling.
|
|
60758
|
+
*/
|
|
60759
|
+
function replaceMatcherNames(matcher, names) {
|
|
60760
|
+
return matcher.replace(MATCHER_TOKEN_PATTERN, (token) => isMatcherName(token) && Object.hasOwn(names, token) ? names[token] ?? token : token);
|
|
60761
|
+
}
|
|
60762
|
+
function invertToolNames(names) {
|
|
60763
|
+
return Object.fromEntries(Object.entries(names).map(([canonical, tool]) => [tool, canonical]));
|
|
60764
|
+
}
|
|
60519
60765
|
// src/features/hooks/cortexcode-hooks.ts
|
|
60520
60766
|
const CORTEXCODE_NO_MATCHER_EVENTS = /* @__PURE__ */ new Set(["beforeSubmitPrompt", "stop"]);
|
|
60521
60767
|
const CANONICAL_TO_CORTEXCODE_TOOL_NAMES = {
|
|
@@ -60526,14 +60772,7 @@ const CANONICAL_TO_CORTEXCODE_TOOL_NAMES = {
|
|
|
60526
60772
|
Grep: "grep",
|
|
60527
60773
|
Glob: "glob"
|
|
60528
60774
|
};
|
|
60529
|
-
const CORTEXCODE_TO_CANONICAL_TOOL_NAMES =
|
|
60530
|
-
const MATCHER_TOKEN_PATTERN = /\\.|\[(?:\\.|[^\]\\])*\]|(?<=^|[^\w\\]|\\\w)[A-Za-z_]\w*(?!\w)/gs;
|
|
60531
|
-
function isMatcherName(token) {
|
|
60532
|
-
return !token.startsWith("\\") && !token.startsWith("[");
|
|
60533
|
-
}
|
|
60534
|
-
function replaceMatcherNames(matcher, names) {
|
|
60535
|
-
return matcher.replace(MATCHER_TOKEN_PATTERN, (token) => isMatcherName(token) && Object.hasOwn(names, token) ? names[token] ?? token : token);
|
|
60536
|
-
}
|
|
60775
|
+
const CORTEXCODE_TO_CANONICAL_TOOL_NAMES = invertToolNames(CANONICAL_TO_CORTEXCODE_TOOL_NAMES);
|
|
60537
60776
|
function canonicalToCortexcodeMatcher({ matcher, eventName, warn }) {
|
|
60538
60777
|
const translated = replaceMatcherNames(matcher, CANONICAL_TO_CORTEXCODE_TOOL_NAMES);
|
|
60539
60778
|
const unmatched = [...translated.matchAll(MATCHER_TOKEN_PATTERN)].map(([token]) => token).filter((token) => isMatcherName(token) && /^[A-Z]/.test(token));
|
|
@@ -65694,6 +65933,61 @@ var TabnineHooks = class TabnineHooks extends ToolHooks {
|
|
|
65694
65933
|
});
|
|
65695
65934
|
}
|
|
65696
65935
|
};
|
|
65936
|
+
// src/features/hooks/trae-hooks.ts
|
|
65937
|
+
const TRAE_NO_MATCHER_EVENTS = /* @__PURE__ */ new Set([
|
|
65938
|
+
"sessionStart",
|
|
65939
|
+
"beforeSubmitPrompt",
|
|
65940
|
+
"stop"
|
|
65941
|
+
]);
|
|
65942
|
+
const CANONICAL_TO_TRAE_TOOL_NAMES = { Bash: "RunCommand" };
|
|
65943
|
+
const TRAE_TO_CANONICAL_TOOL_NAMES = invertToolNames(CANONICAL_TO_TRAE_TOOL_NAMES);
|
|
65944
|
+
const TRAE_SPEC = {
|
|
65945
|
+
displayName: "TRAE",
|
|
65946
|
+
overrideKey: "trae",
|
|
65947
|
+
converterConfig: {
|
|
65948
|
+
supportedEvents: TRAE_HOOK_EVENTS,
|
|
65949
|
+
canonicalToToolEventNames: CANONICAL_TO_TRAE_EVENT_NAMES,
|
|
65950
|
+
toolToCanonicalEventNames: TRAE_TO_CANONICAL_EVENT_NAMES,
|
|
65951
|
+
projectDirVar: "$TRAE_PROJECT_DIR",
|
|
65952
|
+
stripsProjectDirVarOnImportOnly: true,
|
|
65953
|
+
importedProjectDirVarAliases: ["$CLAUDE_PROJECT_DIR"],
|
|
65954
|
+
noMatcherEvents: TRAE_NO_MATCHER_EVENTS,
|
|
65955
|
+
supportedHookTypes: /* @__PURE__ */ new Set(["command"]),
|
|
65956
|
+
groupPassthroughFields: [{
|
|
65957
|
+
canonical: "loop_limit",
|
|
65958
|
+
tool: "loop_limit",
|
|
65959
|
+
valueType: "number",
|
|
65960
|
+
events: /* @__PURE__ */ new Set(["stop"]),
|
|
65961
|
+
subdividesGroup: true,
|
|
65962
|
+
onInvalidImport: "drop-field"
|
|
65963
|
+
}],
|
|
65964
|
+
matcherTranslation: {
|
|
65965
|
+
toTool: ({ matcher }) => replaceMatcherNames(matcher, CANONICAL_TO_TRAE_TOOL_NAMES),
|
|
65966
|
+
toCanonical: (matcher) => replaceMatcherNames(matcher, TRAE_TO_CANONICAL_TOOL_NAMES)
|
|
65967
|
+
}
|
|
65968
|
+
}
|
|
65969
|
+
};
|
|
65970
|
+
/**
|
|
65971
|
+
* TRAE hooks.
|
|
65972
|
+
*
|
|
65973
|
+
* Hooks live under the top-level `hooks` key of the dedicated `.trae/hooks.json`
|
|
65974
|
+
* (project) and `~/.trae/hooks.json` (user scope). The IDE also writes a
|
|
65975
|
+
* `version` key there, so generation merges the `hooks` key into the file (see
|
|
65976
|
+
* `SHARED_CONFIG_OWNERSHIP`) instead of overwriting it.
|
|
65977
|
+
*
|
|
65978
|
+
* @see https://docs.trae.ai/ide/hook-configuration-reference?_lang=en
|
|
65979
|
+
*/
|
|
65980
|
+
var TraeHooks = class extends SettingsJsonHooks {
|
|
65981
|
+
static getSpec() {
|
|
65982
|
+
return TRAE_SPEC;
|
|
65983
|
+
}
|
|
65984
|
+
static getSettablePaths(_options = {}) {
|
|
65985
|
+
return {
|
|
65986
|
+
relativeDirPath: TRAE_DIR,
|
|
65987
|
+
relativeFilePath: TRAE_HOOKS_FILE_NAME
|
|
65988
|
+
};
|
|
65989
|
+
}
|
|
65990
|
+
};
|
|
65697
65991
|
// src/features/hooks/vibe-hooks.ts
|
|
65698
65992
|
const VIBE_DIR = ".vibe";
|
|
65699
65993
|
const VIBE_HOOKS_FILE_NAME = "hooks.toml";
|
|
@@ -66878,6 +67172,21 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
66878
67172
|
supportedHookTypes: ["command"],
|
|
66879
67173
|
supportsMatcher: true
|
|
66880
67174
|
}],
|
|
67175
|
+
["trae", {
|
|
67176
|
+
class: TraeHooks,
|
|
67177
|
+
meta: {
|
|
67178
|
+
supportsProject: true,
|
|
67179
|
+
supportsGlobal: true,
|
|
67180
|
+
supportsImport: true
|
|
67181
|
+
},
|
|
67182
|
+
...converterCapabilities(TraeHooks.getConverterConfig()),
|
|
67183
|
+
supportsMatcher: true,
|
|
67184
|
+
matcherEvents: [
|
|
67185
|
+
"preToolUse",
|
|
67186
|
+
"postToolUse",
|
|
67187
|
+
"notification"
|
|
67188
|
+
]
|
|
67189
|
+
}],
|
|
66881
67190
|
["vibe", {
|
|
66882
67191
|
class: VibeHooks,
|
|
66883
67192
|
meta: {
|
|
@@ -68986,69 +69295,6 @@ var AiassistantMcp = class extends ToolMcp {
|
|
|
68986
69295
|
});
|
|
68987
69296
|
}
|
|
68988
69297
|
};
|
|
68989
|
-
// src/features/shared/amp-settings.ts
|
|
68990
|
-
/**
|
|
68991
|
-
* Parse an Amp `settings.json` / `settings.jsonc` document.
|
|
68992
|
-
*
|
|
68993
|
-
* Shared by the MCP and permissions adapters because both read the very same
|
|
68994
|
-
* file. Two separately maintained copies drifted once already: the permissions
|
|
68995
|
-
* side was hardened while the MCP side kept calling `jsonc-parser` directly,
|
|
68996
|
-
* which left it importing MCP servers reachable only through an injected
|
|
68997
|
-
* `__proto__`.
|
|
68998
|
-
*
|
|
68999
|
-
* {@link parseJsonc} is the strict, sanitizing parser: it throws on any syntax
|
|
69000
|
-
* error instead of returning `jsonc-parser`'s best-effort value, and it
|
|
69001
|
-
* rebuilds every object from its own enumerable entries, so a root `__proto__`
|
|
69002
|
-
* cannot swap the returned object's prototype and `constructor` / `prototype`
|
|
69003
|
-
* never survive as own keys, at any depth.
|
|
69004
|
-
*
|
|
69005
|
-
* Removal is silent, matching every other tool-side adapter (opencode, kilo,
|
|
69006
|
-
* copilot). Reporting the removed keys through `droppedPollutionKeysError` is
|
|
69007
|
-
* reserved for the three files a user authors under `.rulesync/`, whose whole
|
|
69008
|
-
* purpose is to be turned into tool config — a key that vanishes there needs
|
|
69009
|
-
* explaining, whereas here the surrounding settings are the user's own file
|
|
69010
|
-
* and are left alone.
|
|
69011
|
-
*
|
|
69012
|
-
* Note the consequence for a root `__proto__`: sanitizing runs before the
|
|
69013
|
-
* plain-object check, so such a document parses successfully with the key
|
|
69014
|
-
* stripped rather than failing that check. That is deliberate — dropping the
|
|
69015
|
-
* one poisoned key keeps the user's unrelated settings — and matches the
|
|
69016
|
-
* sanitize-before-the-root-check design the shared config gateway documents.
|
|
69017
|
-
*/
|
|
69018
|
-
function parseAmpSettings({ fileContent }) {
|
|
69019
|
-
let parsed;
|
|
69020
|
-
try {
|
|
69021
|
-
parsed = parseJsonc(fileContent || "{}");
|
|
69022
|
-
} catch (error) {
|
|
69023
|
-
throw new Error(`Failed to parse Amp settings: ${formatError(error)}`, { cause: error });
|
|
69024
|
-
}
|
|
69025
|
-
if (!isPlainObject(parsed)) throw new Error("Amp settings must be a JSON object");
|
|
69026
|
-
return parsed;
|
|
69027
|
-
}
|
|
69028
|
-
/**
|
|
69029
|
-
* Probe `<jsonDir>/settings.jsonc` first, falling back to `settings.json`, so
|
|
69030
|
-
* an existing user file is read-modified-written in place instead of a fresh
|
|
69031
|
-
* `.json` sibling being created next to a hand-authored `.jsonc`. Defaults to
|
|
69032
|
-
* `settings.json` when neither file exists: Amp's canonically documented
|
|
69033
|
-
* settings file (`.amp/settings.json`), shared by the MCP and permissions
|
|
69034
|
-
* adapters, so generating both on a clean repo writes a single settings file.
|
|
69035
|
-
*/
|
|
69036
|
-
async function resolveAmpSettingsFile(jsonDir) {
|
|
69037
|
-
const jsoncContent = await readFileContentOrNull(join(jsonDir, AMP_SETTINGS_JSONC_FILE_NAME));
|
|
69038
|
-
if (jsoncContent !== null) return {
|
|
69039
|
-
fileContent: jsoncContent,
|
|
69040
|
-
relativeFilePath: AMP_SETTINGS_JSONC_FILE_NAME
|
|
69041
|
-
};
|
|
69042
|
-
const jsonContent = await readFileContentOrNull(join(jsonDir, AMP_SETTINGS_FILE_NAME));
|
|
69043
|
-
if (jsonContent !== null) return {
|
|
69044
|
-
fileContent: jsonContent,
|
|
69045
|
-
relativeFilePath: AMP_SETTINGS_FILE_NAME
|
|
69046
|
-
};
|
|
69047
|
-
return {
|
|
69048
|
-
fileContent: null,
|
|
69049
|
-
relativeFilePath: AMP_SETTINGS_FILE_NAME
|
|
69050
|
-
};
|
|
69051
|
-
}
|
|
69052
69298
|
// src/features/mcp/amp-mcp.ts
|
|
69053
69299
|
const AMP_MCP_SERVERS_KEY = "amp.mcpServers";
|
|
69054
69300
|
function filterMcpServers(mcpServers) {
|
|
@@ -72034,7 +72280,7 @@ var CopilotMcp = class CopilotMcp extends ToolMcp {
|
|
|
72034
72280
|
fileKey: sharedConfigFileKey(paths),
|
|
72035
72281
|
feature: "mcp",
|
|
72036
72282
|
existingContent,
|
|
72037
|
-
patch: { servers: convertToCopilotFormat(mcpServers) },
|
|
72283
|
+
patch: { servers: undefinedWhenEmpty(convertToCopilotFormat(mcpServers)) },
|
|
72038
72284
|
filePath,
|
|
72039
72285
|
logger
|
|
72040
72286
|
}),
|
|
@@ -74355,14 +74601,20 @@ function resolveGooseUrl(config) {
|
|
|
74355
74601
|
}
|
|
74356
74602
|
/**
|
|
74357
74603
|
* Determines the Goose `type` for a server based on its command/url/transport.
|
|
74604
|
+
*
|
|
74605
|
+
* Goose no longer has an SSE extension type (it skips a `type: sse` entry as
|
|
74606
|
+
* unsupported), so every remote server is written as `streamable_http`.
|
|
74358
74607
|
*/
|
|
74359
74608
|
function resolveGooseType(config, url) {
|
|
74360
74609
|
if (config.command !== void 0) return "stdio";
|
|
74361
|
-
if (url !== void 0) return
|
|
74610
|
+
if (url !== void 0) return "streamable_http";
|
|
74362
74611
|
return canonicalTransport(config) === "builtin" ? "builtin" : "stdio";
|
|
74363
74612
|
}
|
|
74364
74613
|
/**
|
|
74365
|
-
* The Goose extension types that carry an MCP server.
|
|
74614
|
+
* The Goose extension types that carry an MCP server. `sse` is no longer one
|
|
74615
|
+
* Goose reads, but it stays here so a stale `type: sse` entry is still
|
|
74616
|
+
* recognized: imported as canonical `sse` and replaced by the regenerated
|
|
74617
|
+
* same-name `streamable_http` entry. Goose also documents
|
|
74366
74618
|
* `builtin`, `platform`, `frontend` and `inline_python` extensions, which have
|
|
74367
74619
|
* no canonical MCP counterpart: they name capabilities Goose provides itself
|
|
74368
74620
|
* rather than a server rulesync could describe.
|
|
@@ -74422,7 +74674,8 @@ function convertServerToGooseExtension(name, config, logger) {
|
|
|
74422
74674
|
warnWithFallback(logger, `Goose extension "${name}" has no command to run; skipping it rather than writing a stdio extension Goose cannot start to ~/.config/goose/config.yaml.`);
|
|
74423
74675
|
return;
|
|
74424
74676
|
}
|
|
74425
|
-
} else if (gooseType === "
|
|
74677
|
+
} else if (gooseType === "streamable_http") {
|
|
74678
|
+
if (canonicalTransport(config) === "sse") warnWithFallback(logger, `Goose extension "${name}" uses SSE, which Goose no longer supports; writing it as streamable_http, which only works if the server also speaks Streamable HTTP.`);
|
|
74426
74679
|
if (url !== void 0) ext.uri = url;
|
|
74427
74680
|
if (isPlainObject(config.headers)) ext.headers = omitPrototypePollutionKeys(config.headers);
|
|
74428
74681
|
}
|
|
@@ -74435,8 +74688,9 @@ function convertServerToGooseExtension(name, config, logger) {
|
|
|
74435
74688
|
* Converts rulesync canonical MCP servers into Goose `extensions:` entries.
|
|
74436
74689
|
*
|
|
74437
74690
|
* Goose uses a non-standard schema: `name`, `type` (`stdio` | `streamable_http`
|
|
74438
|
-
* | `
|
|
74439
|
-
*
|
|
74691
|
+
* | `builtin`), `cmd`/`args`/`envs` for stdio, `uri`/`headers` for remote, plus
|
|
74692
|
+
* `enabled` and `timeout`. A canonical `sse` server is written as
|
|
74693
|
+
* `streamable_http` with a warning, since Goose dropped SSE support.
|
|
74440
74694
|
*
|
|
74441
74695
|
* `extensions:` is co-owned: alongside the MCP servers rulesync manages it also
|
|
74442
74696
|
* holds Goose's own `builtin`/`platform`/`frontend`/`inline_python` extensions
|
|
@@ -74567,7 +74821,8 @@ function convertToGoosePluginMcpServers(mcpServers, logger) {
|
|
|
74567
74821
|
* `~/.config/goose/config.yaml`. That file also holds other Goose settings
|
|
74568
74822
|
* (model, provider, ...), so generation merges the `extensions:` block into the
|
|
74569
74823
|
* existing config instead of overwriting it, and the file is never deleted.
|
|
74570
|
-
* This location supports both stdio and remote (
|
|
74824
|
+
* This location supports both stdio and remote servers (SSE is written as
|
|
74825
|
+
* `streamable_http`, since Goose no longer reads `type: sse`).
|
|
74571
74826
|
* - **Project**: a stdio-only open-plugin manifest at
|
|
74572
74827
|
* `.agents/plugins/rulesync/.mcp.json` (Goose v1.39.0+), reusing the same
|
|
74573
74828
|
* `.agents/plugins/rulesync/` tree as Goose hooks. The manifest uses the
|
|
@@ -79285,6 +79540,7 @@ var VibeMcp = class VibeMcp extends ToolMcp {
|
|
|
79285
79540
|
const managedNames = new Set(Object.keys(rulesyncServers));
|
|
79286
79541
|
const mcpServers = Object.entries(rulesyncServers).map(([name, server]) => rulesyncMcpServerToVibe(name, server, existingByName.get(name)));
|
|
79287
79542
|
const unmanagedServers = existingServers.filter((server) => !managedNames.has(server.name));
|
|
79543
|
+
const servers = [...mcpServers, ...unmanagedServers];
|
|
79288
79544
|
return new VibeMcp({
|
|
79289
79545
|
outputRoot,
|
|
79290
79546
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -79293,7 +79549,7 @@ var VibeMcp = class VibeMcp extends ToolMcp {
|
|
|
79293
79549
|
fileKey: sharedConfigFileKey(paths),
|
|
79294
79550
|
feature: "mcp",
|
|
79295
79551
|
existingContent,
|
|
79296
|
-
patch: { mcp_servers:
|
|
79552
|
+
patch: { mcp_servers: servers.length > 0 ? servers : void 0 },
|
|
79297
79553
|
filePath
|
|
79298
79554
|
}),
|
|
79299
79555
|
validate,
|
|
@@ -82717,6 +82973,20 @@ function isUnicodeClass(rest) {
|
|
|
82717
82973
|
}));
|
|
82718
82974
|
}
|
|
82719
82975
|
/**
|
|
82976
|
+
* Return a function that, for a `[:` at `index`, gives the position of the
|
|
82977
|
+
* next `:]` (Go reads everything between as the class name), or -1. The
|
|
82978
|
+
* position is cached and `index` only grows, so a long run of `[:` with no
|
|
82979
|
+
* `:]` costs linear time rather than a scan to the end at each one.
|
|
82980
|
+
*/
|
|
82981
|
+
function posixEndFinder(source) {
|
|
82982
|
+
let next = -2;
|
|
82983
|
+
return (index) => {
|
|
82984
|
+
if (!source.startsWith("[:", index)) return -1;
|
|
82985
|
+
if (next !== -1 && next < index + 2) next = source.indexOf(":]", index + 2);
|
|
82986
|
+
return next;
|
|
82987
|
+
};
|
|
82988
|
+
}
|
|
82989
|
+
/**
|
|
82720
82990
|
* Replace each POSIX class (`[:alpha:]`) inside a bracket class with a plain
|
|
82721
82991
|
* character, so JavaScript can compile the rest, or return `undefined` for an
|
|
82722
82992
|
* unknown one. Only inside a bracket is `[:name:]` a POSIX class; a bare
|
|
@@ -82726,6 +82996,7 @@ function replacePosixClasses(source) {
|
|
|
82726
82996
|
let result = "";
|
|
82727
82997
|
let inClass = false;
|
|
82728
82998
|
let index = 0;
|
|
82999
|
+
const findPosixEnd = posixEndFinder(source);
|
|
82729
83000
|
while (index < source.length) {
|
|
82730
83001
|
const character = source[index] ?? "";
|
|
82731
83002
|
if (character === "\\") {
|
|
@@ -82750,11 +83021,11 @@ function replacePosixClasses(source) {
|
|
|
82750
83021
|
}
|
|
82751
83022
|
continue;
|
|
82752
83023
|
}
|
|
82753
|
-
const
|
|
82754
|
-
if (
|
|
82755
|
-
if (!POSIX_CLASSES.has(
|
|
83024
|
+
const posixEnd = findPosixEnd(index);
|
|
83025
|
+
if (posixEnd !== -1) {
|
|
83026
|
+
if (!POSIX_CLASSES.has(source.slice(index + 2, posixEnd).replace(/^\^/, ""))) return;
|
|
82756
83027
|
result += "a";
|
|
82757
|
-
index
|
|
83028
|
+
index = posixEnd + 2;
|
|
82758
83029
|
continue;
|
|
82759
83030
|
}
|
|
82760
83031
|
if (character === "]") inClass = false;
|
|
@@ -82833,6 +83104,7 @@ function hasClassEscapeRangeEnd(source) {
|
|
|
82833
83104
|
let inClass = false;
|
|
82834
83105
|
let afterCharacter = false;
|
|
82835
83106
|
let index = 0;
|
|
83107
|
+
const findPosixEnd = posixEndFinder(source);
|
|
82836
83108
|
while (index < source.length) {
|
|
82837
83109
|
const character = source[index] ?? "";
|
|
82838
83110
|
if (character === "\\") {
|
|
@@ -82848,7 +83120,7 @@ function hasClassEscapeRangeEnd(source) {
|
|
|
82848
83120
|
});
|
|
82849
83121
|
continue;
|
|
82850
83122
|
}
|
|
82851
|
-
const posixEnd =
|
|
83123
|
+
const posixEnd = findPosixEnd(index);
|
|
82852
83124
|
if (character === "]" || posixEnd !== -1) {
|
|
82853
83125
|
inClass = character !== "]";
|
|
82854
83126
|
afterCharacter = false;
|
|
@@ -83410,6 +83682,8 @@ function buildToolPatternEntry({ toolName, pattern }) {
|
|
|
83410
83682
|
* - `execute_url` takes a domain, like `read_url`.
|
|
83411
83683
|
* - `command` and `unsandboxed` take literal words or a per-word `regex:`,
|
|
83412
83684
|
* matched word by word (see `antigravity-command-patterns.ts`).
|
|
83685
|
+
* - `mcp` takes `server/tool` or `server/*`, matched exactly. A canonical
|
|
83686
|
+
* `mcp__<server>__<tool>` category is written as that target.
|
|
83413
83687
|
*
|
|
83414
83688
|
* A glob copied verbatim (`read_file(**\/*.env)`) is a literal path that
|
|
83415
83689
|
* matches nothing, so a deny written that way blocks nothing. A pattern with
|
|
@@ -83433,7 +83707,8 @@ const DENY_HINTS = {
|
|
|
83433
83707
|
read_file: "deny a directory or file path instead",
|
|
83434
83708
|
write_file: "deny a directory or file path instead",
|
|
83435
83709
|
command: "deny the first words of the command instead",
|
|
83436
|
-
unsandboxed: "deny the first words of the command instead"
|
|
83710
|
+
unsandboxed: "deny the first words of the command instead",
|
|
83711
|
+
mcp: "deny the whole MCP server or tool instead"
|
|
83437
83712
|
};
|
|
83438
83713
|
function hasGlob(pattern) {
|
|
83439
83714
|
return BRACE_ALTERNATIVES.test(pattern) || parseGlobPattern(pattern).steps.some((step) => step.kind !== "literal");
|
|
@@ -83489,6 +83764,40 @@ function toTarget(action, pattern) {
|
|
|
83489
83764
|
if (URL_ACTIONS.has(action)) return toUrlTarget(pattern);
|
|
83490
83765
|
return { target: pattern };
|
|
83491
83766
|
}
|
|
83767
|
+
const MCP_CATEGORY_PREFIX = "mcp__";
|
|
83768
|
+
const MCP_TARGET_SEPARATORS = /[/()]/;
|
|
83769
|
+
/**
|
|
83770
|
+
* The `mcp` target of a canonical `mcp__<server>__<tool>` category:
|
|
83771
|
+
* `<server>/<tool>`, `<server>/*` for a whole server (`mcp__<server>`,
|
|
83772
|
+
* `mcp__<server>__*`), or `*` for every MCP tool (`mcp__*`, `mcp__*__*`). The
|
|
83773
|
+
* server ends at the first `__`, as elsewhere in rulesync. Antigravity matches
|
|
83774
|
+
* names exactly, so a glob, an empty name, or a `/` or parenthesis in a name
|
|
83775
|
+
* has no target; when only the tool part is at fault, its server is the `lead`
|
|
83776
|
+
* a deny widens to.
|
|
83777
|
+
*/
|
|
83778
|
+
function toMcpTarget(category) {
|
|
83779
|
+
const remainder = category.slice(5);
|
|
83780
|
+
const separator = remainder.indexOf("__");
|
|
83781
|
+
const server = separator === -1 ? remainder : remainder.slice(0, separator);
|
|
83782
|
+
const tool = separator === -1 ? "*" : remainder.slice(separator + 2);
|
|
83783
|
+
if (server === "*" && tool === "*") return { target: "*" };
|
|
83784
|
+
const skipReason = "Antigravity MCP targets name one server or one tool exactly. Only `mcp__<server>`, `mcp__<server>__*`, `mcp__<server>__<tool>` or `mcp__*` can be expressed";
|
|
83785
|
+
if (server.length === 0 || MCP_TARGET_SEPARATORS.test(server) || hasGlob(server)) return { skipReason };
|
|
83786
|
+
if (tool === "*") return { target: `${server}/*` };
|
|
83787
|
+
return tool.length === 0 || MCP_TARGET_SEPARATORS.test(tool) || hasGlob(tool) ? {
|
|
83788
|
+
skipReason,
|
|
83789
|
+
lead: `${server}/*`
|
|
83790
|
+
} : { target: `${server}/${tool}` };
|
|
83791
|
+
}
|
|
83792
|
+
/** The target of a canonical `mcp__...` rule, with the `lead` a deny widens to. */
|
|
83793
|
+
function toMcpRuleTarget(category, pattern) {
|
|
83794
|
+
const target = toMcpTarget(category);
|
|
83795
|
+
if (pattern === "*" || !("target" in target)) return target;
|
|
83796
|
+
return {
|
|
83797
|
+
skipReason: "Antigravity MCP targets name a server or a tool, not the tool's arguments",
|
|
83798
|
+
lead: target.target
|
|
83799
|
+
};
|
|
83800
|
+
}
|
|
83492
83801
|
/**
|
|
83493
83802
|
* The leading directories of a path glob (`src` for `src/*.ts`), or
|
|
83494
83803
|
* `undefined` when it has none. A directory target covers everything inside
|
|
@@ -83511,6 +83820,20 @@ function toWidenedDenyTarget(action, pattern) {
|
|
|
83511
83820
|
if (ANTIGRAVITY_COMMAND_ACTIONS.has(action)) return toAntigravityCommandLiteralPrefix(pattern);
|
|
83512
83821
|
if (PATH_ACTIONS.has(action)) return toPathLiteralPrefix(pattern);
|
|
83513
83822
|
}
|
|
83823
|
+
/** The entry a deny is widened to, and what it now blocks. */
|
|
83824
|
+
function describeWidenedDeny({ action, prefix, isMcp }) {
|
|
83825
|
+
if (isMcp) return prefix === "*" ? {
|
|
83826
|
+
entry: action,
|
|
83827
|
+
scope: "every MCP tool"
|
|
83828
|
+
} : {
|
|
83829
|
+
entry: `${action}(${prefix})`,
|
|
83830
|
+
scope: `every call of \`${action}(${prefix})\``
|
|
83831
|
+
};
|
|
83832
|
+
return {
|
|
83833
|
+
entry: `${action}(${prefix})`,
|
|
83834
|
+
scope: PATH_ACTIONS.has(action) ? `every path under \`${prefix}\`` : `every command that starts with \`${prefix}\``
|
|
83835
|
+
};
|
|
83836
|
+
}
|
|
83514
83837
|
/**
|
|
83515
83838
|
* Warn about a path target that is a home or variable reference. Antigravity
|
|
83516
83839
|
* documents absolute and workspace-relative paths only, so such a reference
|
|
@@ -83532,14 +83855,20 @@ function warnHomeOrVariablePath({ action, target, rule, decision, logger, toolLa
|
|
|
83532
83855
|
* nothing enforces it, and a widened one is warned about too, because it
|
|
83533
83856
|
* blocks more than it says.
|
|
83534
83857
|
*/
|
|
83535
|
-
function buildAntigravityPermissionEntry({ action, category, pattern, decision, logger, toolLabel }) {
|
|
83536
|
-
const
|
|
83858
|
+
function buildAntigravityPermissionEntry({ action: toolAction, category, pattern, decision, logger, toolLabel }) {
|
|
83859
|
+
const isMcp = category.startsWith(MCP_CATEGORY_PREFIX);
|
|
83860
|
+
const action = isMcp ? "mcp" : toolAction;
|
|
83861
|
+
const result = isMcp ? toMcpRuleTarget(category, pattern) : pattern === "*" ? { target: "*" } : toTarget(action, pattern);
|
|
83537
83862
|
const rule = `${category}: { "${pattern}": "${decision}" }`;
|
|
83538
83863
|
if ("skipReason" in result) {
|
|
83539
|
-
const prefix = decision
|
|
83864
|
+
const prefix = decision !== "deny" ? void 0 : isMcp ? result.lead : toWidenedDenyTarget(action, pattern);
|
|
83540
83865
|
if (prefix !== void 0) {
|
|
83541
|
-
const
|
|
83542
|
-
|
|
83866
|
+
const { entry, scope } = describeWidenedDeny({
|
|
83867
|
+
action,
|
|
83868
|
+
prefix,
|
|
83869
|
+
isMcp
|
|
83870
|
+
});
|
|
83871
|
+
logger.warn(`${toolLabel} permissions: widening ${rule} to ${entry}. ${result.skipReason}. The deny now blocks ${scope}, overriding any allow or ask for them.`);
|
|
83543
83872
|
warnHomeOrVariablePath({
|
|
83544
83873
|
action,
|
|
83545
83874
|
target: prefix,
|
|
@@ -83548,7 +83877,7 @@ function buildAntigravityPermissionEntry({ action, category, pattern, decision,
|
|
|
83548
83877
|
logger,
|
|
83549
83878
|
toolLabel
|
|
83550
83879
|
});
|
|
83551
|
-
return
|
|
83880
|
+
return entry;
|
|
83552
83881
|
}
|
|
83553
83882
|
const consequence = decision === "deny" ? `This deny is NOT enforced in ${toolLabel}; ${DENY_HINTS[action] ?? "write it in a form Antigravity can match"}` : decision === "ask" ? `${toolLabel} falls back to its default for it, so it runs without a prompt when ${toolLabel} is set to always proceed` : `${toolLabel} falls back to its default for it`;
|
|
83554
83883
|
logger.warn(`${toolLabel} permissions: skipping ${rule}. ${result.skipReason}. ${consequence}.`);
|
|
@@ -83692,6 +84021,7 @@ const ANTIGRAVITY_CLI_TO_CANONICAL_TOOL_NAMES = {
|
|
|
83692
84021
|
mcp: "mcp"
|
|
83693
84022
|
};
|
|
83694
84023
|
function toAntigravityCliToolName(canonical) {
|
|
84024
|
+
if (canonical.startsWith("mcp__")) return "mcp";
|
|
83695
84025
|
return CANONICAL_TO_ANTIGRAVITY_CLI_TOOL_NAMES[canonical] ?? canonical;
|
|
83696
84026
|
}
|
|
83697
84027
|
function toCanonicalToolName$9(cliName) {
|
|
@@ -83754,10 +84084,11 @@ var AntigravityCliPermissions = class AntigravityCliPermissions extends ToolPerm
|
|
|
83754
84084
|
logger
|
|
83755
84085
|
});
|
|
83756
84086
|
const managedToolNames = new Set(Object.keys(config.permission).map((category) => toAntigravityCliToolName(category)));
|
|
84087
|
+
const isManagedToolName = (toolName) => managedToolNames.has(toolName) || managedToolNames.has("mcp") && toolName.startsWith("mcp__");
|
|
83757
84088
|
const existingPermissions = settings.permissions ?? {};
|
|
83758
|
-
const preservedAllow = (existingPermissions.allow ?? []).filter((entry) => !
|
|
83759
|
-
const preservedAsk = (existingPermissions.ask ?? []).filter((entry) => !
|
|
83760
|
-
const preservedDeny = (existingPermissions.deny ?? []).filter((entry) => !
|
|
84089
|
+
const preservedAllow = (existingPermissions.allow ?? []).filter((entry) => !isManagedToolName(parseToolPatternEntry(entry).toolName));
|
|
84090
|
+
const preservedAsk = (existingPermissions.ask ?? []).filter((entry) => !isManagedToolName(parseToolPatternEntry(entry).toolName));
|
|
84091
|
+
const preservedDeny = (existingPermissions.deny ?? []).filter((entry) => !isManagedToolName(parseToolPatternEntry(entry).toolName));
|
|
83761
84092
|
const mergedPermissions = { ...existingPermissions };
|
|
83762
84093
|
const mergedAllow = uniq([...preservedAllow, ...allow].toSorted());
|
|
83763
84094
|
const mergedAsk = uniq([...preservedAsk, ...ask].toSorted());
|
|
@@ -83866,6 +84197,7 @@ const IDE_ACTION_TO_CANONICAL = {
|
|
|
83866
84197
|
mcp: "mcp"
|
|
83867
84198
|
};
|
|
83868
84199
|
function toIdeAction(canonical) {
|
|
84200
|
+
if (canonical.startsWith("mcp__")) return "mcp";
|
|
83869
84201
|
return CANONICAL_TO_IDE_ACTION[canonical] ?? canonical;
|
|
83870
84202
|
}
|
|
83871
84203
|
function toCanonicalCategory$2(ideAction) {
|
|
@@ -83922,8 +84254,9 @@ var AntigravityIdePermissions = class AntigravityIdePermissions extends ToolPerm
|
|
|
83922
84254
|
logger
|
|
83923
84255
|
});
|
|
83924
84256
|
const managedActions = new Set(Object.keys(config.permission).map((category) => toIdeAction(category)));
|
|
84257
|
+
const isManagedAction = (action) => managedActions.has(action) || managedActions.has("mcp") && action.startsWith("mcp__");
|
|
83925
84258
|
const existingPermissions = settings.permissions ?? {};
|
|
83926
|
-
const preserve = (entries) => (entries ?? []).filter((entry) => !
|
|
84259
|
+
const preserve = (entries) => (entries ?? []).filter((entry) => !isManagedAction(parseToolPatternEntry(entry).toolName));
|
|
83927
84260
|
const mergedPermissions = { ...existingPermissions };
|
|
83928
84261
|
const mergedAllow = uniq([...preserve(existingPermissions.allow), ...allow].toSorted());
|
|
83929
84262
|
const mergedAsk = uniq([...preserve(existingPermissions.ask), ...ask].toSorted());
|
|
@@ -107934,4 +108267,4 @@ async function importChecksCore(params) {
|
|
|
107934
108267
|
}
|
|
107935
108268
|
export { RulesyncPermissions as $, formatError as $n, isSymlink as $t, CODEBUDDY_DIR as A, MAX_FILE_SIZE as An, withFallbackLoggerTarget as At, CLAUDECODE_LOCAL_RULE_FILE_NAME as B, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as Bn, assertTreeContainsNoSymlinks as Bt, QWENCODE_LOCAL_RULE_FILE_NAME as C, PACKAGING_TOOL_TARGETS as Cn, getSourceTransport as Ct, FACTORYDROID_SETTINGS_LOCAL_FILE_NAME as D, DEPRECATED_FEATURE_REPLACEMENTS as Dn, WarningCollectingLogger as Dt, FACTORYDROID_DIR as E, ALL_FEATURES_WITH_WILDCARD as En, JsonLogger as Et, groupSpellingsByCaseFoldedIdentity as F, RULESYNC_CONFIG_SCHEMA_URL as Fn, PathOutsideRootError as Ft, toInstructionBudgetFile as G, RULESYNC_PERMISSIONS_SCHEMA_URL as Gn, ensureDir as Gt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as H, RULESYNC_MCP_SCHEMA_URL as Hn, checkPathTraversal as Ht, CODEXCLI_BASH_RULES_FILE_NAME as I, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as In, applyFileMode as It, RulesyncSubagentFrontmatterSchema as J, RULESYNC_SKILLS_RELATIVE_DIR_PATH as Jn, findFilesByGlobs as Jt, getLocalSkillDirNames as K, RULESYNC_RELATIVE_DIR_PATH as Kn, fileExists as Kt, CODEXCLI_DIR as L, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as Ln, assertDirectoryIfExists as Lt, AUGMENTCODE_DIR as M, RULESYNC_CHECKS_RELATIVE_DIR_PATH as Mn, withWarnOnceScope as Mt, AUGMENTCODE_SETTINGS_LOCAL_FILE_NAME as N, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as Nn, CLIError as Nt, COMMANDCODE_DIR as O, CURATED_RULES_FEATURE_SUBDIR as On, fallbackLogger as Ot, caseFoldIdentity as P, RULESYNC_CONFIG_RELATIVE_FILE_PATH as Pn, ErrorCodes as Pt, RulesyncRuleFrontmatterSchema as Q, RULESYNC_XDG_CONFIG_HOME_DEFAULT_DIR_NAME as Qn, isFileSystemError as Qt, CLAUDECODE_AGENT_MEMORY_LOCAL_DIR_NAME as R, RULESYNC_HOOKS_RELATIVE_FILE_PATH as Rn, assertNoSymlinkBelowRoot as Rt, QWENCODE_DIR as S, ALL_TOOL_TARGETS_WITH_WILDCARD as Sn, assertTargetsFeaturesExclusive as St, LETTACODE_SETTINGS_LOCAL_FILE_NAME as T, ALL_FEATURES as Tn, ConsoleLogger as Tt, CLAUDECODE_SKILLS_DIR_PATH as U, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as Un, createTempDirectory as Ut, CLAUDECODE_MEMORIES_DIR_NAME as V, RULESYNC_MCP_RELATIVE_FILE_PATH as Vn, assertWritablePathInsideRoot as Vt, findProjectInstructionBudgetFindings as W, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as Wn, directoryExists as Wt, RulesyncSkillFrontmatterSchema as X, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as Xn, getHomeDirectory as Xt, RulesyncSkill as Y, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as Yn, getFileSizeAndExecutableBit as Yt, RulesyncRule as Z, RULESYNC_USER_CONFIG_DIR_NAME as Zn, isFileNotFoundError as Zt, SubagentsProcessor as _, toPosixPath as _n, resolveEffectiveInputRoots as _t, convertFromTool as a, readFileContent as an, stripControlCharactersKeepingLineFeeds as ar, parseJsonc as at, CommandsProcessor as b, mapWithConcurrency as bn, GITIGNORE_DESTINATION_KEY as bt, parseCommaSeparatedList as c, removeDirectoryStrict as cn, RulesyncCheck as ct, shortenToWidth as d, removeFileWithoutFollowingSymlinks as dn, loadYaml as dt, listDirectoryEntryNames as en, truncateText as er, RulesyncMcp as et, McpProcessor as f, removeTempDirectory as fn, isRecord as ft, RulesProcessor as g, splitPathSegments as gn, mergeInputRootConfigs as gt, ChecksProcessor as h, runWithDirectoryRollback as hn, ConfigResolver as ht, inspectInputRoots as i, posixRelativePathEscapesRoot as in, stripControlCharacters as ir, resolveRulesyncSourceWritePath as it, CODEBUDDY_LOCAL_RULE_FILE_NAME as j, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as jn, resetRunWarningState as jt, COMMANDCODE_SETTINGS_LOCAL_FILE_NAME as k, FEATURE_SOURCE_TREE_ENTRIES as kn, warnOnConflictingFlags as kt, ELLIPSIS_WIDTH as l, removeFile as ln, RulesyncCheckFrontmatterSchema as lt, HooksProcessor as m, resolvedRelativePath as mn, SKILL_FILE_NAME as mt, formatSourceLoadFailure as n, listSubdirectoryNames as nn, hasEnclosingMarkOutsideKeycap as nr, RulesyncHooks as nt, isPackagingToolTarget as o, readFileContentOrNull as on, stripControlCharactersKeepingLineFeedsAndTabs as or, RulesyncCommand as ot, IgnoreProcessor as p, resolvePath as pn, SHARED_USER_MANAGED_CONFIG_PATHS as pt, RulesyncSubagent as q, RULESYNC_RULES_RELATIVE_DIR_PATH as qn, fileExistsStrict as qt, generate as r, pathEscapesRoot as rn, quoteForLog as rr, getRulesyncSourceCandidates as rt, getProcessorRegistryEntry as s, removeDirectory as sn, stripHiddenCharacters as sr, RulesyncCommandFrontmatterSchema as st, importFromTool as t, listFilePathsRecursively as tn, hasDeceptiveHiddenCharacters as tr, RulesyncIgnore as tt, displayWidthOf as u, removeFileStrict as un, stringifyFrontmatter as ut, SkillsProcessor as v, writeFileBuffer as vn, CONFLICTING_TARGET_PAIRS as vt, LETTACODE_DIR as w, ToolTargetSchema as wn, findControlCharacter as wt, REASONIX_LOCAL_RULE_FILE_NAME as x, ALL_TOOL_TARGETS as xn, SourceEntrySchema as xt, CRUSH_LOCAL_RULE_FILE_NAME as y, writeFileContent as yn, ConfigFileSchema as yt, CLAUDECODE_DIR as z, RULESYNC_IGNORE_RELATIVE_FILE_PATH as zn, assertPathResolvesInsideRoot as zt };
|
|
107936
108269
|
|
|
107937
|
-
//# sourceMappingURL=import
|
|
108270
|
+
//# sourceMappingURL=import--Y_A56qx.js.map
|