rulesync 16.3.0 → 16.4.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/dist/cli/index.cjs +201 -3
- package/dist/cli/index.js +201 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-Bj5HxY9p.js → import-BwakMyyf.js} +177 -107
- package/dist/import-BwakMyyf.js.map +1 -0
- package/dist/{import-DD-GRmB9.cjs → import-C9wxpZey.cjs} +187 -105
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/package.json +5 -2
- package/dist/import-Bj5HxY9p.js.map +0 -1
|
@@ -2381,6 +2381,7 @@ const PI_HOOK_EVENTS = [
|
|
|
2381
2381
|
"preToolUse",
|
|
2382
2382
|
"postToolUse",
|
|
2383
2383
|
"preModelInvocation",
|
|
2384
|
+
"postModelInvocation",
|
|
2384
2385
|
"beforeSubmitPrompt",
|
|
2385
2386
|
"stop",
|
|
2386
2387
|
"preCompact",
|
|
@@ -3087,6 +3088,7 @@ const CANONICAL_TO_PI_EVENT_NAMES = {
|
|
|
3087
3088
|
preToolUse: "tool_call",
|
|
3088
3089
|
postToolUse: "tool_result",
|
|
3089
3090
|
preModelInvocation: "context",
|
|
3091
|
+
postModelInvocation: "message_end",
|
|
3090
3092
|
beforeSubmitPrompt: "input",
|
|
3091
3093
|
stop: "agent_end",
|
|
3092
3094
|
preCompact: "session_before_compact",
|
|
@@ -6852,24 +6854,14 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6852
6854
|
},
|
|
6853
6855
|
".devin/config.json": {
|
|
6854
6856
|
format: "json",
|
|
6855
|
-
features: {
|
|
6856
|
-
|
|
6857
|
-
|
|
6858
|
-
|
|
6859
|
-
},
|
|
6860
|
-
permissions: {
|
|
6861
|
-
kind: "replace-owned-keys",
|
|
6862
|
-
ownedKeys: ["permissions"]
|
|
6863
|
-
}
|
|
6864
|
-
}
|
|
6857
|
+
features: { permissions: {
|
|
6858
|
+
kind: "replace-owned-keys",
|
|
6859
|
+
ownedKeys: ["permissions"]
|
|
6860
|
+
} }
|
|
6865
6861
|
},
|
|
6866
6862
|
".config/devin/config.json": {
|
|
6867
6863
|
format: "json",
|
|
6868
6864
|
features: {
|
|
6869
|
-
mcp: {
|
|
6870
|
-
kind: "replace-owned-keys",
|
|
6871
|
-
ownedKeys: ["mcpServers"]
|
|
6872
|
-
},
|
|
6873
6865
|
hooks: {
|
|
6874
6866
|
kind: "replace-owned-keys",
|
|
6875
6867
|
ownedKeys: ["hooks"]
|
|
@@ -9069,6 +9061,7 @@ const DEVIN_GLOBAL_CONFIG_DIR_PATH = (0, node_path.join)(".config", "devin");
|
|
|
9069
9061
|
const DEVIN_GLOBAL_AGENTS_DIR_PATH = (0, node_path.join)(DEVIN_GLOBAL_CONFIG_DIR_PATH, "agents");
|
|
9070
9062
|
const DEVIN_GLOBAL_SKILLS_DIR_PATH = (0, node_path.join)(DEVIN_GLOBAL_CONFIG_DIR_PATH, "skills");
|
|
9071
9063
|
const DEVIN_CONFIG_FILE_NAME = "config.json";
|
|
9064
|
+
const DEVIN_MCP_CONFIG_FILE_NAME = "mcp_config.json";
|
|
9072
9065
|
const DEVIN_HOOKS_V1_FILE_NAME = "hooks.v1.json";
|
|
9073
9066
|
const DEVIN_GLOBAL_AGENTS_FILE_NAME = "AGENTS.md";
|
|
9074
9067
|
const DEVIN_IGNORE_FILE_NAME = ".devinignore";
|
|
@@ -11326,6 +11319,7 @@ const QWENCODE_COMMANDS_DIR_PATH = (0, node_path.join)(QWENCODE_DIR, "commands")
|
|
|
11326
11319
|
const QWENCODE_AGENTS_DIR_PATH = (0, node_path.join)(QWENCODE_DIR, "agents");
|
|
11327
11320
|
const QWENCODE_SKILLS_DIR_PATH = (0, node_path.join)(QWENCODE_DIR, "skills");
|
|
11328
11321
|
const QWENCODE_RULE_FILE_NAME = "QWEN.md";
|
|
11322
|
+
const QWENCODE_LOCAL_RULE_FILE_NAME = "QWEN.local.md";
|
|
11329
11323
|
const QWENCODE_IGNORE_FILE_NAME = ".qwenignore";
|
|
11330
11324
|
const QWENCODE_SETTINGS_FILE_NAME = "settings.json";
|
|
11331
11325
|
//#endregion
|
|
@@ -12072,6 +12066,7 @@ const WARP_SKILLS_DIR_PATH = (0, node_path.join)(WARP_DIR, "skills");
|
|
|
12072
12066
|
const WARP_LINUX_DIR = (0, node_path.join)(".config", "warp-terminal");
|
|
12073
12067
|
const WARP_WIN32_DIR = (0, node_path.join)("AppData", "Local", "warp", "Warp", "config");
|
|
12074
12068
|
const WARP_RULE_FILE_NAME = "AGENTS.md";
|
|
12069
|
+
const WARP_GLOBAL_RULE_DIR = ".agents";
|
|
12075
12070
|
const WARP_MCP_FILE_NAME = ".mcp.json";
|
|
12076
12071
|
const WARP_PERMISSIONS_FILE_NAME = "settings.toml";
|
|
12077
12072
|
const WARP_IGNORE_FILE_NAME = ".warpindexingignore";
|
|
@@ -14807,9 +14802,9 @@ const DEVIN_CONVERTER_CONFIG = {
|
|
|
14807
14802
|
* - Project scope: `.devin/hooks.v1.json`. This is a standalone file whose top
|
|
14808
14803
|
* level IS the event map (no wrapper key).
|
|
14809
14804
|
* - Global scope: `~/.config/devin/config.json` under the `"hooks"` key. This
|
|
14810
|
-
* file is shared with the
|
|
14811
|
-
*
|
|
14812
|
-
* never deleted in global mode.
|
|
14805
|
+
* file is shared with the permissions (`permissions`) feature (MCP moved to
|
|
14806
|
+
* the dedicated mcp_config.json in v3000.3), so reads and writes merge into
|
|
14807
|
+
* the existing JSON and the file is never deleted in global mode.
|
|
14813
14808
|
*
|
|
14814
14809
|
* @see https://docs.devin.ai/cli/extensibility/hooks/overview
|
|
14815
14810
|
*/
|
|
@@ -16421,6 +16416,13 @@ var OpencodeHooks = class OpencodeHooks extends ToolHooks {
|
|
|
16421
16416
|
*/
|
|
16422
16417
|
const PI_TOOL_EVENTS = /* @__PURE__ */ new Set(["tool_call", "tool_result"]);
|
|
16423
16418
|
/**
|
|
16419
|
+
* Pi extension events that fire for every message role (user, assistant,
|
|
16420
|
+
* toolResult). The generated handler gates these on the assistant role so a
|
|
16421
|
+
* `postModelInvocation` hook runs once per finalized model response rather
|
|
16422
|
+
* than for every message in the conversation.
|
|
16423
|
+
*/
|
|
16424
|
+
const PI_ASSISTANT_MESSAGE_EVENTS = /* @__PURE__ */ new Set(["message_end"]);
|
|
16425
|
+
/**
|
|
16424
16426
|
* Validate a hook matcher as a regular expression and return it as a JS
|
|
16425
16427
|
* string-literal (JSON.stringify quoting) safe to embed in generated code.
|
|
16426
16428
|
*/
|
|
@@ -16461,7 +16463,10 @@ function buildSubscriptionLines(handlerGroups) {
|
|
|
16461
16463
|
const lines = [];
|
|
16462
16464
|
for (const [piEvent, handlers] of Object.entries(handlerGroups)) {
|
|
16463
16465
|
const usesToolName = PI_TOOL_EVENTS.has(piEvent) && handlers.some((h) => h.matcher);
|
|
16464
|
-
|
|
16466
|
+
const gatesOnAssistant = PI_ASSISTANT_MESSAGE_EVENTS.has(piEvent);
|
|
16467
|
+
const usesEvent = usesToolName || gatesOnAssistant;
|
|
16468
|
+
lines.push(` pi.on(${JSON.stringify(piEvent)}, async (${usesEvent ? "event" : ""}) => {`);
|
|
16469
|
+
if (gatesOnAssistant) lines.push(` if (event.message.role !== "assistant") return;`);
|
|
16465
16470
|
for (const handler of handlers) {
|
|
16466
16471
|
const embeddedCommand = JSON.stringify(handler.command);
|
|
16467
16472
|
if (usesToolName && handler.matcher) {
|
|
@@ -16596,6 +16601,7 @@ function canonicalDefToQwencodeHook(def) {
|
|
|
16596
16601
|
const type = def.type ?? "command";
|
|
16597
16602
|
const isHttp = type === "http";
|
|
16598
16603
|
const isCommand = type === "command";
|
|
16604
|
+
const isPrompt = type === "prompt";
|
|
16599
16605
|
return {
|
|
16600
16606
|
type,
|
|
16601
16607
|
...compact({
|
|
@@ -16605,6 +16611,8 @@ function canonicalDefToQwencodeHook(def) {
|
|
|
16605
16611
|
name: def.name,
|
|
16606
16612
|
description: def.description,
|
|
16607
16613
|
statusMessage: def.statusMessage,
|
|
16614
|
+
prompt: isPrompt ? def.prompt : void 0,
|
|
16615
|
+
model: isPrompt ? def.model : void 0,
|
|
16608
16616
|
async: isCommand ? def.async : void 0,
|
|
16609
16617
|
env: isCommand ? def.env : void 0,
|
|
16610
16618
|
shell: isCommand ? def.shell : void 0,
|
|
@@ -16623,7 +16631,7 @@ function canonicalDefToQwencodeHook(def) {
|
|
|
16623
16631
|
* is needed here — commands are passed through verbatim and any such variable
|
|
16624
16632
|
* reference stays intact for Qwen Code to expand.
|
|
16625
16633
|
*/
|
|
16626
|
-
function canonicalToQwencodeHooks(config) {
|
|
16634
|
+
function canonicalToQwencodeHooks(config, logger) {
|
|
16627
16635
|
const qwencodeSupported = new Set(QWENCODE_HOOK_EVENTS);
|
|
16628
16636
|
const sharedHooks = {};
|
|
16629
16637
|
for (const [event, defs] of Object.entries(config.hooks)) if (qwencodeSupported.has(event)) sharedHooks[event] = defs;
|
|
@@ -16643,6 +16651,7 @@ function canonicalToQwencodeHooks(config) {
|
|
|
16643
16651
|
const byMatcher = /* @__PURE__ */ new Map();
|
|
16644
16652
|
for (const def of definitions) {
|
|
16645
16653
|
if (!qwencodeSupportedTypes.has(def.type ?? "command")) continue;
|
|
16654
|
+
if (def.type === "prompt" && !def.prompt) logger?.warn(`Qwen Code prompt hook on '${eventName}' has no 'prompt' field; Qwen Code will load it and fail it at runtime.`);
|
|
16646
16655
|
const key = def.matcher ?? "";
|
|
16647
16656
|
const list = byMatcher.get(key);
|
|
16648
16657
|
if (list) list.push(def);
|
|
@@ -16681,7 +16690,9 @@ const QwencodeHookEntrySchema = zod_mini.z.looseObject({
|
|
|
16681
16690
|
shell: zod_mini.z.optional(zod_mini.z.string()),
|
|
16682
16691
|
headers: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), zod_mini.z.string())),
|
|
16683
16692
|
allowedEnvVars: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
16684
|
-
once: zod_mini.z.optional(zod_mini.z.boolean())
|
|
16693
|
+
once: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
16694
|
+
prompt: zod_mini.z.optional(zod_mini.z.string()),
|
|
16695
|
+
model: zod_mini.z.optional(zod_mini.z.string())
|
|
16685
16696
|
});
|
|
16686
16697
|
/**
|
|
16687
16698
|
* A matcher group entry in a Qwen Code event array.
|
|
@@ -16696,37 +16707,43 @@ const QwencodeMatcherEntrySchema = zod_mini.z.looseObject({
|
|
|
16696
16707
|
/**
|
|
16697
16708
|
* Convert a single parsed Qwen Code matcher group into canonical hook definitions.
|
|
16698
16709
|
*/
|
|
16710
|
+
function qwencodeHookEntryToCanonical({ hook, sequential, matcher }) {
|
|
16711
|
+
const h = hook;
|
|
16712
|
+
const hookType = h.type === "command" || h.type === "prompt" || h.type === "http" || h.type === "function" ? h.type : "command";
|
|
16713
|
+
const isHttp = hookType === "http";
|
|
16714
|
+
const isCommand = hookType === "command";
|
|
16715
|
+
const isPrompt = hookType === "prompt";
|
|
16716
|
+
const shell = h.shell === "bash" || h.shell === "powershell" ? h.shell : void 0;
|
|
16717
|
+
return {
|
|
16718
|
+
type: hookType,
|
|
16719
|
+
...compact({
|
|
16720
|
+
command: h.command,
|
|
16721
|
+
url: h.url,
|
|
16722
|
+
timeout: h.timeout,
|
|
16723
|
+
name: h.name,
|
|
16724
|
+
description: h.description,
|
|
16725
|
+
statusMessage: h.statusMessage,
|
|
16726
|
+
async: isCommand ? h.async : void 0,
|
|
16727
|
+
env: isCommand ? h.env : void 0,
|
|
16728
|
+
shell: isCommand ? shell : void 0,
|
|
16729
|
+
headers: isHttp ? h.headers : void 0,
|
|
16730
|
+
allowedEnvVars: isHttp ? h.allowedEnvVars : void 0,
|
|
16731
|
+
once: isHttp ? h.once : void 0,
|
|
16732
|
+
prompt: isPrompt ? h.prompt : void 0,
|
|
16733
|
+
model: isPrompt ? h.model : void 0,
|
|
16734
|
+
sequential: sequential ? true : void 0,
|
|
16735
|
+
matcher
|
|
16736
|
+
})
|
|
16737
|
+
};
|
|
16738
|
+
}
|
|
16699
16739
|
function qwencodeMatcherEntryToCanonical(entry) {
|
|
16700
|
-
const defs = [];
|
|
16701
|
-
const hooks = entry.hooks ?? [];
|
|
16702
16740
|
const sequential = entry.sequential === true;
|
|
16703
16741
|
const matcher = entry.matcher !== void 0 && entry.matcher !== null && entry.matcher !== "" ? entry.matcher : void 0;
|
|
16704
|
-
|
|
16705
|
-
|
|
16706
|
-
|
|
16707
|
-
|
|
16708
|
-
|
|
16709
|
-
defs.push({
|
|
16710
|
-
type: hookType,
|
|
16711
|
-
...compact({
|
|
16712
|
-
command: h.command,
|
|
16713
|
-
url: h.url,
|
|
16714
|
-
timeout: h.timeout,
|
|
16715
|
-
name: h.name,
|
|
16716
|
-
description: h.description,
|
|
16717
|
-
statusMessage: h.statusMessage,
|
|
16718
|
-
async: isCommand ? h.async : void 0,
|
|
16719
|
-
env: isCommand ? h.env : void 0,
|
|
16720
|
-
shell: isCommand ? shell : void 0,
|
|
16721
|
-
headers: isHttp ? h.headers : void 0,
|
|
16722
|
-
allowedEnvVars: isHttp ? h.allowedEnvVars : void 0,
|
|
16723
|
-
once: isHttp ? h.once : void 0,
|
|
16724
|
-
sequential: sequential ? true : void 0,
|
|
16725
|
-
matcher
|
|
16726
|
-
})
|
|
16727
|
-
});
|
|
16728
|
-
}
|
|
16729
|
-
return defs;
|
|
16742
|
+
return (entry.hooks ?? []).map((hook) => qwencodeHookEntryToCanonical({
|
|
16743
|
+
hook,
|
|
16744
|
+
sequential,
|
|
16745
|
+
matcher
|
|
16746
|
+
}));
|
|
16730
16747
|
}
|
|
16731
16748
|
/**
|
|
16732
16749
|
* Extract hooks from Qwen Code settings.json into canonical format.
|
|
@@ -16774,12 +16791,12 @@ var QwencodeHooks = class QwencodeHooks extends ToolHooks {
|
|
|
16774
16791
|
validate
|
|
16775
16792
|
});
|
|
16776
16793
|
}
|
|
16777
|
-
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false }) {
|
|
16794
|
+
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
|
|
16778
16795
|
const paths = QwencodeHooks.getSettablePaths({ global });
|
|
16779
16796
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
16780
16797
|
const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
|
|
16781
16798
|
const config = rulesyncHooks.getJson();
|
|
16782
|
-
const patch = { hooks: canonicalToQwencodeHooks(config) };
|
|
16799
|
+
const patch = { hooks: canonicalToQwencodeHooks(config, logger) };
|
|
16783
16800
|
const disableAllHooks = config.qwencode?.disableAllHooks;
|
|
16784
16801
|
if (typeof disableAllHooks === "boolean") patch.disableAllHooks = disableAllHooks;
|
|
16785
16802
|
const fileContent = applySharedConfigPatch({
|
|
@@ -20805,18 +20822,24 @@ var DeepagentsMcp = class DeepagentsMcp extends ToolMcp {
|
|
|
20805
20822
|
/**
|
|
20806
20823
|
* MCP generator for Devin Local (native `.devin/` configuration).
|
|
20807
20824
|
*
|
|
20808
|
-
*
|
|
20809
|
-
*
|
|
20810
|
-
*
|
|
20825
|
+
* Since v3000.3 (the Local 3.6 release), Devin reads MCP servers from a
|
|
20826
|
+
* dedicated `mcpServers`-keyed config file rather than the shared
|
|
20827
|
+
* `config.json`:
|
|
20828
|
+
* - Project scope: `.devin/mcp_config.json`
|
|
20829
|
+
* - Global scope: `~/.config/devin/mcp_config.json`
|
|
20811
20830
|
*
|
|
20812
|
-
*
|
|
20813
|
-
*
|
|
20814
|
-
*
|
|
20815
|
-
*
|
|
20816
|
-
*
|
|
20817
|
-
*
|
|
20831
|
+
* Legacy `mcpServers` entries left in `config.json` are auto-migrated into
|
|
20832
|
+
* the dedicated file on Devin startup, so writing the legacy key would fight
|
|
20833
|
+
* the migration. Import still falls back to the legacy `config.json`
|
|
20834
|
+
* `mcpServers` key when no dedicated file exists, so pre-v3000.3 repos
|
|
20835
|
+
* migrate cleanly. The gitignored `.devin/mcp_config.local.json` override is
|
|
20836
|
+
* the user's personal territory and is never read or written.
|
|
20818
20837
|
*
|
|
20819
|
-
*
|
|
20838
|
+
* Each server is a stdio entry ({ command, args, env }) or a remote entry
|
|
20839
|
+
* ({ serverUrl | url, headers }), and may carry an optional `disabledTools`
|
|
20840
|
+
* array.
|
|
20841
|
+
*
|
|
20842
|
+
* @see https://docs.devin.ai/cli/extensibility/mcp/configuration
|
|
20820
20843
|
*/
|
|
20821
20844
|
var DevinMcp = class DevinMcp extends ToolMcp {
|
|
20822
20845
|
json;
|
|
@@ -20834,31 +20857,27 @@ var DevinMcp = class DevinMcp extends ToolMcp {
|
|
|
20834
20857
|
throw new Error(`Failed to parse Devin MCP config at ${(0, node_path.join)(relativeDirPath, relativeFilePath)}: ${formatError(error)}`, { cause: error });
|
|
20835
20858
|
}
|
|
20836
20859
|
}
|
|
20837
|
-
/**
|
|
20838
|
-
* config.json may carry the permissions/hooks features' keys, so it is never
|
|
20839
|
-
* deleted; only the managed `mcpServers` key is rewritten.
|
|
20840
|
-
*/
|
|
20841
|
-
isDeletable() {
|
|
20842
|
-
return false;
|
|
20843
|
-
}
|
|
20844
20860
|
static getSettablePaths({ global = false } = {}) {
|
|
20845
20861
|
if (global) return {
|
|
20846
20862
|
relativeDirPath: DEVIN_GLOBAL_CONFIG_DIR_PATH,
|
|
20847
|
-
relativeFilePath:
|
|
20863
|
+
relativeFilePath: DEVIN_MCP_CONFIG_FILE_NAME
|
|
20848
20864
|
};
|
|
20849
20865
|
return {
|
|
20850
20866
|
relativeDirPath: DEVIN_DIR,
|
|
20851
|
-
relativeFilePath:
|
|
20867
|
+
relativeFilePath: DEVIN_MCP_CONFIG_FILE_NAME
|
|
20852
20868
|
};
|
|
20853
20869
|
}
|
|
20854
20870
|
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
20855
20871
|
const paths = this.getSettablePaths({ global });
|
|
20856
|
-
|
|
20857
|
-
|
|
20858
|
-
|
|
20859
|
-
|
|
20860
|
-
|
|
20861
|
-
|
|
20872
|
+
let fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath));
|
|
20873
|
+
if (fileContent === null) {
|
|
20874
|
+
const legacyContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, DEVIN_CONFIG_FILE_NAME));
|
|
20875
|
+
if (legacyContent !== null) {
|
|
20876
|
+
const legacyJson = this.parseJsonOrThrow(legacyContent, paths.relativeDirPath, DEVIN_CONFIG_FILE_NAME);
|
|
20877
|
+
fileContent = JSON.stringify({ mcpServers: legacyJson.mcpServers ?? {} }, null, 2);
|
|
20878
|
+
}
|
|
20879
|
+
}
|
|
20880
|
+
const newJson = { mcpServers: this.parseJsonOrThrow(fileContent ?? "{\"mcpServers\":{}}", paths.relativeDirPath, paths.relativeFilePath).mcpServers ?? {} };
|
|
20862
20881
|
return new DevinMcp({
|
|
20863
20882
|
outputRoot,
|
|
20864
20883
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -20868,21 +20887,21 @@ var DevinMcp = class DevinMcp extends ToolMcp {
|
|
|
20868
20887
|
global
|
|
20869
20888
|
});
|
|
20870
20889
|
}
|
|
20871
|
-
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
20890
|
+
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger }) {
|
|
20872
20891
|
const paths = this.getSettablePaths({ global });
|
|
20873
|
-
const
|
|
20874
|
-
|
|
20892
|
+
const existingContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath));
|
|
20893
|
+
if (existingContent !== null) {
|
|
20894
|
+
const existingJson = this.parseJsonOrThrow(existingContent, paths.relativeDirPath, paths.relativeFilePath);
|
|
20895
|
+
const existingServers = existingJson.mcpServers && typeof existingJson.mcpServers === "object" ? Object.keys(existingJson.mcpServers) : [];
|
|
20896
|
+
const managedServers = new Set(Object.keys(rulesyncMcp.getMcpServers()));
|
|
20897
|
+
const dropped = existingServers.filter((name) => !managedServers.has(name));
|
|
20898
|
+
if (dropped.length > 0) logger?.warn(`Devin MCP servers not managed by rulesync will be removed from ${(0, node_path.join)(paths.relativeDirPath, paths.relativeFilePath)}: ${dropped.join(", ")}. Run 'rulesync import' first to keep them, or move them to mcp_config.local.json.`);
|
|
20899
|
+
}
|
|
20875
20900
|
return new DevinMcp({
|
|
20876
20901
|
outputRoot,
|
|
20877
20902
|
relativeDirPath: paths.relativeDirPath,
|
|
20878
20903
|
relativeFilePath: paths.relativeFilePath,
|
|
20879
|
-
fileContent:
|
|
20880
|
-
fileKey: sharedConfigFileKey(paths),
|
|
20881
|
-
feature: "mcp",
|
|
20882
|
-
existingContent,
|
|
20883
|
-
patch: { mcpServers: rulesyncMcp.getMcpServers() },
|
|
20884
|
-
filePath
|
|
20885
|
-
}),
|
|
20904
|
+
fileContent: JSON.stringify({ mcpServers: rulesyncMcp.getMcpServers() }, null, 2),
|
|
20886
20905
|
validate,
|
|
20887
20906
|
global
|
|
20888
20907
|
});
|
|
@@ -27600,9 +27619,10 @@ function buildDevinPermissionEntry(scope, pattern) {
|
|
|
27600
27619
|
* - Project scope: `.devin/config.json`
|
|
27601
27620
|
* - Global scope: `~/.config/devin/config.json`
|
|
27602
27621
|
*
|
|
27603
|
-
*
|
|
27604
|
-
*
|
|
27605
|
-
* the file is never deleted; only the managed
|
|
27622
|
+
* In global mode the config file is shared with the hooks (`hooks`) feature
|
|
27623
|
+
* (MCP moved to the dedicated mcp_config.json in v3000.3), so reads and writes
|
|
27624
|
+
* merge into the existing JSON and the file is never deleted; only the managed
|
|
27625
|
+
* `permissions` key is rewritten.
|
|
27606
27626
|
*
|
|
27607
27627
|
* @see https://docs.devin.ai/cli/reference/permissions
|
|
27608
27628
|
*/
|
|
@@ -31476,6 +31496,10 @@ function ensurePermission(permission, category) {
|
|
|
31476
31496
|
const WARP_GLOBAL_ONLY_MESSAGE = "Warp permissions are global-only; use --global to sync Warp's settings.toml";
|
|
31477
31497
|
const ALLOWLIST_KEY = "agent_mode_command_execution_allowlist";
|
|
31478
31498
|
const DENYLIST_KEY = "agent_mode_command_execution_denylist";
|
|
31499
|
+
const EXECUTION_PROFILES_KEY = "execution_profiles";
|
|
31500
|
+
const DEFAULT_PROFILE_KEY = "default";
|
|
31501
|
+
const PROFILE_ALLOWLIST_KEY = "command_allowlist";
|
|
31502
|
+
const PROFILE_DENYLIST_KEY = "command_denylist";
|
|
31479
31503
|
const WARP_OVERRIDE_KEYS = [
|
|
31480
31504
|
"agent_mode_coding_permissions",
|
|
31481
31505
|
"agent_mode_coding_file_read_allowlist",
|
|
@@ -31503,11 +31527,21 @@ function warpSettingsDir() {
|
|
|
31503
31527
|
/**
|
|
31504
31528
|
* Permissions adapter for Warp.
|
|
31505
31529
|
*
|
|
31506
|
-
* Warp gates **shell command** execution through two regex arrays
|
|
31507
|
-
*
|
|
31508
|
-
* - `
|
|
31509
|
-
*
|
|
31510
|
-
*
|
|
31530
|
+
* Warp gates **shell command** execution through two regex arrays in the
|
|
31531
|
+
* global user `settings.toml`. Since file-backed execution profiles went
|
|
31532
|
+
* Stable (2026-07-28) the authoritative surface is the `default` record of the
|
|
31533
|
+
* `[agents.execution_profiles.<id>]` collection:
|
|
31534
|
+
* - `command_allowlist` — commands that auto-execute.
|
|
31535
|
+
* - `command_denylist` — commands that always require permission (the denylist
|
|
31536
|
+
* wins over the allowlist).
|
|
31537
|
+
*
|
|
31538
|
+
* The legacy `[agents.profiles]` keys
|
|
31539
|
+
* (`agent_mode_command_execution_allowlist` / `denylist`) are consumed only
|
|
31540
|
+
* once by Warp's one-shot migration and are ignored afterwards. Both surfaces
|
|
31541
|
+
* are written: the legacy block keeps un-migrated installs and old clients
|
|
31542
|
+
* working, and the `default` execution profile (merged in place, only when the
|
|
31543
|
+
* collection already exists) keeps migrated installs enforcing the lists.
|
|
31544
|
+
* Import prefers the execution profile and falls back to the legacy keys.
|
|
31511
31545
|
*
|
|
31512
31546
|
* This surface is **global only** — there is no project-scoped Warp permissions
|
|
31513
31547
|
* file. rulesync's canonical `permission.bash` patterns map directly (`allow` →
|
|
@@ -31586,6 +31620,18 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
31586
31620
|
if (mergedDeny.length > 0) profiles[DENYLIST_KEY] = mergedDeny;
|
|
31587
31621
|
else delete profiles[DENYLIST_KEY];
|
|
31588
31622
|
agents.profiles = profiles;
|
|
31623
|
+
if (isRecord(agents[EXECUTION_PROFILES_KEY])) {
|
|
31624
|
+
const executionProfiles = { ...agents[EXECUTION_PROFILES_KEY] };
|
|
31625
|
+
const defaultProfile = isRecord(executionProfiles[DEFAULT_PROFILE_KEY]) ? { ...executionProfiles[DEFAULT_PROFILE_KEY] } : {};
|
|
31626
|
+
if (mergedAllow.length > 0) defaultProfile[PROFILE_ALLOWLIST_KEY] = mergedAllow;
|
|
31627
|
+
else delete defaultProfile[PROFILE_ALLOWLIST_KEY];
|
|
31628
|
+
if (mergedDeny.length > 0) defaultProfile[PROFILE_DENYLIST_KEY] = mergedDeny;
|
|
31629
|
+
else delete defaultProfile[PROFILE_DENYLIST_KEY];
|
|
31630
|
+
executionProfiles[DEFAULT_PROFILE_KEY] = defaultProfile;
|
|
31631
|
+
agents[EXECUTION_PROFILES_KEY] = executionProfiles;
|
|
31632
|
+
const otherProfileIds = Object.keys(executionProfiles).filter((id) => id !== DEFAULT_PROFILE_KEY);
|
|
31633
|
+
if (mergedDeny.length > 0 && otherProfileIds.length > 0 && logger) logger.warn(`Warp command deny rules were written to the 'default' execution profile only; they are not enforced while another profile (${otherProfileIds.join(", ")}) is active.`);
|
|
31634
|
+
}
|
|
31589
31635
|
settings.agents = agents;
|
|
31590
31636
|
return new WarpPermissions({
|
|
31591
31637
|
outputRoot,
|
|
@@ -31605,9 +31651,11 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
31605
31651
|
}
|
|
31606
31652
|
const agents = isRecord(settings.agents) ? settings.agents : {};
|
|
31607
31653
|
const profiles = isRecord(agents.profiles) ? agents.profiles : {};
|
|
31654
|
+
const executionProfiles = isRecord(agents[EXECUTION_PROFILES_KEY]) ? agents[EXECUTION_PROFILES_KEY] : void 0;
|
|
31655
|
+
const defaultProfile = executionProfiles && isRecord(executionProfiles[DEFAULT_PROFILE_KEY]) ? executionProfiles[DEFAULT_PROFILE_KEY] : void 0;
|
|
31608
31656
|
const config = convertWarpToRulesyncPermissions({
|
|
31609
|
-
allow: isStringArray$1(profiles[ALLOWLIST_KEY]) ? profiles[ALLOWLIST_KEY] : [],
|
|
31610
|
-
deny: isStringArray$1(profiles[DENYLIST_KEY]) ? profiles[DENYLIST_KEY] : []
|
|
31657
|
+
allow: defaultProfile ? isStringArray$1(defaultProfile[PROFILE_ALLOWLIST_KEY]) ? defaultProfile[PROFILE_ALLOWLIST_KEY] : [] : isStringArray$1(profiles[ALLOWLIST_KEY]) ? profiles[ALLOWLIST_KEY] : [],
|
|
31658
|
+
deny: defaultProfile ? isStringArray$1(defaultProfile[PROFILE_DENYLIST_KEY]) ? defaultProfile[PROFILE_DENYLIST_KEY] : [] : isStringArray$1(profiles[DENYLIST_KEY]) ? profiles[DENYLIST_KEY] : []
|
|
31611
31659
|
});
|
|
31612
31660
|
const warpOverride = {};
|
|
31613
31661
|
for (const key of WARP_OVERRIDE_KEYS) if (profiles[key] !== void 0) warpOverride[key] = profiles[key];
|
|
@@ -37357,7 +37405,8 @@ const toolSkillFactories = /* @__PURE__ */ new Map([
|
|
|
37357
37405
|
meta: {
|
|
37358
37406
|
supportsProject: true,
|
|
37359
37407
|
supportsSimulated: false,
|
|
37360
|
-
supportsGlobal: true
|
|
37408
|
+
supportsGlobal: true,
|
|
37409
|
+
lenientImport: true
|
|
37361
37410
|
}
|
|
37362
37411
|
}],
|
|
37363
37412
|
["aiassistant", {
|
|
@@ -37794,7 +37843,7 @@ var SkillsProcessor = class extends DirFeatureProcessor {
|
|
|
37794
37843
|
global: this.global
|
|
37795
37844
|
});
|
|
37796
37845
|
} catch (error) {
|
|
37797
|
-
if (!isConfiguredRoot) throw error;
|
|
37846
|
+
if (!isConfiguredRoot && !factory.meta.lenientImport) throw error;
|
|
37798
37847
|
this.logger.warn(`Skipping ${(0, node_path.join)(relativeDirPath, dirName)}: ${formatError(error)}`);
|
|
37799
37848
|
return null;
|
|
37800
37849
|
}
|
|
@@ -46443,6 +46492,13 @@ var QwencodeRule = class QwencodeRule extends ToolRule {
|
|
|
46443
46492
|
toolTarget: "qwencode"
|
|
46444
46493
|
});
|
|
46445
46494
|
}
|
|
46495
|
+
/**
|
|
46496
|
+
* The personal local context file lives under `.qwen/`, not at the project
|
|
46497
|
+
* root where the settable root path points, so override the deletion glob.
|
|
46498
|
+
*/
|
|
46499
|
+
static getLocalRootDeletionGlob({ outputRoot, fileName }) {
|
|
46500
|
+
return (0, node_path.join)(outputRoot, QWENCODE_DIR, fileName);
|
|
46501
|
+
}
|
|
46446
46502
|
};
|
|
46447
46503
|
//#endregion
|
|
46448
46504
|
//#region src/features/rules/reasonix-rule.ts
|
|
@@ -47046,14 +47102,18 @@ var WarpRule = class WarpRule extends ToolRule {
|
|
|
47046
47102
|
root: root ?? false
|
|
47047
47103
|
});
|
|
47048
47104
|
}
|
|
47049
|
-
static getSettablePaths(
|
|
47105
|
+
static getSettablePaths({ global = false } = {}) {
|
|
47106
|
+
if (global) return { root: {
|
|
47107
|
+
relativeDirPath: WARP_GLOBAL_RULE_DIR,
|
|
47108
|
+
relativeFilePath: WARP_RULE_FILE_NAME
|
|
47109
|
+
} };
|
|
47050
47110
|
return { root: {
|
|
47051
47111
|
relativeDirPath: ".",
|
|
47052
47112
|
relativeFilePath: WARP_RULE_FILE_NAME
|
|
47053
47113
|
} };
|
|
47054
47114
|
}
|
|
47055
|
-
static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, validate = true }) {
|
|
47056
|
-
const { root } = this.getSettablePaths();
|
|
47115
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, validate = true, global = false }) {
|
|
47116
|
+
const { root } = this.getSettablePaths({ global });
|
|
47057
47117
|
const fileContent = await readFileContent((0, node_path.join)(outputRoot, (0, node_path.join)(root.relativeDirPath, root.relativeFilePath)));
|
|
47058
47118
|
return new WarpRule({
|
|
47059
47119
|
outputRoot,
|
|
@@ -47064,8 +47124,8 @@ var WarpRule = class WarpRule extends ToolRule {
|
|
|
47064
47124
|
root: true
|
|
47065
47125
|
});
|
|
47066
47126
|
}
|
|
47067
|
-
static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true }) {
|
|
47068
|
-
const { root } = this.getSettablePaths();
|
|
47127
|
+
static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
|
|
47128
|
+
const { root } = this.getSettablePaths({ global });
|
|
47069
47129
|
const isRoot = rulesyncRule.getFrontmatter().root ?? false;
|
|
47070
47130
|
return new WarpRule({
|
|
47071
47131
|
outputRoot,
|
|
@@ -47085,8 +47145,8 @@ var WarpRule = class WarpRule extends ToolRule {
|
|
|
47085
47145
|
error: null
|
|
47086
47146
|
};
|
|
47087
47147
|
}
|
|
47088
|
-
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
47089
|
-
const { root } = this.getSettablePaths();
|
|
47148
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
47149
|
+
const { root } = this.getSettablePaths({ global });
|
|
47090
47150
|
const isRoot = relativeFilePath === root.relativeFilePath && relativeDirPath === root.relativeDirPath;
|
|
47091
47151
|
return new WarpRule({
|
|
47092
47152
|
outputRoot,
|
|
@@ -47459,6 +47519,8 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
47459
47519
|
extension: "md",
|
|
47460
47520
|
supportsGlobal: true,
|
|
47461
47521
|
ruleDiscoveryMode: "auto",
|
|
47522
|
+
localRootMode: "separate-local-file",
|
|
47523
|
+
localRootFileName: QWENCODE_LOCAL_RULE_FILE_NAME,
|
|
47462
47524
|
additionalConventions: { subagents: { subagentClass: QwencodeSubagent } }
|
|
47463
47525
|
}
|
|
47464
47526
|
}],
|
|
@@ -47523,7 +47585,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
47523
47585
|
class: WarpRule,
|
|
47524
47586
|
meta: {
|
|
47525
47587
|
extension: "md",
|
|
47526
|
-
supportsGlobal:
|
|
47588
|
+
supportsGlobal: true,
|
|
47527
47589
|
ruleDiscoveryMode: "toon",
|
|
47528
47590
|
collisionPolicy: "fold"
|
|
47529
47591
|
}
|
|
@@ -47857,6 +47919,14 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
47857
47919
|
validate: true,
|
|
47858
47920
|
root: true
|
|
47859
47921
|
});
|
|
47922
|
+
if (factory.class === QwencodeRule) return new QwencodeRule({
|
|
47923
|
+
outputRoot: this.outputRoot,
|
|
47924
|
+
relativeDirPath: QWENCODE_DIR,
|
|
47925
|
+
relativeFilePath: fileName,
|
|
47926
|
+
fileContent: body,
|
|
47927
|
+
validate: true,
|
|
47928
|
+
root: true
|
|
47929
|
+
});
|
|
47860
47930
|
return null;
|
|
47861
47931
|
}
|
|
47862
47932
|
/**
|
|
@@ -50171,6 +50241,18 @@ Object.defineProperty(exports, "PACKAGING_TOOL_TARGETS", {
|
|
|
50171
50241
|
return PACKAGING_TOOL_TARGETS;
|
|
50172
50242
|
}
|
|
50173
50243
|
});
|
|
50244
|
+
Object.defineProperty(exports, "QWENCODE_DIR", {
|
|
50245
|
+
enumerable: true,
|
|
50246
|
+
get: function() {
|
|
50247
|
+
return QWENCODE_DIR;
|
|
50248
|
+
}
|
|
50249
|
+
});
|
|
50250
|
+
Object.defineProperty(exports, "QWENCODE_LOCAL_RULE_FILE_NAME", {
|
|
50251
|
+
enumerable: true,
|
|
50252
|
+
get: function() {
|
|
50253
|
+
return QWENCODE_LOCAL_RULE_FILE_NAME;
|
|
50254
|
+
}
|
|
50255
|
+
});
|
|
50174
50256
|
Object.defineProperty(exports, "RULESYNC_AIIGNORE_FILE_NAME", {
|
|
50175
50257
|
enumerable: true,
|
|
50176
50258
|
get: function() {
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_import = require("./import-
|
|
2
|
+
const require_import = require("./import-C9wxpZey.cjs");
|
|
3
3
|
//#region src/index.ts
|
|
4
4
|
async function generate(options = {}) {
|
|
5
5
|
const { silent = true, verbose = false, ...rest } = options;
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { K as ConfigResolver, Ot as ALL_TOOL_TARGETS, Q as ConsoleLogger, a as convertFromTool$1, n as checkRulesyncDirExists, on as ALL_FEATURES, r as generate$1, t as importFromTool$1 } from "./import-BwakMyyf.js";
|
|
2
2
|
//#region src/index.ts
|
|
3
3
|
async function generate(options = {}) {
|
|
4
4
|
const { silent = true, verbose = false, ...rest } = options;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rulesync",
|
|
3
|
-
"version": "16.
|
|
3
|
+
"version": "16.4.0",
|
|
4
4
|
"description": "Unified AI rules management CLI tool that generates configuration files for various AI development tools",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
"gray-matter": "4.0.3",
|
|
62
62
|
"js-yaml": "5.2.1",
|
|
63
63
|
"jsonc-parser": "3.3.1",
|
|
64
|
+
"minisearch": "7.2.0",
|
|
64
65
|
"smol-toml": "1.7.0",
|
|
65
66
|
"sury": "10.0.4",
|
|
66
67
|
"zod": "4.4.3"
|
|
@@ -101,12 +102,13 @@
|
|
|
101
102
|
"scripts": {
|
|
102
103
|
"build": "tsdown",
|
|
103
104
|
"check": "pnpm run fmt:check && pnpm run oxlint && pnpm run typecheck",
|
|
105
|
+
"check:docs-content": "pnpm run generate:docs-content && git diff --exit-code src/generated/docs-content.ts",
|
|
104
106
|
"check:gitignore": "pnpm run dev gitignore && git diff --exit-code .gitignore .gitattributes",
|
|
105
107
|
"check:supported-tools": "tsx scripts/generate-supported-tools-tables.ts && git diff --exit-code README.md docs/reference/supported-tools.md",
|
|
106
108
|
"check:sync-skill-docs": "tsx scripts/check-skill-docs-sync.ts",
|
|
107
109
|
"cicheck": "pnpm run cicheck:code && pnpm run cicheck:content",
|
|
108
110
|
"cicheck:code": "pnpm run check && pnpm run test",
|
|
109
|
-
"cicheck:content": "pnpm run check:sync-skill-docs && pnpm run check:gitignore && pnpm run check:supported-tools && pnpm run cspell && pnpm run secretlint",
|
|
111
|
+
"cicheck:content": "pnpm run check:sync-skill-docs && pnpm run check:docs-content && pnpm run check:gitignore && pnpm run check:supported-tools && pnpm run cspell && pnpm run secretlint",
|
|
110
112
|
"cspell": "cspell --no-progress --gitignore .",
|
|
111
113
|
"dev": "tsx src/cli/index.ts",
|
|
112
114
|
"docs:build": "vitepress build docs",
|
|
@@ -116,6 +118,7 @@
|
|
|
116
118
|
"fmt": "oxfmt .",
|
|
117
119
|
"fmt:check": "oxfmt --check .",
|
|
118
120
|
"generate": "pnpm run dev generate",
|
|
121
|
+
"generate:docs-content": "tsx scripts/generate-docs-content.ts",
|
|
119
122
|
"generate:schema": "tsx scripts/generate-json-schema.ts",
|
|
120
123
|
"generate:tables": "tsx scripts/generate-supported-tools-tables.ts",
|
|
121
124
|
"knip": "knip",
|