rulesync 16.31.0 → 16.32.1
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 -0
- package/dist/cli/index.cjs +4 -4
- package/dist/cli/index.js +4 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-D2GhBXJV.js → import-CT7-pKL6.js} +1420 -16
- package/dist/import-CT7-pKL6.js.map +1 -0
- package/dist/{import-Cwe8RKJs.cjs → import-NHZnMc--.cjs} +1436 -32
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +5 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/import-D2GhBXJV.js.map +0 -1
|
@@ -444,36 +444,36 @@ const isFeatureValueEnabled = (value) => {
|
|
|
444
444
|
const parseCommaSeparatedList = (value) => value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
445
445
|
//#endregion
|
|
446
446
|
//#region src/constants/rulesync-paths.ts
|
|
447
|
-
const { join: join$
|
|
447
|
+
const { join: join$340 } = node_path.posix;
|
|
448
448
|
const RULESYNC_CONFIG_RELATIVE_FILE_PATH = "rulesync.jsonc";
|
|
449
449
|
const RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH = "rulesync.local.jsonc";
|
|
450
450
|
const RULESYNC_USER_CONFIG_DIR_NAME = "rulesync";
|
|
451
451
|
const RULESYNC_XDG_CONFIG_HOME_DEFAULT_DIR_NAME = ".config";
|
|
452
452
|
const RULESYNC_RELATIVE_DIR_PATH = ".rulesync";
|
|
453
453
|
const RULES_FEATURE_SUBDIR = "rules";
|
|
454
|
-
const CURATED_RULES_FEATURE_SUBDIR = join$
|
|
454
|
+
const CURATED_RULES_FEATURE_SUBDIR = join$340(RULES_FEATURE_SUBDIR, ".curated");
|
|
455
455
|
const COMMANDS_FEATURE_SUBDIR = "commands";
|
|
456
456
|
const SUBAGENTS_FEATURE_SUBDIR = "subagents";
|
|
457
457
|
const CHECKS_FEATURE_SUBDIR = "checks";
|
|
458
458
|
const SKILLS_FEATURE_SUBDIR = "skills";
|
|
459
|
-
const CURATED_SKILLS_FEATURE_SUBDIR = join$
|
|
460
|
-
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$
|
|
461
|
-
const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$
|
|
462
|
-
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$
|
|
463
|
-
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$
|
|
464
|
-
const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$
|
|
465
|
-
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$
|
|
466
|
-
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$
|
|
467
|
-
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$
|
|
468
|
-
join$
|
|
469
|
-
const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$
|
|
470
|
-
const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$
|
|
459
|
+
const CURATED_SKILLS_FEATURE_SUBDIR = join$340(SKILLS_FEATURE_SUBDIR, ".curated");
|
|
460
|
+
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$340(RULESYNC_RELATIVE_DIR_PATH, RULES_FEATURE_SUBDIR);
|
|
461
|
+
const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$340(RULESYNC_RELATIVE_DIR_PATH, CURATED_RULES_FEATURE_SUBDIR);
|
|
462
|
+
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$340(RULESYNC_RELATIVE_DIR_PATH, COMMANDS_FEATURE_SUBDIR);
|
|
463
|
+
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$340(RULESYNC_RELATIVE_DIR_PATH, SUBAGENTS_FEATURE_SUBDIR);
|
|
464
|
+
const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$340(RULESYNC_RELATIVE_DIR_PATH, CHECKS_FEATURE_SUBDIR);
|
|
465
|
+
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$340(RULESYNC_RELATIVE_DIR_PATH, "mcp.jsonc");
|
|
466
|
+
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$340(RULESYNC_RELATIVE_DIR_PATH, "hooks.jsonc");
|
|
467
|
+
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$340(RULESYNC_RELATIVE_DIR_PATH, "permissions.jsonc");
|
|
468
|
+
join$340(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
|
|
469
|
+
const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$340(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
|
|
470
|
+
const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$340(RULESYNC_RELATIVE_DIR_PATH, "permissions.json");
|
|
471
471
|
const RULESYNC_AIIGNORE_FILE_NAME = ".aiignore";
|
|
472
|
-
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$
|
|
472
|
+
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$340(RULESYNC_RELATIVE_DIR_PATH, ".aiignore");
|
|
473
473
|
const RULESYNC_IGNORE_RELATIVE_FILE_PATH = ".rulesyncignore";
|
|
474
474
|
const RULESYNC_OVERVIEW_FILE_NAME = "overview.md";
|
|
475
|
-
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$
|
|
476
|
-
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$
|
|
475
|
+
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$340(RULESYNC_RELATIVE_DIR_PATH, SKILLS_FEATURE_SUBDIR);
|
|
476
|
+
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$340(RULESYNC_RELATIVE_DIR_PATH, CURATED_SKILLS_FEATURE_SUBDIR);
|
|
477
477
|
const RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync.lock";
|
|
478
478
|
const RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync-npm.lock.json";
|
|
479
479
|
const RULESYNC_MCP_FILE_NAME = "mcp.jsonc";
|
|
@@ -503,6 +503,7 @@ const rulesProcessorToolTargetTuple = [
|
|
|
503
503
|
"cline",
|
|
504
504
|
"codebuddy",
|
|
505
505
|
"codexcli",
|
|
506
|
+
"continue",
|
|
506
507
|
"copilot",
|
|
507
508
|
"copilotcli",
|
|
508
509
|
"cortexcode",
|
|
@@ -546,6 +547,7 @@ const ignoreProcessorToolTargetTuple = [
|
|
|
546
547
|
"claudecode",
|
|
547
548
|
"claudecode-legacy",
|
|
548
549
|
"cline",
|
|
550
|
+
"continue",
|
|
549
551
|
"crush",
|
|
550
552
|
"cursor",
|
|
551
553
|
"hermesagent",
|
|
@@ -577,6 +579,7 @@ const mcpProcessorToolTargetTuple = [
|
|
|
577
579
|
"claudecode-legacy",
|
|
578
580
|
"cline",
|
|
579
581
|
"codexcli",
|
|
582
|
+
"continue",
|
|
580
583
|
"copilot",
|
|
581
584
|
"copilotcli",
|
|
582
585
|
"cortexcode",
|
|
@@ -618,6 +621,7 @@ const commandsProcessorToolTargetTuple = [
|
|
|
618
621
|
"claudecode-legacy",
|
|
619
622
|
"cline",
|
|
620
623
|
"codexcli",
|
|
624
|
+
"continue",
|
|
621
625
|
"copilot",
|
|
622
626
|
"cursor",
|
|
623
627
|
"factorydroid",
|
|
@@ -695,6 +699,7 @@ const skillsProcessorToolTargetTuple = [
|
|
|
695
699
|
"claudecode-legacy",
|
|
696
700
|
"cline",
|
|
697
701
|
"codexcli",
|
|
702
|
+
"continue",
|
|
698
703
|
"copilot",
|
|
699
704
|
"copilotcli",
|
|
700
705
|
"cortexcode",
|
|
@@ -740,6 +745,7 @@ const hooksProcessorToolTargetTuple = [
|
|
|
740
745
|
"claudecode",
|
|
741
746
|
"claudecode-plugin",
|
|
742
747
|
"codexcli",
|
|
748
|
+
"continue",
|
|
743
749
|
"copilot",
|
|
744
750
|
"copilotcli",
|
|
745
751
|
"cortexcode",
|
|
@@ -772,6 +778,7 @@ const permissionsProcessorToolTargetTuple = [
|
|
|
772
778
|
"claudecode",
|
|
773
779
|
"cline",
|
|
774
780
|
"codexcli",
|
|
781
|
+
"continue",
|
|
775
782
|
"copilot",
|
|
776
783
|
"copilotcli",
|
|
777
784
|
"cursor",
|
|
@@ -3284,6 +3291,7 @@ const SHARED_USER_MANAGED_CONFIG_PATHS = [
|
|
|
3284
3291
|
".claude/settings.json",
|
|
3285
3292
|
".claude/settings.local.json",
|
|
3286
3293
|
".codex/config.toml",
|
|
3294
|
+
".continue/settings.json",
|
|
3287
3295
|
".copilot/settings.json",
|
|
3288
3296
|
".github/copilot/settings.json",
|
|
3289
3297
|
".cortex/settings.json",
|
|
@@ -4908,6 +4916,59 @@ const CANONICAL_TO_CORTEXCODE_EVENT_NAMES = {
|
|
|
4908
4916
|
};
|
|
4909
4917
|
const CORTEXCODE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_CORTEXCODE_EVENT_NAMES).map(([k, v]) => [v, k]));
|
|
4910
4918
|
/**
|
|
4919
|
+
* Hook events supported by the Continue CLI (`cn`).
|
|
4920
|
+
*
|
|
4921
|
+
* Continue reads a Claude-Code-compatible `hooks` key from
|
|
4922
|
+
* `~/.continue/settings.json` (user) and `.continue/settings.json` (project):
|
|
4923
|
+
* PascalCase event names, a regex `matcher` over the event's subject (tool
|
|
4924
|
+
* name, session source, ...), and `command` / `http` / `prompt` / `agent`
|
|
4925
|
+
* handlers with a `timeout` in seconds. `$CONTINUE_PROJECT_DIR` resolves to
|
|
4926
|
+
* the working directory inside commands. The event list is the CLI's
|
|
4927
|
+
* `HOOK_EVENT_NAMES`.
|
|
4928
|
+
*
|
|
4929
|
+
* @see https://github.com/continuedev/continue/blob/main/extensions/cli/src/hooks/types.ts
|
|
4930
|
+
* @see https://github.com/continuedev/continue/blob/main/extensions/cli/src/hooks/hookConfig.ts
|
|
4931
|
+
*/
|
|
4932
|
+
const CONTINUE_HOOK_EVENTS = [
|
|
4933
|
+
"preToolUse",
|
|
4934
|
+
"postToolUse",
|
|
4935
|
+
"postToolUseFailure",
|
|
4936
|
+
"permissionRequest",
|
|
4937
|
+
"beforeSubmitPrompt",
|
|
4938
|
+
"sessionStart",
|
|
4939
|
+
"sessionEnd",
|
|
4940
|
+
"stop",
|
|
4941
|
+
"notification",
|
|
4942
|
+
"subagentStart",
|
|
4943
|
+
"subagentStop",
|
|
4944
|
+
"preCompact",
|
|
4945
|
+
"configChange",
|
|
4946
|
+
"teammateIdle",
|
|
4947
|
+
"taskCompleted",
|
|
4948
|
+
"worktreeCreate",
|
|
4949
|
+
"worktreeRemove"
|
|
4950
|
+
];
|
|
4951
|
+
const CANONICAL_TO_CONTINUE_EVENT_NAMES = {
|
|
4952
|
+
preToolUse: "PreToolUse",
|
|
4953
|
+
postToolUse: "PostToolUse",
|
|
4954
|
+
postToolUseFailure: "PostToolUseFailure",
|
|
4955
|
+
permissionRequest: "PermissionRequest",
|
|
4956
|
+
beforeSubmitPrompt: "UserPromptSubmit",
|
|
4957
|
+
sessionStart: "SessionStart",
|
|
4958
|
+
sessionEnd: "SessionEnd",
|
|
4959
|
+
stop: "Stop",
|
|
4960
|
+
notification: "Notification",
|
|
4961
|
+
subagentStart: "SubagentStart",
|
|
4962
|
+
subagentStop: "SubagentStop",
|
|
4963
|
+
preCompact: "PreCompact",
|
|
4964
|
+
configChange: "ConfigChange",
|
|
4965
|
+
teammateIdle: "TeammateIdle",
|
|
4966
|
+
taskCompleted: "TaskCompleted",
|
|
4967
|
+
worktreeCreate: "WorktreeCreate",
|
|
4968
|
+
worktreeRemove: "WorktreeRemove"
|
|
4969
|
+
};
|
|
4970
|
+
const CONTINUE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_CONTINUE_EVENT_NAMES).map(([k, v]) => [v, k]));
|
|
4971
|
+
/**
|
|
4911
4972
|
* Hook events supported by Hermes Agent's native Shell Hooks system.
|
|
4912
4973
|
*
|
|
4913
4974
|
* Hermes validates hook events against a fixed `VALID_HOOKS` set — 37 entries as
|
|
@@ -5062,6 +5123,7 @@ const HooksConfigSchema = zod_mini.z.looseObject({
|
|
|
5062
5123
|
zcode: zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
|
|
5063
5124
|
bob: zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
|
|
5064
5125
|
cortexcode: zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
|
|
5126
|
+
continue: zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
|
|
5065
5127
|
tabnine: zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
|
|
5066
5128
|
qwencode: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
5067
5129
|
hooks: zod_mini.z.optional(hooksRecordSchema),
|
|
@@ -6068,6 +6130,7 @@ const RulesyncMcpFileSchema = zod_mini.z.looseObject({
|
|
|
6068
6130
|
claudecode: zod_mini.z.optional(toolScopedMcpSchema),
|
|
6069
6131
|
cline: zod_mini.z.optional(toolScopedMcpSchema),
|
|
6070
6132
|
codexcli: zod_mini.z.optional(toolScopedMcpSchema),
|
|
6133
|
+
continue: zod_mini.z.optional(toolScopedMcpSchema),
|
|
6071
6134
|
copilot: zod_mini.z.optional(toolScopedMcpSchema),
|
|
6072
6135
|
copilotcli: zod_mini.z.optional(toolScopedMcpSchema),
|
|
6073
6136
|
cursor: zod_mini.z.optional(toolScopedMcpSchema),
|
|
@@ -7701,6 +7764,7 @@ const PermissionsConfigSchema = zod_mini.z.looseObject({
|
|
|
7701
7764
|
zed: zod_mini.z.optional(ZedPermissionsOverrideSchema),
|
|
7702
7765
|
devin: zod_mini.z.optional(DevinPermissionsOverrideSchema),
|
|
7703
7766
|
"antigravity-ide": zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
7767
|
+
continue: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
7704
7768
|
copilot: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
7705
7769
|
copilotcli: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
7706
7770
|
goose: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
@@ -8129,6 +8193,13 @@ const RulesyncRuleFrontmatterSchema = zod_mini.z.object({
|
|
|
8129
8193
|
enabled: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
8130
8194
|
description: zod_mini.z.optional(zod_mini.z.string())
|
|
8131
8195
|
})),
|
|
8196
|
+
continue: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
8197
|
+
alwaysApply: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
8198
|
+
description: zod_mini.z.optional(zod_mini.z.string()),
|
|
8199
|
+
globs: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
8200
|
+
name: zod_mini.z.optional(zod_mini.z.string()),
|
|
8201
|
+
regex: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())]))
|
|
8202
|
+
})),
|
|
8132
8203
|
cursor: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
8133
8204
|
alwaysApply: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
8134
8205
|
description: zod_mini.z.optional(zod_mini.z.string()),
|
|
@@ -9417,6 +9488,7 @@ const RulesyncSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
9417
9488
|
"allowed-tools": zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())]))
|
|
9418
9489
|
})),
|
|
9419
9490
|
cortexcode: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
9491
|
+
continue: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
9420
9492
|
tabnine: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
9421
9493
|
takt: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
9422
9494
|
name: zod_mini.z.optional(zod_mini.z.string()),
|
|
@@ -12996,6 +13068,14 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
12996
13068
|
ownedKeys: ["hooks"]
|
|
12997
13069
|
} }
|
|
12998
13070
|
},
|
|
13071
|
+
".continue/settings.json": {
|
|
13072
|
+
format: "json",
|
|
13073
|
+
invalidRootPolicy: "error",
|
|
13074
|
+
features: { hooks: {
|
|
13075
|
+
kind: "replace-owned-keys",
|
|
13076
|
+
ownedKeys: ["hooks"]
|
|
13077
|
+
} }
|
|
13078
|
+
},
|
|
12999
13079
|
".cortex/settings.json": {
|
|
13000
13080
|
format: "json",
|
|
13001
13081
|
invalidRootPolicy: "error",
|
|
@@ -15197,6 +15277,152 @@ var CodexcliCommand = class CodexcliCommand extends ToolCommand {
|
|
|
15197
15277
|
}
|
|
15198
15278
|
};
|
|
15199
15279
|
//#endregion
|
|
15280
|
+
//#region src/constants/continue-paths.ts
|
|
15281
|
+
const CONTINUE_DIR = ".continue";
|
|
15282
|
+
const CONTINUE_ROOT_RULE_FILE_NAME = "AGENTS.md";
|
|
15283
|
+
const CONTINUE_PROMPTS_DIR_PATH = (0, node_path.join)(CONTINUE_DIR, "prompts");
|
|
15284
|
+
const CONTINUE_MCP_DIR_PATH = (0, node_path.join)(CONTINUE_DIR, "mcpServers");
|
|
15285
|
+
const CONTINUE_MCP_FILE_NAME = "mcp.json";
|
|
15286
|
+
const CONTINUE_SKILLS_DIR_PATH = (0, node_path.join)(CONTINUE_DIR, "skills");
|
|
15287
|
+
const CONTINUE_SETTINGS_FILE_NAME = "settings.json";
|
|
15288
|
+
const CONTINUE_PERMISSIONS_FILE_NAME = "permissions.yaml";
|
|
15289
|
+
const CONTINUE_IGNORE_FILE_NAME = ".continueignore";
|
|
15290
|
+
//#endregion
|
|
15291
|
+
//#region src/features/commands/continue-command.ts
|
|
15292
|
+
/**
|
|
15293
|
+
* Continue prompt files are Markdown files under `<project>/.continue/prompts/`
|
|
15294
|
+
* (project scope) and `~/.continue/prompts/` (user scope). A prompt with
|
|
15295
|
+
* `invokable: true` becomes a `/name` slash command in the chat input and the
|
|
15296
|
+
* CLI; without it the file is treated as a plain rule, so rulesync always sets
|
|
15297
|
+
* the flag. `name` is the command name (the file stem by default) and
|
|
15298
|
+
* `description` the hint shown next to it.
|
|
15299
|
+
*
|
|
15300
|
+
* @see https://docs.continue.dev/customize/deep-dives/prompts
|
|
15301
|
+
*/
|
|
15302
|
+
const ContinueCommandFrontmatterSchema = zod_mini.z.looseObject({
|
|
15303
|
+
name: zod_mini.z.optional(zod_mini.z.string()),
|
|
15304
|
+
description: zod_mini.z.optional(zod_mini.z.string()),
|
|
15305
|
+
invokable: zod_mini.z.optional(zod_mini.z.boolean())
|
|
15306
|
+
});
|
|
15307
|
+
/** The command name Continue derives from a prompt file: its stem. */
|
|
15308
|
+
function commandNameFromFilePath(relativeFilePath) {
|
|
15309
|
+
return (0, node_path.basename)(relativeFilePath).replace(/\.md$/, "");
|
|
15310
|
+
}
|
|
15311
|
+
var ContinueCommand = class ContinueCommand extends ToolCommand {
|
|
15312
|
+
frontmatter;
|
|
15313
|
+
body;
|
|
15314
|
+
constructor({ frontmatter, body, ...rest }) {
|
|
15315
|
+
if (rest.validate) {
|
|
15316
|
+
const result = ContinueCommandFrontmatterSchema.safeParse(frontmatter);
|
|
15317
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${(0, node_path.join)(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
|
|
15318
|
+
}
|
|
15319
|
+
super({
|
|
15320
|
+
...rest,
|
|
15321
|
+
fileContent: stringifyFrontmatter(body, frontmatter)
|
|
15322
|
+
});
|
|
15323
|
+
this.frontmatter = frontmatter;
|
|
15324
|
+
this.body = body;
|
|
15325
|
+
}
|
|
15326
|
+
static getSettablePaths(_options = {}) {
|
|
15327
|
+
return { relativeDirPath: CONTINUE_PROMPTS_DIR_PATH };
|
|
15328
|
+
}
|
|
15329
|
+
getBody() {
|
|
15330
|
+
return this.body;
|
|
15331
|
+
}
|
|
15332
|
+
getFrontmatter() {
|
|
15333
|
+
return this.frontmatter;
|
|
15334
|
+
}
|
|
15335
|
+
toRulesyncCommand() {
|
|
15336
|
+
const { description, name, invokable: _invokable, ...restFields } = this.frontmatter;
|
|
15337
|
+
const continueFields = {
|
|
15338
|
+
...name !== void 0 && name !== commandNameFromFilePath(this.relativeFilePath) && { name },
|
|
15339
|
+
...restFields
|
|
15340
|
+
};
|
|
15341
|
+
const rulesyncFrontmatter = {
|
|
15342
|
+
targets: ["*"],
|
|
15343
|
+
description,
|
|
15344
|
+
...Object.keys(continueFields).length > 0 && { continue: continueFields }
|
|
15345
|
+
};
|
|
15346
|
+
const fileContent = stringifyFrontmatter(this.body, rulesyncFrontmatter);
|
|
15347
|
+
return new RulesyncCommand({
|
|
15348
|
+
outputRoot: ".",
|
|
15349
|
+
frontmatter: rulesyncFrontmatter,
|
|
15350
|
+
body: this.body,
|
|
15351
|
+
relativeDirPath: RulesyncCommand.getSettablePaths().relativeDirPath,
|
|
15352
|
+
relativeFilePath: this.relativeFilePath,
|
|
15353
|
+
fileContent,
|
|
15354
|
+
validate: true
|
|
15355
|
+
});
|
|
15356
|
+
}
|
|
15357
|
+
static fromRulesyncCommand({ outputRoot = process.cwd(), rulesyncCommand, validate = true, global = false }) {
|
|
15358
|
+
const rulesyncFrontmatter = rulesyncCommand.getFrontmatter();
|
|
15359
|
+
const relativeFilePath = rulesyncCommand.getRelativeFilePath();
|
|
15360
|
+
const continueFields = rulesyncFrontmatter.continue ?? {};
|
|
15361
|
+
const continueFrontmatter = {
|
|
15362
|
+
name: commandNameFromFilePath(relativeFilePath),
|
|
15363
|
+
description: rulesyncFrontmatter.description,
|
|
15364
|
+
...continueFields,
|
|
15365
|
+
invokable: true
|
|
15366
|
+
};
|
|
15367
|
+
const body = rulesyncCommand.getBody();
|
|
15368
|
+
const paths = this.getSettablePaths({ global });
|
|
15369
|
+
return new ContinueCommand({
|
|
15370
|
+
outputRoot,
|
|
15371
|
+
frontmatter: continueFrontmatter,
|
|
15372
|
+
body,
|
|
15373
|
+
relativeDirPath: paths.relativeDirPath,
|
|
15374
|
+
relativeFilePath,
|
|
15375
|
+
validate
|
|
15376
|
+
});
|
|
15377
|
+
}
|
|
15378
|
+
validate() {
|
|
15379
|
+
if (!this.frontmatter) return {
|
|
15380
|
+
success: true,
|
|
15381
|
+
error: null
|
|
15382
|
+
};
|
|
15383
|
+
const result = ContinueCommandFrontmatterSchema.safeParse(this.frontmatter);
|
|
15384
|
+
if (result.success) return {
|
|
15385
|
+
success: true,
|
|
15386
|
+
error: null
|
|
15387
|
+
};
|
|
15388
|
+
return {
|
|
15389
|
+
success: false,
|
|
15390
|
+
error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${(0, node_path.join)(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
|
|
15391
|
+
};
|
|
15392
|
+
}
|
|
15393
|
+
static isTargetedByRulesyncCommand(rulesyncCommand) {
|
|
15394
|
+
return this.isTargetedByRulesyncCommandDefault({
|
|
15395
|
+
rulesyncCommand,
|
|
15396
|
+
toolTarget: "continue"
|
|
15397
|
+
});
|
|
15398
|
+
}
|
|
15399
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
15400
|
+
const paths = this.getSettablePaths({ global });
|
|
15401
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
15402
|
+
const { frontmatter, body: content } = parseFrontmatter(await readFileContent(filePath), filePath);
|
|
15403
|
+
const result = ContinueCommandFrontmatterSchema.safeParse(frontmatter);
|
|
15404
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
15405
|
+
return new ContinueCommand({
|
|
15406
|
+
outputRoot,
|
|
15407
|
+
relativeDirPath: paths.relativeDirPath,
|
|
15408
|
+
relativeFilePath,
|
|
15409
|
+
frontmatter: result.data,
|
|
15410
|
+
body: content.trim(),
|
|
15411
|
+
validate
|
|
15412
|
+
});
|
|
15413
|
+
}
|
|
15414
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
15415
|
+
return new ContinueCommand({
|
|
15416
|
+
outputRoot,
|
|
15417
|
+
relativeDirPath,
|
|
15418
|
+
relativeFilePath,
|
|
15419
|
+
frontmatter: { description: "" },
|
|
15420
|
+
body: "",
|
|
15421
|
+
validate: false
|
|
15422
|
+
});
|
|
15423
|
+
}
|
|
15424
|
+
};
|
|
15425
|
+
//#endregion
|
|
15200
15426
|
//#region src/constants/copilot-paths.ts
|
|
15201
15427
|
const COPILOT_DIR = ".copilot";
|
|
15202
15428
|
const GITHUB_DIR = ".github";
|
|
@@ -19297,6 +19523,16 @@ const toolCommandFactories = /* @__PURE__ */ new Map([
|
|
|
19297
19523
|
supportsSubdirectory: false
|
|
19298
19524
|
}
|
|
19299
19525
|
}],
|
|
19526
|
+
["continue", {
|
|
19527
|
+
class: ContinueCommand,
|
|
19528
|
+
meta: {
|
|
19529
|
+
extension: "md",
|
|
19530
|
+
supportsProject: true,
|
|
19531
|
+
supportsGlobal: true,
|
|
19532
|
+
isSimulated: false,
|
|
19533
|
+
supportsSubdirectory: false
|
|
19534
|
+
}
|
|
19535
|
+
}],
|
|
19300
19536
|
["copilot", {
|
|
19301
19537
|
class: CopilotCommand,
|
|
19302
19538
|
meta: {
|
|
@@ -22646,6 +22882,159 @@ var CodexcliHooks = class CodexcliHooks extends ToolHooks {
|
|
|
22646
22882
|
}
|
|
22647
22883
|
};
|
|
22648
22884
|
//#endregion
|
|
22885
|
+
//#region src/features/hooks/continue-hooks.ts
|
|
22886
|
+
const CONTINUE_CONVERTER_CONFIG = {
|
|
22887
|
+
supportedEvents: CONTINUE_HOOK_EVENTS,
|
|
22888
|
+
canonicalToToolEventNames: CANONICAL_TO_CONTINUE_EVENT_NAMES,
|
|
22889
|
+
toolToCanonicalEventNames: CONTINUE_TO_CANONICAL_EVENT_NAMES,
|
|
22890
|
+
projectDirVar: "$CONTINUE_PROJECT_DIR",
|
|
22891
|
+
prefixDotRelativeCommandsOnly: true,
|
|
22892
|
+
noMatcherEvents: /* @__PURE__ */ new Set([
|
|
22893
|
+
"beforeSubmitPrompt",
|
|
22894
|
+
"stop",
|
|
22895
|
+
"teammateIdle",
|
|
22896
|
+
"taskCompleted",
|
|
22897
|
+
"worktreeCreate",
|
|
22898
|
+
"worktreeRemove"
|
|
22899
|
+
]),
|
|
22900
|
+
supportedHookTypes: /* @__PURE__ */ new Set([
|
|
22901
|
+
"command",
|
|
22902
|
+
"prompt",
|
|
22903
|
+
"http",
|
|
22904
|
+
"agent"
|
|
22905
|
+
]),
|
|
22906
|
+
emitsPromptModel: true,
|
|
22907
|
+
stringPassthroughFields: [{
|
|
22908
|
+
canonical: "statusMessage",
|
|
22909
|
+
tool: "statusMessage"
|
|
22910
|
+
}],
|
|
22911
|
+
booleanPassthroughFields: [{
|
|
22912
|
+
canonical: "once",
|
|
22913
|
+
tool: "once"
|
|
22914
|
+
}, {
|
|
22915
|
+
canonical: "async",
|
|
22916
|
+
tool: "async",
|
|
22917
|
+
commandOnly: true
|
|
22918
|
+
}]
|
|
22919
|
+
};
|
|
22920
|
+
/**
|
|
22921
|
+
* Single spelling of the settings/hooks codec/policy: fail closed on an
|
|
22922
|
+
* unparseable root rather than replacing the user's Continue settings with
|
|
22923
|
+
* generated output.
|
|
22924
|
+
*/
|
|
22925
|
+
function parseContinueSettings(fileContent, filePath) {
|
|
22926
|
+
return parseSharedConfig({
|
|
22927
|
+
format: "json",
|
|
22928
|
+
fileContent,
|
|
22929
|
+
filePath,
|
|
22930
|
+
invalidRootPolicy: "error"
|
|
22931
|
+
});
|
|
22932
|
+
}
|
|
22933
|
+
/**
|
|
22934
|
+
* Continue CLI hooks.
|
|
22935
|
+
*
|
|
22936
|
+
* Hooks live under the top-level `hooks` key of `<project>/.continue/settings.json`
|
|
22937
|
+
* (project scope) and `~/.continue/settings.json` (user scope), in the
|
|
22938
|
+
* Claude-Code shape: `{ "<Event>": [{ "matcher"?: "<regex>", "hooks": [{
|
|
22939
|
+
* "type": "command" | "http" | "prompt" | "agent", ..., "timeout"?: <seconds>
|
|
22940
|
+
* }] }] }`. Both files also hold settings rulesync does not own, so generation
|
|
22941
|
+
* merges the `hooks` key into the existing file (see `SHARED_CONFIG_OWNERSHIP`)
|
|
22942
|
+
* instead of overwriting it. The CLI additionally reads
|
|
22943
|
+
* `.continue/settings.local.json` and the Claude Code settings files; rulesync
|
|
22944
|
+
* writes only the committable project file and the user file.
|
|
22945
|
+
*
|
|
22946
|
+
* @see https://github.com/continuedev/continue/blob/main/extensions/cli/src/hooks/hookConfig.ts
|
|
22947
|
+
* @see https://github.com/continuedev/continue/blob/main/extensions/cli/src/hooks/types.ts
|
|
22948
|
+
*/
|
|
22949
|
+
var ContinueHooks = class ContinueHooks extends ToolHooks {
|
|
22950
|
+
constructor(params) {
|
|
22951
|
+
super({
|
|
22952
|
+
...params,
|
|
22953
|
+
fileContent: params.fileContent ?? "{}"
|
|
22954
|
+
});
|
|
22955
|
+
}
|
|
22956
|
+
isDeletable() {
|
|
22957
|
+
return false;
|
|
22958
|
+
}
|
|
22959
|
+
static getSettablePaths(_options = {}) {
|
|
22960
|
+
return {
|
|
22961
|
+
relativeDirPath: CONTINUE_DIR,
|
|
22962
|
+
relativeFilePath: CONTINUE_SETTINGS_FILE_NAME
|
|
22963
|
+
};
|
|
22964
|
+
}
|
|
22965
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
22966
|
+
const paths = ContinueHooks.getSettablePaths({ global });
|
|
22967
|
+
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"hooks\":{}}";
|
|
22968
|
+
return new ContinueHooks({
|
|
22969
|
+
outputRoot,
|
|
22970
|
+
relativeDirPath: paths.relativeDirPath,
|
|
22971
|
+
relativeFilePath: paths.relativeFilePath,
|
|
22972
|
+
fileContent,
|
|
22973
|
+
validate
|
|
22974
|
+
});
|
|
22975
|
+
}
|
|
22976
|
+
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
|
|
22977
|
+
const paths = ContinueHooks.getSettablePaths({ global });
|
|
22978
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
22979
|
+
const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
|
|
22980
|
+
const config = rulesyncHooks.getJson();
|
|
22981
|
+
const hooks = canonicalToToolHooks({
|
|
22982
|
+
config,
|
|
22983
|
+
toolOverrideHooks: config.continue?.hooks,
|
|
22984
|
+
converterConfig: CONTINUE_CONVERTER_CONFIG,
|
|
22985
|
+
logger
|
|
22986
|
+
});
|
|
22987
|
+
const fileContent = applySharedConfigPatch({
|
|
22988
|
+
fileKey: sharedConfigFileKey(paths),
|
|
22989
|
+
feature: "hooks",
|
|
22990
|
+
existingContent,
|
|
22991
|
+
patch: { hooks },
|
|
22992
|
+
filePath,
|
|
22993
|
+
logger
|
|
22994
|
+
});
|
|
22995
|
+
return new ContinueHooks({
|
|
22996
|
+
outputRoot,
|
|
22997
|
+
relativeDirPath: paths.relativeDirPath,
|
|
22998
|
+
relativeFilePath: paths.relativeFilePath,
|
|
22999
|
+
fileContent,
|
|
23000
|
+
validate
|
|
23001
|
+
});
|
|
23002
|
+
}
|
|
23003
|
+
toRulesyncHooks({ logger } = {}) {
|
|
23004
|
+
const configPath = (0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath());
|
|
23005
|
+
let settings;
|
|
23006
|
+
try {
|
|
23007
|
+
settings = parseContinueSettings(this.getFileContent(), configPath);
|
|
23008
|
+
} catch (error) {
|
|
23009
|
+
throw new Error(`Failed to parse Continue hooks content in ${configPath}: ${formatError(error)}`, { cause: error });
|
|
23010
|
+
}
|
|
23011
|
+
const hooks = toolHooksToCanonical({
|
|
23012
|
+
logger,
|
|
23013
|
+
hooks: settings.hooks,
|
|
23014
|
+
converterConfig: CONTINUE_CONVERTER_CONFIG
|
|
23015
|
+
});
|
|
23016
|
+
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
|
|
23017
|
+
hooks,
|
|
23018
|
+
overrideKey: "continue"
|
|
23019
|
+
}), null, 2) });
|
|
23020
|
+
}
|
|
23021
|
+
validate() {
|
|
23022
|
+
return {
|
|
23023
|
+
success: true,
|
|
23024
|
+
error: null
|
|
23025
|
+
};
|
|
23026
|
+
}
|
|
23027
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
23028
|
+
return new ContinueHooks({
|
|
23029
|
+
outputRoot,
|
|
23030
|
+
relativeDirPath,
|
|
23031
|
+
relativeFilePath,
|
|
23032
|
+
fileContent: JSON.stringify({ hooks: {} }, null, 2),
|
|
23033
|
+
validate: false
|
|
23034
|
+
});
|
|
23035
|
+
}
|
|
23036
|
+
};
|
|
23037
|
+
//#endregion
|
|
22649
23038
|
//#region src/features/hooks/copilot-hooks.ts
|
|
22650
23039
|
/**
|
|
22651
23040
|
* Copilot hook entry as stored in .github/hooks/copilot-hooks.json.
|
|
@@ -27628,6 +28017,35 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
27628
28017
|
supportsMatcher: true,
|
|
27629
28018
|
matcherEvents: ["preToolUse", "postToolUse"]
|
|
27630
28019
|
}],
|
|
28020
|
+
["continue", {
|
|
28021
|
+
class: ContinueHooks,
|
|
28022
|
+
meta: {
|
|
28023
|
+
supportsProject: true,
|
|
28024
|
+
supportsGlobal: true,
|
|
28025
|
+
supportsImport: true
|
|
28026
|
+
},
|
|
28027
|
+
supportedEvents: CONTINUE_HOOK_EVENTS,
|
|
28028
|
+
supportedHookTypes: [
|
|
28029
|
+
"command",
|
|
28030
|
+
"http",
|
|
28031
|
+
"prompt",
|
|
28032
|
+
"agent"
|
|
28033
|
+
],
|
|
28034
|
+
supportsMatcher: true,
|
|
28035
|
+
matcherEvents: [
|
|
28036
|
+
"preToolUse",
|
|
28037
|
+
"postToolUse",
|
|
28038
|
+
"postToolUseFailure",
|
|
28039
|
+
"permissionRequest",
|
|
28040
|
+
"sessionStart",
|
|
28041
|
+
"sessionEnd",
|
|
28042
|
+
"notification",
|
|
28043
|
+
"subagentStart",
|
|
28044
|
+
"subagentStop",
|
|
28045
|
+
"preCompact",
|
|
28046
|
+
"configChange"
|
|
28047
|
+
]
|
|
28048
|
+
}],
|
|
27631
28049
|
["cortexcode", {
|
|
27632
28050
|
class: CortexcodeHooks,
|
|
27633
28051
|
meta: {
|
|
@@ -28348,6 +28766,60 @@ var ClineIgnore = class ClineIgnore extends ToolIgnore {
|
|
|
28348
28766
|
}
|
|
28349
28767
|
};
|
|
28350
28768
|
//#endregion
|
|
28769
|
+
//#region src/features/ignore/continue-ignore.ts
|
|
28770
|
+
/**
|
|
28771
|
+
* Continue ignore file implementation.
|
|
28772
|
+
*
|
|
28773
|
+
* - Project scope: the workspace-root `.continueignore`, which follows the
|
|
28774
|
+
* exact same rules as `.gitignore` and is layered on top of it.
|
|
28775
|
+
* - Global scope: `~/.continue/.continueignore`, respected for all workspaces.
|
|
28776
|
+
*
|
|
28777
|
+
* @see https://docs.continue.dev/reference/deprecated-codebase
|
|
28778
|
+
*/
|
|
28779
|
+
var ContinueIgnore = class ContinueIgnore extends ToolIgnore {
|
|
28780
|
+
static getSettablePaths({ global = false } = {}) {
|
|
28781
|
+
return {
|
|
28782
|
+
relativeDirPath: global ? CONTINUE_DIR : ".",
|
|
28783
|
+
relativeFilePath: CONTINUE_IGNORE_FILE_NAME
|
|
28784
|
+
};
|
|
28785
|
+
}
|
|
28786
|
+
toRulesyncIgnore() {
|
|
28787
|
+
return this.toRulesyncIgnoreDefault();
|
|
28788
|
+
}
|
|
28789
|
+
static fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore, global = false }) {
|
|
28790
|
+
const paths = this.getSettablePaths({ global });
|
|
28791
|
+
return new ContinueIgnore({
|
|
28792
|
+
outputRoot,
|
|
28793
|
+
relativeDirPath: paths.relativeDirPath,
|
|
28794
|
+
relativeFilePath: paths.relativeFilePath,
|
|
28795
|
+
fileContent: rulesyncIgnore.getFileContent(),
|
|
28796
|
+
global
|
|
28797
|
+
});
|
|
28798
|
+
}
|
|
28799
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
28800
|
+
const { relativeDirPath, relativeFilePath } = this.getSettablePaths({ global });
|
|
28801
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativeDirPath, relativeFilePath));
|
|
28802
|
+
return new ContinueIgnore({
|
|
28803
|
+
outputRoot,
|
|
28804
|
+
relativeDirPath,
|
|
28805
|
+
relativeFilePath,
|
|
28806
|
+
fileContent,
|
|
28807
|
+
validate,
|
|
28808
|
+
global
|
|
28809
|
+
});
|
|
28810
|
+
}
|
|
28811
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
28812
|
+
return new ContinueIgnore({
|
|
28813
|
+
outputRoot,
|
|
28814
|
+
relativeDirPath,
|
|
28815
|
+
relativeFilePath,
|
|
28816
|
+
fileContent: "",
|
|
28817
|
+
validate: false,
|
|
28818
|
+
global
|
|
28819
|
+
});
|
|
28820
|
+
}
|
|
28821
|
+
};
|
|
28822
|
+
//#endregion
|
|
28351
28823
|
//#region src/constants/crush-paths.ts
|
|
28352
28824
|
const CRUSH_RULE_FILE_NAME = "CRUSH.md";
|
|
28353
28825
|
const CRUSH_GLOBAL_DIR = (0, node_path.join)(".config", "crush");
|
|
@@ -29462,6 +29934,7 @@ const toolIgnoreFactories = /* @__PURE__ */ new Map([
|
|
|
29462
29934
|
["claudecode", { class: ClaudecodeIgnore }],
|
|
29463
29935
|
["claudecode-legacy", { class: ClaudecodeIgnore }],
|
|
29464
29936
|
["cline", { class: ClineIgnore }],
|
|
29937
|
+
["continue", { class: ContinueIgnore }],
|
|
29465
29938
|
["crush", { class: CrushIgnore }],
|
|
29466
29939
|
["cursor", { class: CursorIgnore }],
|
|
29467
29940
|
["hermesagent", { class: HermesagentIgnore }],
|
|
@@ -29482,6 +29955,7 @@ const toolIgnoreFactories = /* @__PURE__ */ new Map([
|
|
|
29482
29955
|
]);
|
|
29483
29956
|
const ignoreProcessorToolTargets = [...toolIgnoreFactories.keys()];
|
|
29484
29957
|
const ignoreProcessorGlobalToolTargets = [
|
|
29958
|
+
"continue",
|
|
29485
29959
|
"devin",
|
|
29486
29960
|
"kiro",
|
|
29487
29961
|
"kiro-cli",
|
|
@@ -30240,13 +30714,17 @@ function isRemoteMcpServer(serverConfig) {
|
|
|
30240
30714
|
function resolveRemoteMcpUrl(serverConfig) {
|
|
30241
30715
|
return serverConfig.url || serverConfig.httpUrl || void 0;
|
|
30242
30716
|
}
|
|
30243
|
-
/**
|
|
30717
|
+
/**
|
|
30718
|
+
* The `command` array a `local` server is spawned with, `args` merged in. An
|
|
30719
|
+
* entry carrying `args` but no `command` comes back empty rather than with
|
|
30720
|
+
* `args[0]` promoted to its program: every adapter writes element 0 back out
|
|
30721
|
+
* as the executable (or the whole array as the command line), so the caller
|
|
30722
|
+
* skips such an entry the same way as one with nothing to spawn at all.
|
|
30723
|
+
*/
|
|
30244
30724
|
function resolveLocalMcpCommand(serverConfig) {
|
|
30245
|
-
const
|
|
30246
|
-
|
|
30247
|
-
|
|
30248
|
-
else commandArray.push(serverConfig.command);
|
|
30249
|
-
}
|
|
30725
|
+
const { command } = serverConfig;
|
|
30726
|
+
const commandArray = Array.isArray(command) ? [...command] : command ? [command] : [];
|
|
30727
|
+
if (commandArray.length === 0) return [];
|
|
30250
30728
|
if (serverConfig.args) commandArray.push(...serverConfig.args);
|
|
30251
30729
|
return commandArray;
|
|
30252
30730
|
}
|
|
@@ -30256,9 +30734,13 @@ function resolveLocalMcpCommand(serverConfig) {
|
|
|
30256
30734
|
* spells the transport out. Writing `{type: "local", command: []}` for it would
|
|
30257
30735
|
* give the tool a server it cannot start and the importer a file it cannot read
|
|
30258
30736
|
* back, so it is skipped out loud instead.
|
|
30737
|
+
*
|
|
30738
|
+
* The server name comes straight from the user's config, so it is quoted
|
|
30739
|
+
* through `quoteValueForWarning` rather than interpolated: a crafted name could
|
|
30740
|
+
* otherwise carry a line break or control characters into the log.
|
|
30259
30741
|
*/
|
|
30260
30742
|
function warnAndSkipMcpServer({ toolName, serverName, reason, logger }) {
|
|
30261
|
-
logger?.warn(`${toolName} MCP: skipping
|
|
30743
|
+
logger?.warn(`${toolName} MCP: skipping ${quoteValueForWarning(serverName)} because it declares ${reason}.`);
|
|
30262
30744
|
return null;
|
|
30263
30745
|
}
|
|
30264
30746
|
/**
|
|
@@ -30346,8 +30828,10 @@ function asBobRemoteType(stated, url) {
|
|
|
30346
30828
|
* streamable HTTP server carries `type: "streamable-http"` and `url`, and an
|
|
30347
30829
|
* SSE server carries a bare `url`. The canonical `transport` alias and the
|
|
30348
30830
|
* Claude-style `httpUrl` alias are folded into `type`/`url`; `env`, `cwd`,
|
|
30349
|
-
* `headers`, `timeout`, `alwaysAllow` and `disabled` pass through
|
|
30350
|
-
*
|
|
30831
|
+
* `headers`, `timeout`, `alwaysAllow` and `disabled` pass through, as Bob
|
|
30832
|
+
* documents all of them (`env` and `headers` with their prototype-pollution
|
|
30833
|
+
* keys dropped, since Bob spreads those maps into the process environment and
|
|
30834
|
+
* the HTTP requests).
|
|
30351
30835
|
*
|
|
30352
30836
|
* Bob Shell documents the same file with an `httpURL` key instead of
|
|
30353
30837
|
* `type` + `url` for streamable HTTP. rulesync writes the IDE spelling (the two
|
|
@@ -31185,6 +31669,230 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
|
|
|
31185
31669
|
}
|
|
31186
31670
|
};
|
|
31187
31671
|
//#endregion
|
|
31672
|
+
//#region src/features/mcp/continue-mcp.ts
|
|
31673
|
+
/**
|
|
31674
|
+
* Parse a Continue MCP file. Continue reads the files under `mcpServers/` as
|
|
31675
|
+
* JSONC, so comments and trailing commas in a hand-written file are accepted;
|
|
31676
|
+
* malformed content or a non-object root (`null`, an array, a scalar) fails
|
|
31677
|
+
* closed rather than being spread into the regenerated file.
|
|
31678
|
+
*/
|
|
31679
|
+
function parseContinueMcpConfig({ fileContent, relativePath }) {
|
|
31680
|
+
let parsed;
|
|
31681
|
+
try {
|
|
31682
|
+
parsed = parseJsonc$6(fileContent);
|
|
31683
|
+
} catch (error) {
|
|
31684
|
+
throw new Error(`Failed to parse Continue MCP config at ${relativePath}: ${formatError(error)}`, { cause: error });
|
|
31685
|
+
}
|
|
31686
|
+
if (!isPlainObject$1(parsed)) throw new Error(`Failed to parse Continue MCP config at ${relativePath}: expected a JSON object at the root`);
|
|
31687
|
+
return parsed;
|
|
31688
|
+
}
|
|
31689
|
+
/**
|
|
31690
|
+
* The remote transport Continue reads a server as. Continue's JSON schema
|
|
31691
|
+
* accepts `type: "sse"` or `type: "http"` (streamable HTTP) for a `url`
|
|
31692
|
+
* server; a bare `url` defaults to `http`, and the canonical
|
|
31693
|
+
* `streamable-http` spelling is folded into it. A `ws(s)://` URL or any other
|
|
31694
|
+
* stated transport has no Continue equivalent, so `undefined` tells the
|
|
31695
|
+
* caller to skip the server.
|
|
31696
|
+
* @see https://docs.continue.dev/customize/deep-dives/mcp
|
|
31697
|
+
*/
|
|
31698
|
+
function asContinueRemoteType(stated, url) {
|
|
31699
|
+
if (stated === "sse") return "sse";
|
|
31700
|
+
if (stated === "http" || stated === "streamable-http") return "http";
|
|
31701
|
+
if (stated === void 0) return /^wss?:\/\//i.test(url) ? void 0 : "http";
|
|
31702
|
+
}
|
|
31703
|
+
/**
|
|
31704
|
+
* Convert the canonical server map to the shape Continue's JSON MCP schema
|
|
31705
|
+
* accepts: a remote server is `{ type, url, headers? }` and a stdio server is
|
|
31706
|
+
* `{ type: "stdio", command, args?, env? }`. Continue validates the whole
|
|
31707
|
+
* file with a strict union — one server that matches neither shape (an
|
|
31708
|
+
* unknown transport, a non-string `env` value) makes Continue drop every
|
|
31709
|
+
* server in the file — so only the documented keys are emitted and anything
|
|
31710
|
+
* else (`timeout`, `oauth`, rulesync-only fields) is left out. `envFile` is
|
|
31711
|
+
* accepted by Continue's schema but ignored with a warning at load time, so
|
|
31712
|
+
* it is dropped here with a warning instead of being written as if it worked.
|
|
31713
|
+
*
|
|
31714
|
+
* A server Continue cannot start or reach — no transport at all, a remote
|
|
31715
|
+
* transport without a URL, a WebSocket URL, or a stdio entry without a
|
|
31716
|
+
* command — is skipped with a warning rather than written in a form that
|
|
31717
|
+
* would invalidate the file.
|
|
31718
|
+
* @see https://docs.continue.dev/customize/deep-dives/mcp
|
|
31719
|
+
*/
|
|
31720
|
+
function convertToContinueFormat(mcpServers, logger) {
|
|
31721
|
+
const result = {};
|
|
31722
|
+
for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
|
|
31723
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(serverName) || !isRecord$1(serverConfig)) continue;
|
|
31724
|
+
if (declaresNoTransport(serverConfig)) {
|
|
31725
|
+
warnAndSkipMcpServer({
|
|
31726
|
+
toolName: "Continue",
|
|
31727
|
+
serverName,
|
|
31728
|
+
reason: "no transport",
|
|
31729
|
+
logger
|
|
31730
|
+
});
|
|
31731
|
+
continue;
|
|
31732
|
+
}
|
|
31733
|
+
const converted = isRemoteMcpServer(serverConfig) ? convertRemoteServer({
|
|
31734
|
+
serverName,
|
|
31735
|
+
serverConfig,
|
|
31736
|
+
logger
|
|
31737
|
+
}) : convertStdioServer({
|
|
31738
|
+
serverName,
|
|
31739
|
+
serverConfig,
|
|
31740
|
+
logger
|
|
31741
|
+
});
|
|
31742
|
+
if (converted !== void 0) result[serverName] = converted;
|
|
31743
|
+
}
|
|
31744
|
+
return result;
|
|
31745
|
+
}
|
|
31746
|
+
function convertRemoteServer({ serverName, serverConfig, logger }) {
|
|
31747
|
+
const url = resolveRemoteMcpUrl(serverConfig);
|
|
31748
|
+
if (!url) {
|
|
31749
|
+
warnAndSkipMcpServer({
|
|
31750
|
+
toolName: "Continue",
|
|
31751
|
+
serverName,
|
|
31752
|
+
reason: "a remote transport without a url",
|
|
31753
|
+
logger
|
|
31754
|
+
});
|
|
31755
|
+
return;
|
|
31756
|
+
}
|
|
31757
|
+
const stated = serverConfig.type ?? serverConfig.transport;
|
|
31758
|
+
const remoteType = asContinueRemoteType(typeof stated === "string" ? stated : void 0, url);
|
|
31759
|
+
if (remoteType === void 0) {
|
|
31760
|
+
warnAndSkipMcpServer({
|
|
31761
|
+
toolName: "Continue",
|
|
31762
|
+
serverName,
|
|
31763
|
+
reason: stated === void 0 ? "a WebSocket url, which Continue's remote transports (http and sse) cannot reach" : `the "${String(stated)}" transport, which Continue does not offer for remote servers (only http and sse)`,
|
|
31764
|
+
logger
|
|
31765
|
+
});
|
|
31766
|
+
return;
|
|
31767
|
+
}
|
|
31768
|
+
const converted = {
|
|
31769
|
+
type: remoteType,
|
|
31770
|
+
url
|
|
31771
|
+
};
|
|
31772
|
+
if (isRecord$1(serverConfig.headers)) converted.headers = omitPrototypePollutionKeys(serverConfig.headers);
|
|
31773
|
+
return converted;
|
|
31774
|
+
}
|
|
31775
|
+
function convertStdioServer({ serverName, serverConfig, logger }) {
|
|
31776
|
+
const [command, ...args] = resolveLocalMcpCommand(serverConfig);
|
|
31777
|
+
if (!command) {
|
|
31778
|
+
warnAndSkipMcpServer({
|
|
31779
|
+
toolName: "Continue",
|
|
31780
|
+
serverName,
|
|
31781
|
+
reason: "a stdio transport without a command",
|
|
31782
|
+
logger
|
|
31783
|
+
});
|
|
31784
|
+
return;
|
|
31785
|
+
}
|
|
31786
|
+
const converted = {
|
|
31787
|
+
type: "stdio",
|
|
31788
|
+
command
|
|
31789
|
+
};
|
|
31790
|
+
if (args.length > 0) converted.args = args;
|
|
31791
|
+
if (isRecord$1(serverConfig.env)) converted.env = omitPrototypePollutionKeys(serverConfig.env);
|
|
31792
|
+
if (typeof serverConfig.envFile === "string") logger?.warn(`Continue ignores "envFile" for MCP servers, so the envFile of server ${quoteValueForWarning(serverName)} was not written; put the variables in "env" instead.`);
|
|
31793
|
+
return converted;
|
|
31794
|
+
}
|
|
31795
|
+
/**
|
|
31796
|
+
* Convert Continue's server map back to the canonical shape. The documented
|
|
31797
|
+
* spelling (`type` + `command`/`url`) is already canonical, so entries pass
|
|
31798
|
+
* through with only prototype-pollution keys dropped.
|
|
31799
|
+
*/
|
|
31800
|
+
function convertFromContinueFormat(mcpServers) {
|
|
31801
|
+
if (!isMcpServers(mcpServers)) return {};
|
|
31802
|
+
const result = {};
|
|
31803
|
+
for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
|
|
31804
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(serverName) || !isRecord$1(serverConfig)) continue;
|
|
31805
|
+
result[serverName] = omitPrototypePollutionKeys(serverConfig);
|
|
31806
|
+
}
|
|
31807
|
+
return result;
|
|
31808
|
+
}
|
|
31809
|
+
/**
|
|
31810
|
+
* Continue MCP configuration.
|
|
31811
|
+
*
|
|
31812
|
+
* Continue loads every `*.json` file under `.continue/mcpServers/` (project)
|
|
31813
|
+
* and `~/.continue/mcpServers/` (global); rulesync owns one of them,
|
|
31814
|
+
* `mcp.json`, in the `{ "mcpServers": { ... } }` shape the docs show. Other
|
|
31815
|
+
* files in the directory, and top-level sibling keys of `mcp.json`, are left
|
|
31816
|
+
* alone. The global file is not deleted by `--delete` (it lives outside the
|
|
31817
|
+
* project), while the project file is rulesync's own and is.
|
|
31818
|
+
*
|
|
31819
|
+
* @see https://docs.continue.dev/customize/deep-dives/mcp
|
|
31820
|
+
*/
|
|
31821
|
+
var ContinueMcp = class ContinueMcp extends ToolMcp {
|
|
31822
|
+
json;
|
|
31823
|
+
constructor(params) {
|
|
31824
|
+
super(params);
|
|
31825
|
+
this.json = this.fileContent === void 0 ? {} : parseContinueMcpConfig({
|
|
31826
|
+
fileContent: this.fileContent,
|
|
31827
|
+
relativePath: (0, node_path.join)(this.relativeDirPath, this.relativeFilePath)
|
|
31828
|
+
});
|
|
31829
|
+
}
|
|
31830
|
+
getJson() {
|
|
31831
|
+
return this.json;
|
|
31832
|
+
}
|
|
31833
|
+
isDeletable() {
|
|
31834
|
+
return !this.global;
|
|
31835
|
+
}
|
|
31836
|
+
static getSettablePaths(_options = {}) {
|
|
31837
|
+
return {
|
|
31838
|
+
relativeDirPath: CONTINUE_MCP_DIR_PATH,
|
|
31839
|
+
relativeFilePath: CONTINUE_MCP_FILE_NAME
|
|
31840
|
+
};
|
|
31841
|
+
}
|
|
31842
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
31843
|
+
const paths = this.getSettablePaths({ global });
|
|
31844
|
+
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"mcpServers\":{}}";
|
|
31845
|
+
return new ContinueMcp({
|
|
31846
|
+
outputRoot,
|
|
31847
|
+
relativeDirPath: paths.relativeDirPath,
|
|
31848
|
+
relativeFilePath: paths.relativeFilePath,
|
|
31849
|
+
fileContent,
|
|
31850
|
+
validate,
|
|
31851
|
+
global
|
|
31852
|
+
});
|
|
31853
|
+
}
|
|
31854
|
+
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger }) {
|
|
31855
|
+
const paths = this.getSettablePaths({ global });
|
|
31856
|
+
const json = parseContinueMcpConfig({
|
|
31857
|
+
fileContent: await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"mcpServers\":{}}",
|
|
31858
|
+
relativePath: (0, node_path.join)(paths.relativeDirPath, paths.relativeFilePath)
|
|
31859
|
+
});
|
|
31860
|
+
const mcpServers = convertToContinueFormat(rulesyncMcp.getMcpServers(), logger);
|
|
31861
|
+
const continueConfig = {
|
|
31862
|
+
...json,
|
|
31863
|
+
mcpServers
|
|
31864
|
+
};
|
|
31865
|
+
return new ContinueMcp({
|
|
31866
|
+
outputRoot,
|
|
31867
|
+
relativeDirPath: paths.relativeDirPath,
|
|
31868
|
+
relativeFilePath: paths.relativeFilePath,
|
|
31869
|
+
fileContent: JSON.stringify(continueConfig, null, 2),
|
|
31870
|
+
validate,
|
|
31871
|
+
global
|
|
31872
|
+
});
|
|
31873
|
+
}
|
|
31874
|
+
toRulesyncMcp() {
|
|
31875
|
+
const mcpServers = convertFromContinueFormat(this.json.mcpServers);
|
|
31876
|
+
return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({ mcpServers }, null, 2) });
|
|
31877
|
+
}
|
|
31878
|
+
validate() {
|
|
31879
|
+
return {
|
|
31880
|
+
success: true,
|
|
31881
|
+
error: null
|
|
31882
|
+
};
|
|
31883
|
+
}
|
|
31884
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
31885
|
+
return new ContinueMcp({
|
|
31886
|
+
outputRoot,
|
|
31887
|
+
relativeDirPath,
|
|
31888
|
+
relativeFilePath,
|
|
31889
|
+
fileContent: "{}",
|
|
31890
|
+
validate: false,
|
|
31891
|
+
global
|
|
31892
|
+
});
|
|
31893
|
+
}
|
|
31894
|
+
};
|
|
31895
|
+
//#endregion
|
|
31188
31896
|
//#region src/features/mcp/copilot-mcp.ts
|
|
31189
31897
|
function convertFromCopilotFormat(copilotConfig) {
|
|
31190
31898
|
return copilotConfig.servers ?? {};
|
|
@@ -36917,6 +37625,15 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
|
|
|
36917
37625
|
supportsDisabledTools: true
|
|
36918
37626
|
}
|
|
36919
37627
|
}],
|
|
37628
|
+
["continue", {
|
|
37629
|
+
class: ContinueMcp,
|
|
37630
|
+
meta: {
|
|
37631
|
+
supportsProject: true,
|
|
37632
|
+
supportsGlobal: true,
|
|
37633
|
+
supportsEnabledTools: false,
|
|
37634
|
+
supportsDisabledTools: false
|
|
37635
|
+
}
|
|
37636
|
+
}],
|
|
36920
37637
|
["copilot", {
|
|
36921
37638
|
class: CopilotMcp,
|
|
36922
37639
|
meta: {
|
|
@@ -38375,7 +39092,7 @@ const ANTIGRAVITY_CLI_TO_CANONICAL_TOOL_NAMES = {
|
|
|
38375
39092
|
function toAntigravityCliToolName(canonical) {
|
|
38376
39093
|
return CANONICAL_TO_ANTIGRAVITY_CLI_TOOL_NAMES[canonical] ?? canonical;
|
|
38377
39094
|
}
|
|
38378
|
-
function toCanonicalToolName$
|
|
39095
|
+
function toCanonicalToolName$7(cliName) {
|
|
38379
39096
|
return ANTIGRAVITY_CLI_TO_CANONICAL_TOOL_NAMES[cliName] ?? cliName;
|
|
38380
39097
|
}
|
|
38381
39098
|
/**
|
|
@@ -38591,7 +39308,7 @@ function convertAntigravityCliToRulesyncPermissions(params) {
|
|
|
38591
39308
|
const processEntries = (entries, action) => {
|
|
38592
39309
|
for (const entry of entries) {
|
|
38593
39310
|
const { toolName, pattern } = parsePermissionEntry$1(entry);
|
|
38594
|
-
const canonical = toCanonicalToolName$
|
|
39311
|
+
const canonical = toCanonicalToolName$7(toolName);
|
|
38595
39312
|
if (!permission[canonical]) permission[canonical] = {};
|
|
38596
39313
|
permission[canonical][pattern] = action;
|
|
38597
39314
|
}
|
|
@@ -38851,7 +39568,7 @@ const AUGMENT_TO_CANONICAL_TOOL_NAMES = Object.fromEntries(Object.entries(CANONI
|
|
|
38851
39568
|
function toAugmentToolName(canonical) {
|
|
38852
39569
|
return CANONICAL_TO_AUGMENT_TOOL_NAMES[canonical] ?? canonical;
|
|
38853
39570
|
}
|
|
38854
|
-
function toCanonicalToolName$
|
|
39571
|
+
function toCanonicalToolName$6(augmentName) {
|
|
38855
39572
|
return AUGMENT_TO_CANONICAL_TOOL_NAMES[augmentName] ?? augmentName;
|
|
38856
39573
|
}
|
|
38857
39574
|
function actionToAugmentType(action) {
|
|
@@ -39298,7 +40015,7 @@ function convertAugmentToRulesyncPermissions({ entries, logger }) {
|
|
|
39298
40015
|
if (isSpecialEntry(entry)) continue;
|
|
39299
40016
|
const type = entry.permission.type;
|
|
39300
40017
|
if (!isBasicAugmentType(type)) continue;
|
|
39301
|
-
const canonical = toCanonicalToolName$
|
|
40018
|
+
const canonical = toCanonicalToolName$6(entry.toolName);
|
|
39302
40019
|
if (forbiddenMapKeys.has(canonical)) {
|
|
39303
40020
|
logger?.warn(`AugmentCode permissions: skipping entry for tool '${entry.toolName}' because it maps to the reserved object key '${canonical}', which cannot be used as a permission key.`);
|
|
39304
40021
|
continue;
|
|
@@ -39533,7 +40250,7 @@ const CLAUDE_TO_CANONICAL_TOOL_NAMES = Object.fromEntries(Object.entries(CANONIC
|
|
|
39533
40250
|
function toClaudeToolName(canonical) {
|
|
39534
40251
|
return CANONICAL_TO_CLAUDE_TOOL_NAMES[canonical] ?? canonical;
|
|
39535
40252
|
}
|
|
39536
|
-
function toCanonicalToolName$
|
|
40253
|
+
function toCanonicalToolName$5(claudeName) {
|
|
39537
40254
|
return CLAUDE_TO_CANONICAL_TOOL_NAMES[claudeName] ?? claudeName;
|
|
39538
40255
|
}
|
|
39539
40256
|
/**
|
|
@@ -40428,7 +41145,7 @@ function convertClaudeToRulesyncPermissions(params) {
|
|
|
40428
41145
|
const processEntries = (entries, action) => {
|
|
40429
41146
|
for (const entry of entries) {
|
|
40430
41147
|
const { toolName, pattern } = parseClaudePermissionEntry(entry);
|
|
40431
|
-
const canonical = toCanonicalToolName$
|
|
41148
|
+
const canonical = toCanonicalToolName$5(toolName);
|
|
40432
41149
|
if (!permission[canonical]) permission[canonical] = {};
|
|
40433
41150
|
permission[canonical][pattern] = action;
|
|
40434
41151
|
}
|
|
@@ -41251,6 +41968,264 @@ function mapBashActionToDecision(action) {
|
|
|
41251
41968
|
return "forbidden";
|
|
41252
41969
|
}
|
|
41253
41970
|
//#endregion
|
|
41971
|
+
//#region src/features/permissions/continue-permissions.ts
|
|
41972
|
+
const CONTINUE_GLOBAL_ONLY_MESSAGE = "Continue permissions are global-only; use --global to sync ~/.continue/permissions.yaml";
|
|
41973
|
+
const CATCH_ALL_PATTERN$4 = "*";
|
|
41974
|
+
const CANONICAL_TO_CONTINUE_TOOL_NAMES = {
|
|
41975
|
+
bash: "Bash",
|
|
41976
|
+
read: "Read",
|
|
41977
|
+
edit: "Edit",
|
|
41978
|
+
write: "Write",
|
|
41979
|
+
webfetch: "Fetch"
|
|
41980
|
+
};
|
|
41981
|
+
const CONTINUE_TO_CANONICAL_TOOL_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_CONTINUE_TOOL_NAMES).map(([k, v]) => [v, k]));
|
|
41982
|
+
const ACTION_TO_CONTINUE_LIST = {
|
|
41983
|
+
allow: "allow",
|
|
41984
|
+
ask: "ask",
|
|
41985
|
+
deny: "exclude"
|
|
41986
|
+
};
|
|
41987
|
+
const CONTINUE_LIST_TO_ACTION = {
|
|
41988
|
+
allow: "allow",
|
|
41989
|
+
ask: "ask",
|
|
41990
|
+
exclude: "deny"
|
|
41991
|
+
};
|
|
41992
|
+
const CONTINUE_PERMISSION_LIST_KEYS = [
|
|
41993
|
+
"exclude",
|
|
41994
|
+
"ask",
|
|
41995
|
+
"allow"
|
|
41996
|
+
];
|
|
41997
|
+
const CONTINUE_PATTERN_RE = /^([^(]+)(?:\(([^)]*)\))?$/;
|
|
41998
|
+
function toContinueToolName(canonical) {
|
|
41999
|
+
return Object.hasOwn(CANONICAL_TO_CONTINUE_TOOL_NAMES, canonical) ? CANONICAL_TO_CONTINUE_TOOL_NAMES[canonical] ?? canonical : canonical;
|
|
42000
|
+
}
|
|
42001
|
+
function toCanonicalToolName$4(continueName) {
|
|
42002
|
+
return Object.hasOwn(CONTINUE_TO_CANONICAL_TOOL_NAMES, continueName) ? CONTINUE_TO_CANONICAL_TOOL_NAMES[continueName] ?? continueName : continueName;
|
|
42003
|
+
}
|
|
42004
|
+
/**
|
|
42005
|
+
* Build a permissions.yaml entry: the bare tool name for the catch-all, else
|
|
42006
|
+
* `Tool(pattern)`.
|
|
42007
|
+
*/
|
|
42008
|
+
function buildContinuePermissionEntry(toolName, pattern) {
|
|
42009
|
+
return pattern === CATCH_ALL_PATTERN$4 ? toolName : `${toolName}(${pattern})`;
|
|
42010
|
+
}
|
|
42011
|
+
/**
|
|
42012
|
+
* Split a permissions.yaml entry into its tool name and argument pattern, or
|
|
42013
|
+
* `undefined` for an entry outside the loader's grammar.
|
|
42014
|
+
*/
|
|
42015
|
+
function parseContinuePermissionEntry(entry) {
|
|
42016
|
+
const match = CONTINUE_PATTERN_RE.exec(entry);
|
|
42017
|
+
if (!match) return;
|
|
42018
|
+
const toolName = match[1]?.trim() ?? "";
|
|
42019
|
+
const pattern = match[2]?.trim() ?? "";
|
|
42020
|
+
if (toolName === "") return;
|
|
42021
|
+
return {
|
|
42022
|
+
toolName,
|
|
42023
|
+
pattern: pattern === "" ? CATCH_ALL_PATTERN$4 : pattern
|
|
42024
|
+
};
|
|
42025
|
+
}
|
|
42026
|
+
/**
|
|
42027
|
+
* Permissions adapter for the Continue CLI (`cn`).
|
|
42028
|
+
*
|
|
42029
|
+
* Continue reads tool policies from `~/.continue/permissions.yaml`, three
|
|
42030
|
+
* string lists (`allow`, `ask`, `exclude`) of `Tool` / `Tool(pattern)` entries
|
|
42031
|
+
* evaluated exclude-first, then ask, then allow, first match wins. `exclude`
|
|
42032
|
+
* disables the tool. A `Tool(pattern)` entry glob-matches (`*` / `?`) the
|
|
42033
|
+
* tool's primary argument — the shell command for `Bash`, the path for
|
|
42034
|
+
* `Read` / `Edit` / `Write`, the URL for `Fetch`. The file is **global only**:
|
|
42035
|
+
* no project-scoped permissions file is read.
|
|
42036
|
+
*
|
|
42037
|
+
* Mapping (rulesync canonical -> Continue):
|
|
42038
|
+
* - Action: `allow` -> `allow`, `ask` -> `ask`, `deny` -> `exclude`.
|
|
42039
|
+
* - Tool name: `bash` -> `Bash`, `read` -> `Read`, `edit` -> `Edit`,
|
|
42040
|
+
* `write` -> `Write`, `webfetch` -> `Fetch`; any other category passes
|
|
42041
|
+
* through verbatim, and the all-tools `*` category becomes the bare `*`
|
|
42042
|
+
* entry that matches every tool.
|
|
42043
|
+
* - Entry: the catch-all `*` pattern is the bare tool name; every other
|
|
42044
|
+
* pattern is written as `Tool(pattern)`. A pattern holding a parenthesis
|
|
42045
|
+
* cannot be written (the loader would refuse the whole file) and is
|
|
42046
|
+
* reported and skipped.
|
|
42047
|
+
*
|
|
42048
|
+
* The CLI appends the user's own "always allow" decisions to the same file,
|
|
42049
|
+
* so entries naming a tool the canonical config does not manage are preserved
|
|
42050
|
+
* verbatim, the managed tools' entries are rebuilt, and the file is never
|
|
42051
|
+
* deleted.
|
|
42052
|
+
*
|
|
42053
|
+
* @see https://docs.continue.dev/cli/tool-permissions
|
|
42054
|
+
* @see https://github.com/continuedev/continue/blob/main/extensions/cli/src/permissions/permissionsYamlLoader.ts
|
|
42055
|
+
* @see https://github.com/continuedev/continue/blob/main/extensions/cli/src/permissions/permissionChecker.ts
|
|
42056
|
+
*/
|
|
42057
|
+
var ContinuePermissions = class ContinuePermissions extends ToolPermissions {
|
|
42058
|
+
constructor(params) {
|
|
42059
|
+
super({
|
|
42060
|
+
...params,
|
|
42061
|
+
fileContent: params.fileContent ?? ""
|
|
42062
|
+
});
|
|
42063
|
+
}
|
|
42064
|
+
isDeletable() {
|
|
42065
|
+
return false;
|
|
42066
|
+
}
|
|
42067
|
+
/**
|
|
42068
|
+
* `permissions.yaml` is Continue's file: rulesync merges into it when it
|
|
42069
|
+
* exists but does not create one that holds three empty lists — an absent
|
|
42070
|
+
* file and empty lists both mean "no policy, ask for everything".
|
|
42071
|
+
*/
|
|
42072
|
+
shouldSkipCreationWhenPayloadEmpty() {
|
|
42073
|
+
return true;
|
|
42074
|
+
}
|
|
42075
|
+
static getSettablePaths(_options) {
|
|
42076
|
+
return {
|
|
42077
|
+
relativeDirPath: CONTINUE_DIR,
|
|
42078
|
+
relativeFilePath: CONTINUE_PERMISSIONS_FILE_NAME
|
|
42079
|
+
};
|
|
42080
|
+
}
|
|
42081
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
42082
|
+
if (!global) throw new Error(CONTINUE_GLOBAL_ONLY_MESSAGE);
|
|
42083
|
+
const paths = ContinuePermissions.getSettablePaths({ global });
|
|
42084
|
+
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
|
|
42085
|
+
return new ContinuePermissions({
|
|
42086
|
+
outputRoot,
|
|
42087
|
+
relativeDirPath: paths.relativeDirPath,
|
|
42088
|
+
relativeFilePath: paths.relativeFilePath,
|
|
42089
|
+
fileContent,
|
|
42090
|
+
validate,
|
|
42091
|
+
global: true
|
|
42092
|
+
});
|
|
42093
|
+
}
|
|
42094
|
+
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, logger, global = false }) {
|
|
42095
|
+
if (!global) throw new Error(CONTINUE_GLOBAL_ONLY_MESSAGE);
|
|
42096
|
+
const paths = ContinuePermissions.getSettablePaths({ global });
|
|
42097
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
42098
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
42099
|
+
let parsed;
|
|
42100
|
+
try {
|
|
42101
|
+
parsed = existingContent.trim() === "" ? {} : loadYaml(existingContent);
|
|
42102
|
+
} catch (error) {
|
|
42103
|
+
throw new Error(`Failed to parse existing Continue permissions.yaml at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
42104
|
+
}
|
|
42105
|
+
const existing = parseContinuePermissionLists(isRecord$1(parsed) ? parsed : {});
|
|
42106
|
+
const config = rulesyncPermissions.getJson();
|
|
42107
|
+
const generated = convertRulesyncToContinuePermissions({
|
|
42108
|
+
config,
|
|
42109
|
+
logger
|
|
42110
|
+
});
|
|
42111
|
+
const managedToolNames = managedContinueToolNames(config);
|
|
42112
|
+
const lists = {};
|
|
42113
|
+
for (const key of CONTINUE_PERMISSION_LIST_KEYS) {
|
|
42114
|
+
const preserved = existing[key].filter((entry) => {
|
|
42115
|
+
const parsedEntry = parseContinuePermissionEntry(entry);
|
|
42116
|
+
return parsedEntry === void 0 || !managedToolNames.has(parsedEntry.toolName);
|
|
42117
|
+
});
|
|
42118
|
+
const merged = [.../* @__PURE__ */ new Set([...preserved, ...generated[key]])];
|
|
42119
|
+
if (merged.length > 0) lists[key] = merged;
|
|
42120
|
+
}
|
|
42121
|
+
return new ContinuePermissions({
|
|
42122
|
+
outputRoot,
|
|
42123
|
+
relativeDirPath: paths.relativeDirPath,
|
|
42124
|
+
relativeFilePath: paths.relativeFilePath,
|
|
42125
|
+
fileContent: Object.keys(lists).length === 0 ? "" : (0, js_yaml.dump)(lists),
|
|
42126
|
+
validate: true,
|
|
42127
|
+
global: true
|
|
42128
|
+
});
|
|
42129
|
+
}
|
|
42130
|
+
toRulesyncPermissions() {
|
|
42131
|
+
let parsed;
|
|
42132
|
+
try {
|
|
42133
|
+
const content = this.getFileContent();
|
|
42134
|
+
parsed = content.trim() === "" ? {} : loadYaml(content);
|
|
42135
|
+
} catch (error) {
|
|
42136
|
+
throw new Error(`Failed to parse Continue permissions content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
42137
|
+
}
|
|
42138
|
+
const rulesyncConfig = convertContinuePermissionsToRulesync(parseContinuePermissionLists(isRecord$1(parsed) ? parsed : {}));
|
|
42139
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(rulesyncConfig, null, 2) });
|
|
42140
|
+
}
|
|
42141
|
+
validate() {
|
|
42142
|
+
return {
|
|
42143
|
+
success: true,
|
|
42144
|
+
error: null
|
|
42145
|
+
};
|
|
42146
|
+
}
|
|
42147
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
42148
|
+
return new ContinuePermissions({
|
|
42149
|
+
outputRoot,
|
|
42150
|
+
relativeDirPath,
|
|
42151
|
+
relativeFilePath,
|
|
42152
|
+
fileContent: "",
|
|
42153
|
+
validate: false,
|
|
42154
|
+
global: true
|
|
42155
|
+
});
|
|
42156
|
+
}
|
|
42157
|
+
};
|
|
42158
|
+
/**
|
|
42159
|
+
* The Continue tool names the canonical config manages, i.e. the names its
|
|
42160
|
+
* categories map to. Entries naming any other tool are the user's (or the
|
|
42161
|
+
* CLI's) and survive a generate.
|
|
42162
|
+
*/
|
|
42163
|
+
function managedContinueToolNames(config) {
|
|
42164
|
+
return new Set(Object.keys(config.permission).map((category) => toContinueToolName(category)));
|
|
42165
|
+
}
|
|
42166
|
+
/**
|
|
42167
|
+
* Read the three lists out of a parsed permissions.yaml, tolerating a missing
|
|
42168
|
+
* or malformed list (the CLI itself discards the file in that case).
|
|
42169
|
+
*/
|
|
42170
|
+
function parseContinuePermissionLists(parsed) {
|
|
42171
|
+
return {
|
|
42172
|
+
exclude: isStringArray$2(parsed.exclude) ? parsed.exclude : [],
|
|
42173
|
+
ask: isStringArray$2(parsed.ask) ? parsed.ask : [],
|
|
42174
|
+
allow: isStringArray$2(parsed.allow) ? parsed.allow : []
|
|
42175
|
+
};
|
|
42176
|
+
}
|
|
42177
|
+
/**
|
|
42178
|
+
* Convert a rulesync permissions config into the three permissions.yaml lists.
|
|
42179
|
+
*/
|
|
42180
|
+
function convertRulesyncToContinuePermissions({ config, logger }) {
|
|
42181
|
+
const lists = {
|
|
42182
|
+
exclude: [],
|
|
42183
|
+
ask: [],
|
|
42184
|
+
allow: []
|
|
42185
|
+
};
|
|
42186
|
+
const actionByEntry = /* @__PURE__ */ new Map();
|
|
42187
|
+
for (const [category, rules] of Object.entries(honorAllToolsOnBash(config.permission))) {
|
|
42188
|
+
if (isPrototypePollutionKey(category)) continue;
|
|
42189
|
+
const toolName = category === "*" ? "*" : toContinueToolName(category);
|
|
42190
|
+
for (const [pattern, action] of Object.entries(rules)) {
|
|
42191
|
+
if (isPrototypePollutionKey(pattern)) continue;
|
|
42192
|
+
if (pattern.includes("(") || pattern.includes(")")) {
|
|
42193
|
+
logger?.warn(`Continue permissions.yaml cannot hold a parenthesis inside a pattern, so the "${action}" rule for "${category}" (pattern ${quoteValueForWarning(pattern)}) was skipped.`);
|
|
42194
|
+
continue;
|
|
42195
|
+
}
|
|
42196
|
+
if (toolName === "*" && pattern !== CATCH_ALL_PATTERN$4) {
|
|
42197
|
+
logger?.warn(`Continue permissions.yaml cannot scope a pattern to every tool, so the "${action}" rule for "*" (pattern ${quoteValueForWarning(pattern)}) was skipped.`);
|
|
42198
|
+
continue;
|
|
42199
|
+
}
|
|
42200
|
+
const entry = buildContinuePermissionEntry(toolName, pattern);
|
|
42201
|
+
const previous = actionByEntry.get(entry);
|
|
42202
|
+
if (previous !== void 0 && previous !== action) logger?.warn(`Continue permissions: rules from different categories both resolve to ${quoteValueForWarning(entry)} with conflicting actions (${previous} and ${action}). Both are written; Continue applies exclude first, then ask, then allow.`);
|
|
42203
|
+
actionByEntry.set(entry, action);
|
|
42204
|
+
lists[ACTION_TO_CONTINUE_LIST[action]].push(entry);
|
|
42205
|
+
}
|
|
42206
|
+
}
|
|
42207
|
+
for (const key of CONTINUE_PERMISSION_LIST_KEYS) lists[key] = [...new Set(lists[key])];
|
|
42208
|
+
return lists;
|
|
42209
|
+
}
|
|
42210
|
+
/**
|
|
42211
|
+
* Convert the three permissions.yaml lists back into a rulesync config.
|
|
42212
|
+
*/
|
|
42213
|
+
function convertContinuePermissionsToRulesync(lists) {
|
|
42214
|
+
const permission = {};
|
|
42215
|
+
for (const key of CONTINUE_PERMISSION_LIST_KEYS) {
|
|
42216
|
+
const action = CONTINUE_LIST_TO_ACTION[key];
|
|
42217
|
+
for (const entry of lists[key]) {
|
|
42218
|
+
const parsedEntry = parseContinuePermissionEntry(entry);
|
|
42219
|
+
if (parsedEntry === void 0) continue;
|
|
42220
|
+
if (isPrototypePollutionKey(parsedEntry.toolName) || isPrototypePollutionKey(parsedEntry.pattern)) continue;
|
|
42221
|
+
const category = toCanonicalToolName$4(parsedEntry.toolName);
|
|
42222
|
+
permission[category] ??= {};
|
|
42223
|
+
permission[category][parsedEntry.pattern] ??= action;
|
|
42224
|
+
}
|
|
42225
|
+
}
|
|
42226
|
+
return { permission };
|
|
42227
|
+
}
|
|
42228
|
+
//#endregion
|
|
41254
42229
|
//#region src/features/permissions/copilot-permissions.ts
|
|
41255
42230
|
/**
|
|
41256
42231
|
* The flat, dotted VS Code setting keys this adapter manages, one per canonical
|
|
@@ -51299,6 +52274,14 @@ const toolPermissionsFactories = /* @__PURE__ */ new Map([
|
|
|
51299
52274
|
supportsImport: true
|
|
51300
52275
|
}
|
|
51301
52276
|
}],
|
|
52277
|
+
["continue", {
|
|
52278
|
+
class: ContinuePermissions,
|
|
52279
|
+
meta: {
|
|
52280
|
+
supportsProject: false,
|
|
52281
|
+
supportsGlobal: true,
|
|
52282
|
+
supportsImport: true
|
|
52283
|
+
}
|
|
52284
|
+
}],
|
|
51302
52285
|
["copilot", {
|
|
51303
52286
|
class: CopilotPermissions,
|
|
51304
52287
|
meta: {
|
|
@@ -54049,6 +55032,154 @@ var CodexCliSkill = class CodexCliSkill extends ToolSkill {
|
|
|
54049
55032
|
}
|
|
54050
55033
|
};
|
|
54051
55034
|
//#endregion
|
|
55035
|
+
//#region src/features/skills/continue-skill.ts
|
|
55036
|
+
const ContinueSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
55037
|
+
name: zod_mini.z.string(),
|
|
55038
|
+
description: zod_mini.z.string()
|
|
55039
|
+
});
|
|
55040
|
+
/**
|
|
55041
|
+
* Represents a Continue skill directory.
|
|
55042
|
+
*
|
|
55043
|
+
* Continue discovers Anthropic-style `<name>/SKILL.md` directories under
|
|
55044
|
+
* `<project>/.continue/skills/` (project scope) and `~/.continue/skills/`
|
|
55045
|
+
* (user scope); it also reads `.claude/skills/`, which the claudecode target
|
|
55046
|
+
* covers. The frontmatter requires `name` (matching the directory name) and
|
|
55047
|
+
* `description`; supporting files next to `SKILL.md` are carried along.
|
|
55048
|
+
*
|
|
55049
|
+
* @see https://github.com/continuedev/continue/blob/main/extensions/cli/src/util/loadMarkdownSkills.ts
|
|
55050
|
+
*/
|
|
55051
|
+
var ContinueSkill = class ContinueSkill extends ToolSkill {
|
|
55052
|
+
constructor({ outputRoot = process.cwd(), relativeDirPath = CONTINUE_SKILLS_DIR_PATH, dirName, frontmatter, body, otherFiles = [], validate = true, global = false }) {
|
|
55053
|
+
super({
|
|
55054
|
+
outputRoot,
|
|
55055
|
+
relativeDirPath,
|
|
55056
|
+
dirName,
|
|
55057
|
+
mainFile: {
|
|
55058
|
+
name: SKILL_FILE_NAME,
|
|
55059
|
+
body,
|
|
55060
|
+
frontmatter: { ...frontmatter }
|
|
55061
|
+
},
|
|
55062
|
+
otherFiles,
|
|
55063
|
+
global
|
|
55064
|
+
});
|
|
55065
|
+
if (validate) {
|
|
55066
|
+
const result = this.validate();
|
|
55067
|
+
if (!result.success) throw result.error;
|
|
55068
|
+
}
|
|
55069
|
+
}
|
|
55070
|
+
static getSettablePaths(_options = {}) {
|
|
55071
|
+
return { relativeDirPath: CONTINUE_SKILLS_DIR_PATH };
|
|
55072
|
+
}
|
|
55073
|
+
getFrontmatter() {
|
|
55074
|
+
return ContinueSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
|
|
55075
|
+
}
|
|
55076
|
+
getBody() {
|
|
55077
|
+
return this.mainFile?.body ?? "";
|
|
55078
|
+
}
|
|
55079
|
+
validate() {
|
|
55080
|
+
if (!this.mainFile) return {
|
|
55081
|
+
success: false,
|
|
55082
|
+
error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
|
|
55083
|
+
};
|
|
55084
|
+
const result = ContinueSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
|
|
55085
|
+
if (!result.success) return {
|
|
55086
|
+
success: false,
|
|
55087
|
+
error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${this.getDirPath()}: ${formatError(result.error)}`)
|
|
55088
|
+
};
|
|
55089
|
+
if (result.data.name !== this.getDirName()) return {
|
|
55090
|
+
success: false,
|
|
55091
|
+
error: /* @__PURE__ */ new Error(`${this.getDirPath()}: frontmatter name (${result.data.name}) must match directory name (${this.getDirName()})`)
|
|
55092
|
+
};
|
|
55093
|
+
return {
|
|
55094
|
+
success: true,
|
|
55095
|
+
error: null
|
|
55096
|
+
};
|
|
55097
|
+
}
|
|
55098
|
+
toRulesyncSkill() {
|
|
55099
|
+
const { name, description, ...continueSection } = this.getFrontmatter();
|
|
55100
|
+
const rulesyncFrontmatter = {
|
|
55101
|
+
name,
|
|
55102
|
+
description,
|
|
55103
|
+
targets: ["*"],
|
|
55104
|
+
...Object.keys(continueSection).length > 0 && { continue: continueSection }
|
|
55105
|
+
};
|
|
55106
|
+
return new RulesyncSkill({
|
|
55107
|
+
outputRoot: this.outputRoot,
|
|
55108
|
+
relativeDirPath: RULESYNC_SKILLS_RELATIVE_DIR_PATH,
|
|
55109
|
+
dirName: this.getDirName(),
|
|
55110
|
+
frontmatter: rulesyncFrontmatter,
|
|
55111
|
+
body: this.getBody(),
|
|
55112
|
+
otherFiles: this.getOtherFiles(),
|
|
55113
|
+
validate: true,
|
|
55114
|
+
global: this.global
|
|
55115
|
+
});
|
|
55116
|
+
}
|
|
55117
|
+
static fromRulesyncSkill({ outputRoot = process.cwd(), rulesyncSkill, validate = true, global = false }) {
|
|
55118
|
+
const settablePaths = ContinueSkill.getSettablePaths({ global });
|
|
55119
|
+
const rulesyncFrontmatter = rulesyncSkill.getFrontmatter();
|
|
55120
|
+
const { name: _sectionName, description: _sectionDescription, ...continueSection } = rulesyncFrontmatter.continue ?? {};
|
|
55121
|
+
const continueFrontmatter = {
|
|
55122
|
+
...continueSection,
|
|
55123
|
+
name: rulesyncFrontmatter.name,
|
|
55124
|
+
description: rulesyncFrontmatter.description
|
|
55125
|
+
};
|
|
55126
|
+
return new ContinueSkill({
|
|
55127
|
+
outputRoot,
|
|
55128
|
+
relativeDirPath: settablePaths.relativeDirPath,
|
|
55129
|
+
dirName: continueFrontmatter.name,
|
|
55130
|
+
frontmatter: continueFrontmatter,
|
|
55131
|
+
body: rulesyncSkill.getBody(),
|
|
55132
|
+
otherFiles: rulesyncSkill.getOtherFiles(),
|
|
55133
|
+
validate,
|
|
55134
|
+
global
|
|
55135
|
+
});
|
|
55136
|
+
}
|
|
55137
|
+
static isTargetedByRulesyncSkill(rulesyncSkill) {
|
|
55138
|
+
const targets = rulesyncSkill.getFrontmatter().targets;
|
|
55139
|
+
return targets.includes("*") || targets.includes("continue");
|
|
55140
|
+
}
|
|
55141
|
+
static async fromDir(params) {
|
|
55142
|
+
const loaded = await this.loadSkillDirContent({
|
|
55143
|
+
...params,
|
|
55144
|
+
getSettablePaths: ContinueSkill.getSettablePaths
|
|
55145
|
+
});
|
|
55146
|
+
const result = ContinueSkillFrontmatterSchema.safeParse(loaded.frontmatter);
|
|
55147
|
+
if (!result.success) {
|
|
55148
|
+
const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
|
|
55149
|
+
throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
|
|
55150
|
+
}
|
|
55151
|
+
if (result.data.name !== loaded.dirName) {
|
|
55152
|
+
const skillFilePath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName, SKILL_FILE_NAME);
|
|
55153
|
+
throw new Error(`Frontmatter name (${result.data.name}) must match directory name (${loaded.dirName}) in ${skillFilePath}`);
|
|
55154
|
+
}
|
|
55155
|
+
return new ContinueSkill({
|
|
55156
|
+
outputRoot: loaded.outputRoot,
|
|
55157
|
+
relativeDirPath: loaded.relativeDirPath,
|
|
55158
|
+
dirName: loaded.dirName,
|
|
55159
|
+
frontmatter: result.data,
|
|
55160
|
+
body: loaded.body,
|
|
55161
|
+
otherFiles: loaded.otherFiles,
|
|
55162
|
+
validate: true,
|
|
55163
|
+
global: loaded.global
|
|
55164
|
+
});
|
|
55165
|
+
}
|
|
55166
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, dirName, global = false }) {
|
|
55167
|
+
return new ContinueSkill({
|
|
55168
|
+
outputRoot,
|
|
55169
|
+
relativeDirPath,
|
|
55170
|
+
dirName,
|
|
55171
|
+
frontmatter: {
|
|
55172
|
+
name: "",
|
|
55173
|
+
description: ""
|
|
55174
|
+
},
|
|
55175
|
+
body: "",
|
|
55176
|
+
otherFiles: [],
|
|
55177
|
+
validate: false,
|
|
55178
|
+
global
|
|
55179
|
+
});
|
|
55180
|
+
}
|
|
55181
|
+
};
|
|
55182
|
+
//#endregion
|
|
54052
55183
|
//#region src/features/skills/copilot-skill.ts
|
|
54053
55184
|
const CopilotSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
54054
55185
|
name: zod_mini.z.string(),
|
|
@@ -58913,6 +60044,14 @@ const toolSkillFactories = /* @__PURE__ */ new Map([
|
|
|
58913
60044
|
supportsGlobal: true
|
|
58914
60045
|
}
|
|
58915
60046
|
}],
|
|
60047
|
+
["continue", {
|
|
60048
|
+
class: ContinueSkill,
|
|
60049
|
+
meta: {
|
|
60050
|
+
supportsProject: true,
|
|
60051
|
+
supportsSimulated: false,
|
|
60052
|
+
supportsGlobal: true
|
|
60053
|
+
}
|
|
60054
|
+
}],
|
|
58916
60055
|
["copilot", {
|
|
58917
60056
|
class: CopilotSkill,
|
|
58918
60057
|
meta: {
|
|
@@ -67481,6 +68620,263 @@ var CodexcliRule = class CodexcliRule extends ToolRule {
|
|
|
67481
68620
|
}
|
|
67482
68621
|
};
|
|
67483
68622
|
//#endregion
|
|
68623
|
+
//#region src/features/rules/continue-rule.ts
|
|
68624
|
+
/**
|
|
68625
|
+
* Frontmatter schema for Continue rule files (`.continue/rules/*.md`).
|
|
68626
|
+
* `globs` and `regex` are documented as either a single pattern or a list.
|
|
68627
|
+
* @see https://docs.continue.dev/customize/deep-dives/rules
|
|
68628
|
+
*/
|
|
68629
|
+
const ContinueRuleFrontmatterSchema = zod_mini.z.looseObject({
|
|
68630
|
+
name: zod_mini.z.optional(zod_mini.z.string()),
|
|
68631
|
+
description: zod_mini.z.optional(zod_mini.z.string()),
|
|
68632
|
+
globs: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
68633
|
+
regex: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
68634
|
+
alwaysApply: zod_mini.z.optional(zod_mini.z.boolean())
|
|
68635
|
+
});
|
|
68636
|
+
/**
|
|
68637
|
+
* Normalizes the documented `string` / `string[]` shapes to the list form the
|
|
68638
|
+
* adapter works with. Continue treats a scalar as one pattern (no comma
|
|
68639
|
+
* splitting), so the scalar simply becomes a one-element list. An empty list
|
|
68640
|
+
* and an empty string both mean "not set".
|
|
68641
|
+
*/
|
|
68642
|
+
function normalizePatternList(value) {
|
|
68643
|
+
if (value === void 0) return;
|
|
68644
|
+
const list = (typeof value === "string" ? [value] : value).filter((pattern) => pattern.trim() !== "");
|
|
68645
|
+
return list.length > 0 ? list : void 0;
|
|
68646
|
+
}
|
|
68647
|
+
/**
|
|
68648
|
+
* A universal glob is redundant on a Continue rule: a `.continue/rules/` file
|
|
68649
|
+
* with no `globs` is already implicitly always-on, and one with `globs` is
|
|
68650
|
+
* only pulled in when a context file matches, so emitting `**\/*` would turn
|
|
68651
|
+
* an always-on rule into a "when any file is in context" rule. Dropped on
|
|
68652
|
+
* generate and mapped back on import.
|
|
68653
|
+
*/
|
|
68654
|
+
const UNIVERSAL_GLOBS$1 = /* @__PURE__ */ new Set(["**/*", "*"]);
|
|
68655
|
+
/**
|
|
68656
|
+
* Rule generator for Continue (continuedev/continue), the open-source IDE
|
|
68657
|
+
* extension and CLI (`cn`).
|
|
68658
|
+
*
|
|
68659
|
+
* Rules format:
|
|
68660
|
+
* - {project}/AGENTS.md (root: true) — read from the workspace root and
|
|
68661
|
+
* always applied.
|
|
68662
|
+
* - {project}/.continue/rules/*.md (root: false) — Markdown with optional
|
|
68663
|
+
* `description` / `globs` / `regex` / `alwaysApply` frontmatter. A file
|
|
68664
|
+
* with no `globs`/`regex` is always applied; one with `globs` is applied
|
|
68665
|
+
* when a context file matches; `alwaysApply: true` forces it on and
|
|
68666
|
+
* `alwaysApply: false` makes it depend on `globs`/`regex` only.
|
|
68667
|
+
* - Global: ~/.continue/rules/*.md. Continue does not read `~/AGENTS.md`, so
|
|
68668
|
+
* the global root rule is written to `~/.continue/rules/AGENTS.md`, the
|
|
68669
|
+
* same layout the Roo adapter uses.
|
|
68670
|
+
*
|
|
68671
|
+
* @see https://docs.continue.dev/customize/deep-dives/rules
|
|
68672
|
+
*/
|
|
68673
|
+
var ContinueRule = class ContinueRule extends ToolRule {
|
|
68674
|
+
frontmatter;
|
|
68675
|
+
body;
|
|
68676
|
+
static getSettablePaths({ global, excludeToolDir } = {}) {
|
|
68677
|
+
const rulesDirPath = buildToolPath(CONTINUE_DIR, "rules", excludeToolDir);
|
|
68678
|
+
if (global) return {
|
|
68679
|
+
root: {
|
|
68680
|
+
relativeDirPath: rulesDirPath,
|
|
68681
|
+
relativeFilePath: CONTINUE_ROOT_RULE_FILE_NAME
|
|
68682
|
+
},
|
|
68683
|
+
nonRoot: { relativeDirPath: rulesDirPath }
|
|
68684
|
+
};
|
|
68685
|
+
return {
|
|
68686
|
+
root: {
|
|
68687
|
+
relativeDirPath: ".",
|
|
68688
|
+
relativeFilePath: CONTINUE_ROOT_RULE_FILE_NAME
|
|
68689
|
+
},
|
|
68690
|
+
nonRoot: { relativeDirPath: rulesDirPath }
|
|
68691
|
+
};
|
|
68692
|
+
}
|
|
68693
|
+
constructor({ frontmatter, body, ...rest }) {
|
|
68694
|
+
if (rest.validate) {
|
|
68695
|
+
const result = ContinueRuleFrontmatterSchema.safeParse(frontmatter);
|
|
68696
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${(0, node_path.join)(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
|
|
68697
|
+
}
|
|
68698
|
+
super({
|
|
68699
|
+
...rest,
|
|
68700
|
+
fileContent: rest.root ? body : ContinueRule.generateFileContent(body, frontmatter)
|
|
68701
|
+
});
|
|
68702
|
+
this.frontmatter = frontmatter;
|
|
68703
|
+
this.body = body;
|
|
68704
|
+
}
|
|
68705
|
+
static generateFileContent(body, frontmatter) {
|
|
68706
|
+
if (frontmatter.name === void 0 && frontmatter.description === void 0 && frontmatter.globs === void 0 && frontmatter.regex === void 0 && frontmatter.alwaysApply === void 0) return body;
|
|
68707
|
+
return stringifyFrontmatter(body, {
|
|
68708
|
+
name: frontmatter.name,
|
|
68709
|
+
description: frontmatter.description,
|
|
68710
|
+
globs: frontmatter.globs,
|
|
68711
|
+
regex: frontmatter.regex,
|
|
68712
|
+
alwaysApply: frontmatter.alwaysApply
|
|
68713
|
+
});
|
|
68714
|
+
}
|
|
68715
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false, relativeDirPath: overrideDirPath }) {
|
|
68716
|
+
const paths = this.getSettablePaths({ global });
|
|
68717
|
+
const rootDirPath = overrideDirPath ?? paths.root.relativeDirPath;
|
|
68718
|
+
if (relativeFilePath === paths.root.relativeFilePath && rootDirPath === paths.root.relativeDirPath) {
|
|
68719
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, rootDirPath, paths.root.relativeFilePath));
|
|
68720
|
+
return new ContinueRule({
|
|
68721
|
+
outputRoot,
|
|
68722
|
+
relativeDirPath: rootDirPath,
|
|
68723
|
+
relativeFilePath: paths.root.relativeFilePath,
|
|
68724
|
+
frontmatter: {},
|
|
68725
|
+
body: fileContent.trim(),
|
|
68726
|
+
validate,
|
|
68727
|
+
root: true
|
|
68728
|
+
});
|
|
68729
|
+
}
|
|
68730
|
+
const relativePath = (0, node_path.join)(paths.nonRoot.relativeDirPath, relativeFilePath);
|
|
68731
|
+
const filePath = (0, node_path.join)(outputRoot, relativePath);
|
|
68732
|
+
const { frontmatter, body: content } = parseFrontmatter(await readFileContent(filePath), filePath);
|
|
68733
|
+
const result = ContinueRuleFrontmatterSchema.safeParse(frontmatter);
|
|
68734
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
68735
|
+
return new ContinueRule({
|
|
68736
|
+
outputRoot,
|
|
68737
|
+
relativeDirPath: paths.nonRoot.relativeDirPath,
|
|
68738
|
+
relativeFilePath,
|
|
68739
|
+
frontmatter: result.data,
|
|
68740
|
+
body: content.trim(),
|
|
68741
|
+
validate,
|
|
68742
|
+
root: false
|
|
68743
|
+
});
|
|
68744
|
+
}
|
|
68745
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
68746
|
+
const paths = this.getSettablePaths({ global });
|
|
68747
|
+
const isRoot = relativeFilePath === paths.root.relativeFilePath && relativeDirPath === paths.root.relativeDirPath;
|
|
68748
|
+
return new ContinueRule({
|
|
68749
|
+
outputRoot,
|
|
68750
|
+
relativeDirPath,
|
|
68751
|
+
relativeFilePath,
|
|
68752
|
+
frontmatter: {},
|
|
68753
|
+
body: "",
|
|
68754
|
+
validate: false,
|
|
68755
|
+
root: isRoot
|
|
68756
|
+
});
|
|
68757
|
+
}
|
|
68758
|
+
/**
|
|
68759
|
+
* Resolves the `globs` to emit. A universal glob is dropped because a rule
|
|
68760
|
+
* without `globs` is already always-on in Continue (see `UNIVERSAL_GLOBS`),
|
|
68761
|
+
* and every glob is dropped alongside `alwaysApply: true`, which forces the
|
|
68762
|
+
* rule on regardless of them.
|
|
68763
|
+
*/
|
|
68764
|
+
static resolveContinueGlobs({ continueGlobs, parentGlobs, alwaysApply }) {
|
|
68765
|
+
if (alwaysApply === true) return;
|
|
68766
|
+
const targetGlobs = continueGlobs ?? parentGlobs;
|
|
68767
|
+
if (!targetGlobs || targetGlobs.length === 0) return;
|
|
68768
|
+
if (targetGlobs.every((glob) => UNIVERSAL_GLOBS$1.has(glob.trim()))) return;
|
|
68769
|
+
return targetGlobs;
|
|
68770
|
+
}
|
|
68771
|
+
static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
|
|
68772
|
+
const rulesyncFrontmatter = rulesyncRule.getFrontmatter();
|
|
68773
|
+
const root = rulesyncFrontmatter.root ?? false;
|
|
68774
|
+
const paths = this.getSettablePaths({ global });
|
|
68775
|
+
const body = rulesyncRule.getBody();
|
|
68776
|
+
if (root) return new ContinueRule({
|
|
68777
|
+
outputRoot,
|
|
68778
|
+
frontmatter: {},
|
|
68779
|
+
body,
|
|
68780
|
+
relativeDirPath: paths.root.relativeDirPath,
|
|
68781
|
+
relativeFilePath: paths.root.relativeFilePath,
|
|
68782
|
+
validate,
|
|
68783
|
+
root
|
|
68784
|
+
});
|
|
68785
|
+
const continueFrontmatter = rulesyncFrontmatter.continue;
|
|
68786
|
+
const continueRuleFrontmatter = {
|
|
68787
|
+
name: continueFrontmatter?.name,
|
|
68788
|
+
description: continueFrontmatter?.description ?? rulesyncFrontmatter.description,
|
|
68789
|
+
globs: ContinueRule.resolveContinueGlobs({
|
|
68790
|
+
continueGlobs: normalizePatternList(continueFrontmatter?.globs),
|
|
68791
|
+
parentGlobs: rulesyncFrontmatter.globs,
|
|
68792
|
+
alwaysApply: continueFrontmatter?.alwaysApply
|
|
68793
|
+
}),
|
|
68794
|
+
regex: normalizePatternList(continueFrontmatter?.regex),
|
|
68795
|
+
alwaysApply: continueFrontmatter?.alwaysApply
|
|
68796
|
+
};
|
|
68797
|
+
return new ContinueRule({
|
|
68798
|
+
outputRoot,
|
|
68799
|
+
frontmatter: continueRuleFrontmatter,
|
|
68800
|
+
body,
|
|
68801
|
+
relativeDirPath: paths.nonRoot.relativeDirPath,
|
|
68802
|
+
relativeFilePath: rulesyncRule.getRelativeFilePath(),
|
|
68803
|
+
validate,
|
|
68804
|
+
root
|
|
68805
|
+
});
|
|
68806
|
+
}
|
|
68807
|
+
toRulesyncRule() {
|
|
68808
|
+
const targets = ["*"];
|
|
68809
|
+
if (this.isRoot()) {
|
|
68810
|
+
const rulesyncFrontmatter = {
|
|
68811
|
+
targets,
|
|
68812
|
+
root: true,
|
|
68813
|
+
description: this.description,
|
|
68814
|
+
globs: ["**/*"]
|
|
68815
|
+
};
|
|
68816
|
+
return new RulesyncRule({
|
|
68817
|
+
outputRoot: this.getOutputRoot(),
|
|
68818
|
+
frontmatter: rulesyncFrontmatter,
|
|
68819
|
+
body: this.body,
|
|
68820
|
+
relativeDirPath: RULESYNC_RULES_RELATIVE_DIR_PATH,
|
|
68821
|
+
relativeFilePath: RULESYNC_OVERVIEW_FILE_NAME,
|
|
68822
|
+
validate: true
|
|
68823
|
+
});
|
|
68824
|
+
}
|
|
68825
|
+
const sourceGlobs = normalizePatternList(this.frontmatter.globs) ?? [];
|
|
68826
|
+
const regex = normalizePatternList(this.frontmatter.regex);
|
|
68827
|
+
const isAlways = this.frontmatter.alwaysApply !== false;
|
|
68828
|
+
const globs = sourceGlobs.length === 0 && isAlways ? ["**/*"] : sourceGlobs;
|
|
68829
|
+
const { name, alwaysApply } = this.frontmatter;
|
|
68830
|
+
const hasContinueSection = name !== void 0 || alwaysApply !== void 0 || regex !== void 0;
|
|
68831
|
+
const rulesyncFrontmatter = {
|
|
68832
|
+
targets,
|
|
68833
|
+
root: false,
|
|
68834
|
+
description: this.frontmatter.description,
|
|
68835
|
+
globs,
|
|
68836
|
+
...hasContinueSection && { continue: {
|
|
68837
|
+
...name !== void 0 && { name },
|
|
68838
|
+
alwaysApply,
|
|
68839
|
+
regex
|
|
68840
|
+
} }
|
|
68841
|
+
};
|
|
68842
|
+
return new RulesyncRule({
|
|
68843
|
+
outputRoot: this.getOutputRoot(),
|
|
68844
|
+
frontmatter: rulesyncFrontmatter,
|
|
68845
|
+
body: this.body,
|
|
68846
|
+
relativeDirPath: RULESYNC_RULES_RELATIVE_DIR_PATH,
|
|
68847
|
+
relativeFilePath: this.getRelativeFilePath(),
|
|
68848
|
+
validate: true
|
|
68849
|
+
});
|
|
68850
|
+
}
|
|
68851
|
+
validate() {
|
|
68852
|
+
if (!this.frontmatter) return {
|
|
68853
|
+
success: true,
|
|
68854
|
+
error: null
|
|
68855
|
+
};
|
|
68856
|
+
const result = ContinueRuleFrontmatterSchema.safeParse(this.frontmatter);
|
|
68857
|
+
if (result.success) return {
|
|
68858
|
+
success: true,
|
|
68859
|
+
error: null
|
|
68860
|
+
};
|
|
68861
|
+
return {
|
|
68862
|
+
success: false,
|
|
68863
|
+
error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${(0, node_path.join)(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
|
|
68864
|
+
};
|
|
68865
|
+
}
|
|
68866
|
+
getFrontmatter() {
|
|
68867
|
+
return this.frontmatter;
|
|
68868
|
+
}
|
|
68869
|
+
getBody() {
|
|
68870
|
+
return this.body;
|
|
68871
|
+
}
|
|
68872
|
+
static isTargetedByRulesyncRule(rulesyncRule) {
|
|
68873
|
+
return this.isTargetedByRulesyncRuleDefault({
|
|
68874
|
+
rulesyncRule,
|
|
68875
|
+
toolTarget: "continue"
|
|
68876
|
+
});
|
|
68877
|
+
}
|
|
68878
|
+
};
|
|
68879
|
+
//#endregion
|
|
67484
68880
|
//#region src/features/rules/copilot-rule.ts
|
|
67485
68881
|
const CopilotRuleFrontmatterSchema = zod_mini.z.looseObject({
|
|
67486
68882
|
description: zod_mini.z.optional(zod_mini.z.string()),
|
|
@@ -71562,6 +72958,14 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
71562
72958
|
collisionPolicy: "fold"
|
|
71563
72959
|
}
|
|
71564
72960
|
}],
|
|
72961
|
+
["continue", {
|
|
72962
|
+
class: ContinueRule,
|
|
72963
|
+
meta: {
|
|
72964
|
+
extension: "md",
|
|
72965
|
+
supportsGlobal: true,
|
|
72966
|
+
ruleDiscoveryMode: "auto"
|
|
72967
|
+
}
|
|
72968
|
+
}],
|
|
71565
72969
|
["copilot", {
|
|
71566
72970
|
class: CopilotRule,
|
|
71567
72971
|
meta: {
|